Add team roles and content management updates
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
privacyModalOpen: boolean
|
||||
privacyContentBlocks: string[]
|
||||
privacyContentHtml: string
|
||||
onClosePrivacy: () => void
|
||||
privacyModalStop: (event: Event) => void
|
||||
accountModalOpen: boolean
|
||||
@@ -32,14 +32,11 @@ const props = defineProps<{
|
||||
</div>
|
||||
<button @click="props.onClosePrivacy" 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>
|
||||
</div>
|
||||
<div style="overflow-y:auto;padding:28px 32px;flex:1;display:flex;flex-direction:column;gap:14px;font-family:'Outfit',sans-serif;font-size:14px;line-height:1.7;color:#6f6685;">
|
||||
<p
|
||||
v-for="(block, index) in props.privacyContentBlocks"
|
||||
:key="`privacy-block-${index}`"
|
||||
style="margin:0;padding:14px 16px;border-radius:16px;border:1px solid #f1ecfb;background:#fcfbff;white-space:pre-wrap;"
|
||||
>
|
||||
{{ block }}
|
||||
</p>
|
||||
<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="props.privacyContentHtml" class="home-privacy-content" v-html="props.privacyContentHtml" />
|
||||
<div v-else style="margin:0;padding:14px 16px;border-radius:16px;border:1px solid #f1ecfb;background:#fcfbff;">
|
||||
Datenschutzerklärung wird geladen.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,3 +96,25 @@ const props = defineProps<{
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home-privacy-content :deep(p) {
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
|
||||
.home-privacy-content :deep(p:last-child),
|
||||
.home-privacy-content :deep(ul:last-child),
|
||||
.home-privacy-content :deep(ol:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.home-privacy-content :deep(ul),
|
||||
.home-privacy-content :deep(ol) {
|
||||
margin: 0 0 14px 20px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.home-privacy-content :deep(li) {
|
||||
margin: 3px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -37,7 +37,7 @@ const props = defineProps<{
|
||||
:style="props.archiveYearButtonStyle(year.active)"
|
||||
>
|
||||
<span>{{ year.label }}</span>
|
||||
<span :style="year.active ? 'display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;border-radius:999px;background:#fff;color:#8b6cdb;font-size:11px;font-weight:800;box-shadow:0 6px 14px rgba(139,108,219,.12);' : 'display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;border-radius:999px;background:#f1ecfb;color:#7355c8;font-size:11px;font-weight:800;'">{{ year.winners.length }}</span>
|
||||
<span :style="year.active ? 'display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;border-radius:999px;background:#fff;color:#8b6cdb;font-size:11px;font-weight:800;box-shadow:0 6px 14px rgba(139,108,219,.12);' : 'display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;border-radius:999px;background:#f1ecfb;color:#7355c8;font-size:11px;font-weight:800;'">{{ year.winnerCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -46,7 +46,7 @@ const props = defineProps<{
|
||||
|
||||
const nominationCatValue = ref(0)
|
||||
const clipCatValue = ref(0)
|
||||
const clipNomValue = ref(0)
|
||||
const clipNomQuery = ref('')
|
||||
|
||||
watch(
|
||||
() => props.catOptions,
|
||||
@@ -61,21 +61,13 @@ watch(
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.clipNomOptions,
|
||||
(options) => {
|
||||
clipNomValue.value = resolveOptionValue(clipNomValue.value, options)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function resolveOptionValue(value: number, options: HomeSelectionOption[]) {
|
||||
return options.some((option) => option.id === value) ? value : options[0]?.id ?? 0
|
||||
}
|
||||
|
||||
function handleClipCategoryChange(value: number) {
|
||||
clipCatValue.value = value
|
||||
clipNomValue.value = 0
|
||||
clipNomQuery.value = ''
|
||||
notifyClipCategoryChange(value)
|
||||
}
|
||||
|
||||
@@ -134,7 +126,7 @@ function notifyClipCategoryChange(value: number) {
|
||||
<section style="display:flex;flex-direction:column;gap:15px;padding:18px;border-radius:18px;background:#fcfaff;border:1px solid #efe7fb;">
|
||||
<div>
|
||||
<h4 style="font-family:'Outfit',sans-serif;font-size:16px;font-weight:800;color:#3f3556;margin:0 0 5px;">VTuber oder Streamer nominieren</h4>
|
||||
<p style="font-size:13px;line-height:1.45;color:#8a8398;margin:0;">Name und Stream-Link gehen direkt in den Admin-Review.</p>
|
||||
<p style="font-size:13px;line-height:1.45;color:#8a8398;margin:0;">Der Stream-Link geht direkt in den Admin-Review; den Anzeigenamen vergibt das Team.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:700;color:#5f44ad;margin-bottom:6px;">Kategorie</label>
|
||||
@@ -145,14 +137,10 @@ function notifyClipCategoryChange(value: number) {
|
||||
:options="props.catOptions"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:700;color:#5f44ad;margin-bottom:6px;">Name <span style="color:#e11d48;">*</span></label>
|
||||
<input data-dc-ref="nominationNameRef" type="text" placeholder="Kanalname oder Anzeigename" maxlength="120" style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;" style-focus="border-color:#8b6cdb;" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:700;color:#5f44ad;margin-bottom:6px;">Stream-Link <span style="color:#e11d48;">*</span></label>
|
||||
<input data-dc-ref="nominationStreamUrlRef" type="url" placeholder="https://twitch.tv/kanal oder https://kick.com/kanal" maxlength="300" style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;" style-focus="border-color:#8b6cdb;" />
|
||||
<p style="font-size:12.5px;line-height:1.45;color:#8a8398;margin:6px 0 0;">Twitch, Kick, YouTube oder ein anderer offizieller Kanal-Link.</p>
|
||||
<input data-dc-ref="nominationStreamUrlRef" type="url" placeholder="https://plattform.de/dein-kanal" maxlength="300" style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;" style-focus="border-color:#8b6cdb;" />
|
||||
<p style="font-size:12.5px;line-height:1.45;color:#8a8398;margin:6px 0 0;">Offizieller Kanal- oder Stream-Link der Person.</p>
|
||||
</div>
|
||||
<button @click="props.submitNomination" :disabled="props.submitting" :style="props.submitting ? 'width:100%;display:flex;align-items:center;justify-content:center;gap:10px;padding:16px;border-radius:13px;border:none;background:#d1c4e9;color:#9e8cc5;font-family:\'Outfit\',sans-serif;font-weight:600;font-size:15px;cursor:not-allowed;' : 'width:100%;display:flex;align-items:center;justify-content:center;gap:10px;padding:16px;border-radius:13px;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 24px rgba(124,86,196,.3);'" style-hover="transform:translateY(-2px);">
|
||||
{{ props.submitting ? 'Speichert ...' : 'Nominierung einreichen' }}
|
||||
@@ -166,7 +154,7 @@ function notifyClipCategoryChange(value: number) {
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:700;color:#5f44ad;margin-bottom:6px;">Clip-Link <span style="color:#e11d48;">*</span></label>
|
||||
<input data-dc-ref="clipUrlRef" type="url" placeholder="https://clips.twitch.tv/... oder YouTube · TikTok" style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;" style-focus="border-color:#8b6cdb;" />
|
||||
<input data-dc-ref="clipUrlRef" type="url" placeholder="https://plattform.de/dein-clip" style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;" style-focus="border-color:#8b6cdb;" />
|
||||
</div>
|
||||
<div class="home-modal__clip-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div>
|
||||
@@ -180,13 +168,19 @@ function notifyClipCategoryChange(value: number) {
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:700;color:#5f44ad;margin-bottom:6px;">Für welche:n VTuber?</label>
|
||||
<HomeSelectDropdown
|
||||
v-model="clipNomValue"
|
||||
data-ref="clipNomRef"
|
||||
label="VTuber fuer Clip auswaehlen"
|
||||
placeholder="Noch keine Kandidat:innen"
|
||||
:options="props.clipNomOptions"
|
||||
<input
|
||||
v-model="clipNomQuery"
|
||||
data-dc-ref="clipNomSearchRef"
|
||||
type="text"
|
||||
list="clip-nominee-options"
|
||||
placeholder="Name suchen"
|
||||
autocomplete="off"
|
||||
style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;"
|
||||
style-focus="border-color:#8b6cdb;"
|
||||
/>
|
||||
<datalist id="clip-nominee-options">
|
||||
<option v-for="option in props.clipNomOptions" :key="option.id" :value="option.label" />
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -235,7 +229,7 @@ function notifyClipCategoryChange(value: number) {
|
||||
<div class="home-modal__clip-body" style="padding:24px 40px 32px;overflow-y:auto;display:flex;flex-direction:column;gap:16px;">
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:700;color:#5f44ad;margin-bottom:6px;">Clip-Link <span style="color:#e11d48;">*</span></label>
|
||||
<input data-dc-ref="clipUrlRef" type="url" placeholder="https://clips.twitch.tv/... oder YouTube · TikTok" style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fbf9ff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;" style-focus="border-color:#8b6cdb;" />
|
||||
<input data-dc-ref="clipUrlRef" type="url" placeholder="https://plattform.de/dein-clip" style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fbf9ff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;" style-focus="border-color:#8b6cdb;" />
|
||||
</div>
|
||||
<div class="home-modal__clip-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div>
|
||||
@@ -249,13 +243,19 @@ function notifyClipCategoryChange(value: number) {
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:700;color:#5f44ad;margin-bottom:6px;">Für welche:n VTuber?</label>
|
||||
<HomeSelectDropdown
|
||||
v-model="clipNomValue"
|
||||
data-ref="clipNomRef"
|
||||
label="VTuber fuer Clip auswaehlen"
|
||||
placeholder="Noch keine Kandidat:innen"
|
||||
:options="props.clipNomOptions"
|
||||
<input
|
||||
v-model="clipNomQuery"
|
||||
data-dc-ref="clipNomSearchRef"
|
||||
type="text"
|
||||
list="clip-nominee-options"
|
||||
placeholder="Name suchen"
|
||||
autocomplete="off"
|
||||
style="width:100%;padding:12px 14px;border-radius:11px;border:1.5px solid #e6dcf6;background:#fbf9ff;font-family:'Outfit',sans-serif;font-size:14.5px;color:#3f3556;outline:none;box-sizing:border-box;"
|
||||
style-focus="border-color:#8b6cdb;"
|
||||
/>
|
||||
<datalist id="clip-nominee-options">
|
||||
<option v-for="option in props.clipNomOptions" :key="option.id" :value="option.label" />
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -26,12 +26,12 @@ const {
|
||||
communitySocialLinks,
|
||||
footerLinks,
|
||||
faqItems,
|
||||
privacyContentBlocks,
|
||||
privacyContentHtml,
|
||||
publicStreamUrl,
|
||||
displayCategories,
|
||||
nominationPhase,
|
||||
votingPhase,
|
||||
reviewPhase,
|
||||
preparationPhase,
|
||||
showPhase,
|
||||
completedPhase,
|
||||
showCountdown,
|
||||
@@ -145,7 +145,7 @@ const {
|
||||
const previewPhaseButtons = [
|
||||
{ key: 'nomination', label: 'Nominierung', hint: 'Einreichen & Clips' },
|
||||
{ key: 'voting', label: 'Voting', hint: 'Community stimmt ab' },
|
||||
{ key: 'review', label: 'Review', hint: 'Auswertung' },
|
||||
{ key: 'preparation', label: 'Aufbereitung', hint: 'Pause vor Show' },
|
||||
{ key: 'show', label: 'Show', hint: 'Live-Finale' },
|
||||
] as const
|
||||
|
||||
@@ -162,7 +162,7 @@ const { setRootEl, landingLoaderVisible, handleNominationSubmit, handleClipSubmi
|
||||
archiveYear,
|
||||
nominationPhase,
|
||||
votingPhase,
|
||||
reviewPhase,
|
||||
preparationPhase,
|
||||
completedPhase,
|
||||
initializeHomeInteractions,
|
||||
submitNomination,
|
||||
@@ -250,7 +250,7 @@ const { setRootEl, landingLoaderVisible, handleNominationSubmit, handleClipSubmi
|
||||
<HomeTimelineSection
|
||||
:nomination-phase="nominationPhase"
|
||||
:voting-phase="votingPhase"
|
||||
:review-phase="reviewPhase"
|
||||
:preparation-phase="preparationPhase"
|
||||
:show-phase="showPhase"
|
||||
:completed-phase="completedPhase"
|
||||
:timeline-line-style="timelineLineStyle"
|
||||
@@ -278,7 +278,7 @@ const { setRootEl, landingLoaderVisible, handleNominationSubmit, handleClipSubmi
|
||||
:section-action-disabled="sectionActionDisabled"
|
||||
:section-action-style="sectionActionStyle"
|
||||
:nomination-phase="nominationPhase"
|
||||
:review-phase="reviewPhase"
|
||||
:preparation-phase="preparationPhase"
|
||||
:on-section-action="onSectionAction"
|
||||
/>
|
||||
|
||||
@@ -342,7 +342,7 @@ const { setRootEl, landingLoaderVisible, handleNominationSubmit, handleClipSubmi
|
||||
:winner-platform-key="winnerPlatformKey"
|
||||
:winner-platform-label="winnerPlatformLabel"
|
||||
:privacy-modal-open="privacyModalOpen"
|
||||
:privacy-content-blocks="privacyContentBlocks"
|
||||
:privacy-content-html="privacyContentHtml"
|
||||
:on-close-privacy="onClosePrivacy"
|
||||
:privacy-modal-stop="privacyModalStop"
|
||||
:account-modal-open="accountModalOpen"
|
||||
|
||||
@@ -58,7 +58,7 @@ defineProps<{
|
||||
winnerPlatformKey: (url: string) => string
|
||||
winnerPlatformLabel: (url: string) => string
|
||||
privacyModalOpen: boolean
|
||||
privacyContentBlocks: string[]
|
||||
privacyContentHtml: string
|
||||
onClosePrivacy: () => void
|
||||
privacyModalStop: (event: Event) => void
|
||||
accountModalOpen: boolean
|
||||
@@ -132,7 +132,7 @@ defineProps<{
|
||||
|
||||
<HomeAccountAndPrivacyModals
|
||||
:privacy-modal-open="privacyModalOpen"
|
||||
:privacy-content-blocks="privacyContentBlocks"
|
||||
:privacy-content-html="privacyContentHtml"
|
||||
:on-close-privacy="onClosePrivacy"
|
||||
:privacy-modal-stop="privacyModalStop"
|
||||
:account-modal-open="accountModalOpen"
|
||||
|
||||
@@ -7,7 +7,7 @@ const props = defineProps<{
|
||||
sectionActionDisabled: boolean
|
||||
sectionActionStyle: string
|
||||
nominationPhase: boolean
|
||||
reviewPhase: boolean
|
||||
preparationPhase: boolean
|
||||
onSectionAction: (event?: Event) => void
|
||||
}>()
|
||||
</script>
|
||||
@@ -72,7 +72,7 @@ const props = defineProps<{
|
||||
:style="props.sectionActionStyle"
|
||||
style-hover="transform:translateY(-2px);"
|
||||
>
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" :fill="props.nominationPhase ? '#E855A5' : props.reviewPhase ? '#B7791F' : '#9146FF'"><path d="M4 2L2.4 6v14.5h5V23h2.7l2.5-2.5h4L21.6 16V2H4zm15.3 13.1l-2.9 2.9h-4.4l-2.5 2.5v-2.5H6.7V3.7h12.6v11.4z"/><path d="M11.1 7.1h1.7v5h-1.7zM15.7 7.1h1.7v5h-1.7z"/></svg>{{ props.sectionActionLabel }}
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" :fill="props.nominationPhase ? '#E855A5' : props.preparationPhase ? '#B7791F' : '#9146FF'"><path d="M4 2L2.4 6v14.5h5V23h2.7l2.5-2.5h4L21.6 16V2H4zm15.3 13.1l-2.9 2.9h-4.4l-2.5 2.5v-2.5H6.7V3.7h12.6v11.4z"/><path d="M11.1 7.1h1.7v5h-1.7zM15.7 7.1h1.7v5h-1.7z"/></svg>{{ props.sectionActionLabel }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { privacyContentToHtml } from '../../lib/privacyContent'
|
||||
|
||||
interface HomeSocialLink {
|
||||
label: string
|
||||
platform: string
|
||||
@@ -7,8 +11,10 @@ interface HomeSocialLink {
|
||||
}
|
||||
|
||||
interface FooterLink {
|
||||
key: string
|
||||
label: string
|
||||
url: string
|
||||
content: string
|
||||
}
|
||||
|
||||
interface SiteContent {
|
||||
@@ -31,6 +37,25 @@ const props = defineProps<{
|
||||
socialSimpleIconColor: (platform: string | null | undefined) => string
|
||||
platformKey: (platform: string | null | undefined) => string
|
||||
}>()
|
||||
|
||||
const activeFooterLink = ref<FooterLink | null>(null)
|
||||
const activeFooterHtml = computed(() => privacyContentToHtml(activeFooterLink.value?.content || ''))
|
||||
|
||||
function openFooterLink(event: Event, link: FooterLink) {
|
||||
if (!link.content.trim()) {
|
||||
if (!link.url.trim()) {
|
||||
event.preventDefault()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
activeFooterLink.value = link
|
||||
}
|
||||
|
||||
function closeFooterLink() {
|
||||
activeFooterLink.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -111,10 +136,83 @@ const props = defineProps<{
|
||||
VTuber Star Award 2026
|
||||
</div>
|
||||
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:8px 24px;font-size:14px;font-weight:500;">
|
||||
<a v-for="link in props.footerLinks" :key="link.label" :href="link.url" target="_blank" rel="noopener" style="color:var(--muted,#8a8398);text-decoration:none;" style-hover="color:var(--accent,#8b6cdb);">{{ link.label }}</a>
|
||||
<template v-for="link in props.footerLinks" :key="link.key || link.label">
|
||||
<button
|
||||
v-if="link.content"
|
||||
type="button"
|
||||
style="background:none;border:none;padding:0;color:var(--muted,#8a8398);text-decoration:none;cursor:pointer;font-size:inherit;font-family:inherit;font-weight:inherit;"
|
||||
style-hover="color:var(--accent,#8b6cdb);"
|
||||
@click="openFooterLink($event, link)"
|
||||
>
|
||||
{{ link.label }}
|
||||
</button>
|
||||
<a
|
||||
v-else
|
||||
:href="link.url || '#'"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
style="color:var(--muted,#8a8398);text-decoration:none;"
|
||||
style-hover="color:var(--accent,#8b6cdb);"
|
||||
@click="openFooterLink($event, link)"
|
||||
>
|
||||
{{ link.label }}
|
||||
</a>
|
||||
</template>
|
||||
<button @click="props.onOpenPrivacy" style="background:none;border:none;padding:0;color:var(--muted,#8a8398);text-decoration:none;cursor:pointer;font-size:inherit;" style-hover="color:var(--accent,#8b6cdb);">Datenschutz</button>
|
||||
</div>
|
||||
<div style="font-size:13px;color:var(--muted,#c9b8da);">© 2026 · Made with ♡ & Chaos</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<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:'Cormorant Garamond',serif;font-weight:700;font-size:26px;margin:0;color:#3f3556;">{{ activeFooterLink.label }}</h2>
|
||||
</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>
|
||||
</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" />
|
||||
<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>
|
||||
<a
|
||||
v-if="activeFooterLink.url"
|
||||
:href="activeFooterLink.url"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
style="display:inline-flex;align-items:center;gap:9px;margin-top:18px;padding:12px 16px;border-radius:14px;background:#f6f1fd;border:1px solid #e6dcf6;color:#6a4fb8;text-decoration:none;font-weight:700;"
|
||||
style-hover="background:#f1ecfb;"
|
||||
>
|
||||
Externe Seite öffnen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home-footer-page-content :deep(p) {
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
|
||||
.home-footer-page-content :deep(p:last-child),
|
||||
.home-footer-page-content :deep(ul:last-child),
|
||||
.home-footer-page-content :deep(ol:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.home-footer-page-content :deep(ul),
|
||||
.home-footer-page-content :deep(ol) {
|
||||
margin: 0 0 14px 20px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.home-footer-page-content :deep(li) {
|
||||
margin: 3px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<section id="ablauf" class="home-section" style="max-width:1200px;margin:0 auto;padding:90px 24px 70px;">
|
||||
<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:'Cormorant Garamond',serif;font-weight:700;font-size:clamp(32px,4vw,48px);margin:0 0 14px;letter-spacing:-.4px;color:#3f3556;">In vier Phasen 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 Review & Auswertung bis ganz zum Schluss zur grossen Show.</p>
|
||||
<h2 style="font-family:'Cormorant Garamond',serif;font-weight:700;font-size:clamp(32px,4vw,48px);margin:0 0 14px;letter-spacing:-.4px;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>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:20px;align-items:start;" data-timeline>
|
||||
@@ -58,29 +58,29 @@
|
||||
</div>
|
||||
|
||||
<div 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);' : reviewPhase ? '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;'">
|
||||
<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>
|
||||
</template>
|
||||
<template v-else-if="reviewPhase">
|
||||
<template v-else-if="preparationPhase">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" 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>
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
</template>
|
||||
</div>
|
||||
<div :style="reviewPhase ? 'background:#fffdf8;border:1px solid #f3ddae;border-radius:20px;padding:24px 20px;box-shadow:0 16px 38px rgba(183,121,31,.14);min-height:320px;' : showPhase || completedPhase ? 'background:#fff;border:1px solid #efe7fb;border-radius:20px;padding:24px 20px;box-shadow:0 12px 30px rgba(124,86,196,.08);min-height:320px;' : 'background:#fbf9ff;border:1px solid #ede5fa;border-radius:20px;padding:24px 20px;box-shadow:0 12px 30px rgba(124,86,196,.06);min-height:320px;'">
|
||||
<div :style="reviewPhase ? 'display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:999px;background:#fff1d6;color:#b7791f;font-size:11px;font-weight:700;letter-spacing:.5px;margin-bottom:12px;' : showPhase || 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;'">{{ reviewPhase ? 'IN PRÜFUNG' : showPhase || completedPhase ? 'ABGESCHLOSSEN' : 'BEVORSTEHEND' }}</div>
|
||||
<h3 style="font-family:'Outfit',sans-serif;font-weight:700;font-size:21px;margin:0 0 6px;color:#3f3556;">Review & Auswertung</h3>
|
||||
<div :style="reviewPhase ? 'font-size:13px;font-weight:600;color:#b7791f;margin-bottom:12px;' : showPhase || completedPhase ? 'font-size:13px;font-weight:600;color:#8b6cdb;margin-bottom:12px;' : 'font-size:13px;font-weight:600;color:#a99fc0;margin-bottom:12px;'">{{ formatTimelineRange('review') }}</div>
|
||||
<p style="font-size:14.5px;line-height:1.6;color:#7d7491;margin:0 0 16px;">Das Team prüft Fairness, Stimmen und Clips, wertet die Ergebnisse aus und bereitet die Show final vor.</p>
|
||||
<div :style="preparationPhase ? 'background:#fffdf8;border:1px solid #f3ddae;border-radius:20px;padding:24px 20px;box-shadow:0 16px 38px rgba(183,121,31,.14);min-height:320px;' : showPhase || completedPhase ? 'background:#fff;border:1px solid #efe7fb;border-radius:20px;padding:24px 20px;box-shadow:0 12px 30px rgba(124,86,196,.08);min-height:320px;' : 'background:#fbf9ff;border:1px solid #ede5fa;border-radius:20px;padding:24px 20px;box-shadow:0 12px 30px rgba(124,86,196,.06);min-height:320px;'">
|
||||
<div :style="preparationPhase ? 'display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:999px;background:#fff1d6;color:#b7791f;font-size:11px;font-weight:700;letter-spacing:.5px;margin-bottom:12px;' : showPhase || 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;'">{{ preparationPhase ? 'AUFBEREITUNG' : showPhase || completedPhase ? 'ABGESCHLOSSEN' : 'BEVORSTEHEND' }}</div>
|
||||
<h3 style="font-family:'Outfit',sans-serif;font-weight:700;font-size:21px;margin:0 0 6px;color:#3f3556;">Aufbereitung</h3>
|
||||
<div :style="preparationPhase ? 'font-size:13px;font-weight:600;color:#b7791f;margin-bottom:12px;' : showPhase || completedPhase ? 'font-size:13px;font-weight:600;color:#8b6cdb;margin-bottom:12px;' : 'font-size:13px;font-weight:600;color:#a99fc0;margin-bottom:12px;'">{{ formatTimelineRange('preparation') }}</div>
|
||||
<p style="font-size:14.5px;line-height:1.6;color:#7d7491;margin:0 0 16px;">Das Team bereitet Clips, Ablauf und Gewinner-Momente für die Show vor.</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled
|
||||
aria-disabled="true"
|
||||
:style="reviewButtonStyle(reviewPhase, showPhase, completedPhase)"
|
||||
:style="preparationButtonStyle(preparationPhase, showPhase, completedPhase)"
|
||||
>
|
||||
✦ {{ reviewPhase ? 'Auswertung läuft' : showPhase || completedPhase ? 'Auswertung abgeschlossen' : 'Review folgt' }}
|
||||
✦ {{ preparationPhase ? 'Aufbereitung läuft' : showPhase || completedPhase ? 'Aufbereitung abgeschlossen' : 'Aufbereitung folgt' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,12 +131,12 @@
|
||||
defineProps<{
|
||||
nominationPhase: boolean
|
||||
votingPhase: boolean
|
||||
reviewPhase: boolean
|
||||
preparationPhase: boolean
|
||||
showPhase: boolean
|
||||
completedPhase: boolean
|
||||
timelineLineStyle: string
|
||||
publicStreamUrl: string
|
||||
formatTimelineRange: (key: 'nomination' | 'voting' | 'review' | 'show') => string
|
||||
formatTimelineRange: (key: 'nomination' | 'voting' | 'preparation' | 'show') => string
|
||||
openNominate: (event?: Event) => void
|
||||
openVote: (event?: Event) => void
|
||||
onTimelineFinalAction: (event?: Event) => void
|
||||
@@ -158,8 +158,8 @@ function voteButtonStyle(votingPhase: boolean) {
|
||||
: "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:not-allowed;font-family:'Outfit',sans-serif;font-weight:600;font-size:14px;box-shadow:none;"
|
||||
}
|
||||
|
||||
function reviewButtonStyle(reviewPhase: boolean, showPhase: boolean, completedPhase: boolean) {
|
||||
if (reviewPhase) {
|
||||
function preparationButtonStyle(preparationPhase: boolean, showPhase: boolean, completedPhase: boolean) {
|
||||
if (preparationPhase) {
|
||||
return "display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:11px 20px;border-radius:11px;background:#fff1d6;color:#8a5a00;border:1px solid #f3ddae;cursor:not-allowed;font-family:'Outfit',sans-serif;font-weight:600;font-size:14px;"
|
||||
}
|
||||
|
||||
|
||||
@@ -2,19 +2,18 @@ import type { CandidateSummary } from '../../types/awards'
|
||||
|
||||
export type HomeInteractionModalKind = 'show' | 'vote' | 'nominate' | 'clip'
|
||||
|
||||
export type HomePreviewPhase = 'nomination' | 'voting' | 'review' | 'show' | 'completed'
|
||||
export type HomePreviewPhase = 'nomination' | 'voting' | 'preparation' | 'show' | 'completed'
|
||||
|
||||
export type HomeSuccessKind = 'vote' | 'show' | 'clip' | 'nomination'
|
||||
|
||||
export interface HomeClipSubmitContext {
|
||||
clipUrl: string
|
||||
selectedNomineeIndex: number
|
||||
selectedNomineeQuery: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface HomeNominationSubmitContext {
|
||||
categoryIndex: number
|
||||
name: string
|
||||
streamUrl: string
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface HomeSelectionOption {
|
||||
export interface HomeArchiveYearItem {
|
||||
year: number
|
||||
label: string
|
||||
winnerCount: number
|
||||
winners: Array<unknown>
|
||||
active: boolean
|
||||
}
|
||||
|
||||
@@ -6,16 +6,30 @@ type AwardsStore = ReturnType<typeof useAwardsStore>
|
||||
|
||||
export function useHomeArchivePresentation(store: AwardsStore, archiveYear: Ref<number>) {
|
||||
const archiveYears = computed(() => {
|
||||
const knownYears = new Set<number>(store.overview.winnersPreview.map((entry) => entry.year))
|
||||
if (store.archive.items.length > 0) {
|
||||
knownYears.add(store.archive.year)
|
||||
const knownYears = new Map<number, number>()
|
||||
for (const entry of store.overview.archiveYears ?? []) {
|
||||
knownYears.set(entry.year, entry.winnerCount)
|
||||
}
|
||||
|
||||
return [...knownYears]
|
||||
.sort((left, right) => right - left)
|
||||
.map((year) => ({
|
||||
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)
|
||||
}
|
||||
|
||||
return [...knownYears.entries()]
|
||||
.sort(([left], [right]) => right - left)
|
||||
.map(([year, winnerCount]) => ({
|
||||
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),
|
||||
active: archiveYear.value === year,
|
||||
}))
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { HomeDisplayCategory, HomeInteractionModalKind } from './homeLandin
|
||||
|
||||
type AwardsStore = ReturnType<typeof useAwardsStore>
|
||||
type AuthStore = ReturnType<typeof useAuthStore>
|
||||
type HomeTimelineKey = 'nomination' | 'voting' | 'review' | 'show'
|
||||
type HomeTimelineKey = 'nomination' | 'voting' | 'preparation' | 'show'
|
||||
|
||||
const CATEGORY_ICONS = ['✦', '★', '✧', '♬', '⚔', '☻', '♡', '✶'] as const
|
||||
|
||||
@@ -40,8 +40,10 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
displayCategories.value.reduce((sum, category) => sum + category.candidates.length, 0),
|
||||
)
|
||||
const bootstrapArchiveYears = computed<Array<{ year: number }>>(() =>
|
||||
store.overview.winnersPreview.length > 0
|
||||
? [...new Set(store.overview.winnersPreview.map((winner) => winner.year))].map((year) => ({ year }))
|
||||
(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 }],
|
||||
)
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export function useHomeLandingState() {
|
||||
hostSocialLinks,
|
||||
communitySocialLinks,
|
||||
footerLinks,
|
||||
privacyContentBlocks,
|
||||
privacyContentHtml,
|
||||
platformKey,
|
||||
isUploadedSocialIcon,
|
||||
socialSimpleIconPath,
|
||||
@@ -101,7 +101,7 @@ export function useHomeLandingState() {
|
||||
const {
|
||||
nominationPhase,
|
||||
votingPhase,
|
||||
reviewPhase,
|
||||
preparationPhase,
|
||||
showPhase,
|
||||
completedPhase,
|
||||
showCountdown,
|
||||
@@ -257,13 +257,13 @@ export function useHomeLandingState() {
|
||||
communitySocialLinks,
|
||||
footerLinks,
|
||||
faqItems,
|
||||
privacyContentBlocks,
|
||||
privacyContentHtml,
|
||||
publicStreamUrl,
|
||||
displayCategories,
|
||||
candidateCount,
|
||||
nominationPhase,
|
||||
votingPhase,
|
||||
reviewPhase,
|
||||
preparationPhase,
|
||||
showPhase,
|
||||
completedPhase,
|
||||
showCountdown,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { HomeNominationSubmitContext } from './homeLandingTypes'
|
||||
|
||||
type AuthStore = ReturnType<typeof useAuthStore>
|
||||
type AwardsStore = ReturnType<typeof useAwardsStore>
|
||||
type HomeTimelineKey = 'nomination' | 'voting' | 'review' | 'show' | 'completed'
|
||||
type HomeTimelineKey = 'nomination' | 'voting' | 'preparation' | 'show' | 'completed'
|
||||
|
||||
interface PhaseCountdownTarget {
|
||||
label: string
|
||||
@@ -35,11 +35,11 @@ interface UseHomeLandingViewEffectsParams {
|
||||
archiveYear: Readonly<Ref<number>>
|
||||
nominationPhase: Readonly<Ref<boolean>>
|
||||
votingPhase: Readonly<Ref<boolean>>
|
||||
reviewPhase: Readonly<Ref<boolean>>
|
||||
preparationPhase: Readonly<Ref<boolean>>
|
||||
completedPhase: Readonly<Ref<boolean>>
|
||||
initializeHomeInteractions: () => Promise<void>
|
||||
submitNomination: (nominationContext: HomeNominationSubmitContext) => Promise<void>
|
||||
submitClip: (clipContext: { clipUrl: string; selectedNomineeIndex: number; description: string }) => Promise<void>
|
||||
submitClip: (clipContext: { clipUrl: string; selectedNomineeQuery: string; description: string }) => Promise<void>
|
||||
}
|
||||
|
||||
export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParams) {
|
||||
@@ -56,7 +56,7 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
archiveYear,
|
||||
nominationPhase,
|
||||
votingPhase,
|
||||
reviewPhase,
|
||||
preparationPhase,
|
||||
completedPhase,
|
||||
initializeHomeInteractions,
|
||||
submitNomination,
|
||||
@@ -66,10 +66,9 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
const rootEl = ref<HTMLElement | null>(null)
|
||||
const landingLoaderVisible = ref(true)
|
||||
const nominationCatEl = ref<HTMLSelectElement | null>(null)
|
||||
const nominationNameEl = ref<HTMLInputElement | null>(null)
|
||||
const nominationStreamUrlEl = ref<HTMLInputElement | null>(null)
|
||||
const clipUrlEl = ref<HTMLInputElement | null>(null)
|
||||
const clipNomEl = ref<HTMLSelectElement | null>(null)
|
||||
const clipNomSearchEl = ref<HTMLInputElement | null>(null)
|
||||
const clipDescEl = ref<HTMLTextAreaElement | null>(null)
|
||||
const countdownRefs = {
|
||||
labelEl: ref<HTMLElement | null>(null),
|
||||
@@ -93,7 +92,7 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
function handleClipSubmit() {
|
||||
return submitClip({
|
||||
clipUrl: clipUrlEl.value?.value.trim() ?? '',
|
||||
selectedNomineeIndex: Number.parseInt(clipNomEl.value?.value || '0', 10) || 0,
|
||||
selectedNomineeQuery: clipNomSearchEl.value?.value.trim() ?? '',
|
||||
description: clipDescEl.value?.value.trim() ?? '',
|
||||
})
|
||||
}
|
||||
@@ -101,7 +100,6 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
function handleNominationSubmit() {
|
||||
return submitNomination({
|
||||
categoryIndex: Number.parseInt(nominationCatEl.value?.value || '0', 10) || 0,
|
||||
name: nominationNameEl.value?.value.trim() ?? '',
|
||||
streamUrl: nominationStreamUrlEl.value?.value.trim() ?? '',
|
||||
})
|
||||
}
|
||||
@@ -120,10 +118,9 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
countdownRefs.bmEl.value = root.querySelector('[data-dc-ref="bmRef"]')
|
||||
countdownRefs.bsEl.value = root.querySelector('[data-dc-ref="bsRef"]')
|
||||
nominationCatEl.value = root.querySelector('[data-dc-ref="nominationCatRef"]')
|
||||
nominationNameEl.value = root.querySelector('[data-dc-ref="nominationNameRef"]')
|
||||
nominationStreamUrlEl.value = root.querySelector('[data-dc-ref="nominationStreamUrlRef"]')
|
||||
clipUrlEl.value = root.querySelector('[data-dc-ref="clipUrlRef"]')
|
||||
clipNomEl.value = root.querySelector('[data-dc-ref="clipNomRef"]')
|
||||
clipNomSearchEl.value = root.querySelector('[data-dc-ref="clipNomSearchRef"]')
|
||||
clipDescEl.value = root.querySelector('[data-dc-ref="clipDescRef"]')
|
||||
}
|
||||
|
||||
@@ -178,7 +175,7 @@ export function useHomeLandingViewEffects(params: UseHomeLandingViewEffectsParam
|
||||
function resolveSelectedPhaseKey(): HomeTimelineKey {
|
||||
if (nominationPhase.value) return 'nomination'
|
||||
if (votingPhase.value) return 'voting'
|
||||
if (reviewPhase.value) return 'review'
|
||||
if (preparationPhase.value) return 'preparation'
|
||||
if (completedPhase.value) return 'completed'
|
||||
return 'show'
|
||||
}
|
||||
@@ -297,7 +294,7 @@ function resolvePhaseCountdownTarget(
|
||||
}
|
||||
|
||||
function buildTimelineSchedule(store: AwardsStore): TimelineScheduleItem[] {
|
||||
const phaseOrder: Exclude<HomeTimelineKey, 'completed'>[] = ['nomination', 'voting', 'review', 'show']
|
||||
const phaseOrder: Exclude<HomeTimelineKey, 'completed'>[] = ['nomination', 'voting', 'preparation', 'show']
|
||||
|
||||
return phaseOrder
|
||||
.map((key): TimelineScheduleItem | null => {
|
||||
@@ -358,8 +355,8 @@ function phaseTitle(key: HomeTimelineKey) {
|
||||
? 'Nominierung'
|
||||
: key === 'voting'
|
||||
? 'Voting'
|
||||
: key === 'review'
|
||||
? 'Review & Auswertung'
|
||||
: key === 'preparation'
|
||||
? 'Aufbereitung'
|
||||
: 'Award Show'
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ export function useHomeParticipationActions(params: {
|
||||
openModal('vote')
|
||||
return
|
||||
}
|
||||
if (previewPhase.value === 'review') {
|
||||
if (previewPhase.value === 'preparation') {
|
||||
event?.preventDefault()
|
||||
return
|
||||
}
|
||||
@@ -180,7 +180,7 @@ export function useHomeParticipationActions(params: {
|
||||
}
|
||||
|
||||
function isHomePreviewPhaseKey(value: string | undefined): value is HomePreviewPhase {
|
||||
return value === 'nomination' || value === 'voting' || value === 'review' || value === 'show'
|
||||
return value === 'nomination' || value === 'voting' || value === 'preparation' || value === 'show'
|
||||
}
|
||||
|
||||
function isCompletedPhase(value: string) {
|
||||
|
||||
@@ -35,9 +35,9 @@ export function useHomeParticipationPresentation(params: {
|
||||
const activeCategory = computed(() => displayCategories.value[activeCat.value] ?? displayCategories.value[0] ?? null)
|
||||
const activeCatName = computed(() => activeCategory.value?.name ?? '')
|
||||
const pickerTitle = computed(() => (modal.value === 'nominate' && previewPhase.value === 'nomination' ? 'Streamer nominieren' : modal.value === 'nominate' ? 'Eingegangene Nominierungen' : 'Deine Stimme zählt'))
|
||||
const pickerSubtitle = computed(() => (modal.value === 'nominate' && previewPhase.value === 'nomination' ? 'Reiche Name und Stream-Link ein. Optional kannst du direkt einen Clip mitschicken.' : modal.value === 'nominate' ? 'Die Nominierungsphase ist abgeschlossen — hier sind alle eingereichten Kandidat:innen.' : 'Wähle pro Kategorie deine:n Favorit:in. Eine Stimme pro Kategorie.'))
|
||||
const pickerSubtitle = computed(() => (modal.value === 'nominate' && previewPhase.value === 'nomination' ? 'Reiche den offiziellen Stream- oder Kanal-Link ein. Den Anzeigenamen vergibt das Team im Review.' : modal.value === 'nominate' ? 'Die Nominierungsphase ist abgeschlossen — hier sind alle eingereichten Kandidat:innen.' : 'Wähle pro Kategorie deine:n Favorit:in. Eine Stimme pro Kategorie.'))
|
||||
const successTitle = computed(() => successKind.value === 'nomination' ? 'Nominierung eingereicht ✦' : successKind.value === 'clip' ? 'Clip eingereicht ✦' : successKind.value === 'show' ? 'Erinnerung aktiviert ✦' : 'Stimme gespeichert ✩')
|
||||
const successText = computed(() => successKind.value === 'nomination' ? 'Danke! Name und Stream-Link wurden gespeichert und landen im Admin-Review.' : successKind.value === 'clip' ? 'Danke! Dein Clip wurde im Backend gespeichert und wird vom Team geprüft.' : successKind.value === 'show' ? `Wir erinnern dich rechtzeitig vor der Award-Show am ${formatShowDate(store)}.` : 'Danke fürs Abstimmen! Deine Auswahl wurde im Backend gespeichert.')
|
||||
const successText = computed(() => successKind.value === 'nomination' ? 'Danke! Der Stream-Link wurde gespeichert und landet im Admin-Review.' : successKind.value === 'clip' ? 'Danke! Dein Clip wurde im Backend gespeichert und wird vom Team geprüft.' : successKind.value === 'show' ? `Wir erinnern dich rechtzeitig vor der Award-Show am ${formatShowDate(store)}.` : 'Danke fürs Abstimmen! Deine Auswahl wurde im Backend gespeichert.')
|
||||
const clipNomOptions = computed(() => (displayCategories.value[clipCatIdx.value]?.candidates ?? []).map((candidate, index) => ({ id: index, label: `${candidate.displayName}` })))
|
||||
const catOptions = computed(() => displayCategories.value.map((category, index) => ({ id: index, label: `${category.icon} ${category.name}` })))
|
||||
const canSubmitClip = computed(() => previewPhase.value === 'nomination' && clipDsgvo.value)
|
||||
|
||||
@@ -113,13 +113,7 @@ export function useHomeParticipationSubmitActions(params: {
|
||||
return
|
||||
}
|
||||
|
||||
const name = nominationContext.name.trim()
|
||||
const streamUrl = nominationContext.streamUrl.trim()
|
||||
if (!name) {
|
||||
formError.value = 'Bitte gib den Namen des VTubers oder Streamers ein.'
|
||||
return
|
||||
}
|
||||
|
||||
if (!streamUrl) {
|
||||
formError.value = 'Bitte füge einen Stream- oder Kanal-Link hinzu.'
|
||||
return
|
||||
@@ -143,7 +137,7 @@ export function useHomeParticipationSubmitActions(params: {
|
||||
year: store.overview.year,
|
||||
categoryId: Number(category.id),
|
||||
twitchUserId: session.twitchUserId,
|
||||
nominations: [{ name, streamUrl }],
|
||||
nominations: [{ streamUrl }],
|
||||
})
|
||||
await loadMyParticipation()
|
||||
submitted.value = true
|
||||
@@ -165,7 +159,12 @@ export function useHomeParticipationSubmitActions(params: {
|
||||
|
||||
const url = clipContext.clipUrl.trim()
|
||||
if (!url) {
|
||||
formError.value = 'Bitte gib einen Twitch- oder YouTube-Clip-Link ein.'
|
||||
formError.value = 'Bitte gib einen Clip-Link ein.'
|
||||
return
|
||||
}
|
||||
|
||||
if (!isHttpUrl(url)) {
|
||||
formError.value = 'Bitte gib einen gültigen http(s)-Link ein.'
|
||||
return
|
||||
}
|
||||
|
||||
@@ -173,7 +172,12 @@ export function useHomeParticipationSubmitActions(params: {
|
||||
try {
|
||||
const session = await ensureViewerSession()
|
||||
const category = displayCategories.value[clipCatIdx.value]
|
||||
const selectedCreator = category?.candidates[clipContext.selectedNomineeIndex]
|
||||
const selectedCreator = resolveSelectedCreator(category, clipContext.selectedNomineeQuery)
|
||||
if (category?.candidates.length && !selectedCreator) {
|
||||
formError.value = 'Bitte wähle einen VTuber aus den Vorschlägen aus.'
|
||||
return
|
||||
}
|
||||
|
||||
await store.submitClip({
|
||||
year: store.overview.year,
|
||||
categoryId: category ? Number(category.id) : null,
|
||||
@@ -204,6 +208,21 @@ export function useHomeParticipationSubmitActions(params: {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSelectedCreator(category: HomeDisplayCategory | undefined, query: string) {
|
||||
const candidates = category?.candidates ?? []
|
||||
const normalizedQuery = normalizeSearchValue(query)
|
||||
if (!normalizedQuery) return null
|
||||
|
||||
return candidates.find((candidate) => normalizeSearchValue(candidate.displayName) === normalizedQuery)
|
||||
?? candidates.find((candidate) => normalizeSearchValue(candidate.channelSlug) === normalizedQuery)
|
||||
?? candidates.find((candidate) => normalizeSearchValue(candidate.displayName).includes(normalizedQuery))
|
||||
?? null
|
||||
}
|
||||
|
||||
function normalizeSearchValue(value: string) {
|
||||
return value.trim().toLowerCase()
|
||||
}
|
||||
|
||||
function isHttpUrl(value: string) {
|
||||
try {
|
||||
const url = new URL(value)
|
||||
|
||||
@@ -10,7 +10,7 @@ export function useHomePhasePresentation(params: {
|
||||
showDate: ComputedRef<string>
|
||||
showStartsAt: ComputedRef<string>
|
||||
currentYear: ComputedRef<string>
|
||||
formatRange: (key: 'nomination' | 'voting' | 'review') => string
|
||||
formatRange: (key: 'nomination' | 'voting' | 'preparation') => string
|
||||
formatShowDate: () => string
|
||||
}) {
|
||||
const {
|
||||
@@ -27,7 +27,7 @@ export function useHomePhasePresentation(params: {
|
||||
|
||||
const nominationPhase = computed(() => previewPhase.value === 'nomination')
|
||||
const votingPhase = computed(() => previewPhase.value === 'voting')
|
||||
const reviewPhase = computed(() => previewPhase.value === 'review')
|
||||
const preparationPhase = computed(() => previewPhase.value === 'preparation')
|
||||
const showPhase = computed(() => previewPhase.value === 'show')
|
||||
const completedPhase = computed(() => previewPhase.value === 'completed')
|
||||
const currentTimestamp = ref(Date.now())
|
||||
@@ -39,13 +39,13 @@ 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 phaseCardTitle = computed(() => completedPhase.value ? 'Award-Jahr abgeschlossen' : nominationPhase.value ? 'Community Nominierung' : votingPhase.value ? 'Community Voting' : reviewPhase.value ? 'Review & Auswertung' : '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 läuft gerade. Reiche deine Favoriten und Highlight-Clips ein.' : votingPhase.value ? 'Die Nominierungsphase ist abgeschlossen.\nJetzt liegt es an dir: Stimme für deine Favoriten!' : reviewPhase.value ? 'Das Voting ist abgeschlossen. Das Team prüft Ergebnisse, Clips und finale Show-Momente.' : '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')}` : reviewPhase.value ? `Review-Zeitraum · ${formatRange('review')}` : `Live · ${formatShowDate()} · ${formatTimeLabel(showStartsAt.value)} Uhr`)
|
||||
const phaseStatusLabel = computed(() => completedPhase.value ? 'ABGESCHLOSSEN' : streamLive.value ? 'LIVE' : showPhase.value ? 'STARTET BALD' : reviewPhase.value ? 'IN PRÜFUNG' : '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;' : reviewPhase.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;')
|
||||
const phasePrimaryLabel = computed(() => completedPhase.value ? '✦ Award beendet' : nominationPhase.value ? '✦ Nominieren & Clip' : votingPhase.value ? '★ Jetzt voten' : reviewPhase.value ? '✦ Auswertung läuft' : '● Zum Live-Stream')
|
||||
const phasePrimaryDisabled = computed(() => reviewPhase.value || completedPhase.value)
|
||||
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 läuft gerade. Reiche deine Favoriten und Highlight-Clips 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;')
|
||||
const phasePrimaryLabel = computed(() => completedPhase.value ? '✦ Award beendet' : nominationPhase.value ? '✦ Nominieren & Clip' : votingPhase.value ? '★ Jetzt voten' : preparationPhase.value ? '✦ Show wird vorbereitet' : '● Zum Live-Stream')
|
||||
const phasePrimaryDisabled = computed(() => preparationPhase.value || completedPhase.value)
|
||||
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;')
|
||||
@@ -68,14 +68,14 @@ export function useHomePhasePresentation(params: {
|
||||
? 'position:absolute;top:27px;left:10%;right:10%;height:3px;background:linear-gradient(90deg,#e2d6f4 0%,#e2d6f4 100%);border-radius:3px;z-index:0;'
|
||||
: votingPhase.value
|
||||
? 'position:absolute;top:27px;left:10%;right:10%;height:3px;background:linear-gradient(90deg,#8b6cdb 0%,#8b6cdb 33.33%,#e2d6f4 33.33%,#e2d6f4 100%);border-radius:3px;z-index:0;'
|
||||
: reviewPhase.value
|
||||
: preparationPhase.value
|
||||
? 'position:absolute;top:27px;left:10%;right:10%;height:3px;background:linear-gradient(90deg,#8b6cdb 0%,#8b6cdb 66.66%,#e2d6f4 66.66%,#e2d6f4 100%);border-radius:3px;z-index:0;'
|
||||
: 'position:absolute;top:27px;left:10%;right:10%;height:3px;background:linear-gradient(90deg,#8b6cdb 0%,#8b6cdb 100%,#e2d6f4 100%,#e2d6f4 100%);border-radius:3px;z-index:0;')
|
||||
const sectionTitle = computed(() => completedPhase.value ? 'Das Award-Jahr ist abgeschlossen ✦' : nominationPhase.value ? 'Jetzt Highlights und Favoriten einreichen ✦' : votingPhase.value ? 'Meine Favoriten unterstützen ⭐' : reviewPhase.value ? 'Das Voting wird gerade ausgewertet ✦' : 'Die Gewinner werden jetzt live gekürt ✦')
|
||||
const sectionText = computed(() => completedPhase.value ? 'Danke an alle, die nominiert, abgestimmt und live mitgefiebert haben. Die nächsten Aktionen sind gesperrt, bis ein neues Award-Jahr startet.' : nominationPhase.value ? 'Reiche deine Lieblingsmomente ein und hilf mit, die stärksten Clips und spannendsten Namen in die Show zu bringen.' : votingPhase.value ? 'Jede Stimme erzählt eine Geschichte. Unterstütze die Creator, die dich zum Lachen, Staunen und Mitfiebern bringen.' : reviewPhase.value ? 'Die Community hat abgestimmt. Jetzt prüft das Team Ergebnisse, Clips und finale Showeinspieler für die Award-Nacht.' : 'Die Bühne ist offen. Schau live zu, wie die Stars der Szene ausgezeichnet werden und die besten Momente gezeigt werden.')
|
||||
const sectionActionLabel = computed(() => completedPhase.value ? 'Award-Jahr abgeschlossen' : nominationPhase.value ? 'Nominieren & Clip einreichen' : votingPhase.value ? 'Mit Twitch anmelden & voten' : reviewPhase.value ? 'Auswertung läuft' : 'Zum Live-Stream')
|
||||
const sectionTitle = computed(() => completedPhase.value ? 'Das Award-Jahr ist abgeschlossen ✦' : nominationPhase.value ? 'Jetzt Highlights und Favoriten einreichen ✦' : votingPhase.value ? 'Meine Favoriten unterstützen ⭐' : preparationPhase.value ? 'Die Show wird vorbereitet ✦' : 'Die Gewinner werden jetzt live gekürt ✦')
|
||||
const sectionText = computed(() => completedPhase.value ? 'Danke an alle, die nominiert, abgestimmt und live mitgefiebert haben. Die nächsten Aktionen sind gesperrt, bis ein neues Award-Jahr startet.' : nominationPhase.value ? 'Reiche deine Lieblingsmomente ein und hilf mit, die stärksten Clips und spannendsten Namen in die Show zu bringen.' : votingPhase.value ? 'Jede Stimme erzählt eine Geschichte. Unterstütze die Creator, die dich zum Lachen, Staunen und Mitfiebern bringen.' : preparationPhase.value ? 'Die Community hat abgestimmt. Jetzt bereitet das Team Clips, Ablauf und Show-Momente für die Award-Nacht vor.' : 'Die Bühne ist offen. Schau live zu, wie die Stars der Szene ausgezeichnet werden und die besten Momente gezeigt werden.')
|
||||
const sectionActionLabel = computed(() => completedPhase.value ? 'Award-Jahr abgeschlossen' : nominationPhase.value ? 'Nominieren & Clip einreichen' : votingPhase.value ? 'Mit Twitch anmelden & voten' : preparationPhase.value ? 'Aufbereitung läuft' : 'Zum Live-Stream')
|
||||
const sectionActionHref = computed(() => showPhase.value ? publicStreamUrl.value : '#')
|
||||
const sectionActionDisabled = computed(() => reviewPhase.value || completedPhase.value)
|
||||
const sectionActionDisabled = computed(() => preparationPhase.value || completedPhase.value)
|
||||
const sectionActionStyle = computed(() => sectionActionDisabled.value
|
||||
? 'display:inline-flex;align-items:center;gap:11px;padding:17px 38px;border-radius:14px;background:rgba(255,255,255,.72);color:#9b8abf;text-decoration:none;font-family:\'Outfit\',sans-serif;font-weight:700;font-size:18px;box-shadow:none;cursor:not-allowed;border:none;'
|
||||
: nominationPhase.value
|
||||
@@ -85,7 +85,7 @@ export function useHomePhasePresentation(params: {
|
||||
return {
|
||||
nominationPhase,
|
||||
votingPhase,
|
||||
reviewPhase,
|
||||
preparationPhase,
|
||||
showPhase,
|
||||
completedPhase,
|
||||
showCountdown,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { computed, type ComputedRef } from 'vue'
|
||||
|
||||
import { privacyContentToHtml } from '../../lib/privacyContent'
|
||||
import { simpleIconForKey } from '../../lib/socialIcons'
|
||||
import type { OverviewResponse } from '../../types/awards'
|
||||
|
||||
@@ -26,15 +27,10 @@ export function useHomeSocialPresentation(siteContent: ComputedRef<OverviewRespo
|
||||
)
|
||||
|
||||
const footerLinks = computed(() =>
|
||||
(siteContent.value.footerLinks ?? []).filter((link) => link?.label && link.url),
|
||||
(siteContent.value.footerLinks ?? []).filter((link) => link?.label && (link.url || link.content)),
|
||||
)
|
||||
|
||||
const privacyContentBlocks = computed(() =>
|
||||
(siteContent.value.privacyPolicyContent || '')
|
||||
.split(/\n{2,}/)
|
||||
.map((block) => block.trim())
|
||||
.filter(Boolean),
|
||||
)
|
||||
const privacyContentHtml = computed(() => privacyContentToHtml(siteContent.value.privacyPolicyContent || ''))
|
||||
|
||||
function platformKey(value: string | null | undefined) {
|
||||
return (value ?? 'link').trim().toLowerCase()
|
||||
@@ -56,7 +52,7 @@ export function useHomeSocialPresentation(siteContent: ComputedRef<OverviewRespo
|
||||
hostSocialLinks,
|
||||
communitySocialLinks,
|
||||
footerLinks,
|
||||
privacyContentBlocks,
|
||||
privacyContentHtml,
|
||||
platformKey,
|
||||
isUploadedSocialIcon,
|
||||
socialSimpleIconPath,
|
||||
|
||||
Reference in New Issue
Block a user