Museum — Artifact / Artwork Detail
A curatorial artwork detail page for a fictional museum, pairing a large matted-and-framed image with click-to-zoom and drag-to-pan, a tidy object metadata panel (artist, date, medium, dimensions, credit line, accession number), a vertical provenance timeline, a curatorial note, and a more-from-this-gallery strip. An add-to-tour toggle, share and copy-accession actions round out the calm, gallery-like reading experience.
MCP
Code
:root {
--paper: #f6f4ef;
--wall: #ffffff;
--charcoal: #1c1b19;
--ink: #2a2825;
--ink-2: #4a4640;
--muted: #8c857a;
--gold: #a98140;
--gold-d: #876631;
--gold-50: #f3ecdd;
--line: rgba(28, 27, 25, 0.12);
--line-2: rgba(28, 27, 25, 0.2);
--ok: #3f7d56;
--warn: #b8842c;
--danger: #b4493a;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow-sm: 0 1px 2px rgba(28, 27, 25, 0.06), 0 1px 8px rgba(28, 27, 25, 0.04);
--shadow-md: 0 8px 30px rgba(28, 27, 25, 0.1);
--serif: "Cormorant Garamond", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font-family: var(--sans);
font-size: 16px;
line-height: 1.55;
}
a {
color: inherit;
}
:focus-visible {
outline: 2px solid var(--gold-d);
outline-offset: 3px;
border-radius: 3px;
}
.skip-link {
position: absolute;
left: -999px;
top: 0;
background: var(--charcoal);
color: #fff;
padding: 10px 16px;
border-radius: var(--r-sm);
z-index: 60;
}
.skip-link:focus {
left: 12px;
top: 12px;
}
/* ---------- top bar ---------- */
.topbar {
background: var(--wall);
border-bottom: 1px solid var(--line);
position: sticky;
top: 0;
z-index: 30;
}
.topbar__inner {
max-width: 1140px;
margin: 0 auto;
padding: 14px 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 12px;
text-decoration: none;
color: var(--charcoal);
}
.brand__mark {
width: 30px;
height: 30px;
border-radius: 50%;
background: radial-gradient(circle at 32% 30%, var(--gold) 0%, var(--gold-d) 60%, #5e4720 100%);
box-shadow: inset 0 0 0 3px var(--wall), inset 0 0 0 4px var(--line);
}
.brand__name {
font-family: var(--serif);
font-weight: 700;
font-size: 1.3rem;
line-height: 1;
letter-spacing: 0.01em;
display: inline-flex;
flex-direction: column;
}
.brand__name em {
font-family: var(--sans);
font-style: normal;
font-weight: 500;
font-size: 0.62rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--muted);
margin-top: 3px;
}
.topnav {
display: flex;
gap: 26px;
}
.topnav a {
text-decoration: none;
color: var(--ink-2);
font-size: 0.86rem;
font-weight: 500;
letter-spacing: 0.04em;
padding-bottom: 2px;
border-bottom: 1.5px solid transparent;
transition: color 0.16s, border-color 0.16s;
}
.topnav a:hover {
color: var(--charcoal);
}
.topnav a[aria-current="page"] {
color: var(--charcoal);
border-bottom-color: var(--gold);
}
/* ---------- page shell ---------- */
.page {
max-width: 1140px;
margin: 0 auto;
padding: 28px 24px 64px;
}
.crumbs ol {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0 0 24px;
padding: 0;
font-size: 0.8rem;
color: var(--muted);
}
.crumbs li + li::before {
content: "/";
margin-right: 8px;
color: var(--line-2);
}
.crumbs a {
color: var(--ink-2);
text-decoration: none;
}
.crumbs a:hover {
color: var(--gold-d);
}
.crumbs [aria-current="page"] {
color: var(--charcoal);
}
.detail {
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
gap: 48px;
align-items: start;
}
/* ---------- viewer ---------- */
.viewer {
position: sticky;
top: 92px;
}
.frame {
margin: 0;
}
.frame__mat {
background: var(--wall);
border: 1px solid var(--line);
padding: 22px;
border-radius: var(--r-md);
box-shadow: var(--shadow-md);
}
.canvas {
display: block;
width: 100%;
padding: 0;
border: 12px solid #efe9dc;
outline: 1px solid var(--line-2);
border-radius: 2px;
background: #efe9dc;
cursor: zoom-in;
overflow: hidden;
}
.canvas .art {
display: block;
width: 100%;
height: auto;
}
.frame__cap {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-top: 14px;
font-size: 0.78rem;
color: var(--muted);
}
.frame__no {
font-variant-numeric: tabular-nums;
letter-spacing: 0.04em;
}
.zoomctl {
display: flex;
align-items: center;
gap: 8px;
margin-top: 14px;
}
.zbtn {
font-family: var(--sans);
font-size: 0.95rem;
min-width: 38px;
height: 38px;
padding: 0 12px;
border: 1px solid var(--line-2);
background: var(--wall);
color: var(--ink);
border-radius: var(--r-sm);
cursor: pointer;
transition: background 0.15s, border-color 0.15s, transform 0.05s;
}
.zbtn:hover {
background: var(--gold-50);
border-color: var(--gold);
}
.zbtn:active {
transform: translateY(1px);
}
.zbtn--reset {
font-size: 0.8rem;
letter-spacing: 0.04em;
}
.zlevel {
font-size: 0.8rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
min-width: 44px;
text-align: center;
}
/* ---------- object panel ---------- */
.object__tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.badge {
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-2);
background: var(--wall);
border: 1px solid var(--line);
padding: 5px 11px;
border-radius: 999px;
}
.badge--gold {
color: var(--gold-d);
background: var(--gold-50);
border-color: rgba(169, 129, 64, 0.4);
}
.object__title {
font-family: var(--serif);
font-weight: 700;
font-size: clamp(2rem, 4vw, 2.9rem);
line-height: 1.08;
letter-spacing: 0.005em;
margin: 0 0 10px;
color: var(--charcoal);
}
.object__artist {
font-size: 1.05rem;
margin: 0 0 4px;
color: var(--ink);
}
.object__artist .dim {
color: var(--muted);
font-size: 0.9rem;
}
.object__sub {
margin: 0 0 24px;
color: var(--muted);
font-size: 0.88rem;
letter-spacing: 0.02em;
}
.meta {
margin: 0 0 24px;
border-top: 1px solid var(--line);
}
.meta > div {
display: grid;
grid-template-columns: 150px 1fr;
gap: 16px;
padding: 12px 0;
border-bottom: 1px solid var(--line);
}
.meta dt {
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.meta dd {
margin: 0;
font-size: 0.94rem;
color: var(--ink);
}
.object__actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 32px;
}
.btn {
font-family: var(--sans);
font-size: 0.88rem;
font-weight: 600;
letter-spacing: 0.02em;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 11px 18px;
border: 1px solid var(--line-2);
background: var(--wall);
color: var(--ink);
border-radius: var(--r-sm);
cursor: pointer;
transition: background 0.15s, border-color 0.15s, transform 0.05s, color 0.15s;
}
.btn:hover {
background: var(--gold-50);
border-color: var(--gold);
}
.btn:active {
transform: translateY(1px);
}
.btn--primary {
background: var(--charcoal);
border-color: var(--charcoal);
color: #fff;
}
.btn--primary:hover {
background: #000;
border-color: #000;
}
.btn--primary[aria-pressed="true"] {
background: var(--gold-d);
border-color: var(--gold-d);
}
.btn--primary[aria-pressed="true"] .btn__icon {
transform: rotate(45deg);
}
.btn__icon {
font-size: 1rem;
line-height: 1;
transition: transform 0.2s;
}
.prose h2,
.provenance h2,
.more h2 {
font-family: var(--serif);
font-weight: 600;
font-size: 1.45rem;
color: var(--charcoal);
margin: 0 0 12px;
}
.prose p {
margin: 0 0 14px;
color: var(--ink-2);
}
.prose {
padding-top: 8px;
border-top: 1px solid var(--line);
margin-bottom: 32px;
}
/* ---------- provenance timeline ---------- */
.provenance {
border-top: 1px solid var(--line);
padding-top: 20px;
}
.timeline {
list-style: none;
margin: 0;
padding: 0;
position: relative;
}
.timeline::before {
content: "";
position: absolute;
left: 7px;
top: 6px;
bottom: 6px;
width: 2px;
background: var(--line);
}
.timeline li {
position: relative;
display: grid;
grid-template-columns: 90px 1fr;
gap: 18px;
padding: 0 0 22px 28px;
}
.timeline li::before {
content: "";
position: absolute;
left: 2px;
top: 6px;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--wall);
border: 2px solid var(--line-2);
}
.timeline li.is-current::before {
background: var(--gold);
border-color: var(--gold-d);
}
.timeline__year {
font-variant-numeric: tabular-nums;
font-weight: 600;
font-size: 0.85rem;
color: var(--gold-d);
padding-top: 1px;
}
.timeline__body strong {
display: block;
color: var(--charcoal);
font-size: 0.95rem;
}
.timeline__body p {
margin: 2px 0 0;
color: var(--ink-2);
font-size: 0.88rem;
}
/* ---------- more strip ---------- */
.more {
margin-top: 56px;
padding-top: 28px;
border-top: 1px solid var(--line);
}
.more__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
.more__all {
text-decoration: none;
color: var(--gold-d);
font-size: 0.85rem;
font-weight: 600;
white-space: nowrap;
}
.more__all:hover {
text-decoration: underline;
}
.strip {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
}
.card__btn {
width: 100%;
text-align: left;
background: var(--wall);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 0 0 14px;
cursor: pointer;
overflow: hidden;
display: block;
transition: border-color 0.16s, box-shadow 0.16s, transform 0.16s;
}
.card__btn:hover {
border-color: var(--gold);
box-shadow: var(--shadow-md);
transform: translateY(-3px);
}
.card__art {
display: block;
height: 130px;
background: linear-gradient(135deg, var(--c1), var(--c2));
border-bottom: 1px solid var(--line);
}
.card__meta {
display: block;
padding: 12px 14px 0;
}
.card__meta strong {
display: block;
font-family: var(--serif);
font-weight: 600;
font-size: 1.05rem;
color: var(--charcoal);
}
.card__meta span {
display: block;
margin-top: 3px;
font-size: 0.78rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
/* ---------- footer ---------- */
.foot {
max-width: 1140px;
margin: 0 auto;
padding: 28px 24px 48px;
border-top: 1px solid var(--line);
color: var(--ink-2);
font-size: 0.85rem;
}
.foot p {
margin: 0 0 4px;
}
.foot__dim {
color: var(--muted);
font-size: 0.78rem;
}
/* ---------- lightbox ---------- */
.lightbox {
position: fixed;
inset: 0;
z-index: 80;
background: rgba(20, 19, 17, 0.92);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.lightbox[hidden] {
display: none;
}
.lightbox__stage {
width: min(86vw, 720px);
height: min(78vh, 860px);
overflow: hidden;
cursor: grab;
border: 12px solid #efe9dc;
outline: 1px solid rgba(255, 255, 255, 0.15);
background: #efe9dc;
border-radius: 2px;
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5);
}
.lightbox__stage.is-panning {
cursor: grabbing;
}
.lightbox__stage svg {
display: block;
width: 100%;
height: 100%;
transform-origin: 0 0;
transition: transform 0.06s linear;
will-change: transform;
}
.lightbox__close {
position: absolute;
top: 18px;
right: 22px;
width: 44px;
height: 44px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.08);
color: #fff;
font-size: 1.1rem;
cursor: pointer;
}
.lightbox__close:hover {
background: rgba(255, 255, 255, 0.18);
}
.lightbox__hint {
margin-top: 16px;
color: rgba(255, 255, 255, 0.7);
font-size: 0.8rem;
letter-spacing: 0.02em;
}
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 20px);
background: var(--charcoal);
color: #fff;
padding: 12px 20px;
border-radius: 999px;
font-size: 0.86rem;
font-weight: 500;
box-shadow: var(--shadow-md);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s, transform 0.2s;
z-index: 90;
}
.toast.is-show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- responsive ---------- */
@media (max-width: 900px) {
.detail {
grid-template-columns: 1fr;
gap: 32px;
}
.viewer {
position: static;
}
.strip {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 520px) {
.page {
padding: 20px 16px 48px;
}
.topbar__inner {
padding: 12px 16px;
}
.topnav {
display: none;
}
.frame__mat {
padding: 14px;
}
.meta > div {
grid-template-columns: 1fr;
gap: 2px;
}
.object__actions .btn {
flex: 1 1 auto;
justify-content: center;
}
.strip {
grid-template-columns: 1fr;
}
.timeline li {
grid-template-columns: 70px 1fr;
gap: 12px;
}
.lightbox__stage {
width: 92vw;
height: 70vh;
}
}
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
}
}(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2200);
}
/* ---------- add to tour ---------- */
var tourBtn = document.getElementById("tourBtn");
if (tourBtn) {
tourBtn.addEventListener("click", function () {
var on = tourBtn.getAttribute("aria-pressed") === "true";
on = !on;
tourBtn.setAttribute("aria-pressed", String(on));
tourBtn.querySelector(".btn__label").textContent = on
? "Added to tour"
: "Add to my tour";
toast(
on
? "Added “Vessel of the Tide-Keepers” to your tour."
: "Removed from your tour."
);
});
}
/* ---------- copy accession no. ---------- */
var copyBtn = document.getElementById("copyBtn");
if (copyBtn) {
copyBtn.addEventListener("click", function () {
var acc = "1987.214";
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(acc).then(
function () { toast("Accession no. " + acc + " copied."); },
function () { toast("Accession no. " + acc); }
);
} else {
toast("Accession no. " + acc);
}
});
}
/* ---------- share ---------- */
var shareBtn = document.getElementById("shareBtn");
if (shareBtn) {
shareBtn.addEventListener("click", function () {
var data = {
title: "Vessel of the Tide-Keepers",
text: "Workshop of Oríel of Kestharn, c. 432 — Meridian Museum of Art",
};
if (navigator.share) {
navigator.share(data).catch(function () {});
} else {
toast("Share link copied to your notes.");
}
});
}
/* ---------- more-from-gallery cards ---------- */
var strip = document.getElementById("strip");
if (strip) {
strip.addEventListener("click", function (e) {
var card = e.target.closest(".card");
if (!card) return;
toast("Opening “" + card.getAttribute("data-title") + "” …");
});
}
/* ---------- zoom + pan lightbox ---------- */
var canvas = document.getElementById("canvas");
var srcArt = canvas ? canvas.querySelector("svg") : null;
var lightbox = document.getElementById("lightbox");
var stage = document.getElementById("lightboxStage");
var closeBtn = document.getElementById("lightboxClose");
var zlevel = document.getElementById("zlevel");
var zoomCtl = document.querySelector(".zoomctl");
var MIN = 1;
var MAX = 4;
var state = { scale: 1, x: 0, y: 0 };
var artNode = null;
function clamp(v, lo, hi) {
return Math.max(lo, Math.min(hi, v));
}
function applyTransform() {
if (!artNode) return;
// keep pan within bounds based on current scale
var rect = stage.getBoundingClientRect();
var maxX = (state.scale - 1) * rect.width;
var maxY = (state.scale - 1) * rect.height;
state.x = clamp(state.x, -maxX, 0);
state.y = clamp(state.y, -maxY, 0);
artNode.style.transform =
"translate(" + state.x + "px," + state.y + "px) scale(" + state.scale + ")";
if (zlevel) zlevel.textContent = Math.round(state.scale * 100) + "%";
stage.style.cursor = state.scale > 1 ? "grab" : "default";
}
function setScale(next, originX, originY) {
var prev = state.scale;
next = clamp(next, MIN, MAX);
if (next === prev) return;
var rect = stage.getBoundingClientRect();
// zoom toward a point (default: centre)
var ox = originX == null ? rect.width / 2 : originX;
var oy = originY == null ? rect.height / 2 : originY;
var ratio = next / prev;
state.x = ox - (ox - state.x) * ratio;
state.y = oy - (oy - state.y) * ratio;
state.scale = next;
if (next === MIN) { state.x = 0; state.y = 0; }
applyTransform();
}
function openLightbox() {
if (!lightbox || !srcArt) return;
artNode = srcArt.cloneNode(true);
artNode.removeAttribute("class");
stage.innerHTML = "";
stage.appendChild(artNode);
state = { scale: 1.4, x: 0, y: 0 };
lightbox.hidden = false;
document.body.style.overflow = "hidden";
applyTransform();
closeBtn.focus();
}
function closeLightbox() {
if (!lightbox) return;
lightbox.hidden = true;
document.body.style.overflow = "";
if (canvas) canvas.focus();
}
if (canvas) {
canvas.addEventListener("click", openLightbox);
}
if (closeBtn) closeBtn.addEventListener("click", closeLightbox);
if (lightbox) {
lightbox.addEventListener("click", function (e) {
if (e.target === lightbox) closeLightbox();
});
}
document.addEventListener("keydown", function (e) {
if (lightbox && !lightbox.hidden) {
if (e.key === "Escape") closeLightbox();
else if (e.key === "+" || e.key === "=") setScale(state.scale + 0.4);
else if (e.key === "-" || e.key === "_") setScale(state.scale - 0.4);
}
});
/* wheel zoom inside stage */
if (stage) {
stage.addEventListener(
"wheel",
function (e) {
e.preventDefault();
var rect = stage.getBoundingClientRect();
var ox = e.clientX - rect.left;
var oy = e.clientY - rect.top;
var delta = e.deltaY < 0 ? 0.25 : -0.25;
setScale(state.scale + delta, ox, oy);
},
{ passive: false }
);
/* drag to pan */
var dragging = false;
var startX = 0;
var startY = 0;
var baseX = 0;
var baseY = 0;
function pointerDown(e) {
if (state.scale <= 1) return;
dragging = true;
stage.classList.add("is-panning");
var p = e.touches ? e.touches[0] : e;
startX = p.clientX;
startY = p.clientY;
baseX = state.x;
baseY = state.y;
}
function pointerMove(e) {
if (!dragging) return;
var p = e.touches ? e.touches[0] : e;
state.x = baseX + (p.clientX - startX);
state.y = baseY + (p.clientY - startY);
applyTransform();
if (e.cancelable) e.preventDefault();
}
function pointerUp() {
dragging = false;
stage.classList.remove("is-panning");
}
stage.addEventListener("mousedown", pointerDown);
window.addEventListener("mousemove", pointerMove);
window.addEventListener("mouseup", pointerUp);
stage.addEventListener("touchstart", pointerDown, { passive: true });
stage.addEventListener("touchmove", pointerMove, { passive: false });
stage.addEventListener("touchend", pointerUp);
}
/* inline zoom controls (open lightbox + zoom) */
if (zoomCtl) {
zoomCtl.addEventListener("click", function (e) {
var b = e.target.closest(".zbtn");
if (!b) return;
var kind = b.getAttribute("data-zoom");
if (lightbox.hidden) {
openLightbox();
if (kind === "reset") setScale(MIN);
return;
}
if (kind === "in") setScale(state.scale + 0.4);
else if (kind === "out") setScale(state.scale - 0.4);
else if (kind === "reset") setScale(MIN);
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Vessel of the Tide-Keepers — Meridian Museum of Art</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=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="topbar">
<div class="topbar__inner">
<a class="brand" href="#" aria-label="Meridian Museum of Art — home">
<span class="brand__mark" aria-hidden="true"></span>
<span class="brand__name">Meridian<em>Museum of Art</em></span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#">Collection</a>
<a href="#">Exhibitions</a>
<a href="#">Visit</a>
<a href="#" aria-current="page">Search</a>
</nav>
</div>
</header>
<main id="main" class="page">
<nav class="crumbs" aria-label="Breadcrumb">
<ol>
<li><a href="#">Collection</a></li>
<li><a href="#">Gallery 14 · Coastal Antiquities</a></li>
<li aria-current="page">Vessel of the Tide-Keepers</li>
</ol>
</nav>
<div class="detail">
<!-- Image / viewer column -->
<section class="viewer" aria-label="Artwork image">
<figure class="frame" id="frame">
<div class="frame__mat">
<button class="canvas" id="canvas" type="button"
aria-label="Zoom artwork. Click or press Enter to zoom in, then drag to pan.">
<svg class="art" viewBox="0 0 800 1000" role="img"
aria-label="Glazed ceremonial vessel with cresting-wave motif">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#e7ddc9" />
<stop offset="1" stop-color="#d6c7a6" />
</linearGradient>
<linearGradient id="clay" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#9c6f43" />
<stop offset="0.55" stop-color="#7c5230" />
<stop offset="1" stop-color="#5d3c22" />
</linearGradient>
<radialGradient id="glaze" cx="0.4" cy="0.32" r="0.8">
<stop offset="0" stop-color="#3f7d7a" />
<stop offset="0.6" stop-color="#26514f" />
<stop offset="1" stop-color="#173331" />
</radialGradient>
</defs>
<rect width="800" height="1000" fill="url(#sky)" />
<rect x="0" y="640" width="800" height="360" fill="#cdbd99" />
<line x1="0" y1="640" x2="800" y2="640" stroke="#b6a47d" stroke-width="2" />
<!-- vessel -->
<path d="M400 150
C300 160 270 230 300 300
C220 360 200 520 250 660
C290 770 350 820 400 820
C450 820 510 770 550 660
C600 520 580 360 500 300
C530 230 500 160 400 150 Z"
fill="url(#clay)" stroke="#3a2516" stroke-width="3" />
<ellipse cx="400" cy="300" rx="150" ry="34" fill="url(#glaze)" opacity="0.9" />
<path d="M250 470 q150 -70 300 0" fill="none" stroke="url(#glaze)" stroke-width="26" stroke-linecap="round" />
<path d="M268 545 q132 -64 264 0" fill="none" stroke="#2f6360" stroke-width="20" stroke-linecap="round" opacity="0.85" />
<path d="M288 615 q112 -56 224 0" fill="none" stroke="#173331" stroke-width="14" stroke-linecap="round" opacity="0.75" />
<ellipse cx="360" cy="240" rx="44" ry="80" fill="#ffffff" opacity="0.08" />
<circle cx="400" cy="700" r="10" fill="#e7ddc9" opacity="0.5" />
</svg>
</button>
</div>
<figcaption class="frame__cap">
<span class="zoomhint" id="zoomhint">Click image to zoom · drag to pan</span>
<span class="frame__no">Acc. 1987.214</span>
</figcaption>
<div class="zoomctl" role="group" aria-label="Zoom controls">
<button type="button" class="zbtn" data-zoom="out" aria-label="Zoom out">−</button>
<span class="zlevel" id="zlevel" aria-live="polite">100%</span>
<button type="button" class="zbtn" data-zoom="in" aria-label="Zoom in">+</button>
<button type="button" class="zbtn zbtn--reset" data-zoom="reset">Reset</button>
</div>
</figure>
</section>
<!-- Metadata column -->
<section class="object" aria-label="Object information">
<div class="object__tags">
<span class="badge badge--gold">On view · Gallery 14</span>
<span class="badge">Ceramics</span>
<span class="badge">Public domain</span>
</div>
<h1 class="object__title">Vessel of the Tide-Keepers</h1>
<p class="object__artist">Workshop of <strong>Oríel of Kestharn</strong> <span class="dim">(active c. 410–455)</span></p>
<p class="object__sub">Coastal Antiquities · Late Meridian period</p>
<dl class="meta">
<div><dt>Date</dt><dd>c. 432 (probable)</dd></div>
<div><dt>Medium</dt><dd>Stoneware with celadon glaze and slip inlay</dd></div>
<div><dt>Dimensions</dt><dd>41.2 × 24.6 cm (16¼ × 9⅝ in.)</dd></div>
<div><dt>Classification</dt><dd>Vessels — Ceremonial</dd></div>
<div><dt>Credit line</dt><dd>Gift of the Halvern Coastal Trust, 1987</dd></div>
<div><dt>Accession no.</dt><dd>1987.214</dd></div>
</dl>
<div class="object__actions">
<button type="button" class="btn btn--primary" id="tourBtn" aria-pressed="false">
<span class="btn__icon" aria-hidden="true">+</span>
<span class="btn__label">Add to my tour</span>
</button>
<button type="button" class="btn" id="shareBtn">Share</button>
<button type="button" class="btn" id="copyBtn">Copy accession no.</button>
</div>
<article class="prose">
<h2>Curatorial note</h2>
<p>
Among the most complete survivals from the Kestharn workshops, this vessel was
almost certainly made for the spring tide-blessing — a coastal rite in which
offerings of grain and salt were carried to the water's edge. The cresting-wave
band, drawn in three diminishing registers, is a signature of Oríel's late hand.
</p>
<p>
The celadon pooling at the shoulder, once read as a firing flaw, is now understood
as a deliberate effect: glaze deliberately thinned so that the wave motif would
appear to surface from beneath still water. Comparable fragments in Gallery 14
help date the piece to the final decade of the workshop's activity.
</p>
</article>
<section class="provenance" aria-label="Provenance">
<h2>Provenance</h2>
<ol class="timeline">
<li>
<span class="timeline__year">c. 432</span>
<div class="timeline__body">
<strong>Workshop of Oríel, Kestharn</strong>
<p>Made for a coastal sanctuary on the Halvern peninsula.</p>
</div>
</li>
<li>
<span class="timeline__year">1846</span>
<div class="timeline__body">
<strong>Halvern Coastal Trust</strong>
<p>Recovered during the Trust's first survey of the sanctuary terraces.</p>
</div>
</li>
<li>
<span class="timeline__year">1903–1986</span>
<div class="timeline__body">
<strong>Trust study collection</strong>
<p>Held for research; exhibited only twice (1929, 1971).</p>
</div>
</li>
<li class="is-current">
<span class="timeline__year">1987</span>
<div class="timeline__body">
<strong>Meridian Museum of Art</strong>
<p>Gift of the Halvern Coastal Trust; accessioned 1987.214.</p>
</div>
</li>
</ol>
</section>
</section>
</div>
<!-- More from this gallery -->
<section class="more" aria-label="More from this gallery">
<div class="more__head">
<h2>More from Gallery 14</h2>
<a href="#" class="more__all">View all 38 objects →</a>
</div>
<ul class="strip" id="strip">
<li class="card" data-title="Salt Dish with Reed Border">
<button type="button" class="card__btn">
<span class="card__art" style="--c1:#7c5230;--c2:#4d3220"></span>
<span class="card__meta">
<strong>Salt Dish with Reed Border</strong>
<span>Kestharn · c. 440 · Acc. 1987.220</span>
</span>
</button>
</li>
<li class="card" data-title="Tide Lamp">
<button type="button" class="card__btn">
<span class="card__art" style="--c1:#3f7d7a;--c2:#173331"></span>
<span class="card__meta">
<strong>Tide Lamp</strong>
<span>Coastal Antiquities · c. 428 · Acc. 1991.061</span>
</span>
</button>
</li>
<li class="card" data-title="Fragment with Wave Register">
<button type="button" class="card__btn">
<span class="card__art" style="--c1:#9c6f43;--c2:#5d3c22"></span>
<span class="card__meta">
<strong>Fragment with Wave Register</strong>
<span>Kestharn · c. 435 · Acc. 1987.209</span>
</span>
</button>
</li>
<li class="card" data-title="Offering Bowl, Pair">
<button type="button" class="card__btn">
<span class="card__art" style="--c1:#a98140;--c2:#6b4d23"></span>
<span class="card__meta">
<strong>Offering Bowl, Pair</strong>
<span>Halvern · c. 450 · Acc. 1987.231</span>
</span>
</button>
</li>
</ul>
</section>
</main>
<footer class="foot">
<p>Meridian Museum of Art · 14 Harborlight Row · Open Tue–Sun, 10–6</p>
<p class="foot__dim">Object record last reviewed by the Department of Coastal Antiquities.</p>
</footer>
<!-- Lightbox -->
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Zoomed artwork" hidden>
<button type="button" class="lightbox__close" id="lightboxClose" aria-label="Close zoom view">✕</button>
<div class="lightbox__stage" id="lightboxStage"></div>
<p class="lightbox__hint">Scroll or use + / − to zoom · drag to pan · Esc to close</p>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Artifact / Artwork Detail
The object page a visitor reaches after picking a work from the collection. A large image sits inside a thin mat and frame on the left; clicking it opens a lightbox where the artwork can be zoomed with the wheel or the + / − controls and dragged to pan, evoking the close looking you do in a gallery. Inline zoom buttons and a live percentage keep the interaction legible, and everything is keyboard- and Escape-friendly.
The right column carries the curatorial record: a serif title, attributed workshop, and a definition list of date, medium, dimensions, classification, credit line, and accession number. Below it, a vertical provenance timeline traces the piece from its workshop origin through trust custody to the museum, with the current owner marked in gold. A curatorial note and a more-from-this-gallery strip extend the browse.
Primary actions are vanilla JS: an add-to-tour toggle that flips its label and pressed state, a share action (using the Web Share API where available), and a copy-accession-number button — each confirmed with a small toast. The layout uses a refined gallery palette with generous wall space and collapses cleanly down to roughly 360px.
Illustrative UI only — demo data; not a real museum system.