Coworking — Suburban Hub Landing
A warm, neighborhood-flavored coworking landing page for a suburban hub, built with a sage-and-clay palette and a friendly Fraunces serif. It pairs a work-near-home hero with live desk availability, a local-community section, family-friendly amenities, flexible monthly or yearly plans, member stories, and a free day-pass form. Vanilla JS adds scroll reveals, animated stats, a billing toggle, amenity expand, validated forms, and toasts.
MCP
Code
:root {
/* Suburban / neighborhood-hub palette */
--sage: #7c8a6a;
--sage-d: #5f6c4f;
--sage-50: #eef1e8;
--clay: #b8704f;
--clay-d: #9a5a3c;
--clay-50: #f6ebe3;
--ivory: #faf6ee;
--cream: #f3ecdf;
--ink: #2c2a24;
--ink-2: #57534a;
--muted: #847e72;
--bg: #f7f2e8;
--surface: #ffffff;
--line: rgba(44, 42, 36, 0.1);
--line-2: rgba(44, 42, 36, 0.18);
--free: #5f8f5f;
--reserved: #c89436;
--occupied: #b8704f;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--shadow-sm: 0 1px 2px rgba(44, 42, 36, 0.06), 0 2px 8px rgba(44, 42, 36, 0.05);
--shadow-md: 0 8px 26px rgba(44, 42, 36, 0.1);
--shadow-lg: 0 22px 60px rgba(44, 42, 36, 0.14);
--serif: "Fraunces", Georgia, "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: var(--sans);
color: var(--ink);
background: var(--bg);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-image:
radial-gradient(900px 400px at 90% -5%, var(--sage-50), transparent 60%),
radial-gradient(700px 360px at -5% 10%, var(--clay-50), transparent 55%);
}
h1, h2, h3, h4 { font-family: var(--serif); font-weight: 600; line-height: 1.12; color: var(--ink); margin: 0; letter-spacing: -0.01em; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; }
.wrap { width: min(1140px, 92vw); margin: 0 auto; }
.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: -48px; z-index: 60;
background: var(--ink); color: var(--ivory); padding: 8px 14px;
border-radius: var(--r-sm); transition: top .2s;
}
.skip-link:focus { top: 12px; }
/* ---------- Buttons ---------- */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
font-family: var(--sans); font-weight: 600; font-size: 0.95rem;
padding: 12px 20px; border-radius: 999px; border: 1px solid transparent;
cursor: pointer; transition: transform .15s ease, box-shadow .2s ease, background .2s, color .2s;
white-space: nowrap;
}
.btn:active { transform: translateY(1px) scale(.99); }
.btn-primary { background: var(--clay); color: #fff; box-shadow: 0 6px 16px rgba(184, 112, 79, 0.32); }
.btn-primary:hover { background: var(--clay-d); box-shadow: 0 10px 22px rgba(184, 112, 79, 0.4); transform: translateY(-1px); }
.btn-ghost { background: var(--surface); color: var(--ink); border-color: var(--line-2); }
.btn-ghost:hover { border-color: var(--sage); color: var(--sage-d); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.btn-block { width: 100%; }
.btn:focus-visible { outline: 3px solid var(--sage); outline-offset: 2px; }
/* ---------- Nav ---------- */
.nav {
position: sticky; top: 0; z-index: 50;
background: color-mix(in srgb, var(--ivory) 86%, transparent);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
}
.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 70px; gap: 16px; }
.brand { display: inline-flex; align-items: center; gap: 9px; font-family: var(--serif); font-weight: 700; font-size: 1.2rem; }
.brand-mark { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; background: var(--sage-50); color: var(--sage-d); }
.nav-links { display: flex; align-items: center; gap: 26px; }
.nav-link { font-weight: 500; color: var(--ink-2); font-size: 0.95rem; transition: color .15s; }
.nav-link:hover { color: var(--clay); }
.nav-cta {
font-weight: 600; padding: 10px 18px; border-radius: 999px;
background: var(--ink); color: var(--ivory); transition: background .2s, transform .15s;
}
.nav-cta:hover { background: var(--sage-d); transform: translateY(-1px); }
.nav-toggle { display: none; flex-direction: column; gap: 5px; width: 44px; height: 44px; align-items: center; justify-content: center; background: transparent; border: 1px solid var(--line-2); border-radius: 12px; cursor: pointer; }
.nav-toggle span { width: 20px; height: 2px; background: var(--ink); border-radius: 2px; transition: transform .25s, opacity .2s; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
/* ---------- Hero ---------- */
.hero { padding: clamp(48px, 8vw, 96px) 0 64px; }
.hero-grid { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(28px, 5vw, 64px); align-items: center; }
.eyebrow {
display: inline-flex; align-items: center; gap: 8px;
font-weight: 600; font-size: 0.82rem; color: var(--sage-d);
background: var(--sage-50); padding: 6px 13px; border-radius: 999px; letter-spacing: .01em;
}
.eyebrow .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--sage); box-shadow: 0 0 0 4px color-mix(in srgb, var(--sage) 25%, transparent); }
.hero-copy h1 { font-size: clamp(2.4rem, 6vw, 4rem); margin: 18px 0 16px; }
.hero-copy h1 em { font-style: italic; color: var(--clay); }
.lead { font-size: clamp(1.05rem, 2vw, 1.22rem); color: var(--ink-2); max-width: 34ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 26px 0 30px; }
.hero-trust { display: flex; gap: 28px; list-style: none; padding: 22px 0 0; margin: 0; border-top: 1px solid var(--line); flex-wrap: wrap; }
.hero-trust li { display: flex; flex-direction: column; }
.hero-trust strong { font-family: var(--serif); font-size: 1.7rem; color: var(--clay-d); }
.hero-trust span { font-size: 0.82rem; color: var(--muted); }
.hero-card { position: relative; }
.photo { border-radius: var(--r-lg); box-shadow: var(--shadow-md); }
.photo-hero {
aspect-ratio: 4 / 3.4;
background:
radial-gradient(120% 90% at 20% 10%, rgba(255,255,255,.5), transparent 40%),
linear-gradient(150deg, var(--sage) 0%, var(--sage-d) 45%, var(--clay) 120%);
position: relative; overflow: hidden;
}
.photo-hero::after {
content: "";
position: absolute; inset: 0;
background-image:
radial-gradient(circle at 78% 78%, rgba(250,246,238,.18) 0 26%, transparent 27%),
repeating-linear-gradient(115deg, rgba(255,255,255,.05) 0 14px, transparent 14px 34px);
}
.avail-card {
position: absolute; left: 50%; transform: translateX(-50%); bottom: -28px;
width: min(330px, 86%);
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
box-shadow: var(--shadow-lg); padding: 14px 16px;
}
.avail-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.live { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; font-size: 0.82rem; color: var(--ink-2); }
.pulse { width: 9px; height: 9px; border-radius: 50%; background: var(--free); animation: pulse 1.8s infinite; }
@keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(95,143,95,.5); } 70% { box-shadow: 0 0 0 7px rgba(95,143,95,0); } 100% { box-shadow: 0 0 0 0 rgba(95,143,95,0); } }
.avail-time { font-size: 0.78rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.avail-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-top: 1px dashed var(--line); }
.avail-row:first-of-type { border-top: 0; }
.swatch { width: 11px; height: 11px; border-radius: 4px; flex: none; }
.swatch.free { background: var(--free); }
.swatch.reserved { background: var(--reserved); }
.swatch.occupied { background: var(--occupied); }
.avail-label { flex: 1; font-size: 0.9rem; font-weight: 500; }
.avail-count { font-size: 0.82rem; font-weight: 600; color: var(--ink-2); }
.avail-count[data-kind="occupied"] { color: var(--occupied); }
/* ---------- Sections ---------- */
.section { padding: clamp(60px, 9vw, 110px) 0; }
.section-head { max-width: 60ch; margin: 0 auto 44px; text-align: center; }
.kicker { display: inline-block; font-weight: 700; font-size: 0.8rem; text-transform: uppercase; letter-spacing: .12em; color: var(--clay); margin-bottom: 12px; }
.kicker.light { color: var(--sage-50); }
.section-head h2 { font-size: clamp(1.8rem, 4vw, 2.6rem); margin-bottom: 14px; }
.section-head p { color: var(--ink-2); font-size: 1.05rem; }
/* Community */
.community { background: linear-gradient(180deg, transparent, var(--cream) 30%, transparent); }
.community-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; }
.feature {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
padding: 26px 22px; box-shadow: var(--shadow-sm); transition: transform .2s, box-shadow .2s, border-color .2s;
}
.feature:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: var(--sage); }
.feature-ico { display: grid; place-items: center; width: 50px; height: 50px; border-radius: 14px; background: var(--sage-50); font-size: 1.5rem; margin-bottom: 14px; }
.feature h3 { font-size: 1.18rem; margin-bottom: 8px; }
.feature p { color: var(--ink-2); font-size: 0.95rem; }
/* Amenities */
.amenities-grid { display: grid; grid-template-columns: 0.95fr 1.05fr; gap: clamp(28px, 5vw, 64px); align-items: center; }
.amenities-media { position: relative; }
.photo-amen {
aspect-ratio: 4 / 4.2;
background:
radial-gradient(120% 80% at 80% 20%, rgba(255,255,255,.45), transparent 45%),
linear-gradient(160deg, var(--clay) 0%, var(--clay-d) 55%, var(--sage-d) 130%);
position: relative; overflow: hidden;
}
.photo-amen::after {
content: "";
position: absolute; inset: 0;
background-image: repeating-linear-gradient(60deg, rgba(255,255,255,.06) 0 18px, transparent 18px 40px);
}
.badge-float {
position: absolute; right: -14px; bottom: 26px;
display: flex; align-items: center; gap: 12px;
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
padding: 12px 16px; box-shadow: var(--shadow-lg);
}
.badge-ico { font-size: 1.6rem; }
.badge-float strong { display: block; font-family: var(--serif); font-size: 1rem; }
.badge-float span { font-size: 0.82rem; color: var(--muted); }
.amenities-copy .kicker { margin-bottom: 12px; }
.amenities-copy h2 { font-size: clamp(1.7rem, 3.5vw, 2.4rem); margin-bottom: 22px; }
.check-list { list-style: none; padding: 0; margin: 0 0 22px; display: grid; gap: 12px; }
.check-list li {
position: relative; padding-left: 36px; color: var(--ink-2); font-size: 1rem; line-height: 1.45;
transition: opacity .25s, transform .25s;
}
.check-list li::before {
content: "✓"; position: absolute; left: 0; top: 0;
width: 24px; height: 24px; display: grid; place-items: center;
background: var(--sage-50); color: var(--sage-d); border-radius: 50%; font-size: 0.8rem; font-weight: 700;
}
.check-list li.is-hidden { display: none; }
/* Plans */
.bill-toggle {
display: inline-flex; gap: 4px; margin: 0 auto 38px; padding: 5px;
background: var(--cream); border: 1px solid var(--line); border-radius: 999px;
width: max-content; max-width: 100%;
}
.plans .bill-toggle { display: flex; }
.section .bill-toggle { margin-left: auto; margin-right: auto; }
.bill-opt {
border: 0; background: transparent; cursor: pointer; padding: 9px 20px; border-radius: 999px;
font-family: var(--sans); font-weight: 600; font-size: 0.92rem; color: var(--ink-2);
display: inline-flex; align-items: center; gap: 8px; transition: background .2s, color .2s, box-shadow .2s;
}
.bill-opt.is-active { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }
.save { font-size: 0.72rem; font-weight: 700; color: var(--sage-d); background: var(--sage-50); padding: 2px 8px; border-radius: 999px; }
.plan-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; align-items: stretch; }
.plan {
position: relative; display: flex; flex-direction: column;
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
padding: 28px 24px; box-shadow: var(--shadow-sm); transition: transform .2s, box-shadow .2s;
}
.plan:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.plan-feature { border: 1.5px solid var(--clay); box-shadow: var(--shadow-md); }
.plan-flag { position: absolute; top: -13px; left: 24px; background: var(--clay); color: #fff; font-size: 0.72rem; font-weight: 700; padding: 5px 12px; border-radius: 999px; letter-spacing: .03em; }
.plan h3 { font-size: 1.4rem; margin-bottom: 4px; }
.plan-tag { color: var(--muted); font-size: 0.88rem; margin-bottom: 18px; min-height: 2.4em; }
.price { display: flex; align-items: baseline; gap: 2px; margin-bottom: 20px; font-family: var(--serif); }
.price .cur { font-size: 1.3rem; color: var(--ink-2); }
.price .amount { font-size: 2.7rem; font-weight: 600; transition: opacity .2s; }
.price .per { font-size: 0.95rem; color: var(--muted); margin-left: 4px; font-family: var(--sans); }
.plan-feats { list-style: none; padding: 0; margin: 0 0 24px; display: grid; gap: 10px; flex: 1; }
.plan-feats li { position: relative; padding-left: 26px; font-size: 0.94rem; color: var(--ink-2); }
.plan-feats li::before { content: "✓"; position: absolute; left: 0; color: var(--sage); font-weight: 800; }
/* Stories */
.stories { background: var(--cream); }
.story-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.story {
margin: 0; background: var(--surface); border: 1px solid var(--line);
border-radius: var(--r-lg); padding: 28px 26px; box-shadow: var(--shadow-sm);
display: flex; flex-direction: column; gap: 20px; transition: transform .2s, box-shadow .2s;
}
.story:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.story blockquote { margin: 0; font-family: var(--serif); font-size: 1.12rem; line-height: 1.45; color: var(--ink); }
.story figcaption { display: flex; align-items: center; gap: 12px; }
.avatar {
width: 44px; height: 44px; border-radius: 50%; flex: none;
background: var(--a, var(--sage)); color: #fff; display: grid; place-items: center;
font-weight: 700; font-size: 0.92rem;
}
.avatar::before { content: attr(data-initials); }
.who { display: flex; flex-direction: column; }
.who strong { font-size: 0.96rem; }
.who span { font-size: 0.82rem; color: var(--muted); }
/* CTA */
.cta { padding-bottom: clamp(70px, 9vw, 120px); }
.cta-card {
display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(28px, 5vw, 56px);
background: linear-gradient(150deg, var(--sage-d), var(--sage) 60%, var(--clay) 140%);
border-radius: var(--r-lg); padding: clamp(32px, 5vw, 56px); color: var(--ivory);
box-shadow: var(--shadow-lg); align-items: center;
}
.cta-card h2 { color: #fff; font-size: clamp(1.7rem, 3.5vw, 2.5rem); margin-bottom: 12px; }
.cta-copy p { color: color-mix(in srgb, var(--ivory) 88%, transparent); font-size: 1.05rem; max-width: 38ch; }
.cta-form { background: var(--surface); border-radius: var(--r-md); padding: 24px; box-shadow: var(--shadow-md); display: grid; gap: 14px; color: var(--ink); }
.field { display: grid; gap: 6px; }
.field label { font-size: 0.85rem; font-weight: 600; color: var(--ink-2); }
.field input, .field select {
font-family: var(--sans); font-size: 0.95rem; padding: 11px 13px;
border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--ivory); color: var(--ink);
transition: border-color .2s, box-shadow .2s;
}
.field input:focus, .field select:focus { outline: none; border-color: var(--sage); box-shadow: 0 0 0 3px var(--sage-50); }
.field input.invalid { border-color: var(--occupied); box-shadow: 0 0 0 3px var(--clay-50); }
.form-note { font-size: 0.78rem; color: var(--muted); text-align: center; }
/* Footer */
.footer { background: var(--ink); color: color-mix(in srgb, var(--ivory) 78%, transparent); padding: 56px 0 28px; }
.footer .brand { color: var(--ivory); }
.footer .brand-mark { background: rgba(250,246,238,.12); color: var(--sage-50); }
.footer-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1.4fr; gap: 32px; padding-bottom: 36px; border-bottom: 1px solid rgba(250,246,238,.12); }
.footer-brand p { margin-top: 14px; font-size: 0.9rem; line-height: 1.6; }
.footer-col h4 { font-family: var(--sans); color: var(--ivory); font-size: 0.92rem; margin-bottom: 14px; font-weight: 600; }
.footer-col a { display: block; font-size: 0.9rem; padding: 4px 0; color: color-mix(in srgb, var(--ivory) 72%, transparent); transition: color .15s; }
.footer-col a:hover { color: var(--clay); }
.mini-sub { display: flex; gap: 8px; }
.mini-sub input { flex: 1; min-width: 0; padding: 10px 12px; border-radius: var(--r-sm); border: 1px solid rgba(250,246,238,.18); background: rgba(250,246,238,.06); color: var(--ivory); font-family: var(--sans); }
.mini-sub input::placeholder { color: color-mix(in srgb, var(--ivory) 55%, transparent); }
.mini-sub input:focus { outline: none; border-color: var(--sage); }
.footer-base { display: flex; justify-content: space-between; gap: 16px; padding-top: 24px; font-size: 0.82rem; color: color-mix(in srgb, var(--ivory) 58%, transparent); flex-wrap: wrap; }
/* Toast */
.toast-wrap { position: fixed; right: 18px; bottom: 18px; z-index: 80; display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }
.toast {
background: var(--ink); color: var(--ivory); padding: 12px 16px; border-radius: var(--r-md);
box-shadow: var(--shadow-lg); font-size: 0.92rem; font-weight: 500; max-width: 320px;
display: flex; align-items: center; gap: 10px;
transform: translateY(14px); opacity: 0; transition: transform .3s, opacity .3s;
border-left: 3px solid var(--sage);
}
.toast.show { transform: translateY(0); opacity: 1; }
.toast .t-ico { color: var(--sage-50); font-size: 1.05rem; }
/* Reveal */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity .6s ease, transform .6s ease; }
.reveal.in { opacity: 1; transform: none; }
/* ---------- Responsive ---------- */
@media (max-width: 940px) {
.hero-grid { grid-template-columns: 1fr; }
.hero-card { max-width: 460px; margin: 0 auto; width: 100%; }
.community-grid { grid-template-columns: repeat(2, 1fr); }
.amenities-grid { grid-template-columns: 1fr; }
.badge-float { right: 16px; }
.plan-grid { grid-template-columns: 1fr; max-width: 460px; margin: 0 auto; }
.story-grid { grid-template-columns: 1fr; }
.cta-card { grid-template-columns: 1fr; }
.footer-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 760px) {
.nav-links {
position: fixed; inset: 70px 0 auto 0;
flex-direction: column; align-items: stretch; gap: 4px;
background: var(--ivory); border-bottom: 1px solid var(--line);
padding: 14px 6vw 22px; box-shadow: var(--shadow-md);
transform: translateY(-130%); transition: transform .3s ease; visibility: hidden;
}
.nav-links.open { transform: translateY(0); visibility: visible; }
.nav-link { padding: 12px 6px; border-bottom: 1px solid var(--line); }
.nav-cta { margin-top: 8px; text-align: center; }
.nav-toggle { display: flex; }
}
@media (max-width: 520px) {
.community-grid { grid-template-columns: 1fr; }
.footer-grid { grid-template-columns: 1fr; }
.hero-trust { gap: 20px; }
.hero-trust strong { font-size: 1.45rem; }
.footer-base { justify-content: flex-start; }
.btn { width: 100%; }
.hero-actions .btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
* { scroll-behavior: auto !important; }
.reveal { opacity: 1; transform: none; transition: none; }
.pulse { animation: none; }
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastWrap = document.getElementById("toastWrap");
function toast(msg, icon) {
if (!toastWrap) return;
var el = document.createElement("div");
el.className = "toast";
el.setAttribute("role", "status");
el.innerHTML = '<span class="t-ico" aria-hidden="true">' + (icon || "🌿") + "</span><span></span>";
el.querySelector("span:last-child").textContent = msg;
toastWrap.appendChild(el);
requestAnimationFrame(function () { el.classList.add("show"); });
setTimeout(function () {
el.classList.remove("show");
setTimeout(function () { el.remove(); }, 320);
}, 3200);
}
/* ---------- Mobile nav ---------- */
var navToggle = document.getElementById("navToggle");
var navLinks = document.getElementById("navLinks");
function closeNav() {
if (!navToggle) return;
navToggle.setAttribute("aria-expanded", "false");
navToggle.setAttribute("aria-label", "Open menu");
navLinks.classList.remove("open");
}
if (navToggle && navLinks) {
navToggle.addEventListener("click", function () {
var open = navToggle.getAttribute("aria-expanded") === "true";
navToggle.setAttribute("aria-expanded", String(!open));
navToggle.setAttribute("aria-label", open ? "Open menu" : "Close menu");
navLinks.classList.toggle("open", !open);
});
navLinks.querySelectorAll("a").forEach(function (a) {
a.addEventListener("click", closeNav);
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") closeNav();
});
}
/* ---------- Scroll reveal ---------- */
var reveals = document.querySelectorAll(".reveal");
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
var delay = parseInt(entry.target.getAttribute("data-reveal-delay") || "0", 10);
setTimeout(function () { entry.target.classList.add("in"); }, delay);
io.unobserve(entry.target);
}
});
}, { threshold: 0.14, rootMargin: "0px 0px -40px 0px" });
reveals.forEach(function (r) { io.observe(r); });
} else {
reveals.forEach(function (r) { r.classList.add("in"); });
}
/* ---------- Animated hero stats ---------- */
var stats = [
{ id: "statMembers", to: 184 },
{ id: "statDesks", to: 6 },
{ id: "statMinutes", to: 4 }
];
function animateStat(node, to) {
var start = 0, dur = 1100, t0 = null;
function step(ts) {
if (!t0) t0 = ts;
var p = Math.min((ts - t0) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
node.textContent = String(Math.round(start + (to - start) * eased));
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
var statsDone = false;
function runStats() {
if (statsDone) return;
statsDone = true;
stats.forEach(function (s) {
var n = document.getElementById(s.id);
if (n) animateStat(n, s.to);
});
}
var hero = document.getElementById("hero");
if (hero && "IntersectionObserver" in window) {
var hio = new IntersectionObserver(function (e) {
if (e[0].isIntersecting) { runStats(); hio.disconnect(); }
}, { threshold: 0.3 });
hio.observe(hero);
} else {
runStats();
}
/* ---------- Live availability clock ---------- */
var availTime = document.getElementById("availTime");
function tick() {
if (!availTime) return;
var now = new Date();
var hh = now.getHours();
var mm = String(now.getMinutes()).padStart(2, "0");
var ampm = hh >= 12 ? "pm" : "am";
var h12 = hh % 12 || 12;
availTime.textContent = "updated " + h12 + ":" + mm + ampm;
}
tick();
setInterval(tick, 30000);
/* ---------- Amenities show more / fewer ---------- */
var amenList = document.getElementById("amenList");
var amenMore = document.getElementById("amenMore");
if (amenList && amenMore) {
var items = Array.prototype.slice.call(amenList.querySelectorAll("li"));
var COLLAPSED = 4;
var expanded = true;
function applyAmen() {
items.forEach(function (li, i) {
li.classList.toggle("is-hidden", !expanded && i >= COLLAPSED);
});
amenMore.textContent = expanded ? "Show fewer" : "Show all amenities";
amenMore.setAttribute("aria-expanded", String(expanded));
}
amenMore.addEventListener("click", function () {
expanded = !expanded;
applyAmen();
});
applyAmen();
}
/* ---------- Plan billing toggle ---------- */
var billOpts = document.querySelectorAll(".bill-opt");
var amounts = document.querySelectorAll(".price .amount");
function setBilling(mode) {
billOpts.forEach(function (b) {
var on = b.getAttribute("data-bill") === mode;
b.classList.toggle("is-active", on);
b.setAttribute("aria-pressed", String(on));
});
amounts.forEach(function (a) {
var val = a.getAttribute("data-" + mode);
if (val == null) return;
a.style.opacity = "0";
setTimeout(function () {
a.textContent = val;
a.style.opacity = "1";
}, 140);
});
}
billOpts.forEach(function (b) {
b.addEventListener("click", function () {
setBilling(b.getAttribute("data-bill"));
});
});
/* ---------- Plan select ---------- */
document.querySelectorAll("[data-plan]").forEach(function (btn) {
btn.addEventListener("click", function () {
toast(btn.getAttribute("data-plan") + " plan saved — see you at the hub!", "🎉");
var visit = document.getElementById("visit");
if (visit) visit.scrollIntoView({ behavior: "smooth", block: "start" });
});
});
/* ---------- Tour form ---------- */
var tourForm = document.getElementById("tourForm");
if (tourForm) {
var nameI = document.getElementById("cfName");
var emailI = document.getElementById("cfEmail");
function validEmail(v) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); }
tourForm.addEventListener("submit", function (e) {
e.preventDefault();
var ok = true;
[nameI, emailI].forEach(function (f) { f.classList.remove("invalid"); });
if (!nameI.value.trim()) { nameI.classList.add("invalid"); ok = false; }
if (!validEmail(emailI.value.trim())) { emailI.classList.add("invalid"); ok = false; }
if (!ok) {
toast("Add your name and a valid email to book.", "✋");
var bad = tourForm.querySelector(".invalid");
if (bad) bad.focus();
return;
}
var first = nameI.value.trim().split(" ")[0];
toast("Thanks " + first + "! Your day pass is held — coffee's on us. ☕", "✅");
tourForm.reset();
});
[nameI, emailI].forEach(function (f) {
f.addEventListener("input", function () { f.classList.remove("invalid"); });
});
}
/* ---------- Footer newsletter ---------- */
var subForm = document.getElementById("subForm");
if (subForm) {
var subEmail = document.getElementById("subEmail");
subForm.addEventListener("submit", function (e) {
e.preventDefault();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(subEmail.value.trim())) {
subEmail.focus();
toast("Pop in a valid email to join the loop.", "✋");
return;
}
toast("You're on the list — neighborly news inbound.", "📬");
subForm.reset();
});
}
/* ---------- Footer year ---------- */
var yr = document.getElementById("year");
if (yr) yr.textContent = String(new Date().getFullYear());
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Maple & Oak — Coworking close to home</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=Fraunces:opsz,[email protected],400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<!-- ============ NAV ============ -->
<header class="nav" id="top">
<div class="wrap nav-inner">
<a class="brand" href="#top" aria-label="Maple & Oak home">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22V12"/><path d="M12 12c0-4 3-6 6-6 0 4-3 6-6 6Z"/><path d="M12 12c0-4-3-6-6-6 0 4 3 6 6 6Z"/></svg>
</span>
<span class="brand-text">Maple & Oak</span>
</a>
<nav class="nav-links" aria-label="Primary">
<a href="#community">Community</a>
<a href="#amenities">Amenities</a>
<a href="#plans">Plans</a>
<a href="#stories">Stories</a>
</nav>
<div class="nav-cta">
<a class="btn btn-ghost" href="#plans">Take a tour</a>
<a class="btn btn-primary" href="#join" data-toast="A welcome email is on its way 🌿">Join the hub</a>
</div>
<button class="nav-toggle" id="navToggle" aria-expanded="false" aria-controls="mobileNav" aria-label="Open menu">
<span></span><span></span><span></span>
</button>
</div>
<div class="mobile-nav" id="mobileNav" hidden>
<a href="#community">Community</a>
<a href="#amenities">Amenities</a>
<a href="#plans">Plans</a>
<a href="#stories">Stories</a>
<a class="btn btn-primary block" href="#join" data-toast="A welcome email is on its way 🌿">Join the hub</a>
</div>
</header>
<main id="main">
<!-- ============ HERO ============ -->
<section class="hero">
<div class="wrap hero-inner">
<div class="hero-copy reveal">
<span class="eyebrow"><span class="dot" aria-hidden="true"></span> Now open in Cedar Heights</span>
<h1>Work near home.<br />Grow with your <em>neighbors.</em></h1>
<p class="lede">
A warm, light-filled coworking hub three minutes from your front door — so you skip
the commute, drop the kids at school, and still get a full day of focused work.
</p>
<div class="hero-actions">
<a class="btn btn-primary btn-lg" href="#plans">See flexible plans</a>
<a class="btn btn-ghost btn-lg" href="#community">Meet the community</a>
</div>
<ul class="hero-meta">
<li><strong>2 min</strong><span>average drive</span></li>
<li><strong>40+</strong><span>local members</span></li>
<li><strong>Free</strong><span>parking & coffee</span></li>
</ul>
</div>
<div class="hero-art reveal">
<div class="photo photo-1" role="img" aria-label="Sunlit open workspace with plants"></div>
<div class="float-card float-occupancy">
<span class="fc-label">Quiet room today</span>
<span class="fc-status"><span class="pill pill-free">3 free</span> of 5 seats</span>
</div>
<div class="float-card float-member">
<span class="avatar" aria-hidden="true" style="--a:#7c8a6a">PR</span>
<div>
<strong>Priya joined</strong>
<span>School-run plan · 2 blocks away</span>
</div>
</div>
</div>
</div>
</section>
<!-- ============ TRUST BAR ============ -->
<section class="trust" aria-label="Neighborhood partners">
<div class="wrap trust-inner">
<span class="trust-label">Loved by folks from</span>
<ul class="trust-logos">
<li>Cedar Heights PTA</li>
<li>Riverside Makers</li>
<li>Oakfield Library</li>
<li>Hilltop Café</li>
<li>Main St. Studio</li>
</ul>
</div>
</section>
<!-- ============ COMMUNITY ============ -->
<section class="section" id="community">
<div class="wrap">
<div class="section-head reveal">
<span class="kicker">Local community</span>
<h2>Your high-street office, with people you actually know</h2>
<p>No more lonely kitchen tables. Maple & Oak is built around the families,
freelancers and small founders who live right here in the neighborhood.</p>
</div>
<div class="grid-3">
<article class="card reveal">
<span class="card-icon" aria-hidden="true">🤝</span>
<h3>Neighbors, not strangers</h3>
<p>Weekly community lunches, a parents' channel, and a notice board for local gigs,
babysitting swaps and lift-shares.</p>
</article>
<article class="card reveal">
<span class="card-icon" aria-hidden="true">🌱</span>
<h3>Rooted in the area</h3>
<p>We stock produce from the Saturday market, hire local cleaners and host
after-school clubs in the back room.</p>
</article>
<article class="card reveal">
<span class="card-icon" aria-hidden="true">☕</span>
<h3>Always a friendly face</h3>
<p>Our host Dana knows everyone's name and coffee order — and which desk has the
best afternoon light.</p>
</article>
</div>
</div>
</section>
<!-- ============ AMENITIES ============ -->
<section class="section section-tint" id="amenities">
<div class="wrap amen-grid">
<div class="amen-copy reveal">
<span class="kicker">Family-friendly amenities</span>
<h2>Everything a workday at home should have — and a few it never could</h2>
<p>Designed for school runs, nursery pickups and the realities of suburban life.
Stay productive without missing a beat at home.</p>
<ul class="feature-list">
<li data-feat>
<span class="feat-ico" aria-hidden="true">🚗</span>
<div><strong>Free on-site parking</strong><span>30 spaces, EV chargers, secure bike racks.</span></div>
</li>
<li data-feat>
<span class="feat-ico" aria-hidden="true">🧸</span>
<div><strong>Kids' corner & quiet nook</strong><span>Supervised play 3–6pm during term breaks.</span></div>
</li>
<li data-feat>
<span class="feat-ico" aria-hidden="true">🔇</span>
<div><strong>Soundproof call booths</strong><span>Four booths you can grab without booking.</span></div>
</li>
<li data-feat>
<span class="feat-ico" aria-hidden="true">🛒</span>
<div><strong>Errand-friendly hours</strong><span>Open 7am–7pm, steps from the shops.</span></div>
</li>
</ul>
</div>
<div class="amen-panel reveal">
<div class="panel-head">
<span>Today at the hub</span>
<span class="pill pill-reserved">Live</span>
</div>
<div class="meter">
<div class="meter-row">
<span>Open desks</span>
<span class="meter-val" id="deskVal">12 / 28 free</span>
</div>
<div class="bar"><span id="deskBar" style="--w:43%"></span></div>
</div>
<ul class="room-list" id="roomList">
<li>
<span class="room-name">Maple Quiet Room</span>
<button class="slot" data-room="Maple Quiet Room" data-time="9:00">9:00</button>
<button class="slot busy" disabled aria-disabled="true">10:30</button>
<button class="slot" data-room="Maple Quiet Room" data-time="13:00">13:00</button>
</li>
<li>
<span class="room-name">Oak Meeting Room</span>
<button class="slot" data-room="Oak Meeting Room" data-time="9:30">9:30</button>
<button class="slot" data-room="Oak Meeting Room" data-time="11:00">11:00</button>
<button class="slot busy" disabled aria-disabled="true">15:00</button>
</li>
<li>
<span class="room-name">Willow Studio</span>
<button class="slot busy" disabled aria-disabled="true">10:00</button>
<button class="slot" data-room="Willow Studio" data-time="14:00">14:00</button>
<button class="slot" data-room="Willow Studio" data-time="16:30">16:30</button>
</li>
</ul>
<p class="panel-foot">Tap a free slot to hold it for 15 minutes.</p>
</div>
</div>
</section>
<!-- ============ PLANS ============ -->
<section class="section" id="plans">
<div class="wrap">
<div class="section-head reveal">
<span class="kicker">Flexible plans</span>
<h2>Pay for the rhythm of your week, not a desk you'll never use</h2>
<div class="bill-toggle" role="group" aria-label="Billing period">
<button class="bt active" id="btMonthly" aria-pressed="true">Monthly</button>
<button class="bt" id="btYearly" aria-pressed="false">Yearly <span class="save">save 15%</span></button>
</div>
</div>
<div class="grid-3 plans">
<article class="plan reveal">
<h3>School-run</h3>
<p class="plan-sub">For the 9:30–2:30 crowd</p>
<p class="price"><span class="cur">$</span><span class="amt" data-m="69" data-y="59">69</span><span class="per">/mo</span></p>
<ul class="plan-feat">
<li>Hot desk, term-time hours</li>
<li>8 call-booth credits</li>
<li>Free parking & coffee</li>
<li>Community events</li>
</ul>
<a class="btn btn-ghost block" href="#join" data-toast="School-run plan held — Dana will call you 🌿">Choose School-run</a>
</article>
<article class="plan plan-feature reveal">
<span class="plan-badge">Most popular</span>
<h3>Neighbor</h3>
<p class="plan-sub">Full days, your local base</p>
<p class="price"><span class="cur">$</span><span class="amt" data-m="149" data-y="127">149</span><span class="per">/mo</span></p>
<ul class="plan-feat">
<li>Dedicated desk, anytime access</li>
<li>Unlimited call booths</li>
<li>3 meeting-room hours / mo</li>
<li>Kids' corner access</li>
<li>2 guest passes / mo</li>
</ul>
<a class="btn btn-primary block" href="#join" data-toast="Neighbor plan held — welcome home 🏡">Choose Neighbor</a>
</article>
<article class="plan reveal">
<h3>Studio</h3>
<p class="plan-sub">For small local teams</p>
<p class="price"><span class="cur">$</span><span class="amt" data-m="420" data-y="357">420</span><span class="per">/mo</span></p>
<ul class="plan-feat">
<li>Private 4-person studio</li>
<li>Lockable storage</li>
<li>10 meeting-room hours / mo</li>
<li>Mailbox & signage</li>
</ul>
<a class="btn btn-ghost block" href="#join" data-toast="Studio enquiry sent — let's find your room 🛋️">Choose Studio</a>
</article>
</div>
<p class="plans-note">No lock-in. Pause any month — we know summer holidays happen.</p>
</div>
</section>
<!-- ============ STORIES ============ -->
<section class="section section-tint" id="stories">
<div class="wrap">
<div class="section-head reveal">
<span class="kicker">Member stories</span>
<h2>From the kitchen table to the high street</h2>
</div>
<div class="stories">
<button class="story-nav prev" id="storyPrev" aria-label="Previous story">‹</button>
<div class="story-track" id="storyTrack" aria-live="polite">
<figure class="story active">
<span class="avatar lg" aria-hidden="true" style="--a:#b8704f">PR</span>
<blockquote>"I reclaimed 90 minutes of commute every day. Now I drop the kids,
walk five minutes, and I'm at my desk with a flat white before 9."</blockquote>
<figcaption><strong>Priya Raman</strong><span>UX designer · Neighbor plan</span></figcaption>
</figure>
<figure class="story">
<span class="avatar lg" aria-hidden="true" style="--a:#7c8a6a">MO</span>
<blockquote>"Our two-person studio costs less than the city office we shared an hour
away — and our clients love how relaxed the meeting room feels."</blockquote>
<figcaption><strong>Marcus & Ola</strong><span>Founders, Hillside Bakes · Studio plan</span></figcaption>
</figure>
<figure class="story">
<span class="avatar lg" aria-hidden="true" style="--a:#6f7a8c">JT</span>
<blockquote>"The kids' corner during half-term saved my sanity. I got real work done
and they made friends with other members' children."</blockquote>
<figcaption><strong>Jordan Tan</strong><span>Accountant · School-run plan</span></figcaption>
</figure>
</div>
<button class="story-nav next" id="storyNext" aria-label="Next story">›</button>
</div>
<div class="story-dots" id="storyDots" role="tablist" aria-label="Choose a story"></div>
</div>
</section>
<!-- ============ CTA / JOIN ============ -->
<section class="cta" id="join">
<div class="wrap cta-inner reveal">
<div class="cta-copy">
<h2>Come and try a day on us</h2>
<p>Pop in for a free day pass. Bring your laptop, meet a few neighbors, and see if
Maple & Oak feels like your kind of place.</p>
<ul class="cta-points">
<li>✓ No commitment, no card needed</li>
<li>✓ Coffee & parking included</li>
<li>✓ A friendly tour with Dana</li>
</ul>
</div>
<form class="cta-form" id="joinForm" novalidate>
<label for="jname">Your name</label>
<input id="jname" name="name" type="text" autocomplete="name" placeholder="Sam from down the road" required />
<label for="jemail">Email</label>
<input id="jemail" name="email" type="email" autocomplete="email" placeholder="[email protected]" required />
<label for="jday">Pick a day</label>
<select id="jday" name="day">
<option>This Friday</option>
<option>Next Monday</option>
<option>Next Wednesday</option>
<option>Whenever suits</option>
</select>
<button class="btn btn-primary btn-lg block" type="submit">Book my free day</button>
<p class="form-msg" id="formMsg" role="status" aria-live="polite"></p>
</form>
</div>
</section>
</main>
<!-- ============ FOOTER ============ -->
<footer class="footer">
<div class="wrap footer-inner">
<div class="footer-brand">
<span class="brand">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22V12"/><path d="M12 12c0-4 3-6 6-6 0 4-3 6-6 6Z"/><path d="M12 12c0-4-3-6-6-6 0 4 3 6 6 6Z"/></svg>
</span>
<span class="brand-text">Maple & Oak</span>
</span>
<p>The neighborhood workspace. 14 Cedar Heights Parade, open 7am–7pm, Mon–Sat.</p>
</div>
<nav class="footer-col" aria-label="Visit">
<h4>Visit</h4>
<a href="#amenities">Amenities</a>
<a href="#plans">Plans & pricing</a>
<a href="#join">Book a tour</a>
</nav>
<nav class="footer-col" aria-label="Community">
<h4>Community</h4>
<a href="#stories">Member stories</a>
<a href="#community">Events</a>
<a href="#" data-toast="Newsletter is fictional — but thanks! 🌿">Neighborhood newsletter</a>
</nav>
<div class="footer-col">
<h4>Say hello</h4>
<a href="#" data-toast="Demo only — not a real inbox 🌿">[email protected]</a>
<a href="#" data-toast="Demo only 🌿">(555) 014-2200</a>
</div>
</div>
<div class="wrap footer-foot">
<span>© 2026 Maple & Oak — a fictional coworking hub.</span>
<span>Built with ☕ in Cedar Heights</span>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Suburban Hub Landing
A full marketing landing page for Maple & Oak, a fictional neighborhood coworking hub that sells the suburban dream: skip the commute and work two streets from home. The design leans into a welcoming, local feel with a sage-green and clay palette, ivory surfaces, soft shadows, and a friendly Fraunces serif for headings paired with Inter for body copy. Sections flow from a reveal-on-scroll hero through community, family-friendly amenities, flexible plans, member stories, and a free day-pass call to action.
Interactions are pure vanilla JS. The hero stats count up when scrolled into view, a live availability card shows free, reserved, and occupied spaces with a ticking “updated” timestamp, and the amenities list expands and collapses. The plans section has a monthly/yearly billing toggle that fades prices between values and surfaces a neighbor discount, while plan buttons drop a toast and scroll to the booking form. Both the tour form and footer newsletter validate inline and confirm with toasts.
Everything is responsive down to roughly 360px, including a slide-down mobile nav, single-column stacking, and full-width buttons. ARIA roles, focus-visible outlines, keyboard-dismissable menus, and a reduced-motion fallback keep it accessible.
Illustrative UI only — fictional coworking space, not a real booking system.