CODE · 3.2 KB
src/library.jsx
Workspace snapshot · 09/04 13:35
import React, { useEffect, useState } from "react";
import "./library.css";
const curated = [
{ id: "curated-food", title: "今夜は、10分でいい。", format_label: "SNS / FOOD", direction: "Warm editorial", imageUrl: "/showcase-food-v2.webp" },
{ id: "curated-music", title: "まだ知らない音に会う。", format_label: "SNS / MUSIC", direction: "Electric collage", imageUrl: "/showcase-music-v2.webp" },
{ id: "curated-travel", title: "週末、知らない町へ。", format_label: "SNS / TRAVEL", direction: "Japanese editorial", imageUrl: "/showcase-travel-v2.webp" },
];
function cleanLabel(value, fallback = "") {
const label = String(value || "").trim();
if (!label || /^(generated banner|生成バナー)/i.test(label)) return fallback;
return label;
}
function formatDate(value) {
if (!value) return "";
return new Intl.DateTimeFormat("ja-JP", { year: "numeric", month: "short", day: "numeric" }).format(new Date(value));
}
export default function Library() {
const [items, setItems] = useState([]);
const [status, setStatus] = useState("loading");
useEffect(() => { fetch("/api/library").then(async (response) => { const data = await response.json(); if (!response.ok) throw new Error(data.error); setItems(data.items || []); setStatus("ready"); }).catch(() => setStatus("error")); }, []);
const allItems = [...curated, ...items];
return <main className="library-shell"><header className="library-head"><a href="/" className="library-back">AI BANNER LAB</a><div className="library-heading"><span>PUBLIC CREATIVE ARCHIVE / {String(allItems.length).padStart(3,"0")}</span><h1>Made in <em>the Lab.</em></h1><p>生成して終わらない。公開されたクリエイティブが、<br/>次のアイデアを動かすためのアーカイブ。</p></div><a className="library-create" href="/">新しいバナーをつくる <span>↗</span></a></header>
<div className="library-ticker"><span>STRATEGY</span><span>COPY</span><span>ART DIRECTION</span><span>GPT-IMAGE</span><span>{allItems.length} WORKS</span></div>
<section className="library-grid">{allItems.map((item, index) => {
const title = cleanLabel(item.title, `Creative ${String(index + 1).padStart(2,"0")}`);
const format = cleanLabel(item.format_label, "AI BANNER");
return <figure className="library-item" key={item.id}><div className="library-image"><img src={item.imageUrl} alt={title} loading={index < 6 ? "eager" : "lazy"}/><span>{String(index + 1).padStart(2,"0")}</span></div><figcaption className="library-meta"><div><small>{format}</small><strong>{title}</strong></div><div className="library-detail">{cleanLabel(item.direction) && <span>{cleanLabel(item.direction)}</span>}{item.width && item.height && <span>{item.width} × {item.height}</span>}{item.published_at && <time>{formatDate(item.published_at)}</time>}</div></figcaption></figure>;
})}</section>
{status === "loading" && <p className="library-status">ARCHIVE LOADING…</p>}{status === "error" && <p className="library-status">生成履歴の読込に失敗しました。作例は表示しています。</p>}
<footer><span>AI BANNER LAB / PUBLIC LIBRARY</span><a href="/">CREATE A BANNER ↗</a></footer>
</main>;
}