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
This commit is contained in:
90
artist.html
Normal file
90
artist.html
Normal file
@ -0,0 +1,90 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Artist — 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 id="content"></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("artist");
|
||||
const id = qs("id");
|
||||
const a = Store.artist(id);
|
||||
if (!a) {
|
||||
mount(
|
||||
"content",
|
||||
`<section class="section"><h1>${t("misc.not_found")}</h1><p><a href="artists.html">${t("cta.all_artists")}</a></p></section>`
|
||||
);
|
||||
} else {
|
||||
const works = (a.works || [])
|
||||
.map(
|
||||
(w) => `
|
||||
<figure class="work">
|
||||
<img src="${w.image}" alt="${L(w.title)}" />
|
||||
<figcaption>
|
||||
<strong>${L(w.title)}<em style="font-style:italic;font-weight:400">, ${w.year}</em></strong>
|
||||
${L(w.medium)}<br>${w.dimensions}
|
||||
</figcaption>
|
||||
</figure>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
const shows = Store.exhibitions()
|
||||
.filter((e) => e.artistIds.includes(a.id))
|
||||
.sort((x, y) => y.startDate.localeCompare(x.startDate));
|
||||
|
||||
mount(
|
||||
"content",
|
||||
`
|
||||
<section class="detail">
|
||||
<div class="eyebrow"><a href="artists.html">${t("nav.artists")}</a> / ${a.name}</div>
|
||||
<div class="detail-header">
|
||||
<img src="${a.portrait}" alt="${a.name}" />
|
||||
<div class="detail-text">
|
||||
<h1 style="margin-bottom:24px">${a.name}</h1>
|
||||
<div class="meta"><span>${L(a.born)}</span><span>${L(a.lives)}</span></div>
|
||||
<p>${L(a.bio)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${works ? `<div class="eyebrow">${t("eyebrow.selected_works")}</div><div class="works-grid">${works}</div>` : ""}
|
||||
|
||||
${
|
||||
shows.length
|
||||
? `<div class="eyebrow" style="margin-top:96px">${t("eyebrow.exhibitions")}</div>
|
||||
<div>${shows
|
||||
.map(
|
||||
(s) => `
|
||||
<div style="display:grid;grid-template-columns:180px 1fr;gap:24px;padding:16px 0;border-top:1px solid var(--line)">
|
||||
<div class="muted" style="font-size:13px">${fmtRange(s.startDate, s.endDate)}</div>
|
||||
<div><a href="exhibition.html?id=${s.id}"><em>${L(s.title)}</em></a><div class="muted" style="font-size:13px">${s.venue}</div></div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")}</div>`
|
||||
: ""
|
||||
}
|
||||
</section>
|
||||
`
|
||||
);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user