Update release notes and deploy workspace
This commit is contained in:
@@ -11,7 +11,6 @@ const props = defineProps<{
|
||||
setArchiveYear: (year: number) => Promise<void>
|
||||
archiveYearButtonStyle: (active: boolean) => string
|
||||
winnerPlatformStyle: (url: string) => string
|
||||
winnerPlatformKey: (url: string) => string
|
||||
winnerPlatformLabel: (url: string) => string
|
||||
}>()
|
||||
|
||||
@@ -57,10 +56,17 @@ function initialsFor(value: string) {
|
||||
<div style="font-size:13px;color:rgba(255,246,251,.78);">{{ props.selectedArchive.winners.length }} Kategorien archiviert</div>
|
||||
</div>
|
||||
<div class="home-archive-modal__winners" style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;">
|
||||
<article
|
||||
<component
|
||||
:is="winner.url ? 'a' : 'article'"
|
||||
v-for="winner in props.selectedArchive.winners"
|
||||
:key="`${props.selectedArchive.year}-${winner.category}`"
|
||||
style="display:grid;grid-template-columns:84px minmax(0,1fr);gap:16px;padding:18px;border-radius:24px;background:rgba(255,255,255,.78);border:1px solid rgba(139,108,219,.12);box-shadow:0 12px 28px rgba(139,108,219,.08);align-items:start;"
|
||||
:href="winner.url || undefined"
|
||||
:target="winner.url ? '_blank' : undefined"
|
||||
:rel="winner.url ? 'noopener' : undefined"
|
||||
:aria-label="winner.url ? `${winner.name} auf ${props.winnerPlatformLabel(winner.url)} öffnen` : undefined"
|
||||
style="display:grid;grid-template-columns:84px minmax(0,1fr);gap:16px;padding:18px;border-radius:24px;background:rgba(255,255,255,.78);border:1px solid rgba(139,108,219,.12);box-shadow:0 12px 28px rgba(139,108,219,.08);align-items:start;color:inherit;text-decoration:none;cursor:pointer;transition:transform .16s ease,box-shadow .16s ease,border-color .16s ease;"
|
||||
style-hover="transform:translateY(-2px);box-shadow:0 18px 38px rgba(139,108,219,.16);border-color:rgba(139,108,219,.26);"
|
||||
style-focus="outline:3px solid rgba(139,108,219,.28);outline-offset:3px;"
|
||||
>
|
||||
<div style="width:84px;height:84px;border-radius:24px;background:linear-gradient(135deg,#ffe1a3,#e7b13e);display:grid;place-items:center;color:#2f1b00;font-family:'Fredoka',sans-serif;font-size:26px;font-weight:800;box-shadow:0 12px 24px rgba(139,108,219,.16);">
|
||||
{{ initialsFor(winner.name) }}
|
||||
@@ -68,54 +74,14 @@ function initialsFor(value: string) {
|
||||
<div style="min-width:0;">
|
||||
<div style="font-size:10px;font-weight:700;letter-spacing:1.6px;text-transform:uppercase;color:#d9942a;margin-bottom:7px;">{{ winner.category }}</div>
|
||||
<div style="font-family:'Fredoka',sans-serif;font-weight:600;font-size:20px;line-height:1.2;color:#3f3556;">{{ winner.name }}</div>
|
||||
<div style="font-size:13px;color:#8a8398;margin-top:5px;">{{ winner.handle }}</div>
|
||||
<div style="font-size:13px;color:#8a8398;margin-top:5px;">Unterkategorie · {{ winner.subcategory }}</div>
|
||||
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:9px;margin-top:12px;">
|
||||
<a :href="winner.url" target="_blank" rel="noopener" :style="props.winnerPlatformStyle(winner.url)" style-hover="transform:translateY(-1px);opacity:.88;">
|
||||
<span :style="props.winnerPlatformStyle(winner.url)">
|
||||
{{ props.winnerPlatformLabel(winner.url) }}
|
||||
</a>
|
||||
<a
|
||||
v-if="winner.clipUrl"
|
||||
:href="winner.clipUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
referrerpolicy="no-referrer"
|
||||
style="display:inline-flex;align-items:center;gap:6px;color:#b7791f;text-decoration:none;font-family:'Outfit',sans-serif;font-weight:800;font-size:12px;"
|
||||
>
|
||||
{{ winner.clipTitle }}
|
||||
</a>
|
||||
</div>
|
||||
<iframe
|
||||
v-if="winner.clipEmbedUrl"
|
||||
:src="winner.clipEmbedUrl"
|
||||
:title="winner.clipEmbedTitle"
|
||||
loading="lazy"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
style="width:100%;aspect-ratio:16/9;border:0;border-radius:14px;background:#1b0d2d;margin-top:12px;"
|
||||
/>
|
||||
<div
|
||||
v-else-if="winner.clipUrl"
|
||||
style="display:flex;align-items:center;justify-content:center;aspect-ratio:16/9;border-radius:14px;background:rgba(255,255,255,.88);border:1px solid rgba(139,108,219,.12);margin-top:12px;padding:14px;text-align:center;"
|
||||
>
|
||||
<a
|
||||
:href="winner.clipUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
referrerpolicy="no-referrer"
|
||||
style="color:#7c5bb4;text-decoration:none;font-family:'Outfit',sans-serif;font-size:13px;font-weight:700;"
|
||||
>
|
||||
Clip extern ansehen
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
style="display:flex;align-items:center;justify-content:center;aspect-ratio:16/9;border-radius:14px;background:rgba(255,248,232,.92);border:1px dashed rgba(231,177,62,.42);margin-top:12px;padding:14px;text-align:center;color:#b7791f;font-family:'Outfit',sans-serif;font-size:13px;font-weight:700;"
|
||||
>
|
||||
Fuer diesen Gewinner ist kein Clip hinterlegt.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,21 +2,52 @@
|
||||
<section id="kategorien" class="home-section" style="max-width:1200px;margin:0 auto;padding:90px 24px;">
|
||||
<div style="text-align:center;margin-bottom:52px;">
|
||||
<div style="font-size:13px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--accent,#ff5fa2);margin-bottom:12px;">✦ Die Awards</div>
|
||||
<h2 style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:clamp(32px,4vw,48px);margin:0 0 14px;letter-spacing:0;">{{ landingCategories.length }} Hauptkategorien · 1 Sternenhimmel</h2>
|
||||
<p style="font-size:17px;color:var(--muted,#c9b8da);max-width:560px;margin:0 auto;">Von Gaming bis Creative - jede Hauptkategorie bringt ihre eigenen Viewer-Tiers und Favoriten mit.</p>
|
||||
<h2 style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:clamp(32px,4vw,48px);margin:0 0 14px;letter-spacing:0;">{{ awardsTitle }}</h2>
|
||||
<p style="font-size:17px;color:var(--muted,#c9b8da);max-width:680px;margin:0 auto;line-height:1.65;">{{ awardsDescription }}</p>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:18px;" data-cat-grid>
|
||||
<div v-for="category in landingCategories" :key="category.id" style="padding:26px 22px;border-radius:22px;background:var(--card,#22123a);border:1px solid var(--line,rgba(255,255,255,.12));transition:transform .2s,border-color .2s;" style-hover="transform:translateY(-5px);border-color:var(--accent,#ff5fa2);">
|
||||
<div style="display:inline-flex;align-items:center;justify-content:center;width:50px;height:50px;border-radius:15px;background:linear-gradient(135deg,var(--accent,#ff5fa2),var(--accent2,#a06bff));color:#fff;font-size:24px;margin-bottom:16px;box-shadow:0 8px 20px var(--glow,rgba(255,95,162,.4));">{{ category.icon }}</div>
|
||||
<h3 style="font-family:'Fredoka',sans-serif;font-weight:600;font-size:19px;margin:0 0 6px;">{{ category.name }}</h3>
|
||||
<p style="font-size:14px;color:var(--muted,#c9b8da);margin:0;line-height:1.5;">{{ category.subcategoryCount }} Viewer-Tiers · {{ category.candidateCount }} Kandidat:innen aktuell.</p>
|
||||
<p v-if="category.description" style="font-size:13px;color:var(--muted,#c9b8da);margin:12px 0 0;line-height:1.5;">{{ category.description }}</p>
|
||||
<div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px;" data-cat-grid>
|
||||
<article v-for="category in landingCategories" :key="category.id" style="position:relative;display:flex;flex-direction:column;gap:18px;min-height:100%;padding:28px 22px 24px;border-radius:28px;background:radial-gradient(circle at 18% 16%,rgba(255,255,255,.98),transparent 26%),radial-gradient(circle at 84% 20%,rgba(255,214,120,.18),transparent 20%),linear-gradient(160deg,rgba(255,255,255,.97),rgba(247,239,255,.96) 52%,rgba(239,229,255,.94));border:1px solid rgba(178,150,232,.28);box-shadow:0 20px 54px rgba(43,20,74,.10), inset 0 1px 0 rgba(255,255,255,.92), inset 0 -1px 0 rgba(176,145,232,.10);transition:transform .2s,border-color .2s,box-shadow .2s;" style-hover="transform:translateY(-5px);border-color:rgba(255,95,162,.34);box-shadow:0 28px 68px rgba(43,20,74,.14), inset 0 1px 0 rgba(255,255,255,.96), inset 0 -1px 0 rgba(176,145,232,.14);">
|
||||
<span aria-hidden="true" style="position:absolute;top:14px;right:16px;font-size:10px;color:rgba(231,177,62,.9);text-shadow:0 0 12px rgba(231,177,62,.42);">✦</span>
|
||||
<span aria-hidden="true" style="position:absolute;top:42px;right:34px;font-size:7px;color:rgba(164,122,238,.72);text-shadow:0 0 10px rgba(164,122,238,.32);">✦</span>
|
||||
<div style="display:inline-flex;align-items:center;justify-content:center;width:60px;height:60px;border-radius:20px;background:radial-gradient(circle at 30% 28%,rgba(255,255,255,.34),transparent 30%),linear-gradient(135deg,var(--accent2,#8b6cdb),#b490ff 58%,#ffd678 140%);color:#fff;font-size:26px;box-shadow:0 16px 36px rgba(139,108,219,.30), inset 0 1px 0 rgba(255,255,255,.32);">{{ category.icon }}</div>
|
||||
<div>
|
||||
<h3 style="font-family:'Fredoka',sans-serif;font-weight:600;font-size:clamp(21px,2.2vw,27px);line-height:1.15;color:#3d2f58;margin:0 0 10px;">{{ category.name }}</h3>
|
||||
<p v-if="category.description" style="font-size:15px;color:#7a6f93;margin:0;line-height:1.65;">{{ category.description }}</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:34px;padding:28px 24px;border-radius:30px;background:linear-gradient(135deg,rgba(255,255,255,.94),rgba(245,236,255,.92));border:1px solid rgba(139,108,219,.14);box-shadow:0 18px 54px rgba(43,20,74,.08);">
|
||||
<div style="max-width:780px;margin:0 auto 28px;text-align:center;">
|
||||
<div style="font-size:13px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:#a05acb;margin-bottom:12px;">✦ Unterkategorien</div>
|
||||
<h3 style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:clamp(28px,3.2vw,40px);line-height:1.15;color:#3d2f58;margin:0 0 14px;">{{ subcategoriesTitle }}</h3>
|
||||
<p style="font-size:16px;color:#7a6f93;line-height:1.7;margin:0;">{{ subcategoriesDescription }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="allSubcategoryNames.length" style="display:flex;flex-wrap:wrap;justify-content:center;gap:12px 10px;">
|
||||
<span v-for="subcategoryName in allSubcategoryNames" :key="subcategoryName" style="position:relative;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 18px;border-radius:999px;background:radial-gradient(circle at 22% 18%,rgba(255,255,255,.95),transparent 34%),linear-gradient(135deg,rgba(255,255,255,.94),rgba(241,232,255,.92) 52%,rgba(234,222,255,.96));border:1px solid rgba(178,150,232,.36);color:#4f3d70;font-size:14px;font-weight:700;line-height:1.2;box-shadow:0 10px 24px rgba(139,108,219,.14), inset 0 1px 0 rgba(255,255,255,.9), inset 0 -1px 0 rgba(176,145,232,.12);">
|
||||
<span aria-hidden="true" style="display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:999px;background:linear-gradient(135deg,rgba(164,122,238,.18),rgba(255,214,120,.22));color:#9a62df;font-size:11px;box-shadow:0 4px 10px rgba(154,98,223,.16), inset 0 1px 0 rgba(255,255,255,.78);">✦</span>
|
||||
<span>{{ subcategoryName }}</span>
|
||||
<span aria-hidden="true" style="position:absolute;top:8px;right:12px;font-size:8px;color:rgba(231,177,62,.82);text-shadow:0 0 10px rgba(231,177,62,.38);">✦</span>
|
||||
</span>
|
||||
</div>
|
||||
<p v-else style="font-size:13px;color:#8d83a0;margin:0;line-height:1.5;text-align:center;">Noch keine Unterkategorien veröffentlicht.</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { plainTextContentFromStorage } from '../../lib/privacyContent'
|
||||
|
||||
type LandingSiteContent = {
|
||||
awardsSectionTitle?: string | null
|
||||
awardsSectionDescription?: string | null
|
||||
subcategoriesSectionTitle?: string | null
|
||||
subcategoriesSectionDescription?: string | null
|
||||
}
|
||||
|
||||
type LandingCategory = {
|
||||
id: string
|
||||
groupName: string
|
||||
@@ -24,11 +55,47 @@ type LandingCategory = {
|
||||
icon: string
|
||||
description: string
|
||||
maxNomineesPerUser: number
|
||||
candidateCount: number
|
||||
subcategoryCount: number
|
||||
subcategoryNames: string[]
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
siteContent: LandingSiteContent
|
||||
landingCategories: LandingCategory[]
|
||||
}>()
|
||||
|
||||
const allSubcategoryNames = computed(() => {
|
||||
const seen = new Set<string>()
|
||||
const names: string[] = []
|
||||
|
||||
for (const category of props.landingCategories) {
|
||||
for (const subcategoryName of category.subcategoryNames) {
|
||||
const normalized = subcategoryName.trim()
|
||||
if (!normalized) continue
|
||||
|
||||
const key = normalized.toLowerCase()
|
||||
if (seen.has(key)) continue
|
||||
|
||||
seen.add(key)
|
||||
names.push(normalized)
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
})
|
||||
|
||||
const awardsTitle = computed(() =>
|
||||
plainTextContentFromStorage(props.siteContent.awardsSectionTitle ?? '') || 'Die Kategorien, die diese Award-Season zum Funkeln bringen',
|
||||
)
|
||||
|
||||
const awardsDescription = computed(() =>
|
||||
plainTextContentFromStorage(props.siteContent.awardsSectionDescription ?? '') || 'Von großen Community-Favoriten bis zu liebgewonnenen Szenen-Highlights: Jede Hauptkategorie entfaltet ihre eigenen Unterkategorien und macht die Show noch spannender.',
|
||||
)
|
||||
|
||||
const subcategoriesTitle = computed(() =>
|
||||
plainTextContentFromStorage(props.siteContent.subcategoriesSectionTitle ?? '') || 'Hier bekommt jede Story ihre Chance auf den großen Moment',
|
||||
)
|
||||
|
||||
const subcategoriesDescription = computed(() =>
|
||||
plainTextContentFromStorage(props.siteContent.subcategoriesSectionDescription ?? '') || 'Zu jeder Kategorie gehören eigene Unterkategorien, damit wirklich jede Reise ihren Platz hat: ob ganz frisch entdeckt, gerade im großen Aufwind oder schon fest in der Szene etabliert. Am Ende kann jede Person den Spot bekommen, der zu ihrer Geschichte passt.',
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { computed, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { Handshake, Mic2, Send, X } from '@lucide/vue'
|
||||
|
||||
import { privacyContentToHtml } from '../../lib/privacyContent'
|
||||
@@ -126,12 +126,19 @@ function closeShowactInfoModal() {
|
||||
emit('update:modalOpen', false)
|
||||
}
|
||||
|
||||
// For multiselect: values are stored as comma-separated strings
|
||||
function getMultiselectValues(fieldId: string): string[] {
|
||||
function getChoiceValues(fieldId: string): string[] {
|
||||
const raw = fieldValues[fieldId] ?? ''
|
||||
return raw ? raw.split(',').map((v) => v.trim()).filter(Boolean) : []
|
||||
}
|
||||
|
||||
function getMultiselectValues(fieldId: string): string[] {
|
||||
return getChoiceValues(fieldId)
|
||||
}
|
||||
|
||||
function getCheckboxValues(fieldId: string): string[] {
|
||||
return getChoiceValues(fieldId)
|
||||
}
|
||||
|
||||
function otherFieldId(fieldId: string) {
|
||||
return `${fieldId}__other`
|
||||
}
|
||||
@@ -140,6 +147,11 @@ function getOtherValue(fieldId: string) {
|
||||
return (fieldValues[otherFieldId(fieldId)] ?? '').trim()
|
||||
}
|
||||
|
||||
function setChoiceValues(fieldId: string, values: string[]) {
|
||||
fieldValues[fieldId] = values.join(', ')
|
||||
delete fieldErrors[fieldId]
|
||||
}
|
||||
|
||||
function setMultiselectValue(fieldId: string, option: string, checked: boolean) {
|
||||
const current = getMultiselectValues(fieldId)
|
||||
if (checked) {
|
||||
@@ -148,18 +160,20 @@ function setMultiselectValue(fieldId: string, option: string, checked: boolean)
|
||||
const idx = current.indexOf(option)
|
||||
if (idx !== -1) current.splice(idx, 1)
|
||||
}
|
||||
fieldValues[fieldId] = current.join(', ')
|
||||
delete fieldErrors[fieldId]
|
||||
setChoiceValues(fieldId, current)
|
||||
}
|
||||
|
||||
function setCheckboxValue(field: ShowactFormField, checked: boolean) {
|
||||
const value = field.options[0] || 'Ja'
|
||||
fieldValues[field.id] = checked ? value : ''
|
||||
delete fieldErrors[field.id]
|
||||
}
|
||||
function setCheckboxOptionValue(field: ShowactFormField, option: string, checked: boolean) {
|
||||
const current = getCheckboxValues(field.id)
|
||||
if (checked) {
|
||||
if (!current.includes(option)) current.push(option)
|
||||
} else {
|
||||
const next = current.filter((value) => value !== option)
|
||||
setChoiceValues(field.id, next)
|
||||
return
|
||||
}
|
||||
|
||||
function isCheckboxChecked(field: ShowactFormField) {
|
||||
return Boolean((fieldValues[field.id] ?? '').trim())
|
||||
setChoiceValues(field.id, current)
|
||||
}
|
||||
|
||||
function updateOtherValue(field: ShowactFormField, value: string) {
|
||||
@@ -173,7 +187,7 @@ function validateField(field: ShowactFormField): string {
|
||||
|
||||
if (field.required && !value && !otherValue) {
|
||||
if (field.type === 'checkbox') {
|
||||
return `Bitte bestaetige „${field.label}".`
|
||||
return `Bitte waehle bei „${field.label}" mindestens eine Checkbox aus.`
|
||||
}
|
||||
|
||||
if (field.type === 'radio' || field.type === 'select' || field.type === 'multiselect') {
|
||||
@@ -333,7 +347,6 @@ async function submitShowact() {
|
||||
const inputStyle = 'padding:13px 16px;border-radius:12px;border:1.5px solid #e6dcf6;background:#fbf9ff;font-family:\'Outfit\',sans-serif;font-size:15px;color:#3f3556;outline:none;transition:border-color .15s;'
|
||||
const labelStyle = 'font-family:\'Outfit\',sans-serif;font-size:13px;font-weight:600;color:#5f44ad;'
|
||||
const errorStyle = 'font-family:\'Outfit\',sans-serif;font-size:12px;font-weight:600;color:#d14b72;line-height:1.45;'
|
||||
const checkboxStyle = 'display:flex;align-items:center;gap:9px;padding:10px 14px;border-radius:12px;border:1.5px solid #e6dcf6;background:#fbf9ff;font-family:\'Outfit\',sans-serif;font-size:14px;color:#3f3556;cursor:pointer;'
|
||||
|
||||
function onFocus(event: FocusEvent) {
|
||||
;(event.target as HTMLElement).style.borderColor = '#8b6cdb'
|
||||
@@ -398,6 +411,14 @@ watch(
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch([localModalOpen, showactInfoModalOpen], ([form, info]) => {
|
||||
document.body.style.overflow = form || info ? 'hidden' : ''
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.body.style.overflow = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -450,7 +471,7 @@ watch(
|
||||
<h2 class="mt-0.5 text-xl font-black text-[#3f3556]">Als Showact auftreten</h2>
|
||||
<p class="mt-1 text-sm leading-6 text-[#746b86]">
|
||||
<template v-if="featureFlags.showactApplicationsEnabled">
|
||||
Hast du eine Idee fuer einen Beitrag? Bewirb dich jetzt und zeig der Community, was du kannst.
|
||||
Hast du eine Idee für einen Beitrag? Bewirb dich jetzt und zeig der Community, was du kannst.
|
||||
</template>
|
||||
<template v-else>
|
||||
Showacts bleiben ein Teil der Award-Show. Aktuell ist das Bewerbungsfenster geschlossen, aber alle Infos bleiben hier erreichbar.
|
||||
@@ -601,52 +622,65 @@ watch(
|
||||
<div v-else-if="field.type === 'multiselect'" style="display:flex;flex-direction:column;gap:6px;margin-bottom:14px;">
|
||||
<span :style="labelStyle">{{ field.label }}{{ field.required ? ' *' : '' }}</span>
|
||||
<span v-if="field.helpText" style="font-family:'Outfit',sans-serif;font-size:12px;line-height:1.45;color:#8a819d;">{{ field.helpText }}</span>
|
||||
<div :style="'display:flex;flex-direction:column;gap:6px;padding:10px 14px;border-radius:12px;border:1.5px solid #e6dcf6;background:#fbf9ff;' + (fieldErrors[field.id] ? 'border-color:#d14b72;background:#fff7fa;' : '')">
|
||||
<div
|
||||
class="home-showact-choice-group"
|
||||
:class="{ 'home-showact-choice-group--invalid': Boolean(fieldErrors[field.id]) }"
|
||||
>
|
||||
<label
|
||||
v-for="opt in field.options"
|
||||
:key="opt"
|
||||
style="display:flex;align-items:center;gap:8px;font-family:'Outfit',sans-serif;font-size:14px;color:#3f3556;cursor:pointer;"
|
||||
class="home-showact-choice-option"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="getMultiselectValues(field.id).includes(opt)"
|
||||
style="width:16px;height:16px;"
|
||||
class="home-showact-choice-input"
|
||||
@change="setMultiselectValue(field.id, opt, ($event.target as HTMLInputElement).checked); updateFieldError(field)"
|
||||
/>
|
||||
{{ opt }}
|
||||
<span>{{ opt }}</span>
|
||||
</label>
|
||||
<label
|
||||
v-if="field.allowOther"
|
||||
style="display:flex;align-items:center;gap:8px;font-family:'Outfit',sans-serif;font-size:14px;color:#3f3556;cursor:pointer;"
|
||||
class="home-showact-choice-option home-showact-choice-option--other"
|
||||
>
|
||||
<span style="width:16px;height:16px;border-radius:4px;border:1.5px solid #d8c9eb;background:#fff;"></span>
|
||||
Andere:
|
||||
<span class="home-showact-choice-faux-input" aria-hidden="true"></span>
|
||||
<span>Andere:</span>
|
||||
<input
|
||||
:value="getOtherValue(field.id)"
|
||||
type="text"
|
||||
placeholder="Eigene Antwort"
|
||||
style="min-width:0;flex:1;border:0;border-bottom:1.5px solid #d8c9eb;background:transparent;padding:4px 2px;font-family:'Outfit',sans-serif;font-size:14px;color:#3f3556;outline:none;"
|
||||
class="home-showact-choice-other-input"
|
||||
@input="updateOtherValue(field, ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
</label>
|
||||
<span v-if="field.options.length === 0" style="font-size:13px;color:#9ca3af;">Keine Optionen konfiguriert.</span>
|
||||
<span v-if="field.options.length === 0" class="home-showact-choice-empty">Keine Optionen konfiguriert.</span>
|
||||
</div>
|
||||
<span v-if="fieldErrors[field.id]" :style="errorStyle">{{ fieldErrors[field.id] }}</span>
|
||||
</div>
|
||||
|
||||
<!-- single checkbox confirmation -->
|
||||
<!-- checkbox group -->
|
||||
<div v-else-if="field.type === 'checkbox'" style="display:flex;flex-direction:column;gap:6px;margin-bottom:14px;">
|
||||
<span :style="labelStyle">{{ field.label }}{{ field.required ? ' *' : '' }}</span>
|
||||
<span v-if="field.helpText" style="font-family:'Outfit',sans-serif;font-size:12px;line-height:1.45;color:#8a819d;">{{ field.helpText }}</span>
|
||||
<label :style="checkboxStyle + (fieldErrors[field.id] ? 'border-color:#d14b72;background:#fff7fa;' : '')">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="isCheckboxChecked(field)"
|
||||
style="width:17px;height:17px;accent-color:#7c56c4;"
|
||||
@change="setCheckboxValue(field, ($event.target as HTMLInputElement).checked); updateFieldError(field)"
|
||||
/>
|
||||
{{ field.options[0] || 'Ja' }}
|
||||
</label>
|
||||
<div
|
||||
class="home-showact-choice-group"
|
||||
:class="{ 'home-showact-choice-group--invalid': Boolean(fieldErrors[field.id]) }"
|
||||
>
|
||||
<label
|
||||
v-for="opt in field.options"
|
||||
:key="opt"
|
||||
class="home-showact-choice-option"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="getCheckboxValues(field.id).includes(opt)"
|
||||
class="home-showact-choice-input"
|
||||
@change="setCheckboxOptionValue(field, opt, ($event.target as HTMLInputElement).checked); updateFieldError(field)"
|
||||
/>
|
||||
<span>{{ opt }}</span>
|
||||
</label>
|
||||
<span v-if="field.options.length === 0" class="home-showact-choice-empty">Keine Checkboxen konfiguriert.</span>
|
||||
</div>
|
||||
<span v-if="fieldErrors[field.id]" :style="errorStyle">{{ fieldErrors[field.id] }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -693,7 +727,7 @@ watch(
|
||||
<div style="overflow-y:auto;padding:28px 36px 32px;flex:1;font-family:'Outfit',sans-serif;font-size:14px;line-height:1.7;color:#6f6685;">
|
||||
<div v-if="showactInfoHtml" class="home-showact-info-content" v-html="showactInfoHtml" />
|
||||
<p v-else style="margin:0;padding:14px 16px;border-radius:16px;border:1px solid #fde68a;background:#fffbeb;color:#92400e;font-weight:600;">
|
||||
Fuer Showacts ist aktuell noch kein Landingpage-Inhalt hinterlegt.
|
||||
Für Showacts ist aktuell noch kein Landingpage-Inhalt hinterlegt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -721,12 +755,88 @@ watch(
|
||||
.home-showact-info-content :deep(li) {
|
||||
margin: 3px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.home-showact-choice-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
border: 1.5px solid #e6dcf6;
|
||||
border-radius: 14px;
|
||||
background: #fbf9ff;
|
||||
transition: border-color .15s ease, background .15s ease;
|
||||
}
|
||||
|
||||
.home-showact-choice-group--invalid {
|
||||
border-color: #d14b72;
|
||||
background: #fff7fa;
|
||||
}
|
||||
|
||||
.home-showact-choice-option {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(139, 108, 219, 0.12);
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
color: #3f3556;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.home-showact-choice-option--other {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.home-showact-choice-input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 2px;
|
||||
accent-color: #7c56c4;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.home-showact-choice-faux-input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 1px;
|
||||
border-radius: 4px;
|
||||
border: 1.5px solid #d8c9eb;
|
||||
background: #fff;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.home-showact-choice-other-input {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
border: 0;
|
||||
border-bottom: 1.5px solid #d8c9eb;
|
||||
background: transparent;
|
||||
padding: 4px 2px;
|
||||
color: #3f3556;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.home-showact-choice-empty {
|
||||
color: #9ca3af;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.home-showact-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.home-showact-choice-option--other {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="home-hero__content" style="position:relative;z-index:3;max-width:1200px;margin:0 auto;padding:60px 24px 70px;">
|
||||
<div class="home-hero__copy" style="max-width:560px;">
|
||||
<div class="home-hero__eyebrow" style="display:flex;align-items:center;gap:10px;font-size:13px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:#a98ddb;margin-bottom:22px;">
|
||||
<span style="color:#e7b13e;animation:spinSlow 9s linear infinite;display:inline-block;">✦</span> Die grosse Community-Auszeichnung
|
||||
<span style="color:#e7b13e;animation:spinSlow 9s linear infinite;display:inline-block;">✦</span> Die große Community-Auszeichnung
|
||||
</div>
|
||||
<h1 style="margin:0 0 4px;font-family:'Cormorant Garamond',serif;font-weight:700;line-height:.92;">
|
||||
<span style="display:block;font-size:clamp(56px,8.4vw,116px);letter-spacing:3px;color:#5f44ad;">VTUBER</span>
|
||||
|
||||
@@ -37,10 +37,12 @@ defineProps<{
|
||||
phasePrimaryActionStyle: string
|
||||
streamEyebrow: string
|
||||
streamTitle: string
|
||||
streamText: string
|
||||
streamMeta: string
|
||||
streamLive: boolean
|
||||
streamLocked: boolean
|
||||
streamLockedLabel: string
|
||||
streamButtonLabel: string
|
||||
streamButtonUrl: string
|
||||
streamLockedTitle: string
|
||||
statOneValue: string
|
||||
statOneLabel: string
|
||||
@@ -95,10 +97,12 @@ defineProps<{
|
||||
:public-stream-url="publicStreamUrl"
|
||||
:stream-eyebrow="streamEyebrow"
|
||||
:stream-title="streamTitle"
|
||||
:stream-text="streamText"
|
||||
:stream-meta="streamMeta"
|
||||
:stream-live="streamLive"
|
||||
:stream-locked="streamLocked"
|
||||
:stream-locked-label="streamLockedLabel"
|
||||
:stream-button-label="streamButtonLabel"
|
||||
:stream-button-url="streamButtonUrl"
|
||||
:stream-locked-title="streamLockedTitle"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
<template>
|
||||
<section class="home-stream-band" style="position:relative;overflow:hidden;background:linear-gradient(115deg,#241640 0%,#3a2168 48%,#5b3aa0 100%);">
|
||||
<HomeStarField :count="13" variant="light" />
|
||||
<div class="home-stream-band__inner" style="max-width:1200px;margin:0 auto;padding:26px 24px;display:flex;align-items:center;justify-content:space-between;gap:26px;flex-wrap:wrap;">
|
||||
<div class="home-stream-band__lead" style="display:flex;align-items:center;gap:18px;">
|
||||
<div class="home-stream-band__inner" style="max-width:1200px;margin:0 auto;padding:26px 24px;display:flex;align-items:center;justify-content:space-between;gap:26px;">
|
||||
<div class="home-stream-band__lead" style="display:flex;align-items:center;justify-content:center;gap:18px;">
|
||||
<div style="flex:none;display:inline-flex;align-items:center;justify-content:center;width:54px;height:54px;border-radius:15px;background:rgba(145,70,255,.22);border:1px solid rgba(255,255,255,.18);">
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="#c9b1ff"><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>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:#c9b1ff;margin-bottom:5px;">{{ streamEyebrow }}</div>
|
||||
<div style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:26px;color:#fff;line-height:1.1;">{{ streamTitle }}</div>
|
||||
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px 14px;font-size:14px;color:rgba(255,255,255,.78);margin-top:6px;">
|
||||
<div class="home-stream-band__copy">
|
||||
<div class="home-stream-band__eyebrow" style="font-size:11px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:#c9b1ff;margin-bottom:7px;">{{ streamEyebrow }}</div>
|
||||
<div class="home-stream-band__headline-row" style="display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap;">
|
||||
<span class="home-stream-band__title" style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:clamp(28px,3vw,36px);color:#fff;line-height:1.02;">
|
||||
{{ streamTitle }}
|
||||
</span>
|
||||
<span class="home-stream-band__countdown-chip" aria-label="Countdown bis zum Finale" style="display:inline-flex;align-items:center;gap:10px;padding:10px 16px;border-radius:999px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.14);box-shadow:inset 0 1px 0 rgba(255,255,255,.08);">
|
||||
<span
|
||||
data-dc-ref="streamCountdownLabelRef"
|
||||
class="home-stream-band__countdown-label"
|
||||
style="font-family:'Outfit',sans-serif;font-weight:700;font-size:14px;color:#e9dcff;line-height:1;"
|
||||
>
|
||||
Finale in
|
||||
</span>
|
||||
<span class="home-stream-band__countdown-time" style="display:inline-flex;align-items:baseline;gap:4px;font-family:'Outfit',sans-serif;font-size:clamp(22px,2.4vw,28px);font-weight:800;line-height:1;color:#fff;">
|
||||
<span data-dc-ref="bdRef">00</span><span class="home-stream-band__countdown-unit" style="opacity:.7;">T</span>
|
||||
<span data-dc-ref="bhRef">00</span><span class="home-stream-band__countdown-unit" style="opacity:.45;">:</span>
|
||||
<span data-dc-ref="bmRef">00</span><span class="home-stream-band__countdown-unit" style="opacity:.45;">:</span>
|
||||
<span data-dc-ref="bsRef">00</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="home-stream-band__meta" style="display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:8px 14px;font-size:14px;color:rgba(255,255,255,.78);margin-top:8px;">
|
||||
<span style="display:inline-flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#c9b1ff" stroke-width="2"><rect x="3" y="4.5" width="18" height="17" rx="2.5"/><path d="M3 9h18M8 2.5v4M16 2.5v4" stroke-linecap="round"/></svg>{{ streamMeta }}</span>
|
||||
<span style="width:4px;height:4px;border-radius:50%;background:rgba(255,255,255,.4);"></span>
|
||||
<span>Countdown bis zur grossen Live-Show</span>
|
||||
<span>{{ streamText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="streamLive || streamLocked">
|
||||
<div class="home-stream-band__actions" :class="{ 'home-stream-band__actions--locked': streamLocked }" style="display:flex;flex-direction:column;align-items:center;gap:9px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;font-size:12px;font-weight:600;letter-spacing:.5px;color:rgba(255,255,255,.7);">
|
||||
<span data-dc-ref="streamCountdownLabelRef">Finale startet in</span>
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;font-family:'Outfit',sans-serif;font-weight:700;color:#fff;">
|
||||
<span data-dc-ref="bdRef">00</span><span style="opacity:.6;">T</span>
|
||||
<span data-dc-ref="bhRef">00</span><span style="opacity:.6;">:</span>
|
||||
<span data-dc-ref="bmRef">00</span><span style="opacity:.6;">:</span>
|
||||
<span data-dc-ref="bsRef">00</span>
|
||||
</span>
|
||||
</div>
|
||||
<a v-if="streamLive" :href="publicStreamUrl" target="_blank" rel="noopener" style="display:inline-flex;align-items:center;gap:11px;padding:16px 28px;border-radius:14px;background:#ec3b5a;color:#fff;text-decoration:none;font-family:'Outfit',sans-serif;font-weight:700;font-size:16px;box-shadow:0 12px 30px rgba(236,59,90,.45);" style-hover="transform:translateY(-2px);">
|
||||
<span style="width:9px;height:9px;border-radius:50%;background:#fff;display:inline-block;animation:pulseGlow 1.4s ease-in-out infinite;"></span>
|
||||
Jetzt live · Zum Stream
|
||||
<div class="home-stream-band__actions" :class="{ 'home-stream-band__actions--locked': streamLocked }" style="display:flex;flex-direction:column;align-items:flex-end;gap:9px;">
|
||||
<a v-if="streamButtonHref" :href="streamButtonHref" target="_blank" rel="noopener" style="display:inline-flex;align-items:center;gap:11px;padding:16px 28px;border-radius:14px;background:#ec3b5a;color:#fff;text-decoration:none;font-family:'Outfit',sans-serif;font-weight:700;font-size:16px;box-shadow:0 12px 30px rgba(236,59,90,.45);" style-hover="transform:translateY(-2px);">
|
||||
<span v-if="streamLive" style="width:9px;height:9px;border-radius:50%;background:#fff;display:inline-block;animation:pulseGlow 1.4s ease-in-out infinite;"></span>
|
||||
{{ streamButtonLabel }}
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</a>
|
||||
<div v-else :title="streamLockedTitle" style="display:inline-flex;align-items:center;gap:9px;padding:14px 24px;border-radius:14px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.16);color:rgba(255,255,255,.62);font-family:'Outfit',sans-serif;font-weight:600;font-size:15px;cursor:not-allowed;">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="rgba(255,255,255,.62)"><path d="M17 9V7a5 5 0 0 0-10 0v2H5v13h14V9h-2zM9 7a3 3 0 0 1 6 0v2H9V7zm3 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg>
|
||||
{{ streamLockedLabel }}
|
||||
{{ streamButtonLabel }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -44,16 +53,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import HomeStarField from './HomeStarField.vue'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
publicStreamUrl: string
|
||||
streamEyebrow: string
|
||||
streamTitle: string
|
||||
streamText: string
|
||||
streamMeta: string
|
||||
streamLive: boolean
|
||||
streamLocked: boolean
|
||||
streamLockedLabel: string
|
||||
streamButtonLabel: string
|
||||
streamButtonUrl: string
|
||||
streamLockedTitle: string
|
||||
}>()
|
||||
|
||||
const streamButtonHref = computed(() => props.streamButtonUrl || (props.streamLive ? props.publicStreamUrl : ''))
|
||||
</script>
|
||||
|
||||
@@ -195,12 +195,12 @@ const nominationRailItems = computed(() => props.catOptions.map((option) => {
|
||||
}
|
||||
}))
|
||||
const nominationProgressText = computed(() =>
|
||||
`${nominationReviewItems.value.length} / ${props.catOptions.length} Kategorien ausgefuellt`,
|
||||
`${nominationReviewItems.value.length} / ${props.catOptions.length} Kategorien ausgefüllt`,
|
||||
)
|
||||
const nominationHelperText = computed(() =>
|
||||
nominationTotalLinks.value > 0
|
||||
? `${nominationTotalLinks.value} Link${nominationTotalLinks.value === 1 ? '' : 's'} bereit. Leere Kategorien werden uebersprungen.`
|
||||
: 'Leere Kategorien kannst du einfach ueberspringen.',
|
||||
? `${nominationTotalLinks.value} Link${nominationTotalLinks.value === 1 ? '' : 's'} bereit. Leere Kategorien werden übersprungen.`
|
||||
: 'Leere Kategorien kannst du einfach überspringen.',
|
||||
)
|
||||
const isLastNominationCategory = computed(() => {
|
||||
const activeIndex = props.catOptions.findIndex((option) => option.id === nominationCatValue.value)
|
||||
@@ -329,7 +329,7 @@ function stripCategoryIcon(value: string) {
|
||||
<div class="home-modal__show" style="padding:38px 40px 40px;">
|
||||
<div style="display:inline-flex;align-items:center;gap:8px;padding:5px 14px;border-radius:999px;background:#f3eefb;color:#a98ddb;font-size:11px;font-weight:700;letter-spacing:1px;text-transform:uppercase;margin-bottom:18px;">Award-Show</div>
|
||||
<h3 style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:28px;line-height:1.14;margin:0 0 10px;color:#3f3556;">Sei live dabei ✦</h3>
|
||||
<p style="font-size:15.5px;line-height:1.6;color:#7d7491;margin:0 0 24px;">Die grosse Live-Show findet am <strong style="color:#5f44ad;">{{ props.formatShowDate() }}</strong> statt. Aktiviere eine Erinnerung, damit du nichts verpasst.</p>
|
||||
<p style="font-size:15.5px;line-height:1.6;color:#7d7491;margin:0 0 24px;">Die große Live-Show findet am <strong style="color:#5f44ad;">{{ props.formatShowDate() }}</strong> statt. Aktiviere eine Erinnerung, damit du nichts verpasst.</p>
|
||||
<div class="home-modal__reminder-form" style="display:flex;gap:10px;margin-bottom:14px;">
|
||||
<input data-dc-ref="emailRef" type="email" placeholder="deine@email.de" style="flex:1;padding:14px 16px;border-radius:12px;border:1.5px solid #e6dcf6;background:#fbf9ff;font-family:'Outfit',sans-serif;font-size:15px;color:#3f3556;outline:none;" style-focus="border-color:#8b6cdb;" />
|
||||
<button @click="props.submitReminder" style="flex:none;padding:14px 24px;border-radius:12px;border:none;background:linear-gradient(135deg,#8b6cdb,#7355c8);color:#fff;font-family:'Outfit',sans-serif;font-weight:600;font-size:15px;cursor:pointer;box-shadow:0 10px 22px rgba(124,86,196,.3);" style-hover="transform:translateY(-2px);">Erinnern</button>
|
||||
@@ -384,12 +384,12 @@ function stripCategoryIcon(value: string) {
|
||||
:helper-text="nominationHelperText"
|
||||
:actions="nominationReviewMode
|
||||
? [
|
||||
{ label: 'Zurueck', tone: 'secondary', onClick: closeNominationReview },
|
||||
{ label: 'Zurück', tone: 'secondary', onClick: closeNominationReview },
|
||||
{ label: props.submitting ? 'Speichert ...' : 'Nominierungen einreichen', disabled: props.submitting || nominationHasErrors || nominationTotalLinks === 0, onClick: submitNominationDrafts },
|
||||
]
|
||||
: [
|
||||
{ label: 'Zurueck', tone: 'secondary', disabled: nominationCatValue === props.catOptions[0]?.id, onClick: goToPreviousNominationCategory },
|
||||
{ label: isLastNominationCategory ? 'Nominierungen pruefen' : 'Weiter', disabled: nominationHasErrors, onClick: isLastNominationCategory ? openNominationReview : goToNextNominationCategory },
|
||||
{ label: 'Zurück', tone: 'secondary', disabled: nominationCatValue === props.catOptions[0]?.id, onClick: goToPreviousNominationCategory },
|
||||
{ label: isLastNominationCategory ? 'Nominierungen prüfen' : 'Weiter', disabled: nominationHasErrors, onClick: isLastNominationCategory ? openNominationReview : goToNextNominationCategory },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
@@ -436,7 +436,7 @@ function stripCategoryIcon(value: string) {
|
||||
<label style="display:block;font-size:13px;font-weight:700;color:#5f44ad;margin-bottom:6px;">Kategorie</label>
|
||||
<HomeSelectDropdown
|
||||
v-model="clipCatValue"
|
||||
label="Kategorie fuer Clip auswaehlen"
|
||||
label="Kategorie für Clip auswählen"
|
||||
:options="props.catOptions"
|
||||
@change="handleClipCategoryChange"
|
||||
/>
|
||||
|
||||
@@ -66,8 +66,10 @@ const {
|
||||
phasePrimaryActionStyle,
|
||||
streamEyebrow,
|
||||
streamTitle,
|
||||
streamText,
|
||||
streamMeta,
|
||||
streamLockedLabel,
|
||||
streamButtonLabel,
|
||||
streamButtonUrl,
|
||||
streamLockedTitle,
|
||||
statOneValue,
|
||||
statOneLabel,
|
||||
@@ -93,6 +95,7 @@ const {
|
||||
clipSubmitStyle,
|
||||
archiveYears,
|
||||
selectedArchive,
|
||||
winnerShowcaseYear,
|
||||
winnerShowcase,
|
||||
activeCat,
|
||||
submitted,
|
||||
@@ -128,7 +131,6 @@ const {
|
||||
onCloseArchive,
|
||||
archiveYearButtonStyle,
|
||||
winnerPlatformStyle,
|
||||
winnerPlatformKey,
|
||||
winnerPlatformLabel,
|
||||
privacyModalStop,
|
||||
accountModalStop,
|
||||
@@ -239,10 +241,12 @@ const { setRootEl, landingLoaderVisible, stickyCountdownVisible, handleClipSubmi
|
||||
:phase-primary-action-style="phasePrimaryActionStyle"
|
||||
:stream-eyebrow="streamEyebrow"
|
||||
:stream-title="streamTitle"
|
||||
:stream-text="streamText"
|
||||
:stream-meta="streamMeta"
|
||||
:stream-live="streamLive"
|
||||
:stream-locked="streamLocked"
|
||||
:stream-locked-label="streamLockedLabel"
|
||||
:stream-button-label="streamButtonLabel"
|
||||
:stream-button-url="streamButtonUrl"
|
||||
:stream-locked-title="streamLockedTitle"
|
||||
:stat-one-value="statOneValue"
|
||||
:stat-one-label="statOneLabel"
|
||||
@@ -273,10 +277,10 @@ const { setRootEl, landingLoaderVisible, stickyCountdownVisible, handleClipSubmi
|
||||
:on-timeline-final-action="onTimelineFinalAction"
|
||||
/>
|
||||
|
||||
<HomeCategoriesSection :landing-categories="landingCategories" />
|
||||
<HomeCategoriesSection :site-content="siteContent" :landing-categories="landingCategories" />
|
||||
|
||||
<HomeWinnerShowcaseSection
|
||||
:selected-archive="selectedArchive"
|
||||
:winner-showcase-year="winnerShowcaseYear"
|
||||
:winner-showcase="winnerShowcase"
|
||||
:initials-for="initialsFor"
|
||||
:on-open-archive="onOpenArchive"
|
||||
@@ -356,7 +360,6 @@ const { setRootEl, landingLoaderVisible, stickyCountdownVisible, handleClipSubmi
|
||||
:set-archive-year="setArchiveYear"
|
||||
:archive-year-button-style="archiveYearButtonStyle"
|
||||
:winner-platform-style="winnerPlatformStyle"
|
||||
:winner-platform-key="winnerPlatformKey"
|
||||
:winner-platform-label="winnerPlatformLabel"
|
||||
:privacy-modal-open="privacyModalOpen"
|
||||
:privacy-content-html="privacyContentHtml"
|
||||
|
||||
@@ -59,7 +59,6 @@ defineProps<{
|
||||
setArchiveYear: (year: number) => Promise<void>
|
||||
archiveYearButtonStyle: (active: boolean) => string
|
||||
winnerPlatformStyle: (url: string) => string
|
||||
winnerPlatformKey: (url: string) => string
|
||||
winnerPlatformLabel: (url: string) => string
|
||||
privacyModalOpen: boolean
|
||||
privacyContentHtml: string
|
||||
@@ -133,7 +132,6 @@ defineProps<{
|
||||
:set-archive-year="setArchiveYear"
|
||||
:archive-year-button-style="archiveYearButtonStyle"
|
||||
:winner-platform-style="winnerPlatformStyle"
|
||||
:winner-platform-key="winnerPlatformKey"
|
||||
:winner-platform-label="winnerPlatformLabel"
|
||||
/>
|
||||
|
||||
|
||||
@@ -11,18 +11,18 @@ const props = defineProps<{
|
||||
|
||||
<template>
|
||||
<section class="home-missing-votes">
|
||||
<p class="home-vote-picker__eyebrow">Pruefen</p>
|
||||
<h4>Nicht alle Kategorien gewaehlt</h4>
|
||||
<p class="home-vote-picker__eyebrow">Prüfen</p>
|
||||
<h4>Nicht alle Kategorien gewählt</h4>
|
||||
<p>
|
||||
In {{ props.missingCategories.length }} Kategorien fehlt noch eine Stimme. Du kannst trotzdem speichern
|
||||
oder zurueckgehen und weitere Kategorien auswaehlen.
|
||||
oder zurückgehen und weitere Kategorien auswählen.
|
||||
</p>
|
||||
<div class="home-missing-votes__chips">
|
||||
<span v-for="category in props.missingCategories" :key="category">{{ category }}</span>
|
||||
</div>
|
||||
<div v-if="!props.hideActions" class="home-missing-votes__actions">
|
||||
<button type="button" class="home-wizard-footer__button home-wizard-footer__button--secondary" @click="props.onBack">
|
||||
Zurueck zum Voting
|
||||
Zurück zum Voting
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
export interface HomeNominationReviewItem {
|
||||
categoryIndex: number
|
||||
categoryName: string
|
||||
@@ -11,36 +13,73 @@ const props = defineProps<{
|
||||
totalCategories: number
|
||||
onEditCategory: (index: number) => void
|
||||
}>()
|
||||
|
||||
const skippedCategoryCount = computed(() =>
|
||||
Math.max(0, props.totalCategories - props.items.length),
|
||||
)
|
||||
|
||||
const reviewStats = computed(() => [
|
||||
{
|
||||
label: 'Links bereit',
|
||||
value: `${props.totalLinks}`,
|
||||
},
|
||||
{
|
||||
label: 'Kategorien gefuellt',
|
||||
value: `${props.items.length}/${props.totalCategories}`,
|
||||
},
|
||||
{
|
||||
label: 'Werden uebersprungen',
|
||||
value: `${skippedCategoryCount.value}`,
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="home-nomination-review">
|
||||
<div class="home-nomination-review__hero">
|
||||
<p class="home-vote-picker__eyebrow">Pruefen</p>
|
||||
<p class="home-vote-picker__eyebrow">Prüfen</p>
|
||||
<h4>Nominierungen einreichen?</h4>
|
||||
<p>
|
||||
Du reichst {{ props.totalLinks }} Links in {{ props.items.length }} von
|
||||
{{ props.totalCategories }} Kategorien ein. Leere Kategorien werden uebersprungen.
|
||||
{{ props.totalCategories }} Kategorien ein. Leere Kategorien werden übersprungen.
|
||||
</p>
|
||||
|
||||
<div class="home-nomination-review__stats">
|
||||
<div v-for="stat in reviewStats" :key="stat.label" class="home-nomination-review__stat">
|
||||
<span>{{ stat.label }}</span>
|
||||
<strong>{{ stat.value }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="props.items.length" class="home-nomination-review__list">
|
||||
<article v-for="item in props.items" :key="item.categoryName" class="home-nomination-review__item">
|
||||
<div>
|
||||
<h5>{{ item.categoryName }}</h5>
|
||||
<div class="home-nomination-review__item-header">
|
||||
<div class="home-nomination-review__item-copy">
|
||||
<p class="home-nomination-review__item-eyebrow">Kategorie</p>
|
||||
<h5>{{ item.categoryName }}</h5>
|
||||
</div>
|
||||
<span>{{ item.links.length }} Link{{ item.links.length === 1 ? '' : 's' }}</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="link in item.links" :key="link">{{ link }}</li>
|
||||
<ul class="home-nomination-review__links">
|
||||
<li v-for="link in item.links" :key="link">
|
||||
<span class="home-nomination-review__link-dot" aria-hidden="true"></span>
|
||||
<span>{{ link }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" @click="props.onEditCategory(item.categoryIndex)">
|
||||
<button class="home-nomination-review__edit" type="button" @click="props.onEditCategory(item.categoryIndex)">
|
||||
Bearbeiten
|
||||
</button>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div v-else class="home-vote-picker__empty">
|
||||
Noch keine Links eingetragen.
|
||||
<div v-else class="home-nomination-review__empty">
|
||||
<div class="home-nomination-review__empty-icon" aria-hidden="true">✦</div>
|
||||
<div class="home-nomination-review__empty-copy">
|
||||
<p class="home-nomination-review__empty-eyebrow">Noch leer</p>
|
||||
<h5>Noch keine Links eingetragen.</h5>
|
||||
<p>Fülle links mindestens eine Kategorie aus, dann erscheint hier deine finale Einreichungsübersicht.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -51,8 +51,8 @@ const props = defineProps<{
|
||||
class="home-nomination-pane__add-link"
|
||||
@click="props.onAddLinkField"
|
||||
>
|
||||
<span>+ Weiteren Link hinzufuegen</span>
|
||||
<small>Noch {{ props.remainingLinkCount }} moeglich</small>
|
||||
<span>+ Weiteren Link hinzufügen</span>
|
||||
<small>Noch {{ props.remainingLinkCount }} möglich</small>
|
||||
</button>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import { privacyContentToHtml } from '../../lib/privacyContent'
|
||||
|
||||
@@ -41,12 +41,29 @@ const props = defineProps<{
|
||||
platformKey: (platform: string | null | undefined) => string
|
||||
}>()
|
||||
|
||||
const openFaqIndex = ref<number | null>(null)
|
||||
|
||||
function toggleFaq(index: number) {
|
||||
openFaqIndex.value = openFaqIndex.value === index ? null : index
|
||||
}
|
||||
|
||||
const activeFooterLink = ref<FooterLink | null>(null)
|
||||
const linkCopied = ref(false)
|
||||
const activeFooterHtml = computed(() => privacyContentToHtml(activeFooterLink.value?.content || ''))
|
||||
const safeNewsletterUrl = computed(() =>
|
||||
isSafePublicUrl(props.siteContent.newsletterUrl) ? props.siteContent.newsletterUrl : '',
|
||||
)
|
||||
const isSponsorFooterModal = computed(() => activeFooterLink.value?.key === 'sponsors')
|
||||
const footerModalEyebrow = computed(() =>
|
||||
isSponsorFooterModal.value ? 'Support the show' : 'Footer Seite',
|
||||
)
|
||||
const footerModalIntro = computed(() => {
|
||||
if (isSponsorFooterModal.value) {
|
||||
return 'Partner, Sponsoren und kreative Wegbegleiter machen diese Award-Nacht erst möglich.'
|
||||
}
|
||||
|
||||
return 'Alle Infos kommen direkt aus dem Landingpage-Content und bleiben dadurch jederzeit aktuell.'
|
||||
})
|
||||
|
||||
function openFooterLink(event: Event, link: FooterLink) {
|
||||
if (link.key === 'showacts' && props.onShowactApply) {
|
||||
@@ -89,6 +106,14 @@ function isSafePublicUrl(value: string | null | undefined) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
watch(activeFooterLink, (link) => {
|
||||
document.body.style.overflow = link ? 'hidden' : ''
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.body.style.overflow = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -155,10 +180,21 @@ function isSafePublicUrl(value: string | null | undefined) {
|
||||
<p style="font-size:17px;color:var(--muted,#8a8398);max-width:520px;margin:0 auto;">Alles, was du über Nominierung, Voting und die Show wissen musst.</p>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:14px;">
|
||||
<details v-for="item in props.faqItems" :key="item.question" style="background:#ffffff;border:1px solid var(--line,rgba(124,86,196,.16));border-radius:16px;padding:4px 22px;box-shadow:0 8px 24px rgba(124,86,196,.07);">
|
||||
<summary style="display:flex;align-items:center;justify-content:space-between;gap:16px;cursor:pointer;list-style:none;padding:18px 0;font-family:'Outfit',sans-serif;font-weight:600;font-size:18px;color:var(--ink,#3f3556);">{{ item.question }}<span style="flex:none;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;background:#f1ecfb;color:#8b6cdb;font-size:16px;transition:transform .2s;">+</span></summary>
|
||||
<p style="margin:0 0 18px;font-size:15px;line-height:1.65;color:var(--muted,#8a8398);">{{ item.answer }}</p>
|
||||
</details>
|
||||
<div
|
||||
v-for="(item, index) in props.faqItems"
|
||||
:key="item.question"
|
||||
class="faq-item"
|
||||
:class="{ 'faq-item--open': openFaqIndex === index }"
|
||||
>
|
||||
<button class="faq-trigger" @click="toggleFaq(index)" :aria-expanded="openFaqIndex === index">
|
||||
<span class="faq-question">
|
||||
<span class="faq-star" aria-hidden="true">✦</span>
|
||||
{{ item.question }}
|
||||
</span>
|
||||
<span class="faq-toggle" aria-hidden="true">{{ openFaqIndex === index ? '×' : '+' }}</span>
|
||||
</button>
|
||||
<p v-show="openFaqIndex === index" class="faq-answer">{{ item.answer }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -199,16 +235,35 @@ function isSafePublicUrl(value: string | null | undefined) {
|
||||
|
||||
<template v-if="activeFooterLink">
|
||||
<div class="home-modal-overlay" @click="closeFooterLink" style="position:fixed;inset:0;z-index:420;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 class="home-modal" @click.stop style="position:relative;width:100%;max-width:720px;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);">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:18px;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;">Footer Seite</div>
|
||||
<h2 style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:26px;margin:0;color:#3f3556;">{{ activeFooterLink.label }}</h2>
|
||||
<div class="home-modal" @click.stop style="position:relative;width:100%;max-width:780px;max-height:88vh;overflow:hidden;display:flex;flex-direction:column;background:linear-gradient(180deg,#fffaff 0%,#ffffff 42%,#faf7ff 100%);border:1px solid rgba(221,208,242,.9);border-radius:32px;box-shadow:0 40px 90px rgba(40,24,70,.4);">
|
||||
<div style="position:relative;display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:28px 32px 24px;border-bottom:1px solid #f1ecfb;flex:none;overflow:hidden;background:radial-gradient(circle at top left,rgba(193,163,255,.22),transparent 42%),radial-gradient(circle at top right,rgba(255,191,219,.2),transparent 36%),linear-gradient(135deg,#fbf7ff 0%,#ffffff 54%,#fff7fb 100%);">
|
||||
<div style="position:absolute;right:88px;top:20px;font-size:14px;color:#d6c3fb;">✦ ✦ ✦</div>
|
||||
<div style="max-width:560px;position:relative;z-index:1;">
|
||||
<div style="display:inline-flex;align-items:center;gap:8px;padding:8px 12px;border-radius:999px;background:rgba(255,255,255,.82);border:1px solid rgba(221,208,242,.95);box-shadow:0 10px 24px rgba(124,86,196,.08);font-size:11px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:#8b6cdb;margin-bottom:14px;">
|
||||
<span style="font-size:12px;">✦</span>
|
||||
{{ footerModalEyebrow }}
|
||||
</div>
|
||||
<h2 style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:clamp(28px,4vw,34px);line-height:1.05;margin:0;color:#3f3556;">{{ activeFooterLink.label }}</h2>
|
||||
<p style="margin:14px 0 0;font-family:'Outfit',sans-serif;font-size:15px;line-height:1.7;color:#7d7491;">
|
||||
{{ footerModalIntro }}
|
||||
</p>
|
||||
</div>
|
||||
<button @click="closeFooterLink" 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;" style-hover="background:#e6dcf6;">✕</button>
|
||||
<button type="button" @click="closeFooterLink" style="position:relative;z-index:1;width:42px;height:42px;border-radius:50%;border:1px solid rgba(221,208,242,.95);background:rgba(255,255,255,.92);color:#8b6cdb;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 12px 28px rgba(124,86,196,.12);" style-hover="background:#f3ebff;">✕</button>
|
||||
</div>
|
||||
<div style="overflow-y:auto;padding:28px 32px;flex:1;font-family:'Outfit',sans-serif;font-size:14px;line-height:1.7;color:#6f6685;">
|
||||
<div v-if="activeFooterHtml" class="home-footer-page-content" v-html="activeFooterHtml" />
|
||||
<div style="overflow-y:auto;padding:28px 32px 32px;flex:1;font-family:'Outfit',sans-serif;font-size:14px;line-height:1.7;color:#6f6685;">
|
||||
<div
|
||||
v-if="isSponsorFooterModal"
|
||||
style="display:grid;gap:14px;margin-bottom:18px;"
|
||||
>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:10px;">
|
||||
<span class="home-sponsor-pill">Für Newcomer, Rising Stars und Legenden</span>
|
||||
<span class="home-sponsor-pill">Community, Show und Sichtbarkeit</span>
|
||||
<span class="home-sponsor-pill">Gemeinsam tragen wir die Bühne</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeFooterHtml" class="home-footer-modal-surface">
|
||||
<div class="home-footer-page-content" v-html="activeFooterHtml" />
|
||||
</div>
|
||||
<p v-else style="margin:0;padding:14px 16px;border-radius:16px;border:1px solid #fde68a;background:#fffbeb;color:#92400e;font-weight:600;">
|
||||
Für diese Footer-Seite ist noch kein Inhalt hinterlegt.
|
||||
</p>
|
||||
@@ -219,6 +274,89 @@ function isSafePublicUrl(value: string | null | undefined) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* FAQ accordion */
|
||||
.faq-item {
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(124, 86, 196, 0.16);
|
||||
border-radius: 16px;
|
||||
padding: 4px 22px;
|
||||
box-shadow: 0 8px 24px rgba(124, 86, 196, 0.07);
|
||||
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.faq-item--open {
|
||||
border-color: rgba(139, 108, 219, 0.45);
|
||||
box-shadow: 0 8px 28px rgba(124, 86, 196, 0.15);
|
||||
background: linear-gradient(135deg, #fdfbff 0%, #f5f0ff 100%);
|
||||
}
|
||||
|
||||
.faq-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
padding: 18px 0;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: #3f3556;
|
||||
text-align: left;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.faq-item--open .faq-trigger {
|
||||
color: #6b44c0;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.faq-star {
|
||||
flex: none;
|
||||
font-size: 11px;
|
||||
color: #c4aef7;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.faq-item--open .faq-star {
|
||||
color: #8b6cdb;
|
||||
}
|
||||
|
||||
.faq-toggle {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
background: #f1ecfb;
|
||||
color: #8b6cdb;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.faq-item--open .faq-toggle {
|
||||
background: #e4d8ff;
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
margin: 0 0 18px;
|
||||
font-size: 15px;
|
||||
line-height: 1.65;
|
||||
color: #8a8398;
|
||||
padding-left: 21px;
|
||||
border-left: 2px solid #d8c8ff;
|
||||
}
|
||||
|
||||
.home-footer-page-content :deep(p) {
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
@@ -238,4 +376,37 @@ function isSafePublicUrl(value: string | null | undefined) {
|
||||
.home-footer-page-content :deep(li) {
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.home-footer-modal-surface {
|
||||
border: 1px solid rgba(221, 208, 242, 0.9);
|
||||
border-radius: 24px;
|
||||
padding: 22px 22px 20px;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(255, 206, 232, 0.16), transparent 26%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(249, 244, 255, 0.96) 100%);
|
||||
box-shadow: 0 20px 44px rgba(124, 86, 196, 0.1);
|
||||
}
|
||||
|
||||
.home-sponsor-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(212, 194, 247, 0.95);
|
||||
background:
|
||||
radial-gradient(circle at top, rgba(255, 255, 255, 0.95), rgba(245, 238, 255, 0.98)),
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(243, 235, 255, 0.96));
|
||||
color: #6d55a8;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
box-shadow: 0 12px 26px rgba(124, 86, 196, 0.08);
|
||||
}
|
||||
|
||||
.home-sponsor-pill::before {
|
||||
content: '✦';
|
||||
color: #b089ff;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<div style="text-align:center;margin-bottom:56px;">
|
||||
<div style="font-size:13px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--accent,#8b6cdb);margin-bottom:12px;">✦ Der Ablauf</div>
|
||||
<h2 style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:clamp(32px,4vw,48px);margin:0 0 14px;letter-spacing:0;color:#3f3556;">In vier Schritten auf die Bühne</h2>
|
||||
<p style="font-size:17px;color:var(--muted,#8a8398);max-width:620px;margin:0 auto;">Von Nominierung und Voting über die Aufbereitung bis ganz zum Schluss zur grossen Show.</p>
|
||||
<p style="font-size:17px;color:var(--muted,#8a8398);max-width:620px;margin:0 auto;">Von Nominierung und Voting über die Aufbereitung bis ganz zum Schluss zur großen Show.</p>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:20px;align-items:start;" data-timeline>
|
||||
<div :style="timelineLineStyle"></div>
|
||||
<div class="timeline-grid" style="position:relative;display:grid;gap:20px;align-items:start;" data-timeline>
|
||||
<div :style="timelineLineStyle" class="timeline-connector-line"></div>
|
||||
|
||||
<div style="position:relative;z-index:1;text-align:center;">
|
||||
<div class="timeline-phase" style="position:relative;z-index:1;text-align:center;">
|
||||
<div :style="nominationPhase ? 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:linear-gradient(135deg,#2bbd6e,#1f9d5a);display:flex;align-items:center;justify-content:center;box-shadow:0 0 0 6px rgba(43,189,110,.18),0 8px 20px rgba(31,157,90,.35);border:4px solid #f4eefb;animation:pulseGlow 2.2s ease-in-out infinite;' : 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:linear-gradient(135deg,#8b6cdb,#7355c8);display:flex;align-items:center;justify-content:center;box-shadow:0 8px 20px rgba(124,86,196,.32);border:4px solid #f4eefb;'">
|
||||
<template v-if="nominationPhase">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="#fff"><path d="M12 17.3l-6.2 3.3 1.2-7-5.1-5 7.1-1 3-6.4 3 6.4 7.1 1-5.1 5 1.2 7z"/></svg>
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;z-index:1;text-align:center;">
|
||||
<div class="timeline-phase" style="position:relative;z-index:1;text-align:center;">
|
||||
<div :style="votingPhase ? 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:linear-gradient(135deg,#2bbd6e,#1f9d5a);display:flex;align-items:center;justify-content:center;box-shadow:0 0 0 6px rgba(43,189,110,.18),0 8px 20px rgba(31,157,90,.35);border:4px solid #f4eefb;animation:pulseGlow 2.2s ease-in-out infinite;' : nominationPhase ? 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:#fff;border:4px solid #e2d6f4;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 12px rgba(124,86,196,.1);' : 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:linear-gradient(135deg,#8b6cdb,#7355c8);display:flex;align-items:center;justify-content:center;box-shadow:0 8px 20px rgba(124,86,196,.32);border:4px solid #f4eefb;'">
|
||||
<template v-if="nominationPhase">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#b9a9dd" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.6 1.4 6.8L12 17.8 5.9 20.4l1.4-6.8L2.2 9l6.9-.7z"/></svg>
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;z-index:1;text-align:center;">
|
||||
<div class="timeline-phase" style="position:relative;z-index:1;text-align:center;">
|
||||
<div :style="nominationPhase || votingPhase ? 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:#fff;border:4px solid #e2d6f4;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 12px rgba(124,86,196,.1);' : preparationPhase ? 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:linear-gradient(135deg,#f7c76a,#b7791f);display:flex;align-items:center;justify-content:center;box-shadow:0 0 0 6px rgba(247,199,106,.18),0 8px 20px rgba(183,121,31,.28);border:4px solid #f4eefb;animation:pulseGlow 2.2s ease-in-out infinite;' : 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:linear-gradient(135deg,#8b6cdb,#7355c8);display:flex;align-items:center;justify-content:center;box-shadow:0 8px 20px rgba(124,86,196,.32);border:4px solid #f4eefb;'">
|
||||
<template v-if="nominationPhase || votingPhase">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#b9a9dd" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
|
||||
@@ -85,7 +85,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;z-index:1;text-align:center;">
|
||||
<div class="timeline-phase" style="position:relative;z-index:1;text-align:center;">
|
||||
<div :style="showPhase ? 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:linear-gradient(135deg,#ec3b5a,#b91c43);display:flex;align-items:center;justify-content:center;box-shadow:0 0 0 6px rgba(236,59,90,.18),0 8px 20px rgba(185,28,67,.28);border:4px solid #f4eefb;animation:pulseGlow 2.2s ease-in-out infinite;' : completedPhase ? 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:linear-gradient(135deg,#8b6cdb,#7355c8);display:flex;align-items:center;justify-content:center;box-shadow:0 8px 20px rgba(124,86,196,.32);border:4px solid #f4eefb;' : 'width:54px;height:54px;margin:0 auto 20px;border-radius:50%;background:#fff;border:4px solid #e2d6f4;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 12px rgba(124,86,196,.1);'">
|
||||
<template v-if="showPhase">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="#fff"><path d="M8 5v14l11-7z"/></svg>
|
||||
@@ -101,7 +101,7 @@
|
||||
<div :style="showPhase ? 'display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:999px;background:#ffe5ec;color:#ec3b5a;font-size:11px;font-weight:700;letter-spacing:.5px;margin-bottom:12px;' : completedPhase ? 'display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:999px;background:#f1ecfb;color:#8b6cdb;font-size:11px;font-weight:700;letter-spacing:.5px;margin-bottom:12px;' : 'display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:999px;background:#f3eefb;color:#a98ddb;font-size:11px;font-weight:700;letter-spacing:.5px;margin-bottom:12px;'">{{ showPhase ? 'JETZT LIVE' : completedPhase ? 'ABGESCHLOSSEN' : 'FINALE' }}</div>
|
||||
<h3 style="font-family:'Outfit',sans-serif;font-weight:700;font-size:21px;margin:0 0 6px;color:#3f3556;">Show</h3>
|
||||
<div :style="showPhase ? 'font-size:13px;font-weight:600;color:#ec3b5a;margin-bottom:12px;' : 'font-size:13px;font-weight:600;color:#a99fc0;margin-bottom:12px;'">{{ formatTimelineRange('show') }}</div>
|
||||
<p style="font-size:14.5px;line-height:1.6;color:#7d7491;margin:0 0 16px;">Die grosse Live-Show auf Twitch: Gewinner werden gekrönt, Clips gezeigt und die Szene feiert gemeinsam.</p>
|
||||
<p style="font-size:14.5px;line-height:1.6;color:#7d7491;margin:0 0 16px;">Die große Live-Show auf Twitch: Gewinner werden gekrönt, Clips gezeigt und die Szene feiert gemeinsam.</p>
|
||||
<a
|
||||
v-if="showPhase"
|
||||
:href="publicStreamUrl"
|
||||
@@ -180,3 +180,35 @@ function finalActionStyle(showPhase: boolean, completedPhase = false) {
|
||||
: "display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:11px 20px;border-radius:11px;background:#f3eefb;color:#a06bd8;border:1px solid #e4d8f6;cursor:pointer;font-family:'Outfit',sans-serif;font-weight:600;font-size:14px;text-decoration:none;"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.timeline-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.timeline-phase {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.timeline-phase > :first-child {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.timeline-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.timeline-connector-line {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.timeline-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -59,18 +59,18 @@ const footerPrimaryLabel = computed(() => {
|
||||
})
|
||||
const footerHelperText = computed(() =>
|
||||
confirmMissingVotes.value
|
||||
? 'Pruefe die offenen Kategorien oder speichere deine aktuelle Auswahl.'
|
||||
? 'Prüfe die offenen Kategorien oder speichere deine aktuelle Auswahl.'
|
||||
: props.readonlyMode
|
||||
? 'Hier siehst du die freigegebenen Kandidat:innen der abgeschlossenen Nominierungsphase.'
|
||||
: props.savedVoteActive
|
||||
? 'Du bearbeitest dein gespeichertes Voting. Speichern ersetzt deine bisherige Auswahl.'
|
||||
: 'Du kannst Kategorien ueberspringen und vor dem Speichern pruefen.',
|
||||
: 'Du kannst Kategorien überspringen und vor dem Speichern prüfen.',
|
||||
)
|
||||
const footerActions = computed(() =>
|
||||
props.readonlyMode
|
||||
? [
|
||||
{
|
||||
label: 'Zurueck',
|
||||
label: 'Zurück',
|
||||
tone: 'secondary' as const,
|
||||
disabled: props.activeCatIndex === 0,
|
||||
onClick: () => props.catList[Math.max(0, props.activeCatIndex - 1)]?.onClick(),
|
||||
@@ -84,12 +84,12 @@ const footerActions = computed(() =>
|
||||
:
|
||||
confirmMissingVotes.value
|
||||
? [
|
||||
{ label: 'Zurueck zum Voting', tone: 'secondary' as const, disabled: props.submitting, onClick: hideMissingVoteConfirm },
|
||||
{ label: 'Zurück zum Voting', tone: 'secondary' as const, disabled: props.submitting, onClick: hideMissingVoteConfirm },
|
||||
{ label: props.submitting ? 'Speichert ...' : submitLabel.value, disabled: props.submitting, onClick: submitConfirmedVote },
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: 'Zurueck',
|
||||
label: 'Zurück',
|
||||
tone: 'secondary' as const,
|
||||
disabled: props.activeCatIndex === 0,
|
||||
onClick: () => props.catList[Math.max(0, props.activeCatIndex - 1)]?.onClick(),
|
||||
@@ -230,17 +230,18 @@ async function submitConfirmedVote() {
|
||||
</article>
|
||||
<article v-else class="home-vote-card" :class="{ 'home-vote-card--selected': nom.selected, 'home-vote-card--missing-clip': !nom.hasClip }">
|
||||
<div v-if="nom.selected" class="home-vote-card__burst" aria-hidden="true">
|
||||
<span class="home-vote-card__burst-flare"></span>
|
||||
<span class="home-vote-card__burst-ring"></span>
|
||||
<span class="home-vote-card__burst-trail home-vote-card__burst-trail--left"></span>
|
||||
<span class="home-vote-card__burst-trail home-vote-card__burst-trail--right"></span>
|
||||
<span class="home-vote-card__burst-trail home-vote-card__burst-trail--up"></span>
|
||||
<span class="home-vote-card__burst-star home-vote-card__burst-star--up">★</span>
|
||||
<span class="home-vote-card__burst-star home-vote-card__burst-star--left">✦</span>
|
||||
<span class="home-vote-card__burst-star home-vote-card__burst-star--right">✦</span>
|
||||
<span class="home-vote-card__burst-star home-vote-card__burst-star--left-small">✧</span>
|
||||
<span class="home-vote-card__burst-star home-vote-card__burst-star--right-small">✧</span>
|
||||
<span class="home-vote-card__burst-star home-vote-card__burst-star--top-small">✦</span>
|
||||
<span class="home-vote-card__sky-glow"></span>
|
||||
<span class="home-vote-card__sky-sweep"></span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--one">✦</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--two">✧</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--three">★</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--four">✦</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--five">✧</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--six">✦</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--seven">✧</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--eight">★</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--nine">✦</span>
|
||||
<span class="home-vote-card__sky-star home-vote-card__sky-star--ten">✧</span>
|
||||
</div>
|
||||
<div class="home-vote-card__top">
|
||||
<div class="home-vote-card__identity">
|
||||
|
||||
@@ -1,63 +1,8 @@
|
||||
<template>
|
||||
<section id="nominierte" style="position:relative;overflow:hidden;background:linear-gradient(115deg,#241640 0%,#3a2168 48%,#5b3aa0 100%);border-top:1px solid rgba(255,255,255,.12);border-bottom:1px solid rgba(255,255,255,.12);">
|
||||
<HomeStarField :count="17" variant="light" />
|
||||
<div class="home-section" style="max-width:1200px;margin:0 auto;padding:90px 24px;">
|
||||
<div style="display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:46px;">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:#ff5fa2;margin-bottom:12px;">★ Die Stars</div>
|
||||
<h2 style="font-family:'Fredoka',sans-serif;font-weight:700;font-size:clamp(32px,4vw,48px);margin:0;letter-spacing:0;color:#fff6fb;">Gewinner {{ selectedArchive.year }}</h2>
|
||||
</div>
|
||||
<button @click="onOpenArchive" style="display:inline-flex;align-items:center;gap:8px;padding:12px 22px;border-radius:999px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.12);color:#fff6fb;font-weight:600;font-size:15px;cursor:pointer;" style-hover="transform:translateY(-2px);background:rgba(255,255,255,.09);">Archiv ansehen →</button>
|
||||
</div>
|
||||
<div class="home-winner-rail" style="overflow-x:auto;overflow-y:hidden;padding:0 18px 12px 0;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.34) rgba(255,255,255,.06);scroll-snap-type:x mandatory;scroll-padding-left:0;">
|
||||
<div class="home-winner-track" style="display:flex;gap:22px;width:max-content;">
|
||||
<article v-for="winner in winnerShowcase" :key="`${selectedArchive.year}-${winner.category}`" class="home-winner-card" style="flex:none;width:390px;border-radius:24px;overflow:hidden;background:#22123a;border:1px solid rgba(255,255,255,.12);">
|
||||
<div style="position:relative;padding:24px 24px 20px;background:radial-gradient(circle at 28% 24%,rgba(255,95,162,.22),transparent 28%),radial-gradient(circle at 74% 78%,rgba(160,107,255,.2),transparent 32%),linear-gradient(180deg,#26133d 0%,#201132 100%);">
|
||||
<div style="display:flex;align-items:center;gap:18px;margin-top:28px;margin-bottom:20px;min-height:120px;">
|
||||
<div style="flex:none;width:104px;height:104px;border-radius:28px;background:linear-gradient(135deg,#ffe1a3,#e7b13e);display:flex;align-items:center;justify-content:center;color:#2f1b00;font-family:'Fredoka',sans-serif;font-size:34px;font-weight:700;box-shadow:0 18px 42px rgba(0,0,0,.22);">{{ initialsFor(winner.name) }}</div>
|
||||
<div style="min-width:0;">
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:#ffd27a;margin-bottom:8px;">{{ winner.category }}</div>
|
||||
<div style="font-family:'Fredoka',sans-serif;font-weight:600;font-size:24px;line-height:1.15;color:#fff6fb;">{{ winner.name }}</div>
|
||||
<div style="font-size:13px;color:#c9b8da;margin-top:6px;">{{ winner.handle }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<a :href="winner.url" target="_blank" rel="noopener" style="display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:13px 18px;border-radius:14px;background:linear-gradient(135deg,#ff5fa2,#a06bff);color:#fff;font-family:'Fredoka',sans-serif;font-weight:600;font-size:14px;text-decoration:none;box-sizing:border-box;">{{ winner.platform }}</a>
|
||||
</div>
|
||||
<div style="padding:12px 24px 24px;background:#1b0d2d;">
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:1.6px;text-transform:uppercase;color:#ffb9d4;margin-bottom:10px;">{{ winner.hasClip ? winner.clipPlatform : 'Archivierter Gewinner' }}</div>
|
||||
<iframe
|
||||
v-if="winner.clipEmbedUrl"
|
||||
:src="winner.clipEmbedUrl"
|
||||
:title="winner.clipEmbedTitle"
|
||||
loading="lazy"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
style="width:100%;aspect-ratio:16/9;border:0;border-radius:18px;background:#12091d;box-shadow:0 16px 34px rgba(0,0,0,.22);"
|
||||
/>
|
||||
<a
|
||||
v-else-if="winner.clipUrl"
|
||||
:href="winner.clipUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
referrerpolicy="no-referrer"
|
||||
style="border-radius:18px;background:#12091d;border:1px solid rgba(255,255,255,.08);aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;color:#fff6fb;font-family:'Fredoka',sans-serif;font-size:17px;text-decoration:none;text-align:center;padding:18px;"
|
||||
>
|
||||
{{ winner.clipTitle }}
|
||||
</a>
|
||||
<div v-else style="border-radius:18px;overflow:hidden;background:#12091d;border:1px solid rgba(255,255,255,.08);aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;color:#c9b8da;font-family:'Fredoka',sans-serif;font-size:18px;">{{ winner.platform }} · {{ winner.handle }}</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import HomeStarField from './HomeStarField.vue'
|
||||
|
||||
type WinnerCard = {
|
||||
categoryGroup: string
|
||||
category: string
|
||||
name: string
|
||||
handle: string
|
||||
@@ -72,12 +17,382 @@ type WinnerCard = {
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
selectedArchive: {
|
||||
year: number
|
||||
winners: WinnerCard[]
|
||||
}
|
||||
winnerShowcaseYear: number
|
||||
winnerShowcase: WinnerCard[]
|
||||
initialsFor: (value: string) => string
|
||||
onOpenArchive: () => Promise<void>
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="nominierte" class="home-winner-section">
|
||||
<HomeStarField :count="17" variant="light" />
|
||||
<div class="home-section home-winner-section__inner">
|
||||
<div class="home-winner-section__header">
|
||||
<div>
|
||||
<div class="home-winner-section__eyebrow">★ Die Stars</div>
|
||||
<h2 class="home-winner-section__title">Gewinner {{ winnerShowcaseYear }}</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="home-winner-section__archive-button"
|
||||
style-hover="transform:translateY(-2px);background:rgba(255,255,255,.09);"
|
||||
@click="onOpenArchive"
|
||||
>
|
||||
Archiv ansehen →
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="winnerShowcase.length > 0" class="home-winner-rail">
|
||||
<div class="home-winner-track">
|
||||
<article
|
||||
v-for="winner in winnerShowcase"
|
||||
:key="`${winnerShowcaseYear}-${winner.category}-${winner.handle}`"
|
||||
class="home-winner-card"
|
||||
>
|
||||
<div class="home-winner-card__spotlight">
|
||||
<div class="home-winner-card__portrait" aria-hidden="true">
|
||||
{{ initialsFor(winner.name) }}
|
||||
</div>
|
||||
<div class="home-winner-card__status">Gewinner</div>
|
||||
<div class="home-winner-card__plaque">
|
||||
<div class="home-winner-card__category">{{ winner.categoryGroup }}</div>
|
||||
<h3 class="home-winner-card__name">{{ winner.name }}</h3>
|
||||
<p class="home-winner-card__subcategory">{{ winner.category }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="home-winner-card__actions">
|
||||
<a
|
||||
:href="winner.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="home-winner-card__profile"
|
||||
>
|
||||
{{ winner.platform }}
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="home-winner-empty">
|
||||
<div class="home-winner-empty__badge">★</div>
|
||||
<div>
|
||||
<h3>Noch keine Gewinner veröffentlicht</h3>
|
||||
<p>Sobald ein Award-Jahr im Adminbereich freigegeben ist, erscheinen die Gewinner hier.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home-winner-section{
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
border-top:1px solid rgba(255,255,255,.12);
|
||||
border-bottom:1px solid rgba(255,255,255,.12);
|
||||
background:
|
||||
radial-gradient(circle at 20% 12%,rgba(255,95,162,.16),transparent 30%),
|
||||
radial-gradient(circle at 82% 24%,rgba(201,177,255,.18),transparent 34%),
|
||||
linear-gradient(115deg,#241640 0%,#3a2168 48%,#5b3aa0 100%);
|
||||
}
|
||||
|
||||
.home-winner-section__inner{
|
||||
position:relative;
|
||||
z-index:1;
|
||||
max-width:1200px;
|
||||
margin:0 auto;
|
||||
padding:90px 24px;
|
||||
}
|
||||
|
||||
.home-winner-section__header{
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
align-items:flex-end;
|
||||
justify-content:space-between;
|
||||
gap:20px;
|
||||
margin-bottom:46px;
|
||||
}
|
||||
|
||||
.home-winner-section__eyebrow{
|
||||
margin-bottom:12px;
|
||||
color:#ff5fa2;
|
||||
font-size:13px;
|
||||
font-weight:700;
|
||||
letter-spacing:1.5px;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
.home-winner-section__title{
|
||||
margin:0;
|
||||
color:#fff6fb;
|
||||
font-family:'Fredoka',sans-serif;
|
||||
font-size:clamp(32px,4vw,48px);
|
||||
font-weight:700;
|
||||
letter-spacing:0;
|
||||
}
|
||||
|
||||
.home-winner-section__archive-button{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
gap:8px;
|
||||
min-height:48px;
|
||||
padding:12px 22px;
|
||||
border:1px solid rgba(255,255,255,.12);
|
||||
border-radius:999px;
|
||||
background:rgba(255,255,255,.05);
|
||||
color:#fff6fb;
|
||||
cursor:pointer;
|
||||
font-family:'Outfit',sans-serif;
|
||||
font-size:15px;
|
||||
font-weight:700;
|
||||
transition:transform .18s ease, background .18s ease, border-color .18s ease;
|
||||
}
|
||||
|
||||
.home-winner-rail{
|
||||
overflow-x:auto;
|
||||
overflow-y:hidden;
|
||||
padding:0 18px 14px 0;
|
||||
scroll-padding-left:0;
|
||||
scrollbar-color:rgba(255,255,255,.34) rgba(255,255,255,.06);
|
||||
scrollbar-width:thin;
|
||||
}
|
||||
|
||||
.home-winner-track{
|
||||
display:flex;
|
||||
gap:24px;
|
||||
width:max-content;
|
||||
scroll-snap-type:x mandatory;
|
||||
}
|
||||
|
||||
.home-winner-card{
|
||||
flex:none;
|
||||
width:374px;
|
||||
min-height:478px;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
justify-content:space-between;
|
||||
border:1px solid rgba(255,255,255,.13);
|
||||
border-radius:28px;
|
||||
background:
|
||||
radial-gradient(circle at 22% 15%,rgba(255,95,162,.14),transparent 30%),
|
||||
radial-gradient(circle at 82% 84%,rgba(160,107,255,.17),transparent 32%),
|
||||
linear-gradient(180deg,rgba(47,29,68,.96),rgba(34,21,50,.98));
|
||||
box-shadow:0 24px 62px rgba(16,7,32,.28);
|
||||
overflow:hidden;
|
||||
scroll-snap-align:start;
|
||||
scroll-snap-stop:always;
|
||||
}
|
||||
|
||||
.home-winner-card__spotlight{
|
||||
position:relative;
|
||||
display:grid;
|
||||
min-height:372px;
|
||||
place-items:center;
|
||||
padding:28px 26px 34px;
|
||||
}
|
||||
|
||||
.home-winner-card__portrait{
|
||||
display:grid;
|
||||
place-items:center;
|
||||
width:min(248px,100%);
|
||||
aspect-ratio:1;
|
||||
border-radius:34%;
|
||||
color:#3a2d30;
|
||||
background:
|
||||
radial-gradient(circle at 32% 28%,rgba(255,244,203,.78),transparent 24%),
|
||||
linear-gradient(145deg,#ffe39a 0%,#f4bf4f 58%,#d6a342 100%);
|
||||
box-shadow:
|
||||
0 24px 58px rgba(0,0,0,.28),
|
||||
inset 0 1px 0 rgba(255,255,255,.46),
|
||||
inset 0 -12px 26px rgba(114,71,0,.12);
|
||||
font-family:'Fredoka',sans-serif;
|
||||
font-size:64px;
|
||||
font-weight:800;
|
||||
letter-spacing:0;
|
||||
}
|
||||
|
||||
.home-winner-card__status{
|
||||
position:absolute;
|
||||
right:22px;
|
||||
top:28px;
|
||||
padding:7px 12px;
|
||||
border:1px solid rgba(255,255,255,.14);
|
||||
border-radius:999px;
|
||||
color:#ffd27a;
|
||||
background:rgba(23,12,36,.48);
|
||||
font-size:11px;
|
||||
font-weight:900;
|
||||
letter-spacing:.14em;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
.home-winner-card__plaque{
|
||||
position:absolute;
|
||||
left:50%;
|
||||
bottom:22px;
|
||||
width:min(236px,calc(100% - 48px));
|
||||
padding:10px 14px 11px;
|
||||
border:1px solid rgba(255,210,122,.24);
|
||||
border-radius:8px;
|
||||
background:linear-gradient(180deg,rgba(58,28,92,.96),rgba(40,19,65,.98));
|
||||
box-shadow:0 18px 36px rgba(14,6,27,.34);
|
||||
text-align:center;
|
||||
transform:translateX(-50%);
|
||||
}
|
||||
|
||||
.home-winner-card__category{
|
||||
color:#ffd27a;
|
||||
font-size:10px;
|
||||
font-weight:900;
|
||||
letter-spacing:.13em;
|
||||
line-height:1.15;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
.home-winner-card__name{
|
||||
margin:4px 0 0;
|
||||
color:#fff6fb;
|
||||
font-family:'Fredoka',sans-serif;
|
||||
font-size:22px;
|
||||
font-weight:700;
|
||||
letter-spacing:0;
|
||||
line-height:1.05;
|
||||
overflow-wrap:anywhere;
|
||||
}
|
||||
|
||||
.home-winner-card__subcategory{
|
||||
margin:4px 0 0;
|
||||
color:#d9c9ee;
|
||||
font-size:13px;
|
||||
font-weight:800;
|
||||
line-height:1.2;
|
||||
overflow-wrap:anywhere;
|
||||
}
|
||||
|
||||
.home-winner-card__actions{
|
||||
display:grid;
|
||||
gap:10px;
|
||||
padding:0 26px 26px;
|
||||
}
|
||||
|
||||
.home-winner-card__profile{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
min-height:48px;
|
||||
padding:12px 18px;
|
||||
border-radius:15px;
|
||||
font-family:'Fredoka',sans-serif;
|
||||
font-size:14px;
|
||||
font-weight:700;
|
||||
text-align:center;
|
||||
text-decoration:none;
|
||||
transition:transform .18s ease, opacity .18s ease, border-color .18s ease;
|
||||
}
|
||||
|
||||
.home-winner-card__profile{
|
||||
color:#fff;
|
||||
background:linear-gradient(135deg,#d36fa7,#a06bff);
|
||||
box-shadow:0 16px 34px rgba(123,76,196,.26);
|
||||
}
|
||||
|
||||
.home-winner-card__profile:hover{
|
||||
opacity:.92;
|
||||
transform:translateY(-2px);
|
||||
}
|
||||
|
||||
.home-winner-empty{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:18px;
|
||||
max-width:720px;
|
||||
padding:24px;
|
||||
border:1px solid rgba(255,255,255,.12);
|
||||
border-radius:24px;
|
||||
color:#fff6fb;
|
||||
background:rgba(32,17,50,.62);
|
||||
box-shadow:0 22px 54px rgba(16,7,32,.22);
|
||||
}
|
||||
|
||||
.home-winner-empty__badge{
|
||||
flex:none;
|
||||
display:grid;
|
||||
place-items:center;
|
||||
width:58px;
|
||||
height:58px;
|
||||
border-radius:20px;
|
||||
color:#3a2d30;
|
||||
background:linear-gradient(145deg,#ffe39a,#e7b13e);
|
||||
font-size:24px;
|
||||
}
|
||||
|
||||
.home-winner-empty h3{
|
||||
margin:0 0 5px;
|
||||
font-family:'Fredoka',sans-serif;
|
||||
font-size:24px;
|
||||
}
|
||||
|
||||
.home-winner-empty p{
|
||||
margin:0;
|
||||
color:#d9c9ee;
|
||||
font-size:15px;
|
||||
line-height:1.5;
|
||||
}
|
||||
|
||||
@media (max-width:720px){
|
||||
.home-winner-section__inner{
|
||||
padding:72px 16px;
|
||||
}
|
||||
|
||||
.home-winner-section__header{
|
||||
align-items:flex-start;
|
||||
margin-bottom:34px;
|
||||
}
|
||||
|
||||
.home-winner-section__archive-button{
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.home-winner-rail{
|
||||
margin-right:-16px;
|
||||
padding-right:16px;
|
||||
}
|
||||
|
||||
.home-winner-track{
|
||||
gap:16px;
|
||||
}
|
||||
|
||||
.home-winner-card{
|
||||
min-height:430px;
|
||||
border-radius:24px;
|
||||
}
|
||||
|
||||
.home-winner-card__spotlight{
|
||||
min-height:324px;
|
||||
padding:24px 22px 30px;
|
||||
}
|
||||
|
||||
.home-winner-card__portrait{
|
||||
width:min(218px,100%);
|
||||
font-size:54px;
|
||||
}
|
||||
|
||||
.home-winner-card__status{
|
||||
right:18px;
|
||||
top:22px;
|
||||
}
|
||||
|
||||
.home-winner-card__actions{
|
||||
padding:0 22px 22px;
|
||||
}
|
||||
|
||||
.home-winner-empty{
|
||||
align-items:flex-start;
|
||||
padding:20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,13 +4,9 @@
|
||||
html,body{margin:0;padding:0;background:#160a26;}
|
||||
@keyframes twinkle{0%,100%{opacity:.25;transform:scale(.7);}50%{opacity:1;transform:scale(1.15);}}
|
||||
@keyframes starAppearRandom{0%,16%,100%{opacity:0;transform:translate(-50%,-50%) scale(.55) rotate(var(--star-rotate));}30%,68%{opacity:var(--star-alpha);transform:translate(-50%,-50%) scale(1) rotate(var(--star-rotate));}84%{opacity:0;transform:translate(-50%,-50%) scale(1.16) rotate(var(--star-rotate));}}
|
||||
@keyframes voteBurstFlare{0%{opacity:.42;transform:translate3d(-50%,-50%,0) scale(.26);}14%{opacity:.88;transform:translate3d(-50%,-50%,0) scale(.88);}64%{opacity:.34;transform:translate3d(-50%,-50%,0) scale(1.42);}100%{opacity:0;transform:translate3d(-50%,-50%,0) scale(1.72);}}
|
||||
@keyframes voteBurstRing{0%{opacity:.36;transform:translate3d(-50%,-50%,0) scale(.36);}16%{opacity:.72;transform:translate3d(-50%,-50%,0) scale(.82);}100%{opacity:0;transform:translate3d(-50%,-50%,0) scale(2.22);}}
|
||||
@keyframes voteTrail{0%{opacity:.3;transform:translate3d(0,-50%,0) rotate(var(--trail-rotate)) scaleX(.12);}18%{opacity:.72;transform:translate3d(0,-50%,0) rotate(var(--trail-rotate)) scaleX(.72);}100%{opacity:0;transform:translate3d(var(--trail-x),calc(-50% + var(--trail-y)),0) rotate(var(--trail-rotate)) scaleX(1.08);}}
|
||||
@keyframes voteStarUp{0%{opacity:.38;transform:translate3d(-50%,-50%,0) scale(.28) rotate(-12deg);}18%{opacity:1;transform:translate3d(-50%,-70%,0) scale(.88) rotate(-2deg);}66%{opacity:1;transform:translate3d(-50%,-170%,0) scale(1.24) rotate(12deg);}100%{opacity:0;transform:translate3d(-50%,-226%,0) scale(.7) rotate(24deg);}}
|
||||
@keyframes voteStarLeft{0%{opacity:.34;transform:translate3d(-50%,-50%,0) scale(.26) rotate(8deg);}18%{opacity:1;transform:translate3d(-70%,-66%,0) scale(.74) rotate(-4deg);}66%{opacity:.98;transform:translate3d(-176%,-128%,0) scale(1) rotate(-20deg);}100%{opacity:0;transform:translate3d(-236%,-82%,0) scale(.56) rotate(-36deg);}}
|
||||
@keyframes voteStarRight{0%{opacity:.34;transform:translate3d(-50%,-50%,0) scale(.26) rotate(-8deg);}18%{opacity:1;transform:translate3d(-32%,-66%,0) scale(.68) rotate(4deg);}66%{opacity:.98;transform:translate3d(88%,-122%,0) scale(.88) rotate(18deg);}100%{opacity:0;transform:translate3d(150%,-76%,0) scale(.5) rotate(34deg);}}
|
||||
@keyframes voteStarDriftSmall{0%{opacity:.28;transform:translate3d(-50%,-50%,0) scale(.22) rotate(0deg);}30%{opacity:.92;transform:translate3d(calc(-50% + var(--star-dx-start)),calc(-50% + var(--star-dy-start)),0) scale(.72) rotate(14deg);}100%{opacity:0;transform:translate3d(calc(-50% + var(--star-dx-end)),calc(-50% + var(--star-dy-end)),0) scale(.28) rotate(42deg);}}
|
||||
@keyframes voteSkyGlow{0%{opacity:0;transform:scale(.98);}24%{opacity:.88;transform:scale(1);}76%{opacity:.34;transform:scale(1.015);}100%{opacity:0;transform:scale(1.03);}}
|
||||
@keyframes voteSkySweep{0%{opacity:0;transform:translate3d(-46%,18%,0) rotate(-9deg) scaleX(.7);}22%{opacity:.62;}64%{opacity:.5;}100%{opacity:0;transform:translate3d(46%,-18%,0) rotate(-9deg) scaleX(1.22);}}
|
||||
@keyframes voteSkyStar{0%,12%{opacity:0;transform:translate(-50%,-50%) scale(.58) rotate(var(--star-rotate));}34%{opacity:1;transform:translate(-50%,-52%) scale(1.12) rotate(var(--star-rotate));}72%{opacity:.72;transform:translate(-50%,-55%) scale(.94) rotate(var(--star-rotate));}100%{opacity:0;transform:translate(-50%,-58%) scale(.72) rotate(var(--star-rotate));}}
|
||||
@keyframes floaty{0%,100%{transform:translateY(0);}50%{transform:translateY(-16px);}}
|
||||
@keyframes floaty2{0%,100%{transform:translateY(0) rotate(-4deg);}50%{transform:translateY(-22px) rotate(4deg);}}
|
||||
@keyframes pulseGlow{0%,100%{opacity:.55;}50%{opacity:1;}}
|
||||
@@ -142,7 +138,9 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
|
||||
.home-sticky-countdown,
|
||||
.home-sticky-countdown__dot,
|
||||
.home-vote-card__burst-star{
|
||||
.home-vote-card__sky-star,
|
||||
.home-vote-card__sky-glow,
|
||||
.home-vote-card__sky-sweep{
|
||||
animation:none;
|
||||
transition:none;
|
||||
}
|
||||
@@ -254,6 +252,57 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
transform:translateY(-1px);
|
||||
}
|
||||
|
||||
.home-stream-band__copy{
|
||||
min-width:0;
|
||||
flex:1 1 auto;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
}
|
||||
|
||||
.home-stream-band__headline-row{
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.home-stream-band__lead{
|
||||
flex:1 1 auto;
|
||||
}
|
||||
|
||||
.home-stream-band__eyebrow,
|
||||
.home-stream-band__meta{
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.home-stream-band__headline{
|
||||
min-width:0;
|
||||
}
|
||||
|
||||
.home-stream-band__title{
|
||||
white-space:nowrap;
|
||||
letter-spacing:.01em;
|
||||
}
|
||||
|
||||
.home-stream-band__countdown-label{
|
||||
white-space:nowrap;
|
||||
opacity:.92;
|
||||
}
|
||||
|
||||
.home-stream-band__countdown-chip{
|
||||
backdrop-filter:blur(10px);
|
||||
-webkit-backdrop-filter:blur(10px);
|
||||
}
|
||||
|
||||
.home-stream-band__countdown-time{
|
||||
font-variant-numeric:tabular-nums;
|
||||
white-space:nowrap;
|
||||
text-shadow:0 4px 18px rgba(11,6,26,.18);
|
||||
}
|
||||
|
||||
.home-stream-band__actions{
|
||||
flex:none;
|
||||
margin-left:auto;
|
||||
}
|
||||
|
||||
.home-landing img,
|
||||
.home-landing svg{
|
||||
max-width:100%;
|
||||
@@ -509,14 +558,17 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
}
|
||||
|
||||
.home-nomination-review__hero{
|
||||
margin-bottom:18px;
|
||||
padding:18px 20px;
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
margin-bottom:20px;
|
||||
padding:22px 24px;
|
||||
border:1px solid rgba(139,108,219,.14);
|
||||
border-radius:18px;
|
||||
border-radius:24px;
|
||||
background:
|
||||
linear-gradient(135deg,rgba(255,255,255,.94),rgba(252,248,255,.94)),
|
||||
radial-gradient(circle at 100% 0,rgba(231,177,62,.12),transparent 34%);
|
||||
box-shadow:0 14px 34px rgba(82,61,128,.07);
|
||||
radial-gradient(circle at top right,rgba(231,177,62,.16),transparent 34%),
|
||||
radial-gradient(circle at 12% 0,rgba(201,177,255,.26),transparent 28%),
|
||||
linear-gradient(145deg,rgba(255,255,255,.96),rgba(250,245,255,.96));
|
||||
box-shadow:0 18px 40px rgba(82,61,128,.08);
|
||||
}
|
||||
|
||||
.home-nomination-review__item h5{
|
||||
@@ -534,6 +586,39 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
line-height:1.5;
|
||||
}
|
||||
|
||||
.home-nomination-review__stats{
|
||||
display:grid;
|
||||
grid-template-columns:repeat(3,minmax(0,1fr));
|
||||
gap:12px;
|
||||
margin-top:18px;
|
||||
}
|
||||
|
||||
.home-nomination-review__stat{
|
||||
display:grid;
|
||||
gap:4px;
|
||||
padding:14px 16px;
|
||||
border:1px solid rgba(139,108,219,.1);
|
||||
border-radius:18px;
|
||||
background:rgba(255,255,255,.72);
|
||||
box-shadow:inset 0 1px 0 rgba(255,255,255,.7);
|
||||
}
|
||||
|
||||
.home-nomination-review__stat span{
|
||||
color:#8f84a7;
|
||||
font-size:11px;
|
||||
font-weight:800;
|
||||
letter-spacing:.08em;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
.home-nomination-review__stat strong{
|
||||
color:#4a356d;
|
||||
font-family:'Fredoka',sans-serif;
|
||||
font-size:22px;
|
||||
font-weight:700;
|
||||
line-height:1;
|
||||
}
|
||||
|
||||
.home-nomination-pane__fields{
|
||||
display:grid;
|
||||
gap:14px;
|
||||
@@ -670,26 +755,39 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
|
||||
.home-nomination-review__list{
|
||||
display:grid;
|
||||
gap:12px;
|
||||
gap:14px;
|
||||
}
|
||||
|
||||
.home-nomination-review__item{
|
||||
display:grid;
|
||||
gap:12px;
|
||||
padding:16px;
|
||||
border:1px solid #efe7fb;
|
||||
border-radius:18px;
|
||||
background:#fff;
|
||||
box-shadow:0 12px 28px rgba(82,61,128,.07);
|
||||
gap:14px;
|
||||
padding:18px;
|
||||
border:1px solid rgba(139,108,219,.12);
|
||||
border-radius:24px;
|
||||
background:linear-gradient(135deg,rgba(255,255,255,.96),rgba(249,244,255,.96));
|
||||
box-shadow:0 16px 34px rgba(82,61,128,.08);
|
||||
}
|
||||
|
||||
.home-nomination-review__item > div{
|
||||
.home-nomination-review__item-header{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
align-items:flex-start;
|
||||
justify-content:space-between;
|
||||
gap:12px;
|
||||
}
|
||||
|
||||
.home-nomination-review__item-copy{
|
||||
min-width:0;
|
||||
}
|
||||
|
||||
.home-nomination-review__item-eyebrow{
|
||||
margin:0 0 6px;
|
||||
color:#9d85d9;
|
||||
font-size:11px;
|
||||
font-weight:900;
|
||||
letter-spacing:.12em;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
.home-nomination-review__item span{
|
||||
padding:4px 8px;
|
||||
border-radius:999px;
|
||||
@@ -699,26 +797,117 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
font-weight:900;
|
||||
}
|
||||
|
||||
.home-nomination-review__item ul{
|
||||
.home-nomination-review__links{
|
||||
display:grid;
|
||||
gap:9px;
|
||||
margin:0;
|
||||
padding-left:18px;
|
||||
padding:0;
|
||||
list-style:none;
|
||||
}
|
||||
|
||||
.home-nomination-review__links li{
|
||||
display:flex;
|
||||
align-items:flex-start;
|
||||
gap:10px;
|
||||
padding:12px 14px;
|
||||
border:1px solid rgba(139,108,219,.1);
|
||||
border-radius:16px;
|
||||
background:rgba(255,255,255,.74);
|
||||
color:#6f6685;
|
||||
font-size:13px;
|
||||
font-weight:700;
|
||||
line-height:1.5;
|
||||
overflow-wrap:anywhere;
|
||||
}
|
||||
|
||||
.home-nomination-review__item button{
|
||||
.home-nomination-review__link-dot{
|
||||
flex:none;
|
||||
width:10px;
|
||||
height:10px;
|
||||
margin-top:5px;
|
||||
border-radius:50%;
|
||||
background:linear-gradient(135deg,#8b6cdb,#e9a8c9);
|
||||
box-shadow:0 0 0 5px rgba(139,108,219,.12);
|
||||
}
|
||||
|
||||
.home-nomination-review__edit{
|
||||
justify-self:start;
|
||||
border:0;
|
||||
border-radius:11px;
|
||||
background:#f1ecfb;
|
||||
border-radius:999px;
|
||||
background:linear-gradient(135deg,#f4effd,#f0e8fb);
|
||||
color:#7658c4;
|
||||
cursor:pointer;
|
||||
font-family:'Outfit',sans-serif;
|
||||
font-size:13px;
|
||||
font-weight:900;
|
||||
padding:9px 13px;
|
||||
padding:10px 14px;
|
||||
box-shadow:0 10px 22px rgba(139,108,219,.1);
|
||||
transition:transform .16s ease,box-shadow .16s ease,background .16s ease,color .16s ease;
|
||||
}
|
||||
|
||||
.home-nomination-review__edit:hover{
|
||||
background:linear-gradient(135deg,#8b6cdb,#7355c8);
|
||||
color:#fff;
|
||||
box-shadow:0 14px 28px rgba(139,108,219,.22);
|
||||
transform:translateY(-1px);
|
||||
}
|
||||
|
||||
.home-nomination-review__empty{
|
||||
display:grid;
|
||||
place-items:center;
|
||||
gap:16px;
|
||||
min-height:240px;
|
||||
padding:34px 24px;
|
||||
border:1px dashed rgba(139,108,219,.32);
|
||||
border-radius:28px;
|
||||
background:
|
||||
radial-gradient(circle at top,rgba(231,177,62,.16),transparent 28%),
|
||||
linear-gradient(145deg,rgba(255,255,255,.92),rgba(247,240,255,.9));
|
||||
box-shadow:inset 0 1px 0 rgba(255,255,255,.78);
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.home-nomination-review__empty-icon{
|
||||
display:grid;
|
||||
place-items:center;
|
||||
width:62px;
|
||||
height:62px;
|
||||
border-radius:22px;
|
||||
background:linear-gradient(135deg,#8b6cdb,#7355c8);
|
||||
color:#fff2bd;
|
||||
font-size:24px;
|
||||
box-shadow:0 18px 38px rgba(139,108,219,.22);
|
||||
}
|
||||
|
||||
.home-nomination-review__empty-copy{
|
||||
display:grid;
|
||||
gap:6px;
|
||||
max-width:420px;
|
||||
}
|
||||
|
||||
.home-nomination-review__empty-eyebrow{
|
||||
margin:0;
|
||||
color:#9d85d9;
|
||||
font-size:11px;
|
||||
font-weight:900;
|
||||
letter-spacing:.16em;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
.home-nomination-review__empty-copy h5{
|
||||
margin:0;
|
||||
color:#3f3556;
|
||||
font-family:'Fredoka',sans-serif;
|
||||
font-size:28px;
|
||||
line-height:1.05;
|
||||
}
|
||||
|
||||
.home-nomination-review__empty-copy p{
|
||||
margin:0;
|
||||
color:#8a8398;
|
||||
font-size:14px;
|
||||
font-weight:700;
|
||||
line-height:1.6;
|
||||
}
|
||||
|
||||
.home-wizard-footer{
|
||||
@@ -1245,133 +1434,152 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
contain:paint;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-flare,
|
||||
.home-vote-card__burst-ring{
|
||||
.home-vote-card__sky-glow,
|
||||
.home-vote-card__sky-sweep,
|
||||
.home-vote-card__sky-star{
|
||||
position:absolute;
|
||||
left:50%;
|
||||
top:36%;
|
||||
opacity:0;
|
||||
pointer-events:none;
|
||||
transform:translate(-50%,-50%) scale(.2);
|
||||
will-change:transform,opacity;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-flare{
|
||||
width:124px;
|
||||
height:124px;
|
||||
border-radius:50%;
|
||||
.home-vote-card__sky-glow{
|
||||
inset:-1px;
|
||||
border-radius:inherit;
|
||||
opacity:0;
|
||||
background:
|
||||
radial-gradient(circle,rgba(255,255,255,.92) 0 9%,rgba(255,210,122,.55) 26%,rgba(233,168,201,.24) 48%,transparent 72%);
|
||||
filter:blur(1.2px);
|
||||
animation:voteBurstFlare .9s cubic-bezier(.2,.78,.22,1) forwards;
|
||||
radial-gradient(circle at 24% 22%,rgba(255,242,189,.46),transparent 12%),
|
||||
radial-gradient(circle at 72% 28%,rgba(201,177,255,.4),transparent 15%),
|
||||
radial-gradient(circle at 44% 76%,rgba(233,168,201,.26),transparent 18%),
|
||||
linear-gradient(135deg,rgba(37,20,72,.24),rgba(139,108,219,.18) 44%,rgba(255,210,122,.12));
|
||||
box-shadow:inset 0 0 0 1px rgba(255,242,189,.28),0 18px 46px rgba(78,46,139,.18);
|
||||
animation:voteSkyGlow 1.05s cubic-bezier(.22,.8,.24,1) forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-ring{
|
||||
width:82px;
|
||||
height:82px;
|
||||
border:1.5px solid rgba(255,242,189,.74);
|
||||
border-radius:50%;
|
||||
box-shadow:0 0 16px rgba(255,210,122,.34),0 0 28px rgba(201,177,255,.18);
|
||||
animation:voteBurstRing .92s cubic-bezier(.2,.78,.22,1) .02s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-trail{
|
||||
position:absolute;
|
||||
left:50%;
|
||||
top:36%;
|
||||
width:86px;
|
||||
height:5px;
|
||||
.home-vote-card__sky-sweep{
|
||||
left:-18%;
|
||||
right:-18%;
|
||||
top:4%;
|
||||
height:92%;
|
||||
border-radius:999px;
|
||||
opacity:0;
|
||||
pointer-events:none;
|
||||
transform-origin:left center;
|
||||
transform:translate3d(0,-50%,0) rotate(var(--trail-rotate)) scaleX(.12);
|
||||
background:linear-gradient(90deg,rgba(255,255,255,0),rgba(255,242,189,.92),rgba(201,177,255,.42),rgba(255,255,255,0));
|
||||
box-shadow:0 0 14px rgba(255,210,122,.42),0 0 22px rgba(139,108,219,.16);
|
||||
will-change:transform,opacity;
|
||||
background:
|
||||
linear-gradient(90deg,transparent 0%,rgba(255,210,122,.16) 18%,rgba(255,143,192,.2) 34%,rgba(201,177,255,.24) 50%,rgba(122,215,255,.18) 66%,rgba(255,242,189,.2) 82%,transparent 100%),
|
||||
radial-gradient(ellipse at center,rgba(255,255,255,.36),transparent 62%);
|
||||
filter:blur(14px);
|
||||
transform-origin:center;
|
||||
animation:voteSkySweep 1.45s cubic-bezier(.18,.84,.22,1) .05s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-trail--left{
|
||||
--trail-rotate:-150deg;
|
||||
--trail-x:-20px;
|
||||
--trail-y:-13px;
|
||||
animation:voteTrail .58s cubic-bezier(.22,.76,.24,1) .06s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-trail--right{
|
||||
--trail-rotate:-36deg;
|
||||
--trail-x:16px;
|
||||
--trail-y:-13px;
|
||||
animation:voteTrail .56s cubic-bezier(.22,.76,.24,1) .08s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-trail--up{
|
||||
--trail-rotate:-90deg;
|
||||
--trail-x:0px;
|
||||
--trail-y:-20px;
|
||||
width:96px;
|
||||
animation:voteTrail .62s cubic-bezier(.22,.76,.24,1) .04s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-star{
|
||||
position:absolute;
|
||||
.home-vote-card__sky-star{
|
||||
left:50%;
|
||||
top:36%;
|
||||
color:#ffd27a;
|
||||
top:50%;
|
||||
color:#fff2bd;
|
||||
font-family:'Fredoka',sans-serif;
|
||||
line-height:1;
|
||||
opacity:0;
|
||||
text-shadow:0 0 8px rgba(255,255,255,.85),0 0 14px rgba(255,210,122,.78),0 0 24px rgba(139,108,219,.28);
|
||||
transform:translate(-50%,-50%) scale(.2);
|
||||
text-shadow:0 0 8px rgba(255,255,255,.86),0 0 18px rgba(255,210,122,.72),0 0 28px rgba(139,108,219,.22);
|
||||
transform:translate(-50%,-50%) scale(.58) rotate(var(--star-rotate));
|
||||
will-change:transform,opacity;
|
||||
animation:voteSkyStar var(--star-duration) cubic-bezier(.2,.76,.2,1) var(--star-delay) forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-star--up{
|
||||
color:#fff2bd;
|
||||
font-size:36px;
|
||||
animation:voteStarUp .96s cubic-bezier(.2,.78,.22,1) .02s forwards;
|
||||
.home-vote-card__sky-star--one{
|
||||
--star-rotate:-12deg;
|
||||
--star-duration:.92s;
|
||||
--star-delay:.1s;
|
||||
left:24%;
|
||||
top:30%;
|
||||
color:#ffd27a;
|
||||
font-size:19px;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-star--left{
|
||||
color:#e9a8c9;
|
||||
font-size:24px;
|
||||
animation:voteStarLeft .9s cubic-bezier(.2,.78,.22,1) .06s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-star--right{
|
||||
color:#c9b1ff;
|
||||
font-size:21px;
|
||||
animation:voteStarRight .88s cubic-bezier(.2,.78,.22,1) .08s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-star--left-small{
|
||||
--star-dx-start:-18px;
|
||||
--star-dy-start:-14px;
|
||||
--star-dx-end:-92px;
|
||||
--star-dy-end:-2px;
|
||||
color:#fff2bd;
|
||||
.home-vote-card__sky-star--two{
|
||||
--star-rotate:18deg;
|
||||
--star-duration:.98s;
|
||||
--star-delay:.18s;
|
||||
left:42%;
|
||||
top:22%;
|
||||
color:#fff7d8;
|
||||
font-size:13px;
|
||||
animation:voteStarDriftSmall .68s cubic-bezier(.2,.78,.22,1) .12s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-star--right-small{
|
||||
--star-dx-start:18px;
|
||||
--star-dy-start:-12px;
|
||||
--star-dx-end:88px;
|
||||
--star-dy-end:-8px;
|
||||
.home-vote-card__sky-star--three{
|
||||
--star-rotate:0deg;
|
||||
--star-duration:1.04s;
|
||||
--star-delay:.06s;
|
||||
left:58%;
|
||||
top:38%;
|
||||
color:#fff;
|
||||
font-size:12px;
|
||||
}
|
||||
|
||||
.home-vote-card__sky-star--four{
|
||||
--star-rotate:22deg;
|
||||
--star-duration:.96s;
|
||||
--star-delay:.24s;
|
||||
left:72%;
|
||||
top:26%;
|
||||
color:#c9b1ff;
|
||||
font-size:17px;
|
||||
}
|
||||
|
||||
.home-vote-card__sky-star--five{
|
||||
--star-rotate:-18deg;
|
||||
--star-duration:.88s;
|
||||
--star-delay:.3s;
|
||||
left:80%;
|
||||
top:54%;
|
||||
color:#e9a8c9;
|
||||
font-size:12px;
|
||||
animation:voteStarDriftSmall .66s cubic-bezier(.2,.78,.22,1) .15s forwards;
|
||||
}
|
||||
|
||||
.home-vote-card__burst-star--top-small{
|
||||
--star-dx-start:4px;
|
||||
--star-dy-start:-18px;
|
||||
--star-dx-end:34px;
|
||||
--star-dy-end:-88px;
|
||||
.home-vote-card__sky-star--six{
|
||||
--star-rotate:12deg;
|
||||
--star-duration:1.02s;
|
||||
--star-delay:.2s;
|
||||
left:18%;
|
||||
top:58%;
|
||||
color:#fff7d8;
|
||||
font-size:11px;
|
||||
}
|
||||
|
||||
.home-vote-card__sky-star--seven{
|
||||
--star-rotate:-24deg;
|
||||
--star-duration:1.08s;
|
||||
--star-delay:.34s;
|
||||
left:34%;
|
||||
top:68%;
|
||||
color:#c9b1ff;
|
||||
font-size:14px;
|
||||
animation:voteStarDriftSmall .72s cubic-bezier(.2,.78,.22,1) .18s forwards;
|
||||
font-size:15px;
|
||||
}
|
||||
|
||||
.home-vote-card__sky-star--eight{
|
||||
--star-rotate:8deg;
|
||||
--star-duration:.96s;
|
||||
--star-delay:.16s;
|
||||
left:51%;
|
||||
top:64%;
|
||||
color:#ffd27a;
|
||||
font-size:10px;
|
||||
}
|
||||
|
||||
.home-vote-card__sky-star--nine{
|
||||
--star-rotate:-8deg;
|
||||
--star-duration:1.1s;
|
||||
--star-delay:.28s;
|
||||
left:66%;
|
||||
top:70%;
|
||||
color:#fff;
|
||||
font-size:13px;
|
||||
}
|
||||
|
||||
.home-vote-card__sky-star--ten{
|
||||
--star-rotate:26deg;
|
||||
--star-duration:.94s;
|
||||
--star-delay:.4s;
|
||||
left:88%;
|
||||
top:38%;
|
||||
color:#ffb8d8;
|
||||
font-size:11px;
|
||||
}
|
||||
|
||||
.home-vote-picker__empty{
|
||||
@@ -1763,13 +1971,43 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
|
||||
.home-stream-band__lead{
|
||||
align-items:flex-start!important;
|
||||
justify-content:flex-start!important;
|
||||
gap:14px!important;
|
||||
width:100%!important;
|
||||
}
|
||||
|
||||
.home-stream-band__copy{
|
||||
align-items:flex-start!important;
|
||||
}
|
||||
|
||||
.home-stream-band__eyebrow,
|
||||
.home-stream-band__meta{
|
||||
text-align:left!important;
|
||||
}
|
||||
|
||||
.home-stream-band__actions{
|
||||
width:100%!important;
|
||||
align-items:stretch!important;
|
||||
justify-content:center!important;
|
||||
margin-left:0!important;
|
||||
}
|
||||
|
||||
.home-stream-band__headline-row{
|
||||
justify-content:flex-start!important;
|
||||
gap:8px!important;
|
||||
}
|
||||
|
||||
.home-stream-band__title{
|
||||
white-space:normal!important;
|
||||
}
|
||||
|
||||
.home-stream-band__countdown-chip{
|
||||
flex-wrap:wrap!important;
|
||||
row-gap:6px!important;
|
||||
}
|
||||
|
||||
.home-stream-band__countdown-time{
|
||||
font-size:clamp(22px,7vw,28px)!important;
|
||||
}
|
||||
|
||||
.home-stream-band__actions > a,
|
||||
@@ -1821,7 +2059,7 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
grid-template-columns:1fr!important;
|
||||
}
|
||||
|
||||
[data-cat-grid] > div{
|
||||
[data-cat-grid] > *{
|
||||
border-radius:18px!important;
|
||||
padding:22px 20px!important;
|
||||
}
|
||||
@@ -2016,6 +2254,15 @@ html,body{margin:0;padding:0;background:#160a26;}
|
||||
flex-direction:column!important;
|
||||
}
|
||||
|
||||
.home-nomination-review__stats{
|
||||
grid-template-columns:1fr!important;
|
||||
}
|
||||
|
||||
.home-nomination-review__item-header{
|
||||
flex-direction:column!important;
|
||||
align-items:flex-start!important;
|
||||
}
|
||||
|
||||
.home-nomination-field__header{
|
||||
align-items:flex-start!important;
|
||||
flex-direction:column!important;
|
||||
|
||||
@@ -35,6 +35,5 @@ export interface HomeLandingCategory {
|
||||
icon: string
|
||||
description: string
|
||||
maxNomineesPerUser: number
|
||||
candidateCount: number
|
||||
subcategoryCount: number
|
||||
subcategoryNames: string[]
|
||||
}
|
||||
|
||||
@@ -52,16 +52,9 @@ export interface HomeArchiveYearItem {
|
||||
|
||||
export interface HomeArchiveWinnerItem {
|
||||
category: string
|
||||
subcategory: string
|
||||
name: string
|
||||
handle: string
|
||||
platform: string
|
||||
url: string
|
||||
clipUrl: string | null
|
||||
clipTitle: string
|
||||
clipPlatform: string
|
||||
clipEmbedUrl: string | null
|
||||
clipEmbedTitle: string
|
||||
hasClip: boolean
|
||||
}
|
||||
|
||||
export interface HomeSelectedArchive {
|
||||
|
||||
@@ -12,15 +12,6 @@ export function useHomeArchivePresentation(store: AwardsStore, archiveYear: Ref<
|
||||
knownYears.set(entry.year, entry.winnerCount)
|
||||
}
|
||||
|
||||
for (const entry of store.overview.winnersPreview) {
|
||||
if (!knownYears.has(entry.year)) {
|
||||
knownYears.set(
|
||||
entry.year,
|
||||
store.overview.winnersPreview.filter((winner) => winner.year === entry.year).length,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (store.archive.items.length > 0) {
|
||||
knownYears.set(store.archive.year, store.archive.items.length)
|
||||
}
|
||||
@@ -31,7 +22,7 @@ export function useHomeArchivePresentation(store: AwardsStore, archiveYear: Ref<
|
||||
year,
|
||||
label: String(year),
|
||||
winnerCount: year === store.archive.year ? store.archive.items.length : winnerCount,
|
||||
winners: year === store.archive.year ? store.archive.items : store.overview.winnersPreview.filter((entry) => entry.year === year),
|
||||
winners: year === store.archive.year ? store.archive.items : [],
|
||||
active: archiveYear.value === year,
|
||||
}))
|
||||
})
|
||||
@@ -41,7 +32,15 @@ export function useHomeArchivePresentation(store: AwardsStore, archiveYear: Ref<
|
||||
winners: store.archive.items.map((winner) => toArchiveWinnerItem(winner)),
|
||||
}))
|
||||
|
||||
const winnerShowcase = computed(() => selectedArchive.value.winners.slice(0, 4))
|
||||
const winnerShowcaseYear = computed(() =>
|
||||
store.overview.winnersPreview[0]?.year ?? selectedArchive.value.year,
|
||||
)
|
||||
|
||||
const winnerShowcase = computed(() => {
|
||||
const publishedWinners = store.overview.winnersPreview.map((winner) => toWinnerShowcaseItem(winner))
|
||||
const archiveFallback = store.archive.items.map((winner) => toWinnerShowcaseItem(winner))
|
||||
return (publishedWinners.length > 0 ? publishedWinners : archiveFallback).slice(0, 8)
|
||||
})
|
||||
|
||||
function archiveYearButtonStyle(active: boolean) {
|
||||
return active
|
||||
@@ -50,6 +49,21 @@ export function useHomeArchivePresentation(store: AwardsStore, archiveYear: Ref<
|
||||
}
|
||||
|
||||
function toArchiveWinnerItem(winner: {
|
||||
categoryGroup?: string | null
|
||||
category: string
|
||||
winnerName: string
|
||||
winnerUrl: string
|
||||
}) {
|
||||
return {
|
||||
category: winner.category,
|
||||
subcategory: winner.categoryGroup?.trim() || winner.category,
|
||||
name: winner.winnerName,
|
||||
url: winner.winnerUrl,
|
||||
}
|
||||
}
|
||||
|
||||
function toWinnerShowcaseItem(winner: {
|
||||
categoryGroup?: string | null
|
||||
category: string
|
||||
winnerName: string
|
||||
winnerSlug: string
|
||||
@@ -66,7 +80,9 @@ export function useHomeArchivePresentation(store: AwardsStore, archiveYear: Ref<
|
||||
const clipEmbed = clipUrl && winner.clipEmbedStatus !== 'link_only' && winner.clipEmbedStatus !== 'blocked'
|
||||
? buildClipEmbed(clipUrl)
|
||||
: null
|
||||
|
||||
return {
|
||||
categoryGroup: winner.categoryGroup?.trim() || winner.category,
|
||||
category: winner.category,
|
||||
name: winner.winnerName,
|
||||
handle: winner.winnerSlug,
|
||||
@@ -114,9 +130,9 @@ export function useHomeArchivePresentation(store: AwardsStore, archiveYear: Ref<
|
||||
return {
|
||||
archiveYears,
|
||||
selectedArchive,
|
||||
winnerShowcaseYear,
|
||||
winnerShowcase,
|
||||
archiveYearButtonStyle,
|
||||
winnerPlatformKey,
|
||||
winnerPlatformLabel,
|
||||
winnerPlatformStyle,
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
const faqItems = computed(() =>
|
||||
(store.overview.faq ?? []).filter((item) => item?.question && item.answer),
|
||||
)
|
||||
const publicStreamUrl = computed(() => store.overview.showStreamUrl || 'https://twitch.tv/jayuhime')
|
||||
const publicStreamUrl = computed(() =>
|
||||
store.overview.siteContent.streamBanner.liveButtonUrl
|
||||
|| 'https://twitch.tv/jayuhime',
|
||||
)
|
||||
const showDate = computed(() => store.overview.showDate)
|
||||
const showStartsAt = computed(() => store.overview.showStartsAt || '20:00:00')
|
||||
const currentYear = computed(() => store.overview.year ? String(store.overview.year) : '')
|
||||
@@ -42,7 +45,9 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
if (store.overview.featuredCategories.length > 0) {
|
||||
return store.overview.featuredCategories.map((category, index) => {
|
||||
const matchingSubcategories = displayCategories.value.filter((item) => item.groupName === category.groupName)
|
||||
const candidateCount = matchingSubcategories.reduce((sum, item) => sum + item.candidates.length, 0)
|
||||
const subcategoryNames = matchingSubcategories
|
||||
.map((item) => item.name.trim())
|
||||
.filter(Boolean)
|
||||
return {
|
||||
id: String(category.id),
|
||||
groupName: category.groupName,
|
||||
@@ -50,8 +55,7 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
icon: CATEGORY_ICONS[index % CATEGORY_ICONS.length] ?? '✦',
|
||||
description: category.description,
|
||||
maxNomineesPerUser: category.maxNomineesPerUser,
|
||||
candidateCount,
|
||||
subcategoryCount: matchingSubcategories.length,
|
||||
subcategoryNames,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -65,6 +69,9 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
|
||||
seenGroups.add(groupKey)
|
||||
const matchingSubcategories = displayCategories.value.filter((item) => item.groupName.trim().toLowerCase() === groupKey)
|
||||
const subcategoryNames = matchingSubcategories
|
||||
.map((item) => item.name.trim())
|
||||
.filter(Boolean)
|
||||
return [{
|
||||
id: category.id,
|
||||
groupName: category.groupName,
|
||||
@@ -72,8 +79,7 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
icon: CATEGORY_ICONS[index % CATEGORY_ICONS.length] ?? '✦',
|
||||
description: '',
|
||||
maxNomineesPerUser: category.maxNomineesPerUser,
|
||||
candidateCount: matchingSubcategories.reduce((sum, item) => sum + item.candidates.length, 0),
|
||||
subcategoryCount: matchingSubcategories.length,
|
||||
subcategoryNames,
|
||||
}]
|
||||
})
|
||||
})
|
||||
@@ -83,8 +89,6 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
const bootstrapArchiveYears = computed<Array<{ year: number }>>(() =>
|
||||
(store.overview.archiveYears ?? []).length > 0
|
||||
? (store.overview.archiveYears ?? []).map((entry) => ({ year: entry.year }))
|
||||
: store.overview.winnersPreview.length > 0
|
||||
? [...new Set(store.overview.winnersPreview.map((winner) => winner.year))].map((year) => ({ year }))
|
||||
: [{ year: store.overview.year - 1 }],
|
||||
)
|
||||
|
||||
|
||||
@@ -95,9 +95,9 @@ export function useHomeLandingState() {
|
||||
const {
|
||||
archiveYears,
|
||||
selectedArchive,
|
||||
winnerShowcaseYear,
|
||||
winnerShowcase,
|
||||
archiveYearButtonStyle,
|
||||
winnerPlatformKey,
|
||||
winnerPlatformLabel,
|
||||
winnerPlatformStyle,
|
||||
} = useHomeArchivePresentation(store, archiveYear)
|
||||
@@ -120,8 +120,10 @@ export function useHomeLandingState() {
|
||||
phasePrimaryActionStyle,
|
||||
streamEyebrow,
|
||||
streamTitle,
|
||||
streamText,
|
||||
streamMeta,
|
||||
streamLockedLabel,
|
||||
streamButtonLabel,
|
||||
streamButtonUrl,
|
||||
streamLockedTitle,
|
||||
statOneValue,
|
||||
statOneLabel,
|
||||
@@ -140,6 +142,7 @@ export function useHomeLandingState() {
|
||||
previewPhase,
|
||||
landingCategories,
|
||||
candidateCount,
|
||||
siteContent,
|
||||
publicStreamUrl,
|
||||
showDate,
|
||||
showStartsAt,
|
||||
@@ -299,8 +302,10 @@ export function useHomeLandingState() {
|
||||
phasePrimaryActionStyle,
|
||||
streamEyebrow,
|
||||
streamTitle,
|
||||
streamText,
|
||||
streamMeta,
|
||||
streamLockedLabel,
|
||||
streamButtonLabel,
|
||||
streamButtonUrl,
|
||||
streamLockedTitle,
|
||||
statOneValue,
|
||||
statOneLabel,
|
||||
@@ -328,6 +333,7 @@ export function useHomeLandingState() {
|
||||
clipSubmissionDisabledMessage,
|
||||
archiveYears,
|
||||
selectedArchive,
|
||||
winnerShowcaseYear,
|
||||
winnerShowcase,
|
||||
activeCat,
|
||||
submitted,
|
||||
@@ -365,7 +371,6 @@ export function useHomeLandingState() {
|
||||
onCloseArchive,
|
||||
archiveYearButtonStyle,
|
||||
winnerPlatformStyle,
|
||||
winnerPlatformKey,
|
||||
winnerPlatformLabel,
|
||||
privacyModalStop: stop,
|
||||
accountModalStop: stop,
|
||||
|
||||
@@ -86,6 +86,17 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
let landingLoaderTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function syncScrollLock() {
|
||||
const shouldLock = landingLoaderVisible.value
|
||||
|| modalOpen.value
|
||||
|| accountModalOpen.value
|
||||
|| privacyModalOpen.value
|
||||
|| archiveModalOpen.value
|
||||
|
||||
document.documentElement.style.overflow = shouldLock ? 'hidden' : ''
|
||||
document.body.style.overflow = shouldLock ? 'hidden' : ''
|
||||
}
|
||||
|
||||
function setRootEl(element: Element | ComponentPublicInstance | null) {
|
||||
rootEl.value = element instanceof HTMLElement ? element : null
|
||||
}
|
||||
@@ -170,7 +181,7 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
const showCountdown = split(showCompleted ? 0 : showStarted ? now - showTarget : showTarget - now)
|
||||
|
||||
setText(countdownRefs.labelEl.value, activeTarget.label)
|
||||
setText(countdownRefs.streamLabelEl.value, showCompleted ? 'Award abgeschlossen' : showStarted ? 'Stream läuft seit' : 'Finale startet in')
|
||||
setText(countdownRefs.streamLabelEl.value, showCompleted ? 'Award abgeschlossen' : showStarted ? 'Live seit' : 'Finale in')
|
||||
setText(countdownRefs.dEl.value, pad(activeCountdown.d))
|
||||
setText(countdownRefs.hEl.value, pad(activeCountdown.h))
|
||||
setText(countdownRefs.mEl.value, pad(activeCountdown.m))
|
||||
@@ -209,8 +220,8 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
return 'show'
|
||||
}
|
||||
|
||||
watch([modalOpen, accountModalOpen, privacyModalOpen, archiveModalOpen], () => {
|
||||
document.body.style.overflow = modalOpen.value || accountModalOpen.value || privacyModalOpen.value || archiveModalOpen.value ? 'hidden' : ''
|
||||
watch([landingLoaderVisible, modalOpen, accountModalOpen, privacyModalOpen, archiveModalOpen], () => {
|
||||
syncScrollLock()
|
||||
nextTick(setupDom)
|
||||
updateStickyCountdownVisibility()
|
||||
})
|
||||
@@ -220,6 +231,7 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
syncScrollLock()
|
||||
if (!authStore.hydrated) {
|
||||
await authStore.hydrate()
|
||||
}
|
||||
@@ -244,6 +256,7 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.documentElement.style.overflow = ''
|
||||
document.body.style.overflow = ''
|
||||
window.removeEventListener('scroll', updateStickyCountdownVisibility)
|
||||
window.removeEventListener('resize', updateStickyCountdownVisibility)
|
||||
|
||||
@@ -226,7 +226,7 @@ export function useHomeParticipationSubmitActions(params: {
|
||||
candidateId: selectedCreator?.id ?? null,
|
||||
twitchUserId: session.twitchUserId,
|
||||
clipUrl: url,
|
||||
title: clipContext.description || `Clip fuer ${selectedCreator?.displayName ?? 'die Award-Show'}`,
|
||||
title: clipContext.description || `Clip für ${selectedCreator?.displayName ?? 'die Award-Show'}`,
|
||||
creator: selectedCreator?.displayName ?? fallbackCreatorName.value,
|
||||
})
|
||||
submitted.value = true
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { computed, onBeforeUnmount, ref, type ComputedRef, type Ref } from 'vue'
|
||||
|
||||
import { plainTextContentFromStorage } from '../../lib/privacyContent'
|
||||
import type { OverviewResponse } from '../../types/awards'
|
||||
import type { HomeLandingCategory, HomePreviewPhase } from './homeLandingTypes'
|
||||
|
||||
export function useHomePhasePresentation(params: {
|
||||
previewPhase: Ref<HomePreviewPhase>
|
||||
landingCategories: ComputedRef<HomeLandingCategory[]>
|
||||
candidateCount: ComputedRef<number>
|
||||
siteContent: ComputedRef<OverviewResponse['siteContent']>
|
||||
publicStreamUrl: ComputedRef<string>
|
||||
showDate: ComputedRef<string>
|
||||
showStartsAt: ComputedRef<string>
|
||||
@@ -17,6 +20,7 @@ export function useHomePhasePresentation(params: {
|
||||
previewPhase,
|
||||
landingCategories,
|
||||
candidateCount,
|
||||
siteContent,
|
||||
publicStreamUrl,
|
||||
showDate,
|
||||
showStartsAt,
|
||||
@@ -39,8 +43,10 @@ export function useHomePhasePresentation(params: {
|
||||
const showStartMs = computed(() => parseShowStartMs(showDate.value, showStartsAt.value))
|
||||
const streamLive = computed(() => showPhase.value && !Number.isNaN(showStartMs.value) && currentTimestamp.value >= showStartMs.value)
|
||||
const streamLocked = computed(() => !streamLive.value)
|
||||
const streamBanner = computed(() => siteContent.value.streamBanner ?? defaultStreamBannerContent())
|
||||
const useCompletedStreamBanner = computed(() => completedPhase.value && streamBanner.value.useCompletedContent)
|
||||
const phaseCardTitle = computed(() => completedPhase.value ? 'Award-Jahr abgeschlossen' : nominationPhase.value ? 'Community Nominierung' : votingPhase.value ? 'Community Voting' : preparationPhase.value ? 'Aufbereitung bis zur Show' : 'Award Show Live')
|
||||
const phaseCardDescription = computed(() => completedPhase.value ? 'Die grosse Award-Show ist beendet. Das Jahr ist abgeschlossen und alle Teilnahme-Aktionen sind gesperrt.' : nominationPhase.value ? 'Die Nominierungsphase laeuft gerade. Reiche pro Kategorie die erlaubte Anzahl an Stream- oder Kanal-Links ein.' : votingPhase.value ? 'Die Nominierungsphase ist abgeschlossen.\nJetzt liegt es an dir: Stimme fuer deine Favoriten!' : preparationPhase.value ? 'Das Voting ist abgeschlossen. Das Team bereitet Clips, Ablauf und Gewinner-Momente fuer die Show vor.' : 'Die Award-Show laeuft jetzt live. Zeit fuer Buehne, Gewinner:innen und ganz viel Glitzer.')
|
||||
const phaseCardDescription = computed(() => completedPhase.value ? 'Die große Award-Show ist beendet. Das Jahr ist abgeschlossen und alle Teilnahme-Aktionen sind gesperrt.' : nominationPhase.value ? 'Die Nominierungsphase läuft gerade. Reiche pro Kategorie die erlaubte Anzahl an Stream- oder Kanal-Links ein.' : votingPhase.value ? 'Die Nominierungsphase ist abgeschlossen.\nJetzt liegt es an dir: Stimme für deine Favoriten!' : preparationPhase.value ? 'Das Voting ist abgeschlossen. Das Team bereitet Clips, Ablauf und Gewinner-Momente für die Show vor.' : 'Die Award-Show läuft jetzt live. Zeit für Bühne, Gewinner:innen und ganz viel Glitzer.')
|
||||
const phaseCardRange = computed(() => completedPhase.value ? `Finale abgeschlossen · ${formatShowDate()}` : nominationPhase.value ? `Nominierungszeitraum · ${formatRange('nomination')}` : votingPhase.value ? `Voting-Zeitraum · ${formatRange('voting')}` : preparationPhase.value ? `Aufbereitungszeit · ${formatRange('preparation')}` : `Live · ${formatShowDate()} · ${formatTimeLabel(showStartsAt.value)} Uhr`)
|
||||
const phaseStatusLabel = computed(() => completedPhase.value ? 'ABGESCHLOSSEN' : streamLive.value ? 'LIVE' : showPhase.value ? 'STARTET BALD' : preparationPhase.value ? 'IN AUFBEREITUNG' : 'AKTIV')
|
||||
const phaseStatusStyle = computed(() => completedPhase.value ? 'display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:999px;background:#f1ecfb;color:#8b6cdb;font-size:11px;font-weight:700;letter-spacing:.5px;' : showPhase.value ? 'display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:999px;background:#ffe5ec;color:#ec3b5a;font-size:11px;font-weight:700;letter-spacing:.5px;' : preparationPhase.value ? 'display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:999px;background:#fff1d6;color:#b7791f;font-size:11px;font-weight:700;letter-spacing:.5px;' : 'display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:999px;background:#e3f7ec;color:#1f9d5a;font-size:11px;font-weight:700;letter-spacing:.5px;')
|
||||
@@ -49,10 +55,30 @@ export function useHomePhasePresentation(params: {
|
||||
const phasePrimaryActionStyle = computed(() => phasePrimaryDisabled.value
|
||||
? 'display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:15px 18px;border-radius:13px;background:#eee7f8;color:#9b8abf;border:none;text-decoration:none;font-family:\'Outfit\',sans-serif;font-weight:600;font-size:15px;box-shadow:none;cursor:not-allowed;'
|
||||
: 'display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:15px 18px;border-radius:13px;background:linear-gradient(135deg,#8b6cdb,#7355c8);color:#fff;border:none;text-decoration:none;font-family:\'Outfit\',sans-serif;font-weight:600;font-size:15px;box-shadow:0 10px 22px rgba(124,86,196,.32);cursor:pointer;')
|
||||
const streamEyebrow = computed(() => 'Das grosse Finale')
|
||||
const streamTitle = computed(() => 'Award-Show Countdown')
|
||||
const streamMeta = computed(() => `Finale am ${formatShowDate()} · ${formatTimeLabel(showStartsAt.value)} Uhr`)
|
||||
const streamLockedLabel = computed(() => completedPhase.value ? 'Award abgeschlossen' : 'Stream noch gesperrt')
|
||||
const streamEyebrow = computed(() => useCompletedStreamBanner.value
|
||||
? firstText(streamBanner.value.completedEyebrow, 'Danke fürs Mitfiebern')
|
||||
: firstText(streamBanner.value.eyebrow, 'Das große Finale'))
|
||||
const streamTitle = computed(() => useCompletedStreamBanner.value
|
||||
? firstText(streamBanner.value.completedTitle, 'Award-Show abgeschlossen')
|
||||
: firstText(streamBanner.value.title, 'Award-Show Finale'))
|
||||
const streamText = computed(() => useCompletedStreamBanner.value
|
||||
? firstText(streamBanner.value.completedText, 'Die große Award-Show ist vorbei. Danke an alle, die live dabei waren.')
|
||||
: firstText(streamBanner.value.text, 'Countdown bis zur großen Live-Show'))
|
||||
const streamMeta = computed(() => completedPhase.value ? `Finale abgeschlossen · ${formatShowDate()}` : `Finale am ${formatShowDate()} · ${formatTimeLabel(showStartsAt.value)} Uhr`)
|
||||
const streamButtonLabel = computed(() => {
|
||||
if (useCompletedStreamBanner.value) {
|
||||
return firstText(streamBanner.value.completedButtonLabel, 'Highlights ansehen')
|
||||
}
|
||||
|
||||
return streamLive.value
|
||||
? firstText(streamBanner.value.liveButtonLabel, 'Jetzt live · Zum Stream')
|
||||
: firstText(streamBanner.value.lockedButtonLabel, 'Stream noch gesperrt')
|
||||
})
|
||||
const streamButtonUrl = computed(() => useCompletedStreamBanner.value
|
||||
? (streamBanner.value.completedButtonUrl ?? '').trim()
|
||||
: streamLive.value
|
||||
? (streamBanner.value.liveButtonUrl ?? '').trim()
|
||||
: '')
|
||||
const streamLockedTitle = computed(() => completedPhase.value ? 'Die Award-Show ist beendet' : 'Verfügbar, sobald die Show startet')
|
||||
const statOneValue = computed(() => completedPhase.value ? 'DONE' : streamLive.value ? 'LIVE' : String(candidateCount.value))
|
||||
const statOneLabel = computed(() => completedPhase.value ? 'Jahr-Status' : showPhase.value ? 'Show-Status' : 'Kandidat:innen')
|
||||
@@ -101,8 +127,10 @@ export function useHomePhasePresentation(params: {
|
||||
phasePrimaryActionStyle,
|
||||
streamEyebrow,
|
||||
streamTitle,
|
||||
streamText,
|
||||
streamMeta,
|
||||
streamLockedLabel,
|
||||
streamButtonLabel,
|
||||
streamButtonUrl,
|
||||
streamLockedTitle,
|
||||
statOneValue,
|
||||
statOneLabel,
|
||||
@@ -134,3 +162,25 @@ function normalizeTime(value: string) {
|
||||
function formatTimeLabel(value: string) {
|
||||
return normalizeTime(value).slice(0, 5)
|
||||
}
|
||||
|
||||
function firstText(value: string | null | undefined, fallback: string) {
|
||||
const trimmed = plainTextContentFromStorage(value ?? '')
|
||||
return trimmed || fallback
|
||||
}
|
||||
|
||||
function defaultStreamBannerContent(): OverviewResponse['siteContent']['streamBanner'] {
|
||||
return {
|
||||
eyebrow: 'Das große Finale',
|
||||
title: 'Award-Show Finale',
|
||||
text: 'Countdown bis zur großen Live-Show',
|
||||
liveButtonLabel: 'Jetzt live · Zum Stream',
|
||||
liveButtonUrl: '',
|
||||
lockedButtonLabel: 'Stream noch gesperrt',
|
||||
useCompletedContent: false,
|
||||
completedEyebrow: 'Danke fürs Mitfiebern',
|
||||
completedTitle: 'Award-Show abgeschlossen',
|
||||
completedText: 'Die große Award-Show ist vorbei. Danke an alle, die live dabei waren.',
|
||||
completedButtonLabel: 'Highlights ansehen',
|
||||
completedButtonUrl: '',
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user