Museum — Natural History Museum Landing
An earthy, exploratory marketing landing page for a fictional natural history museum, built with vanilla HTML, CSS and JavaScript. It opens on a fossil-cast hero, then walks visitors through eleven filterable collection halls, hands-on discovery exhibits, a sticky visit planner that tallies your time inside, dated family programs, and a ticket reservation card. Slab-serif headings, bone and forest-green tones and amber accents give it a museum-of-nature feel, with toasts, keyboard support and full responsiveness.
MCP
Code
:root {
/* Natural History palette */
--bone: #efe9dc;
--paper: #efe9dc;
--wall: #fbf8f1;
--forest: #2f4a36;
--forest-d: #213829;
--forest-l: #3a5a40;
--amber: #c8842a;
--amber-d: #a8691b;
--amber-50: #f4e6cf;
--ink: #25201a;
--ink-2: #564d40;
--muted: #897e6c;
--ocean: #2f5d6b;
--line: rgba(37, 32, 26, 0.12);
--line-2: rgba(37, 32, 26, 0.2);
--ok: #3f7d56;
--warn: #b8842c;
--danger: #b4493a;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow-sm: 0 1px 2px rgba(37, 32, 26, 0.06);
--shadow-md: 0 10px 30px rgba(37, 32, 26, 0.1);
--shadow-lg: 0 24px 60px rgba(37, 32, 26, 0.16);
--serif: "Bitter", Georgia, "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--wrap: 1140px;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: var(--sans);
color: var(--ink);
background: var(--paper);
line-height: 1.55;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3 { font-family: var(--serif); line-height: 1.12; margin: 0; color: var(--ink); }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img, svg { display: block; max-width: 100%; }
:focus-visible {
outline: 3px solid var(--amber);
outline-offset: 2px;
border-radius: 4px;
}
.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.skip-link {
position: absolute; left: 12px; top: -60px; z-index: 200;
background: var(--forest); color: #fff; padding: 10px 16px;
border-radius: var(--r-sm); transition: top .18s ease;
}
.skip-link:focus { top: 12px; }
.eyebrow {
display: inline-block;
font-family: var(--sans);
font-size: 0.74rem;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--amber-d);
}
/* ---------- Buttons ---------- */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
font-family: var(--sans); font-weight: 600; font-size: 0.92rem;
padding: 10px 18px; border-radius: var(--r-sm);
border: 1px solid transparent; cursor: pointer; background: none;
transition: transform .12s ease, background .18s ease, box-shadow .18s ease, color .18s ease;
white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
.btn--solid { background: var(--forest); color: #fbf8f1; box-shadow: var(--shadow-sm); }
.btn--solid:hover { background: var(--forest-d); box-shadow: var(--shadow-md); }
.btn--outline { border-color: var(--line-2); color: var(--ink); background: var(--wall); }
.btn--outline:hover { border-color: var(--forest); color: var(--forest); }
.btn--ghost { color: var(--ink-2); }
.btn--ghost:hover { color: var(--forest); }
.btn--lg { padding: 14px 26px; font-size: 1rem; }
.btn--block { width: 100%; }
/* ---------- Topbar ---------- */
.topbar {
position: sticky; top: 0; z-index: 100;
background: rgba(251, 248, 241, 0.92);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
}
.topbar__inner { display: flex; align-items: center; gap: 24px; height: 72px; }
.brand { display: flex; align-items: center; gap: 12px; margin-right: auto; }
.brand__mark {
display: grid; place-items: center; width: 42px; height: 42px;
border-radius: var(--r-sm); background: var(--forest); color: var(--amber-50);
}
.brand__text { display: flex; flex-direction: column; line-height: 1.1; }
.brand__text strong { font-family: var(--serif); font-size: 1.08rem; letter-spacing: 0.01em; }
.brand__text em { font-style: normal; font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.nav { display: flex; gap: 26px; }
.nav a {
font-weight: 500; font-size: 0.94rem; color: var(--ink-2);
padding: 6px 2px; position: relative;
}
.nav a::after {
content: ""; position: absolute; left: 0; bottom: 0; height: 2px; width: 0;
background: var(--amber); transition: width .2s ease;
}
.nav a:hover { color: var(--ink); }
.nav a:hover::after { width: 100%; }
.topbar__actions { display: flex; align-items: center; gap: 12px; }
.menu-toggle {
display: none; flex-direction: column; gap: 5px; width: 42px; height: 42px;
border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--wall);
align-items: center; justify-content: center; cursor: pointer;
}
.menu-toggle span { width: 18px; height: 2px; background: var(--ink); border-radius: 2px; }
.hours-panel { border-top: 1px solid var(--line); background: var(--amber-50); }
.hours-panel__inner { padding: 16px 24px; display: flex; flex-wrap: wrap; gap: 24px; align-items: center; font-size: 0.9rem; }
.hours-panel__inner > p { font-family: var(--serif); color: var(--forest-d); }
.hours-panel ul { display: flex; flex-wrap: wrap; gap: 18px; margin: 0; padding: 0; list-style: none; color: var(--ink-2); }
.hours-panel li span { color: var(--ink); font-weight: 600; margin-left: 6px; }
/* ---------- Mobile nav ---------- */
.mobile-nav {
position: sticky; top: 72px; z-index: 99;
background: var(--wall); border-bottom: 1px solid var(--line);
display: flex; flex-direction: column; padding: 12px 24px; gap: 4px;
}
.mobile-nav a { padding: 12px 4px; border-bottom: 1px solid var(--line); font-weight: 500; }
.mobile-nav a:last-child { border-bottom: 0; margin-top: 8px; }
/* ---------- Hero ---------- */
.hero { background:
radial-gradient(1200px 500px at 80% -10%, rgba(200,132,42,0.16), transparent 60%),
radial-gradient(900px 600px at -10% 110%, rgba(47,74,54,0.12), transparent 60%),
var(--paper);
border-bottom: 1px solid var(--line);
}
.hero__inner {
display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 56px;
align-items: center; padding: 72px 24px 80px;
}
.hero__copy .eyebrow { margin-bottom: 16px; }
.hero h1 { font-size: clamp(2.4rem, 5.2vw, 3.9rem); font-weight: 700; letter-spacing: -0.01em; }
.lede { margin-top: 20px; font-size: 1.12rem; color: var(--ink-2); max-width: 46ch; }
.lede em { color: var(--forest); font-style: italic; }
.hero__cta { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 30px; }
.hero__stats { display: flex; gap: 36px; margin: 40px 0 0; padding-top: 26px; border-top: 1px solid var(--line); }
.hero__stats div dt { font-size: 0.74rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); margin: 0; }
.hero__stats div dd { margin: 4px 0 0; font-family: var(--serif); font-size: 1.7rem; color: var(--forest-d); }
.hero__art { margin: 0; position: relative; }
.specimen {
background: linear-gradient(160deg, #fbf8f1, #ece3cf);
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
padding: 22px;
box-shadow: var(--shadow-lg);
position: relative;
}
.specimen::before {
content: ""; position: absolute; inset: 12px; border: 1px solid var(--line);
border-radius: var(--r-md); pointer-events: none;
}
.specimen svg { width: 100%; height: auto; filter: drop-shadow(0 6px 12px rgba(37,32,26,0.12)); }
.hero__art figcaption {
margin-top: 16px; display: flex; flex-direction: column; gap: 4px;
font-size: 0.86rem; color: var(--muted);
}
.hero__art figcaption strong { font-family: var(--serif); font-size: 1.05rem; color: var(--ink); }
.hero__art .tag { align-self: flex-start; margin-bottom: 2px; }
/* ---------- Tags ---------- */
.tag {
display: inline-flex; align-items: center;
font-size: 0.7rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
padding: 4px 10px; border-radius: 999px;
background: var(--amber-50); color: var(--amber-d);
}
.tag--green { background: rgba(47,74,54,0.12); color: var(--forest-d); }
.tag--amber { background: var(--amber-50); color: var(--amber-d); }
.tag--ocean { background: rgba(47,93,107,0.14); color: var(--ocean); }
.tag--earth { background: rgba(122,106,78,0.16); color: #5e503a; }
/* ---------- Ticker ---------- */
.ticker { overflow: hidden; background: var(--forest); color: var(--amber-50); border-bottom: 1px solid var(--forest-d); }
.ticker__track {
display: flex; gap: 0; white-space: nowrap; will-change: transform;
animation: scroll-x 28s linear infinite;
}
.ticker__track span {
font-family: var(--serif); font-size: 0.96rem; letter-spacing: 0.03em;
padding: 12px 26px; position: relative;
}
.ticker__track span::after { content: "✦"; position: absolute; right: -4px; top: 50%; transform: translateY(-50%); color: var(--amber); font-size: 0.6rem; }
.ticker:hover .ticker__track { animation-play-state: paused; }
@keyframes scroll-x { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* ---------- Sections ---------- */
.section { padding: 80px 0; }
.section--alt { background: var(--wall); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.section__head { max-width: 60ch; margin-bottom: 36px; }
.section__head h2 { font-size: clamp(1.9rem, 3.6vw, 2.6rem); font-weight: 700; margin: 12px 0 14px; }
.section__head p { color: var(--ink-2); font-size: 1.05rem; }
/* ---------- Filters ---------- */
.filters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 32px; }
.chip {
font-family: var(--sans); font-weight: 500; font-size: 0.9rem;
padding: 8px 16px; border-radius: 999px; cursor: pointer;
border: 1px solid var(--line-2); background: var(--wall); color: var(--ink-2);
transition: all .16s ease;
}
.chip:hover { border-color: var(--forest); color: var(--forest); }
.chip.is-active { background: var(--forest); color: #fbf8f1; border-color: var(--forest); }
/* ---------- Halls grid ---------- */
.halls { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.hall {
background: var(--wall); border: 1px solid var(--line);
border-radius: var(--r-lg); overflow: hidden;
display: flex; flex-direction: column;
transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.hall:hover, .hall:focus-visible { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: var(--line-2); }
.hall.is-hidden { display: none; }
.hall__art {
height: 150px; display: grid; place-items: center;
background: linear-gradient(155deg, var(--g1), var(--g2));
position: relative;
}
.hall__art::after { content: ""; position: absolute; inset: 10px; border: 1px solid rgba(251,248,241,0.25); border-radius: var(--r-md); }
.hall__art svg { width: 88px; height: auto; position: relative; z-index: 1; opacity: 0.95; }
.hall__body { padding: 20px 22px 24px; display: flex; flex-direction: column; gap: 8px; }
.hall__body .tag { align-self: flex-start; }
.hall__body h3 { font-size: 1.32rem; font-weight: 600; }
.hall__body p { color: var(--ink-2); font-size: 0.95rem; }
.hall__body em { color: var(--forest); font-style: italic; }
.hall__meta { margin-top: 2px; font-size: 0.8rem; color: var(--muted); letter-spacing: 0.02em; }
.halls__empty { text-align: center; color: var(--muted); padding: 40px 0; font-family: var(--serif); font-size: 1.1rem; }
/* ---------- Hands-on ---------- */
.hands { display: grid; grid-template-columns: 1.2fr 0.8fr; gap: 48px; align-items: start; }
.hands__copy h2 { font-size: clamp(1.9rem, 3.6vw, 2.6rem); margin: 12px 0 14px; }
.hands__copy > p { color: var(--ink-2); font-size: 1.05rem; max-width: 48ch; }
.hands__list { list-style: none; margin: 26px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.hands__list li {
display: flex; align-items: center; gap: 16px;
padding: 16px 18px; background: var(--paper); border: 1px solid var(--line);
border-radius: var(--r-md);
}
.hands__icon {
flex: none; width: 46px; height: 46px; display: grid; place-items: center;
font-size: 1.4rem; background: var(--amber-50); border-radius: var(--r-sm);
}
.hands__list li > div { flex: 1; }
.hands__list li strong { font-family: var(--serif); font-size: 1.05rem; }
.hands__list li p { font-size: 0.88rem; color: var(--ink-2); }
/* ---------- Planner ---------- */
.planner {
position: sticky; top: 96px;
background: var(--forest); color: var(--bone);
border-radius: var(--r-lg); padding: 26px; box-shadow: var(--shadow-md);
}
.planner h3 { color: #fbf8f1; font-size: 1.4rem; }
.planner__hint { color: rgba(239,233,220,0.72); font-size: 0.9rem; margin-top: 6px; }
.planner__list { list-style: none; margin: 18px 0; padding: 0; display: flex; flex-direction: column; gap: 8px; min-height: 40px; }
.planner__list li {
display: flex; align-items: center; justify-content: space-between; gap: 10px;
background: rgba(251,248,241,0.08); border: 1px solid rgba(251,248,241,0.16);
border-radius: var(--r-sm); padding: 10px 12px; font-size: 0.92rem;
}
.planner__list li .min { color: var(--amber); font-weight: 600; margin-left: auto; }
.planner__list .planner__remove {
background: none; border: 0; color: rgba(239,233,220,0.7); cursor: pointer;
font-size: 1.1rem; line-height: 1; padding: 0 2px;
}
.planner__list .planner__remove:hover { color: #fff; }
.planner__empty { justify-content: center; color: rgba(239,233,220,0.6); font-style: italic; }
.planner__add { display: flex; gap: 8px; }
.planner__add select {
flex: 1; padding: 11px 12px; border-radius: var(--r-sm);
border: 1px solid rgba(251,248,241,0.22); background: rgba(251,248,241,0.06);
color: var(--bone); font-family: var(--sans); font-size: 0.9rem;
}
.planner__add select option { color: var(--ink); }
.planner__total {
display: flex; justify-content: space-between; align-items: baseline;
margin-top: 20px; padding-top: 18px; border-top: 1px solid rgba(251,248,241,0.18);
}
.planner__total span { font-size: 0.86rem; color: rgba(239,233,220,0.72); text-transform: uppercase; letter-spacing: 0.08em; }
.planner__total strong { font-family: var(--serif); font-size: 1.7rem; color: var(--amber); }
/* ---------- Programs ---------- */
.programs { display: flex; flex-direction: column; gap: 16px; }
.program {
display: flex; align-items: center; gap: 24px;
background: var(--wall); border: 1px solid var(--line);
border-radius: var(--r-lg); padding: 20px 24px;
transition: border-color .16s ease, box-shadow .16s ease;
}
.program:hover { border-color: var(--line-2); box-shadow: var(--shadow-sm); }
.program__date {
flex: none; width: 76px; height: 76px; border-radius: var(--r-md);
background: var(--amber-50); color: var(--amber-d);
display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.program__date span { font-size: 0.72rem; font-weight: 600; letter-spacing: 0.1em; }
.program__date strong { font-family: var(--serif); font-size: 1.8rem; line-height: 1; color: var(--ink); }
.program__body { flex: 1; display: flex; flex-direction: column; gap: 6px; }
.program__body .tag { align-self: flex-start; }
.program__body h3 { font-size: 1.28rem; font-weight: 600; }
.program__body p { color: var(--ink-2); font-size: 0.94rem; }
.program__meta { color: var(--muted) !important; font-size: 0.85rem !important; }
/* ---------- CTA / Tickets ---------- */
.section--cta { background:
radial-gradient(900px 500px at 110% 0%, rgba(200,132,42,0.14), transparent 60%),
var(--paper);
}
.cta { display: grid; grid-template-columns: 1fr 0.9fr; gap: 52px; align-items: start; }
.cta__copy h2 { font-size: clamp(1.8rem, 3.4vw, 2.5rem); margin: 12px 0 14px; max-width: 18ch; }
.cta__copy > p { color: var(--ink-2); font-size: 1.05rem; max-width: 46ch; }
.cta__facts { list-style: none; margin: 28px 0 0; padding: 0; display: grid; gap: 14px; }
.cta__facts li { display: flex; justify-content: space-between; gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--line); }
.cta__facts li strong { font-family: var(--serif); color: var(--ink); }
.cta__facts li span { color: var(--ink-2); text-align: right; }
.ticketcard {
background: var(--wall); border: 1px solid var(--line-2);
border-radius: var(--r-lg); padding: 28px; box-shadow: var(--shadow-md);
}
.ticketcard h3 { font-size: 1.4rem; margin-bottom: 18px; }
.field { margin-bottom: 16px; }
.field label { display: block; font-size: 0.82rem; font-weight: 600; color: var(--ink-2); margin-bottom: 6px; }
.field input, .field select {
width: 100%; padding: 12px 14px; font-family: var(--sans); font-size: 0.95rem;
border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--paper); color: var(--ink);
}
.field input:focus, .field select:focus { border-color: var(--forest); outline: none; box-shadow: 0 0 0 3px rgba(47,74,54,0.14); }
.field--inline { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.ticketcard__note { font-size: 0.84rem; color: var(--muted); margin: 4px 0 18px; }
/* ---------- Footer ---------- */
.footer { background: var(--forest); color: var(--bone); padding: 56px 0 28px; }
.footer__inner { display: grid; grid-template-columns: 1.4fr 1fr 1.2fr; gap: 36px; align-items: start; }
.footer__brand strong { font-family: var(--serif); font-size: 1.2rem; color: #fbf8f1; }
.footer__brand p { color: rgba(239,233,220,0.7); font-size: 0.9rem; margin-top: 6px; }
.footer__nav { display: flex; flex-direction: column; gap: 10px; }
.footer__nav a { color: rgba(239,233,220,0.82); font-size: 0.94rem; }
.footer__nav a:hover { color: var(--amber); }
.footer__news label { display: block; font-size: 0.9rem; margin-bottom: 10px; color: rgba(239,233,220,0.9); }
.footer__newsrow { display: flex; gap: 8px; }
.footer__newsrow input {
flex: 1; padding: 11px 13px; border-radius: var(--r-sm); border: 1px solid rgba(251,248,241,0.22);
background: rgba(251,248,241,0.08); color: var(--bone); font-family: var(--sans); font-size: 0.9rem;
}
.footer__newsrow input::placeholder { color: rgba(239,233,220,0.55); }
.footer__legal { text-align: center; margin-top: 40px; padding-top: 20px; border-top: 1px solid rgba(251,248,241,0.14); color: rgba(239,233,220,0.6); font-size: 0.84rem; }
/* ---------- Toast ---------- */
.toast {
position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 24px);
background: var(--ink); color: var(--bone);
padding: 13px 20px; border-radius: var(--r-md); box-shadow: var(--shadow-lg);
font-size: 0.92rem; font-weight: 500; opacity: 0; pointer-events: none;
transition: opacity .24s ease, transform .24s ease; z-index: 300; max-width: 90vw;
}
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }
.toast::before { content: "✦ "; color: var(--amber); }
/* ---------- Responsive ---------- */
@media (max-width: 940px) {
.hero__inner { grid-template-columns: 1fr; gap: 40px; padding: 56px 24px 64px; }
.halls { grid-template-columns: repeat(2, 1fr); }
.hands { grid-template-columns: 1fr; gap: 32px; }
.planner { position: static; }
.cta { grid-template-columns: 1fr; gap: 36px; }
.footer__inner { grid-template-columns: 1fr 1fr; }
.nav { display: none; }
.menu-toggle { display: flex; }
}
@media (max-width: 520px) {
.wrap { padding: 0 18px; }
.topbar__inner { height: 64px; gap: 12px; }
.topbar__actions .btn--ghost { display: none; }
.brand__text em { display: none; }
.mobile-nav { top: 64px; }
.section { padding: 56px 0; }
.halls { grid-template-columns: 1fr; }
.hero__stats { gap: 22px; flex-wrap: wrap; }
.program { flex-direction: column; align-items: flex-start; gap: 14px; }
.program__date { flex-direction: row; gap: 8px; width: auto; height: auto; padding: 8px 14px; }
.program__date strong { font-size: 1.3rem; }
.program > .btn { width: 100%; }
.footer__inner { grid-template-columns: 1fr; }
.field--inline { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto; transition-duration: 0.001ms !important; }
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2600);
}
/* ---------- Today's hours panel ---------- */
var hoursBtn = document.getElementById("hoursBtn");
var hoursPanel = document.getElementById("hoursPanel");
if (hoursBtn && hoursPanel) {
hoursBtn.addEventListener("click", function () {
var open = hoursPanel.hasAttribute("hidden");
if (open) {
hoursPanel.removeAttribute("hidden");
} else {
hoursPanel.setAttribute("hidden", "");
}
hoursBtn.setAttribute("aria-expanded", String(open));
});
}
/* ---------- Mobile menu ---------- */
var menuToggle = document.getElementById("menuToggle");
var mobileNav = document.getElementById("mobileNav");
if (menuToggle && mobileNav) {
menuToggle.addEventListener("click", function () {
var open = mobileNav.hasAttribute("hidden");
if (open) {
mobileNav.removeAttribute("hidden");
} else {
mobileNav.setAttribute("hidden", "");
}
menuToggle.setAttribute("aria-expanded", String(open));
});
mobileNav.addEventListener("click", function (e) {
if (e.target.tagName === "A") {
mobileNav.setAttribute("hidden", "");
menuToggle.setAttribute("aria-expanded", "false");
}
});
}
/* ---------- Hall filtering ---------- */
var chips = Array.prototype.slice.call(document.querySelectorAll(".chip"));
var halls = Array.prototype.slice.call(document.querySelectorAll(".hall"));
var emptyMsg = document.getElementById("hallsEmpty");
function applyFilter(filter) {
var visible = 0;
halls.forEach(function (hall) {
var match = filter === "all" || hall.getAttribute("data-cat") === filter;
hall.classList.toggle("is-hidden", !match);
if (match) visible++;
});
if (emptyMsg) emptyMsg.toggleAttribute("hidden", visible !== 0);
}
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-selected", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-selected", "true");
applyFilter(chip.getAttribute("data-filter"));
});
});
/* ---------- Visit planner ---------- */
var plannerSelect = document.getElementById("plannerSelect");
var plannerAdd = document.getElementById("plannerAdd");
var plannerList = document.getElementById("plannerList");
var plannerEmpty = document.getElementById("plannerEmpty");
var plannerTotal = document.getElementById("plannerTotal");
var route = [];
function decode(s) {
var t = document.createElement("textarea");
t.innerHTML = s;
return t.value;
}
function renderPlanner() {
// clear except empty placeholder
Array.prototype.slice.call(plannerList.querySelectorAll(".planner__item")).forEach(function (el) {
el.remove();
});
var total = 0;
route.forEach(function (item, idx) {
total += item.min;
var li = document.createElement("li");
li.className = "planner__item";
var name = document.createElement("span");
name.textContent = decode(item.name);
var min = document.createElement("span");
min.className = "min";
min.textContent = item.min + " min";
var rm = document.createElement("button");
rm.className = "planner__remove";
rm.type = "button";
rm.setAttribute("aria-label", "Remove " + decode(item.name));
rm.innerHTML = "×";
rm.addEventListener("click", function () {
route.splice(idx, 1);
renderPlanner();
toast("Removed from your route.");
});
li.appendChild(name);
li.appendChild(min);
li.appendChild(rm);
plannerList.appendChild(li);
});
plannerEmpty.style.display = route.length ? "none" : "";
plannerTotal.textContent = total + " min";
}
if (plannerAdd && plannerSelect) {
plannerAdd.addEventListener("click", function () {
var opt = plannerSelect.options[plannerSelect.selectedIndex];
if (!opt || !opt.value) {
toast("Choose a hall to add first.");
return;
}
if (route.some(function (r) { return r.name === opt.value; })) {
toast("That hall is already on your route.");
return;
}
route.push({ name: opt.value, min: parseInt(opt.getAttribute("data-min"), 10) || 0 });
renderPlanner();
toast("Added " + decode(opt.value) + " to your route.");
plannerSelect.selectedIndex = 0;
});
}
/* ---------- Program reservations ---------- */
Array.prototype.slice.call(document.querySelectorAll("[data-prog]")).forEach(function (btn) {
btn.addEventListener("click", function () {
toast("Reserved a spot for “" + btn.getAttribute("data-prog") + "”. Check your email.");
btn.textContent = "Reserved ✓";
btn.disabled = true;
btn.classList.remove("btn--outline");
btn.classList.add("btn--solid");
});
});
/* ---------- Ticket form ---------- */
var ticketForm = document.getElementById("ticketForm");
if (ticketForm) {
// default date = today
var tDate = document.getElementById("tDate");
if (tDate) {
var now = new Date();
tDate.value = now.toISOString().slice(0, 10);
tDate.min = now.toISOString().slice(0, 10);
}
ticketForm.addEventListener("submit", function (e) {
e.preventDefault();
var date = ticketForm.date.value;
var slot = ticketForm.slot.value;
var adults = parseInt(ticketForm.adults.value, 10) || 0;
var children = parseInt(ticketForm.children.value, 10) || 0;
if (!date || !slot) {
toast("Pick a date and an entry slot.");
return;
}
if (adults + children === 0) {
toast("Add at least one visitor.");
return;
}
toast("Reserved " + (adults + children) + " ticket(s) for " + slot + ". See you soon!");
ticketForm.reset();
if (tDate) tDate.value = new Date().toISOString().slice(0, 10);
});
}
/* ---------- Newsletter ---------- */
var newsForm = document.getElementById("newsForm");
if (newsForm) {
newsForm.addEventListener("submit", function (e) {
e.preventDefault();
var email = document.getElementById("newsEmail");
if (!email.value || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.value)) {
toast("Enter a valid email address.");
return;
}
toast("You're on the list — thanks for joining!");
newsForm.reset();
});
}
/* ---------- Header tickets tracking nicety ---------- */
Array.prototype.slice.call(document.querySelectorAll("[data-track]")).forEach(function (el) {
el.addEventListener("click", function () {
// smooth scroll handled by CSS; nothing to do, just a hook
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Strathmore Natural History Museum</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=Bitter: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="wrap topbar__inner">
<a class="brand" href="#main" aria-label="Strathmore Natural History Museum home">
<span class="brand__mark" aria-hidden="true">
<svg viewBox="0 0 32 32" width="28" height="28">
<path d="M4 26c5-10 9-15 14-18 2 4 4 9 4 16-4-3-7-4-11-4 3-2 6-3 9-3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="24" cy="9" r="1.6" fill="currentColor"/>
</svg>
</span>
<span class="brand__text">
<strong>Strathmore</strong>
<em>Natural History Museum</em>
</span>
</a>
<nav class="nav" aria-label="Primary">
<a href="#halls">Halls</a>
<a href="#handson">Hands-On</a>
<a href="#programs">Programs</a>
<a href="#visit">Visit</a>
</nav>
<div class="topbar__actions">
<button class="btn btn--ghost" id="hoursBtn" type="button" aria-expanded="false" aria-controls="hoursPanel">
Today's hours
</button>
<a class="btn btn--solid" href="#tickets" data-track="header-tickets">Get tickets</a>
<button class="menu-toggle" id="menuToggle" type="button" aria-expanded="false" aria-controls="mobileNav" aria-label="Open menu">
<span></span><span></span><span></span>
</button>
</div>
</div>
<div class="hours-panel" id="hoursPanel" hidden>
<div class="wrap hours-panel__inner">
<p><strong>Open today · 10:00 – 17:30</strong> · Last entry 16:45</p>
<ul>
<li>Mon–Fri <span>10:00 – 17:30</span></li>
<li>Sat–Sun <span>09:30 – 18:00</span></li>
<li>First Thursday late <span>10:00 – 21:00</span></li>
</ul>
</div>
</div>
</header>
<nav class="mobile-nav" id="mobileNav" aria-label="Mobile" hidden>
<a href="#halls">Halls</a>
<a href="#handson">Hands-On</a>
<a href="#programs">Programs</a>
<a href="#visit">Visit</a>
<a href="#tickets" class="btn btn--solid">Get tickets</a>
</nav>
<main id="main">
<!-- HERO -->
<section class="hero" aria-labelledby="heroHeading">
<div class="wrap hero__inner">
<div class="hero__copy">
<span class="eyebrow">Now on view · Gallery of Deep Time</span>
<h1 id="heroHeading">Four billion years, under one roof.</h1>
<p class="lede">
Walk beneath a 26-metre <em>Brachyurus strathmorei</em>, hold a meteorite older than
the Sun, and trace the long story of life from the first cell to the modern coral reef.
</p>
<div class="hero__cta">
<a class="btn btn--solid btn--lg" href="#tickets" data-track="hero-tickets">Plan your visit</a>
<a class="btn btn--outline btn--lg" href="#halls">Explore the halls</a>
</div>
<dl class="hero__stats">
<div><dt>Specimens</dt><dd>2.4M</dd></div>
<div><dt>Galleries</dt><dd>11</dd></div>
<div><dt>Founded</dt><dd>1887</dd></div>
</dl>
</div>
<figure class="hero__art" aria-label="Mounted skeleton of a long-necked dinosaur">
<div class="specimen">
<svg viewBox="0 0 360 320" role="img" aria-hidden="true" preserveAspectRatio="xMidYMid meet">
<defs>
<linearGradient id="bone" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#f4eede"/>
<stop offset="1" stop-color="#d9cdab"/>
</linearGradient>
</defs>
<!-- ground -->
<line x1="20" y1="290" x2="340" y2="290" stroke="rgba(47,74,54,0.35)" stroke-width="2"/>
<!-- silhouette of sauropod -->
<path d="M40 280 C60 200 70 150 120 120 C150 100 150 70 165 50 C175 36 195 34 205 48
C214 60 206 74 196 80 C184 88 178 102 184 116 C210 128 250 140 280 170
C300 190 312 230 318 280 L300 280 C292 236 280 206 262 188
C236 162 198 158 176 150 C168 178 170 230 176 280 L158 280
C150 232 150 184 156 154 C140 158 124 174 116 200 C110 224 108 254 110 280 Z"
fill="url(#bone)" stroke="#9c8a5e" stroke-width="2" stroke-linejoin="round"/>
<!-- rib detail -->
<g stroke="#bfa977" stroke-width="2" fill="none" opacity="0.7">
<path d="M196 130 q-10 30 -6 56"/>
<path d="M214 136 q-8 32 -2 58"/>
<path d="M232 146 q-6 30 0 54"/>
</g>
<circle cx="200" cy="52" r="3" fill="#2f4a36"/>
</svg>
</div>
<figcaption>
<span class="tag tag--amber">Hall of Giants</span>
<strong>“Strathmore Sauropod”</strong>
<span>Cast · Late Jurassic, ~152 Ma · Cat. NHM-PV-0014</span>
</figcaption>
</figure>
</div>
</section>
<!-- TICKER -->
<div class="ticker" aria-hidden="true">
<div class="ticker__track" id="tickerTrack">
<span>Trilobites</span><span>Meteorites</span><span>Mineral Vault</span>
<span>Whale Hall</span><span>Human Origins</span><span>Insectarium</span>
<span>Tide Pool Lab</span><span>Gem Gallery</span>
</div>
</div>
<!-- HALLS -->
<section class="section" id="halls" aria-labelledby="hallsHeading">
<div class="wrap">
<header class="section__head">
<span class="eyebrow">Permanent collection</span>
<h2 id="hallsHeading">Eleven halls of the living world</h2>
<p>From the age of dinosaurs to the chemistry of the Earth, each gallery is a doorway
into deep time. Filter by what you'd like to discover.</p>
</header>
<div class="filters" role="tablist" aria-label="Filter halls">
<button class="chip is-active" role="tab" aria-selected="true" data-filter="all">All halls</button>
<button class="chip" role="tab" aria-selected="false" data-filter="dino">Dinosaurs</button>
<button class="chip" role="tab" aria-selected="false" data-filter="earth">Minerals</button>
<button class="chip" role="tab" aria-selected="false" data-filter="ocean">Ocean</button>
<button class="chip" role="tab" aria-selected="false" data-filter="human">Human Origins</button>
</div>
<div class="halls" id="hallsGrid">
<article class="hall" data-cat="dino" tabindex="0">
<div class="hall__art" style="--g1:#3a5a40;--g2:#1e3326;">
<svg viewBox="0 0 80 60" aria-hidden="true"><path d="M8 50 C18 30 24 20 38 16 C40 10 46 8 50 12 C54 16 50 20 46 22 C58 26 70 34 74 50 L66 50 C62 38 54 32 46 30 C42 40 44 50 44 50 L36 50 C34 40 36 30 36 28 C28 30 22 38 20 50 Z" fill="rgba(244,238,222,0.92)"/></svg>
</div>
<div class="hall__body">
<span class="tag tag--green">Level 2</span>
<h3>Hall of Giants</h3>
<p>Towering sauropods, a hunting <em>Allosaurus</em>, and a fossil dig you can touch.</p>
<span class="hall__meta">38 mounted specimens</span>
</div>
</article>
<article class="hall" data-cat="earth" tabindex="0">
<div class="hall__art" style="--g1:#c8842a;--g2:#8a5512;">
<svg viewBox="0 0 80 60" aria-hidden="true"><path d="M40 8 L58 24 L50 52 L30 52 L22 24 Z" fill="rgba(244,238,222,0.92)"/><path d="M40 8 L40 52 M22 24 L58 24" stroke="rgba(47,74,54,0.4)" stroke-width="1.5"/></svg>
</div>
<div class="hall__body">
<span class="tag tag--amber">Level 1</span>
<h3>The Mineral Vault</h3>
<p>Glowing fluorescent minerals, a 4.6-billion-year meteorite, and rare crystal forms.</p>
<span class="hall__meta">1,200 minerals · gem theatre</span>
</div>
</article>
<article class="hall" data-cat="ocean" tabindex="0">
<div class="hall__art" style="--g1:#2f5d6b;--g2:#163844;">
<svg viewBox="0 0 80 60" aria-hidden="true"><path d="M6 40 Q20 28 34 36 Q44 24 56 34 Q66 26 74 38 C70 46 60 50 40 50 C20 50 10 46 6 40 Z" fill="rgba(244,238,222,0.92)"/><circle cx="60" cy="20" r="3" fill="rgba(244,238,222,0.7)"/></svg>
</div>
<div class="hall__body">
<span class="tag tag--ocean">Level 1</span>
<h3>Ocean & the Whale</h3>
<p>A 24-metre blue whale model hangs above coral dioramas and a live tide-pool lab.</p>
<span class="hall__meta">Live touch tank daily</span>
</div>
</article>
<article class="hall" data-cat="human" tabindex="0">
<div class="hall__art" style="--g1:#7a6a4e;--g2:#473c28;">
<svg viewBox="0 0 80 60" aria-hidden="true"><circle cx="40" cy="24" r="14" fill="rgba(244,238,222,0.92)"/><path d="M26 30 Q40 50 54 30" fill="none" stroke="rgba(47,74,54,0.4)" stroke-width="2"/><circle cx="34" cy="22" r="2.4" fill="#2f4a36"/><circle cx="46" cy="22" r="2.4" fill="#2f4a36"/></svg>
</div>
<div class="hall__body">
<span class="tag tag--earth">Level 3</span>
<h3>Human Origins</h3>
<p>Seven million years of our family tree, from <em>Sahelanthropus</em> to the first cities.</p>
<span class="hall__meta">Cast crania · DNA wall</span>
</div>
</article>
<article class="hall" data-cat="dino" tabindex="0">
<div class="hall__art" style="--g1:#4a5a32;--g2:#28331a;">
<svg viewBox="0 0 80 60" aria-hidden="true"><ellipse cx="40" cy="34" rx="26" ry="16" fill="rgba(244,238,222,0.92)"/><path d="M14 34 q26 -22 52 0 M14 34 q26 22 52 0" fill="none" stroke="rgba(47,74,54,0.35)" stroke-width="1.5"/></svg>
</div>
<div class="hall__body">
<span class="tag tag--green">Level 2</span>
<h3>Before the Dinosaurs</h3>
<p>Trilobites, sea scorpions and the strange life of the Cambrian explosion.</p>
<span class="hall__meta">Burgess Shale casts</span>
</div>
</article>
<article class="hall" data-cat="earth" tabindex="0">
<div class="hall__art" style="--g1:#b85c2c;--g2:#7a3a16;">
<svg viewBox="0 0 80 60" aria-hidden="true"><circle cx="40" cy="30" r="18" fill="rgba(244,238,222,0.92)"/><path d="M40 12 v36 M22 30 h36 M27 17 l26 26 M53 17 l-26 26" stroke="rgba(47,74,54,0.35)" stroke-width="1.4"/></svg>
</div>
<div class="hall__body">
<span class="tag tag--amber">Level 1</span>
<h3>Restless Earth</h3>
<p>Stand on a quake simulator, watch a volcano section, and read the planet's heat.</p>
<span class="hall__meta">Seismograph live feed</span>
</div>
</article>
</div>
<p class="halls__empty" id="hallsEmpty" hidden>No halls in this category — try “All halls.”</p>
</div>
</section>
<!-- HANDS-ON -->
<section class="section section--alt" id="handson" aria-labelledby="handsHeading">
<div class="wrap hands">
<div class="hands__copy">
<span class="eyebrow">Get your hands dirty</span>
<h2 id="handsHeading">Hands-on exhibits</h2>
<p>Discovery is a contact sport here. Brush real sediment for fossils, sort minerals by
hardness, and meet a keeper at the touch table.</p>
<ul class="hands__list">
<li>
<span class="hands__icon" aria-hidden="true">⛏</span>
<div><strong>The Dig Pit</strong><p>Excavate cast bones with brush and trowel. Ages 4+.</p></div>
<span class="tag tag--green">Open</span>
</li>
<li>
<span class="hands__icon" aria-hidden="true">🔬</span>
<div><strong>Microscope Bench</strong><p>Pollen, sand grains and feathers at 400×.</p></div>
<span class="tag tag--green">Open</span>
</li>
<li>
<span class="hands__icon" aria-hidden="true">🐚</span>
<div><strong>Tide-Pool Lab</strong><p>Touch sea stars and urchins with a marine guide.</p></div>
<span class="tag tag--amber">Sessions</span>
</li>
<li>
<span class="hands__icon" aria-hidden="true">🪨</span>
<div><strong>Mineral Sorting Table</strong><p>Streak, scratch and weigh real specimens.</p></div>
<span class="tag tag--green">Open</span>
</li>
</ul>
</div>
<aside class="planner" aria-label="Build your visit">
<h3>Build your visit</h3>
<p class="planner__hint">Add halls and we'll estimate your time inside.</p>
<ul class="planner__list" id="plannerList">
<li class="planner__empty" id="plannerEmpty">Your route is empty. Add a hall below.</li>
</ul>
<div class="planner__add">
<label for="plannerSelect" class="sr-only">Add a hall</label>
<select id="plannerSelect">
<option value="" disabled selected>Add a hall…</option>
<option data-min="45" value="Hall of Giants">Hall of Giants (45 min)</option>
<option data-min="30" value="The Mineral Vault">The Mineral Vault (30 min)</option>
<option data-min="40" value="Ocean & the Whale">Ocean & the Whale (40 min)</option>
<option data-min="35" value="Human Origins">Human Origins (35 min)</option>
<option data-min="25" value="Restless Earth">Restless Earth (25 min)</option>
</select>
<button class="btn btn--solid" id="plannerAdd" type="button">Add</button>
</div>
<div class="planner__total">
<span>Estimated time</span>
<strong id="plannerTotal">0 min</strong>
</div>
</aside>
</div>
</section>
<!-- PROGRAMS -->
<section class="section" id="programs" aria-labelledby="progHeading">
<div class="wrap">
<header class="section__head">
<span class="eyebrow">For families & schools</span>
<h2 id="progHeading">Programs & events</h2>
<p>Sleep beside a sauropod, follow a fossil-hunter for a morning, or join a weekend
family trail. Members book first.</p>
</header>
<div class="programs">
<article class="program">
<div class="program__date"><span>JUN</span><strong>21</strong></div>
<div class="program__body">
<span class="tag tag--amber">Family</span>
<h3>Dinosaur Sleepover</h3>
<p>Torch-lit night trail, fossil stories and breakfast under the whale. Ages 6–11.</p>
<p class="program__meta">18:30 – 09:00 · £48 per child + guardian</p>
</div>
<button class="btn btn--outline" data-prog="Dinosaur Sleepover">Reserve</button>
</article>
<article class="program">
<div class="program__date"><span>JUN</span><strong>24</strong></div>
<div class="program__body">
<span class="tag tag--green">Workshop</span>
<h3>Junior Palaeontologist</h3>
<p>Cast a real ammonite, learn field tools, and tour the prep lab with a curator.</p>
<p class="program__meta">10:00 – 12:30 · £22 · Ages 8–14</p>
</div>
<button class="btn btn--outline" data-prog="Junior Palaeontologist">Reserve</button>
</article>
<article class="program">
<div class="program__date"><span>JUL</span><strong>02</strong></div>
<div class="program__body">
<span class="tag tag--earth">Talk</span>
<h3>Meteorites & the Early Solar System</h3>
<p>Dr. Imani Osei on what space rocks tell us about the Earth's first 100 million years.</p>
<p class="program__meta">19:00 · Free for members · £9 general</p>
</div>
<button class="btn btn--outline" data-prog="Meteorites Talk">Reserve</button>
</article>
</div>
</div>
</section>
<!-- TICKETS / VISIT -->
<section class="section section--cta" id="tickets" aria-labelledby="ticketsHeading">
<div class="wrap cta">
<div class="cta__copy" id="visit">
<span class="eyebrow">Plan your visit</span>
<h2 id="ticketsHeading">General admission is free. Special exhibitions ticketed.</h2>
<p>Find us at 14 Strand Quay, Strathmore. Step-free throughout, with sensory-quiet
mornings on the first Sunday of every month.</p>
<ul class="cta__facts">
<li><strong>Open today</strong><span>10:00 – 17:30</span></li>
<li><strong>Getting here</strong><span>Quayside station · 4 min walk</span></li>
<li><strong>Members</strong><span>Unlimited entry · priority events</span></li>
</ul>
</div>
<form class="ticketcard" id="ticketForm" novalidate>
<h3>Reserve a time slot</h3>
<div class="field">
<label for="tDate">Date</label>
<input type="date" id="tDate" name="date" required />
</div>
<div class="field">
<label for="tSlot">Entry slot</label>
<select id="tSlot" name="slot" required>
<option value="" disabled selected>Choose a time…</option>
<option>10:00 – 11:00</option>
<option>11:30 – 12:30</option>
<option>13:00 – 14:00</option>
<option>15:00 – 16:00</option>
</select>
</div>
<div class="field field--inline">
<div>
<label for="tAdult">Adults</label>
<input type="number" id="tAdult" name="adults" min="0" max="10" value="2" />
</div>
<div>
<label for="tChild">Children</label>
<input type="number" id="tChild" name="children" min="0" max="10" value="0" />
</div>
</div>
<p class="ticketcard__note">General admission free · add Gallery of Deep Time for £14/adult.</p>
<button class="btn btn--solid btn--lg btn--block" type="submit">Reserve tickets</button>
</form>
</div>
</section>
</main>
<footer class="footer">
<div class="wrap footer__inner">
<div class="footer__brand">
<strong>Strathmore Natural History Museum</strong>
<p>14 Strand Quay · Strathmore · Open daily</p>
</div>
<nav class="footer__nav" aria-label="Footer">
<a href="#halls">Halls</a>
<a href="#handson">Hands-On</a>
<a href="#programs">Programs</a>
<a href="#tickets">Tickets</a>
</nav>
<form class="footer__news" id="newsForm" novalidate>
<label for="newsEmail">What's new at the museum</label>
<div class="footer__newsrow">
<input type="email" id="newsEmail" placeholder="[email protected]" required />
<button class="btn btn--solid" type="submit">Join</button>
</div>
</form>
</div>
<p class="footer__legal">Demo experience · fictional institution & specimens.</p>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Natural History Museum Landing
A complete marketing landing page for the fictional Strathmore Natural History Museum. It opens on a calm, wall-space hero pairing a long-necked sauropod cast rendered in inline SVG with admission stats, then runs an animated marquee of collection highlights. Earthy bone, forest-green and amber tones plus slab-serif headings give the page a museum-of-nature character, while generous whitespace and thin specimen mats keep it curatorial rather than cluttered.
The page is genuinely interactive. The collection halls grid filters live by category (dinosaurs, minerals, ocean, human origins) with an empty-state fallback, and a sticky visit planner lets you assemble a route from a dropdown, removing duplicates and tallying an estimated time inside. Family programs can be reserved inline — buttons flip to a confirmed state — and a ticket reservation card validates date, slot and visitor counts before confirming. A small toast() helper surfaces every action.
Everything is vanilla and self-contained: a sticky header with a collapsible “today’s hours” panel, an accessible mobile menu, focus-visible states, ARIA on tabs and live regions, reduced-motion handling, and a layout that reflows cleanly down to ~360px.
Illustrative UI only — demo data; not a real museum system.