Implement creator studio and upload updates

This commit is contained in:
2026-04-04 10:12:02 +02:00
parent 1da7d3bf88
commit 0b216b7ecd
15107 changed files with 31206 additions and 626514 deletions

View File

@@ -4,7 +4,7 @@ const FALLBACK_MD = 'https://files.skinbase.org/default/missing_md.webp'
const FALLBACK_LG = 'https://files.skinbase.org/default/missing_lg.webp'
const FALLBACK_XL = 'https://files.skinbase.org/default/missing_xl.webp'
export default function ArtworkHero({ artwork, presentMd, presentLg, presentXl, onOpenViewer, hasPrev, hasNext, onPrev, onNext }) {
export default function ArtworkHero({ artwork, presentMd, presentLg, presentXl, mediaWidth = null, mediaHeight = null, mediaKey = 'cover', onOpenViewer, hasPrev, hasNext, onPrev, onNext }) {
const [isLoaded, setIsLoaded] = useState(false)
const mdSource = presentMd?.url || artwork?.thumbs?.md?.url || null
@@ -18,8 +18,8 @@ export default function ArtworkHero({ artwork, presentMd, presentLg, presentXl,
const hasRealArtworkImage = Boolean(mdSource || lgSource || xlSource)
const blurBackdropSrc = mdSource || lgSource || xlSource || null
const dbWidth = Number(artwork?.width)
const dbHeight = Number(artwork?.height)
const dbWidth = Number(mediaWidth ?? artwork?.width)
const dbHeight = Number(mediaHeight ?? artwork?.height)
const hasDbDims = dbWidth > 0 && dbHeight > 0
// Natural dimensions — seeded from DB if available, otherwise probed from
@@ -28,6 +28,16 @@ export default function ArtworkHero({ artwork, presentMd, presentLg, presentXl,
hasDbDims ? { w: dbWidth, h: dbHeight } : null
)
useEffect(() => {
setIsLoaded(false)
if (hasDbDims) {
setNaturalDims({ w: dbWidth, h: dbHeight })
return
}
setNaturalDims(null)
}, [mediaKey, hasDbDims, dbWidth, dbHeight])
// Probe the xl image to discover real dimensions when DB has none
useEffect(() => {
if (naturalDims || !xlSource) return

View File

@@ -0,0 +1,69 @@
import React from 'react'
export default function ArtworkMediaStrip({ items = [], selectedId = null, onSelect }) {
if (!Array.isArray(items) || items.length <= 1) {
return null
}
return (
<div className="mt-4 rounded-[1.75rem] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] p-4 shadow-[0_18px_60px_rgba(2,8,23,0.24)] backdrop-blur">
<div className="mb-3 flex items-center justify-between gap-3">
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-white/45">Gallery</p>
<p className="mt-1 text-sm text-white/60">Switch between the default cover and additional archive screenshots.</p>
</div>
<span className="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-[11px] text-white/65">
{items.length} views
</span>
</div>
<div className="flex gap-3 overflow-x-auto pb-1">
{items.map((item) => {
const active = item.id === selectedId
return (
<button
key={item.id}
type="button"
onClick={() => onSelect?.(item.id)}
aria-pressed={active}
className={[
'group shrink-0 rounded-2xl border p-2 text-left transition-all',
active
? 'border-sky-300/45 bg-sky-400/12 shadow-[0_0_0_1px_rgba(56,189,248,0.18)]'
: 'border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.06]',
].join(' ')}
>
<div className="h-20 w-28 overflow-hidden rounded-xl bg-black/30 ring-1 ring-white/10 sm:h-24 sm:w-36">
{item.thumbUrl ? (
<img
src={item.thumbUrl}
alt={item.label}
className="h-full w-full object-cover transition duration-300 group-hover:scale-[1.03]"
loading="lazy"
decoding="async"
/>
) : (
<div className="grid h-full w-full place-items-center text-white/30">
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
)}
</div>
<div className="mt-2 flex items-center justify-between gap-2 px-1">
<span className="truncate text-xs font-medium text-white/80">{item.label}</span>
<span className={[
'rounded-full px-2 py-0.5 text-[10px]',
active ? 'bg-sky-300/20 text-sky-100' : 'bg-white/10 text-white/45',
].join(' ')}>
{active ? 'Showing' : 'View'}
</span>
</div>
</button>
)
})}
</div>
</div>
)
}

View File

@@ -425,9 +425,7 @@ export default function ArtworkRecommendationsRails({ artwork, related = [] }) {
? `/discover/trending`
: '/discover/trending'
const similarHref = artwork?.name
? `/search?q=${encodeURIComponent(artwork.name)}`
: '/search'
const similarHref = artwork?.id ? `/art/${artwork.id}/similar` : null
return (
<div className="space-y-14">