Files
SkinbaseNova/resources/js/components/worlds/ActiveWorldSpotlight.jsx

151 lines
7.9 KiB
JavaScript

import React, { useEffect, useRef } from 'react'
import WorldCampaignMeta from './WorldCampaignMeta'
import WorldCard from './WorldCard'
import WorldStatusBadge from './WorldStatusBadge'
import { trackWorldSourceClick, trackWorldSourceImpression, withWorldSource } from '../../lib/worldAnalytics'
export default function ActiveWorldSpotlight({
spotlight,
secondary = [],
indexUrl = '/worlds',
eyebrow = 'World spotlight',
secondaryTitle = 'Campaign rail',
className = '',
sourceSurface = '',
sourceDetail = '',
secondarySourceSurface = '',
secondarySourceDetail = '',
}) {
const spotlightRef = useRef(null)
const primaryHref = spotlight && sourceSurface ? withWorldSource(spotlight.public_url || spotlight.cta_url, sourceSurface, sourceDetail) : (spotlight?.public_url || spotlight?.cta_url)
useEffect(() => {
if (!spotlight?.id || !sourceSurface || typeof window === 'undefined') {
return undefined
}
const node = spotlightRef.current
if (!node) {
return undefined
}
if (typeof window.IntersectionObserver !== 'function') {
trackWorldSourceImpression({
worldId: spotlight.id,
worldTitle: spotlight.title || spotlight.headline,
sourceSurface,
sourceDetail,
sectionKey: 'spotlight',
})
return undefined
}
const observer = new window.IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting || entry.intersectionRatio < 0.45) {
return
}
trackWorldSourceImpression({
worldId: spotlight.id,
worldTitle: spotlight.title || spotlight.headline,
sourceSurface,
sourceDetail,
sectionKey: 'spotlight',
})
observer.disconnect()
})
}, { threshold: [0.45] })
observer.observe(node)
return () => observer.disconnect()
}, [spotlight?.headline, spotlight?.id, spotlight?.title, sourceDetail, sourceSurface])
if (!spotlight) {
return null
}
return (
<section className={className}>
<div
ref={spotlightRef}
className="group relative overflow-hidden rounded-[32px] border border-white/10 bg-slate-950/70"
style={{
'--world-accent': spotlight.theme?.accent_color || '#f97316',
'--world-accent-secondary': spotlight.theme?.accent_color_secondary || '#0f172a',
}}
>
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_color-mix(in_srgb,var(--world-accent)_30%,transparent),_transparent_36%),linear-gradient(135deg,_color-mix(in_srgb,var(--world-accent-secondary)_92%,black),_rgba(2,6,23,0.98))]" />
{spotlight.cover_url ? <img src={spotlight.cover_url} alt={spotlight.title || spotlight.headline} className="absolute inset-0 h-full w-full object-cover opacity-20 transition duration-500 group-hover:scale-[1.03]" /> : null}
<div className="absolute inset-0 bg-gradient-to-r from-slate-950 via-slate-950/84 to-slate-950/28" />
<div className="relative grid gap-8 px-6 py-7 sm:px-8 lg:grid-cols-[minmax(0,1.2fr)_20rem] lg:px-10">
<div>
<div className="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/70">{eyebrow}</div>
<div className="mt-4 flex flex-wrap gap-2">
{(Array.isArray(spotlight.status_badges) ? spotlight.status_badges : []).map((badge) => (
<WorldStatusBadge key={badge.label} badge={badge} />
))}
{spotlight.campaign_label ? <WorldStatusBadge badge={{ label: spotlight.campaign_label, tone: 'slate' }} /> : null}
</div>
{spotlight.title && spotlight.headline && spotlight.title !== spotlight.headline ? <p className="mt-5 text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-100/70">{spotlight.title}</p> : null}
<h2 className="mt-3 text-3xl font-semibold tracking-[-0.04em] text-white sm:text-4xl">{spotlight.headline || spotlight.title}</h2>
{spotlight.tagline ? <p className="mt-2 text-sm uppercase tracking-[0.18em] text-white/55">{spotlight.tagline}</p> : null}
{spotlight.summary ? <p className="mt-4 max-w-3xl text-sm leading-7 text-slate-200/88">{spotlight.summary}</p> : null}
<WorldCampaignMeta world={spotlight} className="mt-6" />
{spotlight.supporting_item ? (
<a href={spotlight.supporting_item.url} className="mt-6 inline-flex max-w-xl items-center gap-3 rounded-[22px] border border-white/12 bg-black/25 px-4 py-3 text-left text-sm text-slate-100 transition hover:border-white/20 hover:bg-white/[0.06]">
<div className="min-w-0">
<div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400">{spotlight.supporting_item.entity_label || 'Related item'}</div>
<div className="mt-1 truncate font-semibold text-white">{spotlight.supporting_item.title}</div>
{spotlight.supporting_item.context_label ? <div className="mt-1 text-xs text-slate-300/80">{spotlight.supporting_item.context_label}</div> : null}
</div>
<i className="fa-solid fa-arrow-right shrink-0 text-xs text-sky-100" />
</a>
) : null}
<div className="mt-6 flex flex-wrap gap-3">
<a href={primaryHref} onClick={() => trackWorldSourceClick({ worldId: spotlight.id, worldTitle: spotlight.title || spotlight.headline, sourceSurface, sourceDetail })} className="inline-flex items-center gap-2 rounded-full bg-white px-4 py-2.5 text-sm font-semibold text-slate-950 transition group-hover:bg-sky-100">
{spotlight.cta_label || 'Explore world'}
<i className="fa-solid fa-arrow-right" />
</a>
<a href={indexUrl} className="inline-flex items-center gap-2 rounded-full border border-white/12 bg-white/[0.05] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
Browse all worlds
</a>
</div>
</div>
<div className="rounded-[28px] border border-white/12 bg-black/25 p-5 text-white backdrop-blur-sm">
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">Campaign state</div>
<div className="mt-3 flex items-center gap-3 text-lg font-semibold">
<i className={spotlight.icon_name || 'fa-solid fa-globe'} />
<span>{spotlight.theme?.label || 'Editorial world'}</span>
</div>
{spotlight.timeframe_label ? <div className="mt-4 text-sm text-slate-300">{spotlight.timeframe_label}</div> : null}
{spotlight.promotion_window_label ? <div className="mt-2 text-sm text-slate-400">{spotlight.promotion_window_label}</div> : null}
{Number(spotlight.live_submission_count || 0) > 0 ? <div className="mt-5 rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-4 py-3 text-sm text-emerald-100">{spotlight.live_submission_count} live submissions are already part of this campaign.</div> : null}
</div>
</div>
</div>
{Array.isArray(secondary) && secondary.length > 0 ? (
<div className="mt-6">
<div className="mb-4 flex items-end justify-between gap-4">
<div>
<h3 className="text-xl font-semibold tracking-[-0.03em] text-white">{secondaryTitle}</h3>
<p className="mt-1 text-sm leading-6 text-slate-400">More live or upcoming worlds that are being actively surfaced right now.</p>
</div>
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">{secondary.length} worlds</div>
</div>
<div className="grid gap-4 xl:grid-cols-3">
{secondary.map((world) => <WorldCard key={world.id} world={world} compact sourceSurface={secondarySourceSurface || sourceSurface} sourceDetail={secondarySourceDetail || sourceDetail} />)}
</div>
</div>
) : null}
</section>
)
}