Coworking — Spaces / Tour
A warm industrial spaces-and-tour page for a fictional warehouse coworking studio. Four space-type cards — hot desk, dedicated desk, private office and meeting room — carry live availability badges, pricing, photo galleries and amenity chips. A type filter narrows the grid, clicking a photo opens a keyboard-navigable lightbox, hovering amenities floats a tooltip, and a virtual-tour teaser plays through narrated stops. A book CTA wires every button to a validated tour-request form, all on concrete, amber and plant-green tones.
MCP
Code
:root {
--concrete: #efeae3;
--concrete-d: #e2dcd2;
--amber: #e8902b;
--amber-d: #cc7918;
--amber-50: #fdf1e2;
--char: #1c1b19;
--ink: #26241f;
--ink-2: #4a463e;
--muted: #7b766c;
--bg: #f6f3ee;
--surface: #ffffff;
--plant: #5f7a52;
--line: rgba(28, 27, 25, 0.1);
--line-2: rgba(28, 27, 25, 0.18);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--occupied: #d4503e;
--free: #2f9e6f;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(28, 27, 25, 0.06), 0 6px 18px rgba(28, 27, 25, 0.06);
--sh-2: 0 10px 34px rgba(28, 27, 25, 0.14);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
h1, h2, h3, h4 { margin: 0; line-height: 1.12; letter-spacing: -0.02em; color: var(--char); }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
.wrap { width: min(1140px, 92vw); margin-inline: auto; }
/* ---------- reveal ---------- */
[data-reveal] { opacity: 0; transform: translateY(16px); transition: opacity 0.6s ease, transform 0.6s ease; }
[data-reveal].is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
[data-reveal] { opacity: 1; transform: none; transition: none; }
}
/* ---------- buttons ---------- */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
font: inherit; font-weight: 600; font-size: 0.95rem;
padding: 0.7em 1.25em; border-radius: 999px; border: 1px solid transparent;
cursor: pointer; transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 3px solid rgba(232, 144, 43, 0.5); outline-offset: 2px; }
.btn--amber { background: var(--amber); color: #fff; box-shadow: 0 6px 16px rgba(232, 144, 43, 0.32); }
.btn--amber:hover { background: var(--amber-d); transform: translateY(-2px); }
.btn--dark { background: var(--char); color: var(--concrete); }
.btn--dark:hover { background: #000; transform: translateY(-2px); }
.btn--ghost { background: transparent; color: var(--char); border-color: var(--line-2); }
.btn--ghost:hover { background: var(--surface); border-color: var(--char); }
.btn--block { width: 100%; }
/* ---------- header ---------- */
.site {
position: sticky; top: 0; z-index: 30;
background: rgba(246, 243, 238, 0.82);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--line);
}
.site__in { display: flex; align-items: center; gap: 1.5rem; padding: 0.9rem 0; }
.brand { display: inline-flex; align-items: center; gap: 0.55rem; font-weight: 800; font-size: 1.1rem; }
.brand__mark {
width: 30px; height: 30px; display: grid; place-items: center;
background: var(--char); color: var(--amber); border-radius: 9px; font-size: 1rem;
}
.brand__name em { font-style: normal; color: var(--amber-d); }
.nav { display: flex; gap: 1.3rem; margin-left: auto; font-weight: 500; color: var(--ink-2); }
.nav a { position: relative; padding: 0.2em 0; }
.nav a:hover { color: var(--char); }
.nav a::after {
content: ""; position: absolute; left: 0; right: 100%; bottom: -2px; height: 2px;
background: var(--amber); transition: right 0.25s ease;
}
.nav a:hover::after { right: 0; }
.site .btn { padding: 0.55em 1.1em; font-size: 0.9rem; }
/* ---------- hero ---------- */
.hero { padding: clamp(2.5rem, 6vw, 5rem) 0 clamp(2rem, 5vw, 4rem); }
.hero__in { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(1.5rem, 4vw, 3.5rem); align-items: center; }
.eyebrow {
display: inline-flex; align-items: center; gap: 0.5rem;
font-size: 0.8rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
color: var(--amber-d); background: var(--amber-50); padding: 0.4em 0.85em; border-radius: 999px;
border: 1px solid rgba(232, 144, 43, 0.25);
}
.eyebrow .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 3px rgba(47, 158, 111, 0.2); }
.hero h1 { font-size: clamp(2rem, 5vw, 3.3rem); font-weight: 800; margin: 1rem 0 0.9rem; }
.lede { font-size: clamp(1rem, 1.6vw, 1.18rem); color: var(--ink-2); max-width: 34ch; }
.hero__cta { display: flex; gap: 0.75rem; margin: 1.5rem 0 1.8rem; flex-wrap: wrap; }
.hero__stats { list-style: none; display: flex; gap: 2rem; padding: 0; margin: 0; border-top: 1px solid var(--line); padding-top: 1.2rem; }
.hero__stats strong { display: block; font-size: 1.5rem; font-weight: 800; color: var(--char); }
.hero__stats span { font-size: 0.82rem; color: var(--muted); }
.hero__art { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 0.8rem; height: clamp(320px, 38vw, 440px); }
.photo {
position: relative; border-radius: var(--r-md); overflow: hidden;
border: 1px solid var(--line); box-shadow: var(--sh-1);
}
.photo__tag {
position: absolute; left: 10px; bottom: 10px; z-index: 2;
font-size: 0.72rem; font-weight: 600; color: #fff;
background: rgba(28, 27, 25, 0.55); backdrop-filter: blur(4px);
padding: 0.25em 0.65em; border-radius: 999px;
}
.photo--hero.ph-1 { grid-row: span 2; }
.ph-1 { background: linear-gradient(150deg, #d98a2b, #cc7918 55%, #8a5314); }
.ph-2 { background: linear-gradient(150deg, #6f8a60, var(--plant) 60%, #3f5436); }
.ph-3 { background: linear-gradient(150deg, #3a3835, #1c1b19 70%, #100f0e); }
/* ---------- sections ---------- */
.section { padding: clamp(2.5rem, 6vw, 5rem) 0; }
.section__head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 2rem; }
.kicker { font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--amber-d); }
.section__head h2 { font-size: clamp(1.6rem, 3.4vw, 2.4rem); font-weight: 800; margin-top: 0.4rem; }
/* ---------- filters ---------- */
.filters { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.chip {
font: inherit; font-weight: 600; font-size: 0.88rem;
padding: 0.5em 1em; border-radius: 999px; cursor: pointer;
background: var(--surface); color: var(--ink-2); border: 1px solid var(--line-2);
transition: all 0.15s ease;
}
.chip:hover { border-color: var(--char); color: var(--char); }
.chip:focus-visible { outline: 3px solid rgba(232, 144, 43, 0.5); outline-offset: 2px; }
.chip.is-active { background: var(--char); color: var(--concrete); border-color: var(--char); }
/* ---------- cards ---------- */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 1.4rem; }
.card {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
overflow: hidden; display: flex; flex-direction: column; box-shadow: var(--sh-1);
transition: transform 0.45s cubic-bezier(.2,.7,.3,1), box-shadow 0.3s ease, opacity 0.4s ease;
}
.card:hover { transform: translateY(-6px); box-shadow: var(--sh-2); }
.card.is-hidden { display: none; }
.card__media { position: relative; aspect-ratio: 16 / 10; }
.card__photo { position: absolute; inset: 0; }
.card__badge {
position: absolute; top: 10px; left: 10px; z-index: 3;
display: inline-flex; align-items: center; gap: 0.4rem;
font-size: 0.74rem; font-weight: 600; color: var(--char);
background: rgba(255, 255, 255, 0.92); padding: 0.32em 0.7em; border-radius: 999px;
box-shadow: var(--sh-1);
}
.status-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.status-dot.free { background: var(--free); }
.status-dot.occupied { background: var(--occupied); }
.status-dot.reserved { background: var(--warn); }
.card__zoom {
position: absolute; bottom: 10px; right: 10px; z-index: 3;
width: 34px; height: 34px; border-radius: 50%; cursor: pointer;
border: none; background: rgba(28, 27, 25, 0.6); color: #fff; font-size: 1rem;
backdrop-filter: blur(4px); transition: background 0.15s ease, transform 0.15s ease;
}
.card__zoom:hover { background: var(--amber); transform: scale(1.08); }
.card__zoom:focus-visible { outline: 3px solid rgba(255,255,255,0.8); outline-offset: 2px; }
.card__body { padding: 1.1rem 1.15rem 1.2rem; display: flex; flex-direction: column; flex: 1; }
.card__row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.8rem; }
.card__row h3 { font-size: 1.25rem; font-weight: 700; }
.price { font-size: 1.15rem; font-weight: 800; color: var(--amber-d); white-space: nowrap; }
.price small { font-size: 0.72rem; font-weight: 600; color: var(--muted); }
.card__desc { font-size: 0.9rem; color: var(--ink-2); margin: 0.55rem 0 0.9rem; }
.amen { list-style: none; padding: 0; margin: 0 0 1.1rem; display: flex; flex-wrap: wrap; gap: 0.45rem; }
.amen li {
font-size: 0.78rem; font-weight: 600; color: var(--ink-2);
background: var(--concrete); border: 1px solid var(--line); border-radius: 999px;
padding: 0.34em 0.75em; cursor: help; position: relative;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.amen li:hover, .amen li:focus-visible { background: var(--amber-50); color: var(--amber-d); border-color: rgba(232,144,43,0.4); outline: none; }
.card__body .btn { margin-top: auto; }
/* gallery photo gradients */
.g-hot-1 { background: linear-gradient(155deg, #e2a25a, #cc7918 60%, #9a5a14); }
.g-hot-2 { background: linear-gradient(155deg, #efb873, #d98a2b 70%, #8a5314); }
.g-hot-3 { background: linear-gradient(155deg, #4a463e, #26241f 70%, #141310); }
.g-ded-1 { background: linear-gradient(155deg, #7d9670, var(--plant) 65%, #3f5436); }
.g-ded-2 { background: linear-gradient(155deg, #57534b, #2f2c27 70%, #1a1815); }
.g-ded-3 { background: linear-gradient(155deg, #8aa37c, #5f7a52 60%, #41553a); }
.g-off-1 { background: linear-gradient(155deg, #3a3835, #1c1b19 65%, #0e0d0c); }
.g-off-2 { background: linear-gradient(155deg, #d98a2b, #cc7918 60%, #7a4a12); }
.g-off-3 { background: linear-gradient(155deg, #6b6760, #3a3733 70%, #211f1c); }
.g-met-1 { background: linear-gradient(155deg, #e2a25a, #b06d1c 65%, #6e430f); }
.g-met-2 { background: linear-gradient(155deg, #4f4b43, #26241f 70%, #100f0e); }
.g-met-3 { background: linear-gradient(155deg, #8aa37c, #5f7a52 55%, #41553a); }
.ph-tour { background: linear-gradient(135deg, #d98a2b, #1c1b19 80%); }
.empty { text-align: center; color: var(--muted); padding: 2rem 0; font-weight: 500; }
/* ---------- tour ---------- */
.section--tour { background: var(--char); color: var(--concrete); }
.section--tour .kicker { color: var(--amber); }
.section--tour h2 { color: #fff; }
.tour { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
.tour__media { position: relative; }
.photo--tour { aspect-ratio: 16 / 10; }
.tour__play {
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
display: inline-flex; align-items: center; gap: 0.7rem; cursor: pointer;
background: rgba(255, 255, 255, 0.95); color: var(--char); border: none;
padding: 0.7rem 1.2rem 0.7rem 0.7rem; border-radius: 999px; font: inherit; font-weight: 700;
box-shadow: var(--sh-2); transition: transform 0.2s ease, background 0.2s ease;
}
.tour__play:hover { transform: translate(-50%, -50%) scale(1.05); }
.tour__play:focus-visible { outline: 3px solid var(--amber); outline-offset: 3px; }
.tour__tri { width: 38px; height: 38px; border-radius: 50%; background: var(--amber); color: #fff; display: grid; place-items: center; font-size: 0.95rem; }
.tour__play.is-playing .tour__tri { background: var(--danger); }
.tour h2 { font-size: clamp(1.5rem, 3vw, 2.2rem); margin: 0.5rem 0 0.9rem; }
.tour__copy p { color: rgba(239, 234, 227, 0.78); max-width: 38ch; }
.tour__chips { list-style: none; padding: 0; margin: 1.4rem 0 0; display: grid; gap: 0.5rem; }
.tour__chips li {
font-size: 0.85rem; font-weight: 500; color: var(--concrete);
padding: 0.5em 0.85em; border: 1px solid rgba(239, 234, 227, 0.18); border-radius: var(--r-sm);
background: rgba(255, 255, 255, 0.04);
}
/* ---------- amenities grid ---------- */
.amgrid { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
.amgrid li {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
padding: 1.3rem; box-shadow: var(--sh-1); transition: transform 0.3s ease, border-color 0.2s ease;
}
.amgrid li:hover { transform: translateY(-4px); border-color: rgba(232,144,43,0.4); }
.am__ic { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 12px; background: var(--amber-50); color: var(--amber-d); font-size: 1.3rem; margin-bottom: 0.8rem; }
.amgrid h4 { font-size: 1.05rem; font-weight: 700; margin-bottom: 0.3rem; }
.amgrid p { font-size: 0.88rem; color: var(--ink-2); }
/* ---------- CTA ---------- */
.section--cta { background: var(--amber-50); border-block: 1px solid rgba(232,144,43,0.2); }
.cta { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
.cta__copy h2 { font-size: clamp(1.6rem, 3.4vw, 2.4rem); font-weight: 800; }
.cta__copy p { color: var(--ink-2); margin-top: 0.7rem; max-width: 36ch; }
.cta__form { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 1.5rem; box-shadow: var(--sh-1); display: grid; gap: 1rem; }
.field { display: grid; gap: 0.35rem; }
.field span { font-size: 0.82rem; font-weight: 600; color: var(--ink-2); }
.field input, .field select {
font: inherit; padding: 0.7em 0.85em; border-radius: var(--r-sm);
border: 1px solid var(--line-2); background: var(--bg); color: var(--ink);
}
.field input:focus, .field select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px rgba(232,144,43,0.18); background: #fff; }
.field input.invalid { border-color: var(--danger); box-shadow: 0 0 0 3px rgba(212,80,62,0.18); }
/* ---------- footer ---------- */
.foot { border-top: 1px solid var(--line); padding: 1.5rem 0; }
.foot__in { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; font-size: 0.85rem; color: var(--ink-2); }
.foot__muted { color: var(--muted); }
/* ---------- lightbox ---------- */
.lightbox {
position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center;
gap: 0.5rem; padding: 1rem; background: rgba(20, 19, 17, 0.82); backdrop-filter: blur(6px);
animation: fade 0.2s ease;
}
.lightbox[hidden] { display: none; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.lb__fig { margin: 0; width: min(720px, 86vw); }
.lb__photo { position: relative; aspect-ratio: 16 / 10; border-radius: var(--r-md); overflow: hidden; box-shadow: var(--sh-2); }
.lb__cap { text-align: center; color: var(--concrete); font-size: 0.85rem; font-weight: 600; margin-top: 0.8rem; }
.lb__close { position: absolute; top: 1rem; right: 1rem; }
.lb__close, .lb__nav {
width: 44px; height: 44px; border-radius: 50%; cursor: pointer; border: none;
background: rgba(255,255,255,0.14); color: #fff; font-size: 1.3rem; flex: none;
transition: background 0.15s ease, transform 0.15s ease;
}
.lb__close:hover, .lb__nav:hover { background: var(--amber); transform: scale(1.06); }
.lb__close:focus-visible, .lb__nav:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
/* ---------- toast ---------- */
.toast {
position: fixed; left: 50%; bottom: 1.5rem; transform: translate(-50%, 140%);
z-index: 120; background: var(--char); color: var(--concrete);
padding: 0.85em 1.3em; border-radius: 999px; font-weight: 600; font-size: 0.9rem;
box-shadow: var(--sh-2); transition: transform 0.4s cubic-bezier(.2,.8,.2,1); max-width: 90vw;
}
.toast.show { transform: translate(-50%, 0); }
.toast::before { content: "✓"; color: var(--amber); margin-right: 0.5em; font-weight: 800; }
/* ---------- amenity tooltip ---------- */
.tip {
position: fixed; z-index: 130; pointer-events: none;
background: var(--char); color: var(--concrete); font-size: 0.8rem; font-weight: 500;
padding: 0.55em 0.8em; border-radius: var(--r-sm); max-width: 220px; box-shadow: var(--sh-2);
transform: translate(-50%, -8px); opacity: 0; transition: opacity 0.15s ease, transform 0.15s ease;
}
.tip.show { opacity: 1; transform: translate(-50%, -12px); }
.tip::after {
content: ""; position: absolute; left: 50%; bottom: -5px; transform: translateX(-50%);
border: 5px solid transparent; border-top-color: var(--char); border-bottom: 0;
}
/* ---------- responsive ---------- */
@media (max-width: 900px) {
.hero__in, .tour, .cta { grid-template-columns: 1fr; }
.hero__art { height: 300px; }
.tour__media { order: -1; }
}
@media (max-width: 520px) {
.nav { display: none; }
.site__in { gap: 0.8rem; }
.hero__stats { gap: 1.3rem; }
.hero__stats strong { font-size: 1.25rem; }
.section__head { margin-bottom: 1.4rem; }
.grid { grid-template-columns: 1fr; }
.filters { width: 100%; overflow-x: auto; padding-bottom: 4px; flex-wrap: nowrap; }
.chip { flex: none; }
.foot__in { flex-direction: column; gap: 0.3rem; }
.lb__nav { position: absolute; top: 50%; }
.lb__prev { left: 0.6rem; }
.lb__next { right: 0.6rem; }
}(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2600);
}
/* ---------- reveal on scroll ---------- */
var reveals = Array.prototype.slice.call(document.querySelectorAll("[data-reveal]"));
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { e.target.classList.add("is-in"); io.unobserve(e.target); }
});
}, { threshold: 0.12, rootMargin: "0px 0px -40px 0px" });
reveals.forEach(function (el) { io.observe(el); });
} else {
reveals.forEach(function (el) { el.classList.add("is-in"); });
}
/* ---------- space filter ---------- */
var chips = Array.prototype.slice.call(document.querySelectorAll(".chip"));
var cards = Array.prototype.slice.call(document.querySelectorAll(".card"));
var emptyMsg = document.getElementById("emptyMsg");
function applyFilter(type) {
var shown = 0;
cards.forEach(function (card) {
var match = type === "all" || card.getAttribute("data-type") === type;
card.classList.toggle("is-hidden", !match);
if (match) shown++;
});
if (emptyMsg) emptyMsg.hidden = shown !== 0;
}
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
applyFilter(chip.getAttribute("data-filter"));
});
});
/* ---------- gallery lightbox ---------- */
var lb = document.getElementById("lightbox");
var lbPhoto = document.getElementById("lbPhoto");
var lbTag = document.getElementById("lbTag");
var lbCount = document.getElementById("lbCount");
var lbClose = document.getElementById("lbClose");
var lbPrev = document.getElementById("lbPrev");
var lbNext = document.getElementById("lbNext");
var gallery = [];
var gIndex = 0;
var lastFocus = null;
function renderSlide() {
var item = gallery[gIndex];
if (!item) return;
lbPhoto.className = "lb__photo " + item.cls;
lbTag.textContent = item.tag;
lbCount.textContent = (gIndex + 1) + " / " + gallery.length;
}
function openGallery(data, trigger) {
if (!data || !data.length) return;
gallery = data;
gIndex = 0;
lastFocus = trigger || document.activeElement;
renderSlide();
lb.hidden = false;
lbClose.focus();
document.body.style.overflow = "hidden";
}
function closeGallery() {
lb.hidden = true;
document.body.style.overflow = "";
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
function step(dir) {
gIndex = (gIndex + dir + gallery.length) % gallery.length;
renderSlide();
}
document.querySelectorAll(".gal").forEach(function (galEl) {
var trigger = galEl.querySelector(".card__zoom");
var open = function () {
var data;
try { data = JSON.parse(galEl.getAttribute("data-gallery") || "[]"); }
catch (err) { data = []; }
openGallery(data, trigger);
};
if (trigger) trigger.addEventListener("click", open);
galEl.addEventListener("click", function (e) {
if (e.target.closest(".card__zoom")) return;
if (e.target.closest(".card__photo")) open();
});
});
if (lbClose) lbClose.addEventListener("click", closeGallery);
if (lbPrev) lbPrev.addEventListener("click", function () { step(-1); });
if (lbNext) lbNext.addEventListener("click", function () { step(1); });
if (lb) lb.addEventListener("click", function (e) { if (e.target === lb) closeGallery(); });
document.addEventListener("keydown", function (e) {
if (lb.hidden) return;
if (e.key === "Escape") closeGallery();
else if (e.key === "ArrowRight") step(1);
else if (e.key === "ArrowLeft") step(-1);
});
/* ---------- amenity tooltips ---------- */
var tip = document.getElementById("tip");
var tipItems = Array.prototype.slice.call(document.querySelectorAll(".amen li[data-tip]"));
function showTip(el) {
var msg = el.getAttribute("data-tip");
if (!msg) return;
tip.textContent = msg;
tip.hidden = false;
var r = el.getBoundingClientRect();
tip.style.left = (r.left + r.width / 2) + "px";
tip.style.top = (r.top) + "px";
requestAnimationFrame(function () { tip.classList.add("show"); });
}
function hideTip() {
tip.classList.remove("show");
setTimeout(function () { if (!tip.classList.contains("show")) tip.hidden = true; }, 160);
}
tipItems.forEach(function (el) {
el.setAttribute("tabindex", "0");
el.addEventListener("mouseenter", function () { showTip(el); });
el.addEventListener("mouseleave", hideTip);
el.addEventListener("focus", function () { showTip(el); });
el.addEventListener("blur", hideTip);
});
/* ---------- virtual tour teaser ---------- */
var tourPlay = document.getElementById("tourPlay");
if (tourPlay) {
var label = tourPlay.querySelector(".tour__playlabel");
var tri = tourPlay.querySelector(".tour__tri");
var playing = false;
var tick = 0;
var tourTimer;
var stops = ["Main floor", "Studios", "Lounge & café", "Rooftop deck"];
tourPlay.addEventListener("click", function () {
playing = !playing;
tourPlay.classList.toggle("is-playing", playing);
if (playing) {
tri.textContent = "❚❚";
toast("Tour playing — fictional 360° loop");
tourTimer = setInterval(function () {
tick = (tick + 1) % stops.length;
label.textContent = "Now: " + stops[tick];
}, 1600);
} else {
tri.textContent = "▶";
clearInterval(tourTimer);
label.textContent = "Virtual tour · 2:14";
}
});
}
/* ---------- book form + CTAs ---------- */
var bookForm = document.getElementById("bookForm");
var bookName = document.getElementById("bookName");
var bookSpace = document.getElementById("bookSpace");
document.querySelectorAll("[data-book]").forEach(function (el) {
el.addEventListener("click", function () {
var space = el.getAttribute("data-book");
if (bookSpace) {
for (var i = 0; i < bookSpace.options.length; i++) {
if (bookSpace.options[i].value === space || bookSpace.options[i].text === space) {
bookSpace.selectedIndex = i; break;
}
}
}
setTimeout(function () { if (bookName) bookName.focus(); }, 450);
});
});
if (bookForm) {
bookForm.addEventListener("submit", function (e) {
e.preventDefault();
var name = (bookName.value || "").trim();
if (name.length < 2) {
bookName.classList.add("invalid");
bookName.focus();
toast("Add your name so we know who's coming");
return;
}
bookName.classList.remove("invalid");
var space = bookSpace ? bookSpace.value : "the studio";
toast("Thanks " + name.split(" ")[0] + " — tour request for " + space + " sent");
bookForm.reset();
});
bookName.addEventListener("input", function () { bookName.classList.remove("invalid"); });
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Loftworks Studio — Spaces & Tour</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=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="site" data-reveal>
<div class="wrap site__in">
<a class="brand" href="#top">
<span class="brand__mark" aria-hidden="true">◳</span>
<span class="brand__name">Loftworks <em>Studio</em></span>
</a>
<nav class="nav" aria-label="Primary">
<a href="#spaces">Spaces</a>
<a href="#tour">Tour</a>
<a href="#amenities">Amenities</a>
</nav>
<a class="btn btn--amber" href="#book" data-book="Loftworks Studio">Book a tour</a>
</div>
</header>
<main id="top">
<section class="hero" data-reveal>
<div class="wrap hero__in">
<div class="hero__copy">
<span class="eyebrow"><span class="dot"></span> Now leasing · Eastside Warehouse</span>
<h1>A warm warehouse for people who make things.</h1>
<p class="lede">Hot desks, dedicated stations, private offices and bookable studios — wrapped in concrete, amber light and a lot of plants. Tour the space, find your spot, claim it.</p>
<div class="hero__cta">
<a class="btn btn--amber" href="#book" data-book="Loftworks Studio">Book a tour</a>
<a class="btn btn--ghost" href="#tour">Take the virtual tour</a>
</div>
<ul class="hero__stats">
<li><strong>14k</strong><span>sq ft of floor</span></li>
<li><strong>92</strong><span>seats & rooms</span></li>
<li><strong>24/7</strong><span>member access</span></li>
</ul>
</div>
<div class="hero__art" aria-hidden="true">
<div class="photo photo--hero ph-1"><span class="photo__tag">Main floor</span></div>
<div class="photo photo--hero ph-2"><span class="photo__tag">Lounge</span></div>
<div class="photo photo--hero ph-3"><span class="photo__tag">Studio 4</span></div>
</div>
</div>
</section>
<section class="section" id="spaces">
<div class="wrap">
<div class="section__head" data-reveal>
<div>
<span class="kicker">Choose your space</span>
<h2>Four ways to work here</h2>
</div>
<div class="filters" role="group" aria-label="Filter spaces by type">
<button class="chip is-active" data-filter="all" aria-pressed="true">All</button>
<button class="chip" data-filter="hot" aria-pressed="false">Hot desk</button>
<button class="chip" data-filter="dedicated" aria-pressed="false">Dedicated</button>
<button class="chip" data-filter="office" aria-pressed="false">Private office</button>
<button class="chip" data-filter="meeting" aria-pressed="false">Meeting room</button>
</div>
</div>
<div class="grid" id="spaceGrid">
<!-- Hot desk -->
<article class="card" data-type="hot" data-reveal>
<div class="card__media gal" data-gallery='[
{"cls":"g-hot-1","tag":"Open bench"},
{"cls":"g-hot-2","tag":"Window row"},
{"cls":"g-hot-3","tag":"Quiet nook"}
]'>
<div class="card__photo g-hot-1"><span class="photo__tag">Open bench</span></div>
<span class="card__badge"><span class="status-dot free"></span> 8 open now</span>
<button class="card__zoom" type="button" aria-label="Open gallery for hot desk">⤢</button>
</div>
<div class="card__body">
<div class="card__row">
<h3>Hot desk</h3>
<span class="price">$19<small>/day</small></span>
</div>
<p class="card__desc">Grab any open seat on the main floor. First in, best light. Includes café credits and locker.</p>
<ul class="amen">
<li data-tip="Symmetric 1 Gb fibre, hardwired at every bench.">Gigabit wifi</li>
<li data-tip="Unlimited drip coffee + 2 barista drinks daily.">Café credits</li>
<li data-tip="Day lockers, first-come, with combo locks.">Day locker</li>
</ul>
<a class="btn btn--dark btn--block" href="#book" data-book="Hot desk">Book this space</a>
</div>
</article>
<!-- Dedicated -->
<article class="card" data-type="dedicated" data-reveal>
<div class="card__media gal" data-gallery='[
{"cls":"g-ded-1","tag":"Your desk"},
{"cls":"g-ded-2","tag":"Storage wall"},
{"cls":"g-ded-3","tag":"Plant corner"}
]'>
<div class="card__photo g-ded-1"><span class="photo__tag">Your desk</span></div>
<span class="card__badge"><span class="status-dot reserved"></span> 3 left</span>
<button class="card__zoom" type="button" aria-label="Open gallery for dedicated desk">⤢</button>
</div>
<div class="card__body">
<div class="card__row">
<h3>Dedicated desk</h3>
<span class="price">$289<small>/mo</small></span>
</div>
<p class="card__desc">A sit-stand desk that's always yours, with a lockable cabinet and your name on the wall.</p>
<ul class="amen">
<li data-tip="Electric sit-stand frame, 27" monitor on request.">Sit-stand desk</li>
<li data-tip="Lockable steel cabinet beside your desk.">Locked storage</li>
<li data-tip="Mail handling + a real street address.">Business address</li>
</ul>
<a class="btn btn--dark btn--block" href="#book" data-book="Dedicated desk">Book this space</a>
</div>
</article>
<!-- Private office -->
<article class="card" data-type="office" data-reveal>
<div class="card__media gal" data-gallery='[
{"cls":"g-off-1","tag":"Suite 2A"},
{"cls":"g-off-2","tag":"Glass front"},
{"cls":"g-off-3","tag":"Team table"}
]'>
<div class="card__photo g-off-1"><span class="photo__tag">Suite 2A</span></div>
<span class="card__badge"><span class="status-dot free"></span> 2 suites</span>
<button class="card__zoom" type="button" aria-label="Open gallery for private office">⤢</button>
</div>
<div class="card__body">
<div class="card__row">
<h3>Private office</h3>
<span class="price">$1,450<small>/mo</small></span>
</div>
<p class="card__desc">A glass-front suite for 4–8 people, behind a keyfob door. Furnish it, brand it, lock it.</p>
<ul class="amen">
<li data-tip="Frosted glass front with your logo decal.">Branded glass</li>
<li data-tip="24/7 keyfob entry for your whole team.">Keyfob door</li>
<li data-tip="Daily cleaning + restock of your suite.">Daily cleaning</li>
</ul>
<a class="btn btn--dark btn--block" href="#book" data-book="Private office">Book this space</a>
</div>
</article>
<!-- Meeting room -->
<article class="card" data-type="meeting" data-reveal>
<div class="card__media gal" data-gallery='[
{"cls":"g-met-1","tag":"The Foundry"},
{"cls":"g-met-2","tag":"Screen wall"},
{"cls":"g-met-3","tag":"Round table"}
]'>
<div class="card__photo g-met-1"><span class="photo__tag">The Foundry</span></div>
<span class="card__badge"><span class="status-dot occupied"></span> Busy till 2pm</span>
<button class="card__zoom" type="button" aria-label="Open gallery for meeting room">⤢</button>
</div>
<div class="card__body">
<div class="card__row">
<h3>Meeting room</h3>
<span class="price">$32<small>/hr</small></span>
</div>
<p class="card__desc">Book The Foundry or The Annex by the hour. 4K screen, whiteboard wall, real chairs.</p>
<ul class="amen">
<li data-tip="65" 4K display with wireless casting.">4K + casting</li>
<li data-tip="Full-wall whiteboard with markers stocked.">Whiteboard wall</li>
<li data-tip="Ceiling mics + speakerphone for hybrid calls.">Hybrid audio</li>
</ul>
<a class="btn btn--dark btn--block" href="#book" data-book="Meeting room">Book this space</a>
</div>
</article>
</div>
<p class="empty" id="emptyMsg" hidden>No spaces of that type right now — try another filter.</p>
</div>
</section>
<section class="section section--tour" id="tour">
<div class="wrap tour">
<div class="tour__media" data-reveal>
<div class="photo photo--tour ph-tour"><span class="photo__tag">Eastside floor · loop</span></div>
<button class="tour__play" id="tourPlay" type="button" aria-label="Play virtual tour teaser">
<span class="tour__tri" aria-hidden="true">▶</span>
<span class="tour__playlabel">Virtual tour · 2:14</span>
</button>
</div>
<div class="tour__copy" data-reveal>
<span class="kicker">Can't come by?</span>
<h2>Walk the whole studio from your couch.</h2>
<p>A 360° loop through the main floor, the lounge, both private wings and every meeting room — narrated by our community manager. No login, no email wall.</p>
<ol class="tour__chips">
<li>00:00 · Main floor</li>
<li>00:48 · Studios</li>
<li>01:32 · Lounge & café</li>
<li>01:59 · Rooftop deck</li>
</ol>
</div>
</div>
</section>
<section class="section" id="amenities">
<div class="wrap">
<div class="section__head" data-reveal>
<div>
<span class="kicker">In every membership</span>
<h2>Amenities, no asterisks</h2>
</div>
</div>
<ul class="amgrid">
<li data-reveal><span class="am__ic">▤</span><h4>Gigabit fibre</h4><p>Symmetric 1 Gb, hardwired & wifi 6 everywhere.</p></li>
<li data-reveal><span class="am__ic">☕</span><h4>Café & bar</h4><p>Drip coffee free, barista & tap on credits.</p></li>
<li data-reveal><span class="am__ic">⌧</span><h4>Phone booths</h4><p>Nine soundproofed booths, never a fight for one.</p></li>
<li data-reveal><span class="am__ic">▦</span><h4>Print & ship</h4><p>Colour laser, large format, mail handling.</p></li>
<li data-reveal><span class="am__ic">❀</span><h4>Plant-filled</h4><p>200+ plants, real daylight, operable windows.</p></li>
<li data-reveal><span class="am__ic">⚿</span><h4>24/7 access</h4><p>QR keyfob entry, on-site security overnight.</p></li>
</ul>
</div>
</section>
<section class="section section--cta" id="book" data-reveal>
<div class="wrap cta">
<div class="cta__copy">
<h2>Come see it in person.</h2>
<p>Pick a slot and we'll walk you through the space, no pressure. Tours run twice daily.</p>
</div>
<form class="cta__form" id="bookForm" novalidate>
<label class="field">
<span>Your name</span>
<input type="text" name="name" id="bookName" autocomplete="name" placeholder="Robin Vega" required />
</label>
<label class="field">
<span>Space of interest</span>
<select name="space" id="bookSpace">
<option>Hot desk</option>
<option>Dedicated desk</option>
<option>Private office</option>
<option>Meeting room</option>
<option>Loftworks Studio</option>
</select>
</label>
<button class="btn btn--amber btn--block" type="submit">Request tour</button>
</form>
</div>
</section>
</main>
<footer class="foot">
<div class="wrap foot__in">
<span>Loftworks Studio · 118 Foundry Lane, Eastside</span>
<span class="foot__muted">Illustrative UI — fictional space.</span>
</div>
</footer>
<!-- Lightbox -->
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Space gallery" hidden>
<button class="lb__close" id="lbClose" type="button" aria-label="Close gallery">✕</button>
<button class="lb__nav lb__prev" id="lbPrev" type="button" aria-label="Previous photo">‹</button>
<figure class="lb__fig">
<div class="lb__photo" id="lbPhoto"><span class="photo__tag" id="lbTag">Photo</span></div>
<figcaption class="lb__cap"><span id="lbCount">1 / 3</span></figcaption>
</figure>
<button class="lb__nav lb__next" id="lbNext" type="button" aria-label="Next photo">›</button>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<div class="tip" id="tip" role="tooltip" hidden></div>
<script src="script.js"></script>
</body>
</html>Spaces / Tour
A marketing spaces page for Loftworks Studio, a fictional Eastside warehouse coworking space. A sticky header and warm-concrete hero set the tone with gradient industrial-photo placeholders, live stats and twin calls to action. The heart of the page is a four-card grid — hot desk, dedicated desk, private office and meeting room — each with a live availability badge tinted free, reserved or occupied, a price, a short pitch and a row of amenity chips. A chip filter above the grid narrows it to a single space type, fading out the rest and showing an empty-state line when nothing matches.
Every card is built to be explored. Clicking a card photo or its zoom control opens a lightbox you can page through with the arrow keys or on-screen buttons, with focus trapped to the dialog and restored on close. Amenity chips reveal a floating tooltip on hover or keyboard focus, the virtual-tour teaser toggles play and cycles through narrated stops, and a small toast helper confirms each action. The book CTA links every “Book” button to a tour-request form that pre-selects the chosen space, validates the visitor’s name and confirms the request — all keyboard-usable and responsive down to a single column on narrow screens.
Reveal-on-scroll animations stagger the sections in as you move down the page, respecting reduced-motion preferences. The whole thing runs on vanilla JS with no dependencies, leaning on the coworking palette of warm concrete, amber accents and plant green.
Illustrative UI only — fictional coworking space, not a real booking system.