more fixes

This commit is contained in:
2026-03-12 07:22:38 +01:00
parent 547215cbe8
commit 4f576ceb04
226 changed files with 14380 additions and 4453 deletions

View File

@@ -18,7 +18,8 @@ export default function ArtworkCard({ art, loading = 'lazy', fetchPriority = nul
const category = (art.category_name || art.category || '').trim();
const likes = art.likes ?? art.favourites ?? 0;
const comments = art.comments_count ?? art.comment_count ?? 0;
const views = art.views ?? art.views_count ?? art.view_count ?? 0;
const downloads = art.downloads ?? art.downloads_count ?? art.download_count ?? 0;
const imgSrc = art.thumb || art.thumb_url || art.thumbnail_url || '/images/placeholder.jpg';
const imgSrcset = art.thumb_srcset || imgSrc;
@@ -74,7 +75,7 @@ export default function ArtworkCard({ art, loading = 'lazy', fetchPriority = nul
const imgClass = [
'nova-card-main-image',
'absolute inset-0 h-full w-full object-cover',
'transition-[transform,filter] duration-300 ease-out group-hover:scale-[1.04]',
'transition-[transform,filter] duration-150 ease-out group-hover:scale-[1.03]',
loading !== 'eager' ? 'blur-sm scale-[1.02] data-blur-preview' : '',
].join(' ');
@@ -97,7 +98,7 @@ export default function ArtworkCard({ art, loading = 'lazy', fetchPriority = nul
href={cardUrl}
className="group relative block overflow-hidden rounded-2xl ring-1 ring-white/5 bg-black/20
shadow-lg shadow-black/40
transition-all duration-300 ease-out
transition-all duration-150 ease-out
hover:scale-[1.02] hover:-translate-y-px hover:ring-white/15
hover:shadow-[0_8px_30px_rgba(0,0,0,0.6),0_0_0_1px_rgba(255,255,255,0.08)]
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70"
@@ -112,6 +113,12 @@ export default function ArtworkCard({ art, loading = 'lazy', fetchPriority = nul
>
<div className="absolute inset-0 bg-gradient-to-br from-white/10 via-white/5 to-transparent pointer-events-none" />
<div className="pointer-events-none absolute right-2 top-2 z-20 flex items-center gap-1.5 rounded-full border border-white/10 bg-black/45 px-2 py-1 text-[10px] text-white/85 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
<span className="inline-flex items-center gap-1"><i className="fa-solid fa-heart text-[9px] text-rose-300" />{likes}</span>
<span className="inline-flex items-center gap-1"><i className="fa-solid fa-eye text-[9px] text-sky-300" />{views}</span>
<span className="inline-flex items-center gap-1"><i className="fa-solid fa-download text-[9px] text-emerald-300" />{downloads}</span>
</div>
<img
ref={imgRef}
src={imgSrc}
@@ -145,7 +152,7 @@ export default function ArtworkCard({ art, loading = 'lazy', fetchPriority = nul
)}
</span>
</span>
<span className="shrink-0"> {likes} · 💬 {comments}</span>
<span className="shrink-0"> {likes} · 👁 {views} · {downloads}</span>
</div>
{metaParts.length > 0 && (
<div className="mt-1 text-[11px] text-white/70">

View File

@@ -56,10 +56,39 @@ async function fetchPageData(url) {
// JSON fast-path (if controller ever returns JSON)
if (ct.includes('application/json')) {
const json = await res.json();
// Support multiple API payload shapes across endpoints.
const artworks = Array.isArray(json.artworks)
? json.artworks
: Array.isArray(json.data)
? json.data
: Array.isArray(json.items)
? json.items
: Array.isArray(json.results)
? json.results
: [];
const nextCursor = json.next_cursor
?? json.nextCursor
?? json.meta?.next_cursor
?? null;
const nextPageUrl = json.next_page_url
?? json.nextPageUrl
?? json.meta?.next_page_url
?? null;
const hasMore = typeof json.has_more === 'boolean'
? json.has_more
: typeof json.hasMore === 'boolean'
? json.hasMore
: null;
return {
artworks: json.artworks ?? [],
nextCursor: json.next_cursor ?? null,
nextPageUrl: json.next_page_url ?? null,
artworks,
nextCursor,
nextPageUrl,
hasMore,
};
}
@@ -76,6 +105,7 @@ async function fetchPageData(url) {
artworks,
nextCursor: el.dataset.nextCursor || null,
nextPageUrl: el.dataset.nextPageUrl || null,
hasMore: null,
};
}
@@ -148,6 +178,7 @@ const SKELETON_COUNT = 10;
* rankApiEndpoint string|null /api/rank/* endpoint; used as fallback data
* source when no SSR artworks are available
* rankType string|null Ranking API ?type= param (trending|new_hot|best)
* gridClassName string|null Optional CSS class override for grid columns/gaps
*/
function MasonryGallery({
artworks: initialArtworks = [],
@@ -158,6 +189,7 @@ function MasonryGallery({
limit = 40,
rankApiEndpoint = null,
rankType = null,
gridClassName = null,
}) {
const [artworks, setArtworks] = useState(initialArtworks);
const [nextCursor, setNextCursor] = useState(initialNextCursor);
@@ -234,7 +266,7 @@ function MasonryGallery({
setLoading(true);
try {
const { artworks: newItems, nextCursor: nc, nextPageUrl: np } =
const { artworks: newItems, nextCursor: nc, nextPageUrl: np, hasMore } =
await fetchPageData(fetchUrl);
if (!newItems.length) {
@@ -243,7 +275,7 @@ function MasonryGallery({
setArtworks((prev) => [...prev, ...newItems]);
if (cursorEndpoint) {
setNextCursor(nc);
if (!nc) setDone(true);
if (hasMore === false || !nc) setDone(true);
} else {
setNextPageUrl(np);
if (!np) setDone(true);
@@ -272,7 +304,7 @@ function MasonryGallery({
// Gallery V2 spec §7: 5 col desktop / 3 tablet / 2 mobile for all gallery pages.
// Discover feeds (home/discover page) retain the same 5-col layout.
const gridClass = 'grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-6';
const gridClass = gridClassName || 'grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-6';
// ── Render ─────────────────────────────────────────────────────────────
return (