Files
vtuber-awards/frontend/src/components/home/useHomePhasePresentation.ts
T
2026-06-25 19:52:46 +02:00

137 lines
10 KiB
TypeScript

import { computed, onBeforeUnmount, ref, type ComputedRef, type Ref } from 'vue'
import type { HomeDisplayCategory, HomePreviewPhase } from './homeLandingTypes'
export function useHomePhasePresentation(params: {
previewPhase: Ref<HomePreviewPhase>
displayCategories: ComputedRef<HomeDisplayCategory[]>
candidateCount: ComputedRef<number>
publicStreamUrl: ComputedRef<string>
showDate: ComputedRef<string>
showStartsAt: ComputedRef<string>
currentYear: ComputedRef<string>
formatRange: (key: 'nomination' | 'voting' | 'preparation') => string
formatShowDate: () => string
}) {
const {
previewPhase,
displayCategories,
candidateCount,
publicStreamUrl,
showDate,
showStartsAt,
currentYear,
formatRange,
formatShowDate,
} = params
const nominationPhase = computed(() => previewPhase.value === 'nomination')
const votingPhase = computed(() => previewPhase.value === 'voting')
const preparationPhase = computed(() => previewPhase.value === 'preparation')
const showPhase = computed(() => previewPhase.value === 'show')
const completedPhase = computed(() => previewPhase.value === 'completed')
const currentTimestamp = ref(Date.now())
const ticker = setInterval(() => {
currentTimestamp.value = Date.now()
}, 1000)
onBeforeUnmount(() => clearInterval(ticker))
const showCountdown = computed(() => true)
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' : 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;')
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 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')
const statTwoValue = computed(() => String(displayCategories.value.length))
const statThreeValue = computed(() => currentYear.value || '—')
const statThreeLabel = computed(() => 'Award-Jahr')
const categoryIntroText = computed(() => {
const count = displayCategories.value.length
const countLabel = count === 1 ? 'Eine Kategorie' : `${count || 'Alle'} Kategorien`
return `${countLabel}, eine Show, eine Szene. Stimm für deine Lieblings-VTuber ab und sei live dabei, wenn die Gewinner feststehen.`
})
const timelineLineStyle = computed(() => nominationPhase.value
? '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;'
: 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 ⭐' : 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(() => 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
? 'display:inline-flex;align-items:center;gap:11px;padding:17px 38px;border-radius:14px;background:#fff;color:#e855a5;text-decoration:none;font-family:\'Outfit\',sans-serif;font-weight:700;font-size:18px;box-shadow:0 14px 34px rgba(0,0,0,.22);'
: 'display:inline-flex;align-items:center;gap:11px;padding:17px 38px;border-radius:14px;background:#fff;color:#7a3fd0;text-decoration:none;font-family:\'Outfit\',sans-serif;font-weight:700;font-size:18px;box-shadow:0 14px 34px rgba(0,0,0,.22);')
return {
nominationPhase,
votingPhase,
preparationPhase,
showPhase,
completedPhase,
showCountdown,
streamLive,
streamLocked,
phaseCardTitle,
phaseCardDescription,
phaseCardRange,
phaseStatusLabel,
phaseStatusStyle,
phasePrimaryLabel,
phasePrimaryDisabled,
phasePrimaryActionStyle,
streamEyebrow,
streamTitle,
streamMeta,
streamLockedLabel,
streamLockedTitle,
statOneValue,
statOneLabel,
statTwoValue,
statThreeValue,
statThreeLabel,
categoryIntroText,
timelineLineStyle,
sectionTitle,
sectionText,
sectionActionLabel,
sectionActionHref,
sectionActionDisabled,
sectionActionStyle,
}
}
function parseShowStartMs(showDate: string, showStartsAt: string) {
if (!showDate) return Number.NaN
const time = normalizeTime(showStartsAt)
return new Date(`${showDate}T${time}`).getTime()
}
function normalizeTime(value: string) {
if (!value) return '20:00:00'
return value.length === 5 ? `${value}:00` : value
}
function formatTimeLabel(value: string) {
return normalizeTime(value).slice(0, 5)
}