Files
jimi-gallery/exhibitions.html
yakenator 098b55e3b0 feat: initial Jimi Gallery prototype
- Public site (Home/Artists/Exhibitions/News/About/Contact) with EN/KO/JA i18n
- Admin panel with login, CRUD, image upload, multilingual editing
- Exhibition slider/lightbox view
- FastAPI + MongoDB backend, JWT auth
- Docker Compose deployment, behind nginx at jimi.yakenator.io
2026-04-25 12:47:36 +09:00

96 lines
3.4 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Exhibitions — Jimi Gallery</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;600&family=Cormorant+Garamond:wght@400;500;600&family=Inter:wght@300;400;500&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="assets/styles.css" />
</head>
<body>
<div id="nav-slot"></div>
<main>
<section class="page-title">
<div class="eyebrow" id="eyebrow"></div>
<h1 id="title"></h1>
</section>
<section class="section-tight">
<div class="filters" id="filters"></div>
<div id="list" class="grid grid-2"></div>
</section>
</main>
<div id="footer-slot"></div>
<script src="assets/i18n.js"></script>
<script src="assets/data.js"></script>
<script src="assets/app.js"></script>
<script>
(async () => {
try { await Store.load(); } catch (e) { return showBootError(e); }
renderChrome("exhibitions");
document.getElementById("eyebrow").textContent = t("eyebrow.program");
document.getElementById("title").textContent = t("title.exhibitions");
const filterDefs = [
["all", t("filter.all")],
["current", t("status.current")],
["upcoming", t("status.upcoming")],
["past", t("status.past")]
];
document.getElementById("filters").innerHTML = filterDefs
.map(
([f, label], i) =>
`<button data-f="${f}" class="${i === 0 ? "active" : ""}">${label}</button>`
)
.join("");
const order = { current: 0, upcoming: 1, past: 2 };
function render(filter) {
const items = Store.exhibitions()
.filter((e) => filter === "all" || e.status === filter)
.sort((a, b) => {
if (order[a.status] !== order[b.status])
return order[a.status] - order[b.status];
return b.startDate.localeCompare(a.startDate);
});
mount(
"list",
items
.map((e) => {
const artistNames = e.artistIds
.map((id) => Store.artist(id)?.name)
.filter(Boolean)
.join(", ");
return `
<a href="exhibition.html?id=${e.id}">
<img class="card-image wide" src="${e.hero}" alt="${L(e.title)}" />
<div class="eyebrow" style="margin-top:8px;margin-bottom:8px">${t("status." + e.status)}</div>
<div class="card-title" style="font-family:var(--serif);font-size:24px;margin-bottom:6px">${artistNames} — <em>${L(e.title)}</em></div>
<div class="card-sub">${fmtRange(e.startDate, e.endDate)} · ${e.venue}</div>
</a>
`;
})
.join("")
);
}
render("all");
document.querySelectorAll("#filters button").forEach((b) => {
b.addEventListener("click", () => {
document
.querySelectorAll("#filters button")
.forEach((x) => x.classList.remove("active"));
b.classList.add("active");
render(b.dataset.f);
});
});
})();
</script>
</body>
</html>