240 lines
11 KiB
JavaScript
240 lines
11 KiB
JavaScript
import React, { useState } from 'react'
|
|
import Modal from '../ui/Modal'
|
|
|
|
function EvolutionArtworkCard({ card }) {
|
|
if (!card) return null
|
|
|
|
const shouldBlur = Boolean(card?.maturity?.should_blur)
|
|
|
|
return (
|
|
<a
|
|
href={card.url}
|
|
className="group block overflow-hidden rounded-[26px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.06),rgba(255,255,255,0.03))] transition hover:border-white/20 hover:bg-white/[0.07]"
|
|
>
|
|
<div className="relative aspect-[1.08/1] overflow-hidden bg-slate-950">
|
|
{card.thumbnail ? (
|
|
<img
|
|
src={card.thumbnail}
|
|
alt={card.title}
|
|
className={`h-full w-full object-cover transition duration-500 group-hover:scale-[1.03] ${shouldBlur ? 'scale-[1.03] blur-xl' : ''}`}
|
|
/>
|
|
) : (
|
|
<div className="flex h-full w-full items-center justify-center text-slate-600">
|
|
<i className="fa-solid fa-image text-3xl" />
|
|
</div>
|
|
)}
|
|
|
|
<div className="absolute left-4 top-4 inline-flex items-center gap-2 rounded-full border border-white/10 bg-black/45 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-[0.18em] text-white backdrop-blur-md">
|
|
<span className="h-1.5 w-1.5 rounded-full bg-sky-300" />
|
|
{card.role_label}
|
|
</div>
|
|
|
|
{shouldBlur ? (
|
|
<div className="absolute inset-x-0 bottom-0 bg-[linear-gradient(180deg,rgba(15,23,42,0),rgba(15,23,42,0.9))] px-4 py-4 text-sm text-white/85">
|
|
Mature artwork preview is softened for your current viewer settings.
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="space-y-2 px-4 py-4 sm:px-5">
|
|
<div className="flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">
|
|
{card.content_type ? <span>{card.content_type}</span> : null}
|
|
{card.category ? <span className="text-slate-500">{card.category}</span> : null}
|
|
</div>
|
|
<h3 className="text-lg font-semibold tracking-[-0.02em] text-white">{card.title}</h3>
|
|
<div className="flex flex-wrap items-center gap-2 text-sm text-slate-300">
|
|
<span>{card.publisher}</span>
|
|
{card.year ? <span className="text-slate-500">{card.year}</span> : null}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
)
|
|
}
|
|
|
|
function ComparisonModal({ item, open, onClose }) {
|
|
if (!item) return null
|
|
|
|
return (
|
|
<Modal open={open} onClose={onClose} title={item.compare?.title || 'Compare versions'} size="full">
|
|
<div className="space-y-6">
|
|
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
|
<div>
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">{item.heading}</div>
|
|
<h3 className="mt-2 text-2xl font-semibold tracking-[-0.03em] text-white">{item.relation_label}</h3>
|
|
<p className="mt-2 max-w-3xl text-sm leading-7 text-slate-300">{item.summary}</p>
|
|
</div>
|
|
{item.years_apart_label ? (
|
|
<div className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm text-slate-200">
|
|
<i className="fa-regular fa-clock" aria-hidden="true" />
|
|
{item.years_apart_label}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="grid gap-5 lg:grid-cols-2">
|
|
{[item.before, item.after].map((card) => (
|
|
<div key={`${item.id}-${card.id}`} className="overflow-hidden rounded-[28px] border border-white/10 bg-white/[0.03]">
|
|
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
|
|
<div>
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">{card.role_label}</div>
|
|
<div className="mt-1 text-base font-semibold text-white">{card.title}</div>
|
|
</div>
|
|
<a href={card.url} className="inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2 text-sm font-medium text-white transition hover:bg-white/[0.08]">
|
|
Open
|
|
<i className="fa-solid fa-arrow-up-right-from-square text-slate-500" aria-hidden="true" />
|
|
</a>
|
|
</div>
|
|
|
|
<div className="relative aspect-[4/3] overflow-hidden bg-slate-950">
|
|
{card.image_lg ? (
|
|
<img
|
|
src={card.image_lg}
|
|
alt={card.title}
|
|
className={`h-full w-full object-cover ${card?.maturity?.should_blur ? 'scale-[1.03] blur-xl' : ''}`}
|
|
/>
|
|
) : (
|
|
<div className="flex h-full w-full items-center justify-center text-slate-600">
|
|
<i className="fa-solid fa-image text-4xl" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-2 px-5 py-4 text-sm text-slate-300">
|
|
<span>{card.publisher}</span>
|
|
{card.year ? <span className="text-slate-500">{card.year}</span> : null}
|
|
{card.category ? <span className="rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300">{card.category}</span> : null}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{item.note ? (
|
|
<div className="rounded-[26px] border border-sky-300/20 bg-sky-300/10 px-5 py-4 text-sm leading-7 text-sky-50">
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-sky-100/80">Creator note</div>
|
|
<p className="mt-2 whitespace-pre-wrap">{item.note}</p>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
function EvolutionStoryBlock({ item, onCompare }) {
|
|
if (!item) return null
|
|
|
|
return (
|
|
<section className="overflow-hidden rounded-[30px] border border-white/[0.08] bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.16),transparent_34%),linear-gradient(180deg,rgba(255,255,255,0.06),rgba(255,255,255,0.025))] p-5 shadow-[0_22px_55px_rgba(2,6,23,0.26)] backdrop-blur-xl sm:p-6">
|
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
<div className="max-w-2xl">
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80">{item.heading}</div>
|
|
<h2 className="mt-2 text-[28px] font-semibold tracking-[-0.04em] text-white">{item.relation_label}</h2>
|
|
<p className="mt-2 text-sm leading-7 text-slate-200/90">{item.summary}</p>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-2 lg:justify-end">
|
|
{item.years_apart_label ? (
|
|
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.16em] text-slate-200">
|
|
<i className="fa-regular fa-clock" aria-hidden="true" />
|
|
{item.years_apart_label}
|
|
</span>
|
|
) : null}
|
|
{item.compare?.available ? (
|
|
<button
|
|
type="button"
|
|
onClick={() => onCompare(item)}
|
|
className="inline-flex items-center gap-2 rounded-2xl border border-sky-300/25 bg-sky-300/12 px-4 py-2.5 text-sm font-semibold text-sky-100 transition hover:bg-sky-300/18"
|
|
>
|
|
<i className="fa-solid fa-up-right-and-down-left-from-center" aria-hidden="true" />
|
|
Compare side by side
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-5 grid gap-4 lg:grid-cols-2">
|
|
<EvolutionArtworkCard card={item.before} />
|
|
<EvolutionArtworkCard card={item.after} />
|
|
</div>
|
|
|
|
{item.note ? (
|
|
<div className="mt-5 rounded-[24px] border border-white/10 bg-black/20 px-4 py-4 text-sm leading-7 text-slate-200/90">
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400">Creator note</div>
|
|
<p className="mt-2 whitespace-pre-wrap">{item.note}</p>
|
|
</div>
|
|
) : null}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
function EvolutionUpdates({ updates, onCompare }) {
|
|
if (!updates?.length) return null
|
|
|
|
return (
|
|
<section className="rounded-[30px] border border-white/[0.08] bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] p-5 shadow-[0_22px_55px_rgba(0,0,0,0.18)] backdrop-blur-xl sm:p-6">
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
|
|
<div>
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.24em] text-white/55">Updated Versions</div>
|
|
<h2 className="mt-2 text-2xl font-semibold tracking-[-0.03em] text-white">This piece has later evolutions</h2>
|
|
</div>
|
|
<div className="text-sm text-slate-400">Follow how the creator revisited the idea over time.</div>
|
|
</div>
|
|
|
|
<div className="mt-5 space-y-4">
|
|
{updates.map((item) => (
|
|
<article key={item.id} className="rounded-[26px] border border-white/10 bg-white/[0.03] p-4 sm:p-5">
|
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
<div className="max-w-2xl">
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400">{item.heading}</div>
|
|
<h3 className="mt-2 text-xl font-semibold tracking-[-0.02em] text-white">{item.after?.title}</h3>
|
|
<p className="mt-2 text-sm leading-7 text-slate-300">{item.summary}</p>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{item.years_apart_label ? (
|
|
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.16em] text-slate-200">
|
|
<i className="fa-regular fa-clock" aria-hidden="true" />
|
|
{item.years_apart_label}
|
|
</span>
|
|
) : null}
|
|
{item.compare?.available ? (
|
|
<button
|
|
type="button"
|
|
onClick={() => onCompare(item)}
|
|
className="inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]"
|
|
>
|
|
Compare
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 grid gap-4 lg:grid-cols-2">
|
|
<EvolutionArtworkCard card={item.before} />
|
|
<EvolutionArtworkCard card={item.after} />
|
|
</div>
|
|
|
|
{item.note ? <p className="mt-4 text-sm leading-7 text-slate-300">{item.note}</p> : null}
|
|
</article>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export default function ArtworkEvolutionPanel({ evolution }) {
|
|
const [compareItem, setCompareItem] = useState(null)
|
|
|
|
if (!evolution?.primary && !evolution?.updates?.length) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="space-y-5">
|
|
{evolution.primary ? <EvolutionStoryBlock item={evolution.primary} onCompare={setCompareItem} /> : null}
|
|
{evolution.updates?.length ? <EvolutionUpdates updates={evolution.updates} onCompare={setCompareItem} /> : null}
|
|
</div>
|
|
|
|
<ComparisonModal item={compareItem} open={Boolean(compareItem)} onClose={() => setCompareItem(null)} />
|
|
</>
|
|
)
|
|
} |