DIY — Workshop Classes
Makerspace classes landing page in a workshop-blueprint aesthetic: a kraft graph-paper hero with a stencil Learn by making headline, a weekly schedule of class cards with navy date blocks, level badges, seats-left meters that turn amber when nearly full, and working Enroll buttons that decrement seats with a toast. Includes level filter chips, an instructors row with mono specialty tags, a dashed membership banner with a perks list, and a four-item FAQ accordion.
MCP
Code
:root {
--blueprint: #0f2a4a;
--blueprint-d: #0a1e36;
--blueprint-l: #16385f;
--grid: rgba(140, 180, 220, 0.14);
--grid-major: rgba(140, 180, 220, 0.26);
--orange: #ff6b35;
--orange-d: #e0521f;
--orange-soft: #ffe3d6;
--kraft: #d9c7a7;
--kraft-l: #efe6d4;
--ink: #1c2733;
--ink-2: #3d4a57;
--muted: #6b7885;
--bg: #f4f1ea;
--white: #ffffff;
--line: rgba(28, 39, 51, 0.12);
--line-2: rgba(28, 39, 51, 0.2);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow: 0 2px 6px rgba(28, 39, 51, 0.06), 0 10px 30px rgba(28, 39, 51, 0.08);
--shadow-lg: 0 6px 16px rgba(28, 39, 51, 0.1), 0 24px 48px rgba(28, 39, 51, 0.12);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
color: var(--ink);
background: var(--bg);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.mono {
font-family: "JetBrains Mono", ui-monospace, monospace;
}
h1,
h2,
h3 {
margin: 0;
}
img {
max-width: 100%;
}
/* ============ BUTTONS ============ */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 22px;
border-radius: var(--r-sm);
border: 2px solid transparent;
font: 700 0.9rem/1 "Inter", system-ui, sans-serif;
letter-spacing: 0.03em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.btn:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.btn:active {
transform: translateY(1px);
}
.btn-orange {
background: var(--orange);
color: #fff;
box-shadow: 0 3px 0 var(--orange-d);
}
.btn-orange:hover {
background: var(--orange-d);
box-shadow: 0 2px 0 #b8420f;
}
.btn-ghost {
background: transparent;
color: var(--ink);
border-color: var(--ink);
}
.btn-ghost:hover {
background: var(--ink);
color: var(--kraft-l);
}
.btn-sm {
padding: 9px 16px;
font-size: 0.78rem;
}
.btn[disabled] {
background: var(--muted);
box-shadow: none;
cursor: not-allowed;
opacity: 0.7;
}
/* ============ TOPBAR ============ */
.topbar {
position: sticky;
top: 0;
z-index: 50;
display: flex;
align-items: center;
gap: 24px;
padding: 12px 28px;
background: rgba(244, 241, 234, 0.92);
backdrop-filter: blur(8px);
border-bottom: 2px solid var(--ink);
}
.brand {
display: flex;
align-items: baseline;
gap: 10px;
text-decoration: none;
color: var(--ink);
}
.brand-mark {
font-size: 1.2rem;
color: var(--orange);
}
.brand-name {
font-weight: 800;
letter-spacing: 0.08em;
font-size: 1rem;
}
.brand-tag {
font-size: 0.62rem;
color: var(--muted);
letter-spacing: 0.08em;
}
.topnav {
display: flex;
gap: 20px;
margin-left: auto;
}
.topnav a {
color: var(--ink-2);
text-decoration: none;
font-weight: 600;
font-size: 0.88rem;
padding: 4px 2px;
border-bottom: 2px solid transparent;
transition: color 0.15s, border-color 0.15s;
}
.topnav a:hover,
.topnav a:focus-visible {
color: var(--orange-d);
border-bottom-color: var(--orange);
}
/* ============ HERO ============ */
.hero {
position: relative;
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 48px;
align-items: center;
padding: 80px 48px 88px;
background-color: var(--kraft-l);
background-image:
linear-gradient(rgba(28, 39, 51, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(28, 39, 51, 0.05) 1px, transparent 1px),
linear-gradient(rgba(28, 39, 51, 0.09) 1px, transparent 1px),
linear-gradient(90deg, rgba(28, 39, 51, 0.09) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
border-bottom: 3px solid var(--ink);
overflow: hidden;
}
.hero-kicker {
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.12em;
color: var(--ink-2);
margin: 0 0 18px;
}
.hero-title {
font-size: clamp(3rem, 8vw, 5.6rem);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
line-height: 0.95;
color: var(--ink);
-webkit-text-stroke: 0;
}
.hero-sub {
max-width: 46ch;
margin: 22px 0 30px;
font-size: 1.05rem;
color: var(--ink-2);
}
.hero-ctas {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
.hero-stats {
display: flex;
gap: 36px;
margin: 44px 0 0;
padding: 0;
list-style: none;
}
.hero-stats li {
display: flex;
flex-direction: column;
gap: 2px;
}
.stat-num {
font-size: 1.6rem;
font-weight: 700;
color: var(--orange-d);
}
.stat-lbl {
font-size: 0.78rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
/* Hero spec plate card */
.hero-card {
align-self: center;
transform: rotate(1.5deg);
}
.spec-plate {
background: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
border: 2px solid var(--blueprint-d);
border-radius: var(--r-md);
padding: 26px 24px;
box-shadow: var(--shadow-lg);
}
.spec-row {
display: flex;
justify-content: space-between;
gap: 24px;
padding: 10px 0;
border-bottom: 1px dashed rgba(140, 180, 220, 0.35);
color: var(--kraft-l);
font-size: 0.78rem;
letter-spacing: 0.06em;
}
.spec-row:last-child {
border-bottom: none;
}
.spec-row span:first-child {
color: rgba(217, 199, 167, 0.75);
}
.cut-line {
margin-top: 16px;
text-align: center;
font-size: 0.7rem;
color: var(--ink-2);
letter-spacing: 0.08em;
border-top: 2px dashed var(--line-2);
padding-top: 10px;
}
/* ============ SECTIONS ============ */
.section {
max-width: 1120px;
margin: 0 auto;
padding: 72px 32px;
}
.section-dark {
max-width: none;
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
border-top: 3px solid var(--ink);
border-bottom: 3px solid var(--ink);
}
.section-dark > * {
max-width: 1120px;
margin-left: auto;
margin-right: auto;
}
.section-head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 20px;
margin-bottom: 28px;
}
.eyebrow {
margin: 0 0 8px;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.14em;
color: var(--orange-d);
}
.eyebrow-light {
color: var(--orange);
}
.section-title {
font-size: clamp(1.6rem, 4vw, 2.2rem);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.title-light {
color: var(--kraft-l);
}
/* ============ FILTER CHIPS ============ */
.filter-chips {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.chip {
padding: 8px 16px;
border-radius: 999px;
border: 2px solid var(--ink);
background: var(--white);
color: var(--ink);
font: 700 0.78rem/1 "Inter", system-ui, sans-serif;
text-transform: uppercase;
letter-spacing: 0.05em;
cursor: pointer;
transition: background 0.15s, color 0.15s, transform 0.15s;
}
.chip:hover {
transform: translateY(-1px);
}
.chip:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.chip.is-active {
background: var(--ink);
color: var(--kraft-l);
}
.filter-count {
margin: 0 0 18px;
font-size: 0.7rem;
letter-spacing: 0.1em;
color: var(--muted);
}
/* ============ CLASS CARDS ============ */
.class-list {
display: flex;
flex-direction: column;
gap: 18px;
}
.class-card {
display: grid;
grid-template-columns: 92px 1fr auto;
gap: 22px;
align-items: center;
background: var(--white);
border: 1px solid var(--line);
border-left: 5px solid var(--orange);
border-radius: var(--r-md);
padding: 22px 24px;
box-shadow: var(--shadow);
transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.25s ease;
}
.class-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-lg);
}
.class-card.is-hidden {
display: none;
}
.class-card.is-full {
border-left-color: var(--muted);
}
.date-block {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0;
background: var(--blueprint);
color: var(--kraft-l);
border-radius: var(--r-sm);
padding: 12px 8px;
min-height: 92px;
}
.date-dow {
font-size: 0.66rem;
font-weight: 600;
letter-spacing: 0.14em;
color: var(--orange);
}
.date-day {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
}
.date-mon {
font-size: 0.66rem;
letter-spacing: 0.14em;
color: rgba(239, 230, 212, 0.7);
}
.class-top {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.class-title {
font-size: 1.2rem;
font-weight: 800;
}
.badge {
display: inline-block;
padding: 4px 10px;
border-radius: 999px;
font: 700 0.66rem/1.2 "JetBrains Mono", ui-monospace, monospace;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.badge-beginner {
background: rgba(47, 158, 111, 0.14);
color: #1e7a53;
border: 1px solid rgba(47, 158, 111, 0.4);
}
.badge-inter {
background: rgba(217, 138, 43, 0.14);
color: #a4671b;
border: 1px solid rgba(217, 138, 43, 0.45);
}
.class-desc {
margin: 8px 0 10px;
color: var(--ink-2);
font-size: 0.92rem;
max-width: 60ch;
}
.class-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--muted);
}
.dot {
color: var(--orange);
}
/* Seats meter */
.seat-wrap {
display: flex;
align-items: center;
gap: 12px;
margin-top: 14px;
}
.seat-meter {
width: 140px;
height: 10px;
border-radius: 999px;
background: rgba(28, 39, 51, 0.1);
overflow: hidden;
border: 1px solid var(--line);
}
.seat-fill {
height: 100%;
border-radius: 999px;
background: var(--ok);
transition: width 0.35s ease, background 0.35s ease;
}
.seat-fill.is-low {
background: var(--warn);
}
.seat-fill.is-empty {
background: var(--danger);
}
.seat-label {
font-size: 0.68rem;
letter-spacing: 0.08em;
color: var(--ink-2);
}
.seat-label b {
color: var(--ink);
}
/* Price / enroll column */
.class-side {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12px;
}
.price {
font-size: 1.5rem;
font-weight: 700;
color: var(--ink);
}
.empty-note {
margin: 24px 0 0;
padding: 22px;
text-align: center;
border: 2px dashed var(--line-2);
border-radius: var(--r-md);
color: var(--muted);
font-size: 0.75rem;
letter-spacing: 0.1em;
}
/* ============ INSTRUCTORS ============ */
.instructor-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
}
.instructor-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(140, 180, 220, 0.28);
border-radius: var(--r-md);
padding: 24px 20px;
transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.instructor-card:hover {
transform: translateY(-4px);
border-color: var(--orange);
background: rgba(255, 255, 255, 0.08);
}
.avatar {
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: var(--av, var(--orange));
color: #fff;
font: 800 1.05rem/1 "Inter", system-ui, sans-serif;
letter-spacing: 0.04em;
margin-bottom: 14px;
border: 3px solid rgba(255, 255, 255, 0.25);
}
.instructor-name {
color: var(--white);
font-size: 1.02rem;
font-weight: 700;
}
.instructor-bio {
margin: 6px 0 14px;
color: rgba(239, 230, 212, 0.82);
font-size: 0.84rem;
}
.tag-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tag {
padding: 4px 8px;
border-radius: 4px;
border: 1px solid rgba(255, 107, 53, 0.5);
color: var(--orange);
font-size: 0.6rem;
font-weight: 600;
letter-spacing: 0.08em;
}
/* ============ MEMBERSHIP ============ */
.membership-banner {
display: grid;
grid-template-columns: 1.35fr 1fr;
gap: 44px;
align-items: center;
background: var(--white);
border: 2px dashed var(--line-2);
border-radius: var(--r-lg);
padding: 48px;
box-shadow: var(--shadow);
}
.membership-title {
font-size: clamp(1.7rem, 4vw, 2.4rem);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
line-height: 1.1;
}
.membership-sub {
color: var(--ink-2);
margin: 14px 0 20px;
max-width: 50ch;
}
.perks {
list-style: none;
margin: 0 0 26px;
padding: 0;
display: grid;
gap: 10px;
}
.perks li {
display: flex;
align-items: baseline;
gap: 10px;
font-weight: 500;
color: var(--ink);
}
.perk-check {
flex: none;
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--ok);
color: #fff;
font-size: 0.7rem;
font-weight: 800;
transform: translateY(2px);
}
.membership-ctas {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px;
}
.membership-note {
font-size: 0.68rem;
letter-spacing: 0.08em;
color: var(--muted);
}
/* Member card visual */
.member-card {
background: var(--kraft);
border: 2px solid var(--ink);
border-radius: var(--r-md);
padding: 22px 22px 0;
box-shadow: var(--shadow-lg);
transform: rotate(-2deg);
transition: transform 0.25s ease;
overflow: hidden;
}
.member-card:hover {
transform: rotate(0deg) scale(1.02);
}
.member-card-top {
display: flex;
justify-content: space-between;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--ink-2);
margin-bottom: 20px;
}
.member-card-name {
font: 800 1.6rem/1 "Inter", system-ui, sans-serif;
letter-spacing: 0.08em;
margin-bottom: 18px;
}
.member-card-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-top: 1px dashed var(--line-2);
font-size: 0.7rem;
letter-spacing: 0.08em;
color: var(--ink-2);
}
.member-card-stripe {
height: 14px;
margin: 16px -22px 0;
background: repeating-linear-gradient(
-45deg,
var(--orange) 0 12px,
var(--ink) 12px 24px
);
}
/* ============ FAQ ============ */
.section-faq {
padding-top: 0;
}
.faq-list {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 780px;
}
.faq-item {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
overflow: hidden;
transition: border-color 0.15s;
}
.faq-item[open] {
border-color: var(--orange);
}
.faq-item summary {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
padding: 18px 22px;
cursor: pointer;
font-weight: 700;
font-size: 0.98rem;
list-style: none;
user-select: none;
transition: background 0.15s;
}
.faq-item summary::-webkit-details-marker {
display: none;
}
.faq-item summary:hover {
background: var(--kraft-l);
}
.faq-item summary:focus-visible {
outline: 3px solid var(--orange);
outline-offset: -3px;
}
.faq-icon {
flex: none;
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 2px solid var(--ink);
border-radius: 50%;
font-size: 1rem;
font-weight: 700;
transition: transform 0.2s ease, background 0.2s, color 0.2s;
}
.faq-item[open] .faq-icon {
transform: rotate(45deg);
background: var(--orange);
border-color: var(--orange);
color: #fff;
}
.faq-item p {
margin: 0;
padding: 0 22px 20px;
color: var(--ink-2);
font-size: 0.92rem;
max-width: 68ch;
}
/* ============ FOOTER ============ */
.footer {
background: var(--ink);
color: var(--kraft-l);
padding: 26px 32px;
}
.footer-inner {
max-width: 1120px;
margin: 0 auto;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
font-size: 0.68rem;
letter-spacing: 0.1em;
}
/* ============ TOAST ============ */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 20px);
background: var(--ink);
color: var(--kraft-l);
border-left: 4px solid var(--orange);
padding: 13px 20px;
border-radius: var(--r-sm);
font-size: 0.75rem;
letter-spacing: 0.05em;
box-shadow: var(--shadow-lg);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 100;
max-width: min(92vw, 480px);
text-align: center;
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
/* ============ RESPONSIVE ============ */
@media (max-width: 900px) {
.hero {
grid-template-columns: 1fr;
padding: 56px 28px 64px;
}
.hero-card {
max-width: 420px;
}
.instructor-grid {
grid-template-columns: repeat(2, 1fr);
}
.membership-banner {
grid-template-columns: 1fr;
padding: 36px 28px;
}
.member-card {
max-width: 360px;
}
.topnav {
display: none;
}
}
@media (max-width: 680px) {
.class-card {
grid-template-columns: 72px 1fr;
gap: 16px;
}
.date-block {
min-height: 78px;
}
.date-day {
font-size: 1.6rem;
}
.class-side {
grid-column: 1 / -1;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-top: 1px dashed var(--line-2);
padding-top: 14px;
}
}
@media (max-width: 520px) {
.topbar {
padding: 10px 16px;
gap: 12px;
}
.brand-tag {
display: none;
}
.section {
padding: 52px 18px;
}
.hero {
padding: 44px 18px 52px;
}
.hero-stats {
gap: 22px;
flex-wrap: wrap;
}
.hero-ctas .btn {
width: 100%;
}
.instructor-grid {
grid-template-columns: 1fr;
}
.seat-wrap {
flex-wrap: wrap;
}
.seat-meter {
width: 100%;
}
.membership-ctas .btn {
width: 100%;
}
.footer-inner {
justify-content: center;
text-align: center;
}
}/* Bolt & Bevel — Workshop Classes
Vanilla JS: level filter, seat meters, enroll flow, hero counters, toast. */
(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2600);
}
/* ---------- Seat meters ---------- */
var LOW_THRESHOLD = 0.34; // <= ~1/3 of seats -> amber
function paintMeter(fill) {
var seats = parseInt(fill.dataset.seats, 10);
var total = parseInt(fill.dataset.total, 10);
var ratio = total > 0 ? seats / total : 0;
fill.style.width = Math.round(ratio * 100) + "%";
fill.classList.toggle("is-low", seats > 0 && ratio <= LOW_THRESHOLD);
fill.classList.toggle("is-empty", seats === 0);
var meter = fill.closest(".seat-meter");
if (meter) {
meter.setAttribute("aria-label", seats + " of " + total + " seats left");
}
}
document.querySelectorAll(".seat-fill").forEach(paintMeter);
/* ---------- Enroll buttons ---------- */
document.querySelectorAll(".btn-enroll").forEach(function (btn) {
btn.addEventListener("click", function () {
var card = btn.closest(".class-card");
var fill = card.querySelector(".seat-fill");
var numEl = card.querySelector(".seat-num");
var seats = parseInt(fill.dataset.seats, 10);
var name = btn.dataset.class;
if (seats <= 0) return;
seats -= 1;
fill.dataset.seats = String(seats);
numEl.textContent = String(seats);
paintMeter(fill);
if (seats === 0) {
card.classList.add("is-full");
btn.disabled = true;
btn.textContent = "Class full";
var label = card.querySelector(".seat-label");
if (label) label.innerHTML = '<b class="seat-num">0</b>/' + fill.dataset.total + " — WAITLIST OPEN";
toast("LAST SEAT CLAIMED — “" + name.toUpperCase() + "” IS NOW FULL");
} else if (seats === 1) {
toast("SEAT RESERVED FOR “" + name.toUpperCase() + "” — ONLY 1 LEFT!");
} else {
toast("SEAT RESERVED FOR “" + name.toUpperCase() + "” — SEE YOU AT THE BENCH");
}
});
});
/* ---------- Level filter chips ---------- */
var chips = document.querySelectorAll(".chip");
var cards = document.querySelectorAll(".class-card");
var countEl = document.getElementById("filterCount");
var emptyNote = document.getElementById("emptyNote");
function applyFilter(level) {
var visible = 0;
cards.forEach(function (card) {
var show = level === "all" || card.dataset.level === level;
card.classList.toggle("is-hidden", !show);
if (show) visible += 1;
});
countEl.textContent = "SHOWING " + visible + " OF " + cards.length + " CLASSES";
emptyNote.hidden = visible !== 0;
}
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
var active = c === chip;
c.classList.toggle("is-active", active);
c.setAttribute("aria-pressed", String(active));
});
applyFilter(chip.dataset.level);
});
});
/* ---------- Membership join ---------- */
var joinBtn = document.getElementById("joinBtn");
if (joinBtn) {
joinBtn.addEventListener("click", function () {
joinBtn.disabled = true;
joinBtn.textContent = "Card BB-4172 reserved";
toast("WELCOME ABOARD — YOUR MEMBER CARD IS WAITING AT THE FRONT DESK");
});
}
/* ---------- Hero stat counters ---------- */
function animateCount(el) {
var target = parseInt(el.dataset.count, 10);
var duration = 900;
var start = null;
function step(ts) {
if (start === null) start = ts;
var p = Math.min((ts - start) / duration, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = String(Math.round(target * eased));
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
var statEls = document.querySelectorAll(".stat-num");
if ("IntersectionObserver" in window) {
var seen = new WeakSet();
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting && !seen.has(entry.target)) {
seen.add(entry.target);
animateCount(entry.target);
io.unobserve(entry.target);
}
});
}, { threshold: 0.4 });
statEls.forEach(function (el) { io.observe(el); });
} else {
statEls.forEach(function (el) {
el.textContent = el.dataset.count;
});
}
/* ---------- FAQ: close others when one opens ---------- */
var faqs = document.querySelectorAll(".faq-item");
faqs.forEach(function (item) {
item.addEventListener("toggle", function () {
if (!item.open) return;
faqs.forEach(function (other) {
if (other !== item && other.open) other.open = false;
});
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bolt & Bevel Makerspace — Workshop Classes</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&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- ============ TOP BAR ============ -->
<header class="topbar">
<a class="brand" href="#top" aria-label="Bolt and Bevel Makerspace home">
<span class="brand-mark" aria-hidden="true">⚙</span>
<span class="brand-name">BOLT & BEVEL</span>
<span class="brand-tag mono">MAKERSPACE · UNIT 7</span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#schedule">Schedule</a>
<a href="#instructors">Instructors</a>
<a href="#membership">Membership</a>
<a href="#faq">FAQ</a>
</nav>
<a class="btn btn-orange btn-sm" href="#schedule">Book a class</a>
</header>
<!-- ============ HERO ============ -->
<section class="hero" id="top">
<div class="hero-inner">
<p class="hero-kicker mono">EST. 2019 · 14 BENCHES · 6 MACHINES · 1 VERY LOUD BANDSAW</p>
<h1 class="hero-title">Learn<br />by making</h1>
<p class="hero-sub">
Hands-on workshop classes for absolute beginners and tinkerers alike.
Small groups, real tools, and something finished to take home — solder
smoke and sawdust included, free of charge.
</p>
<div class="hero-ctas">
<a class="btn btn-orange" href="#schedule">See this week's classes</a>
<a class="btn btn-ghost" href="#membership">Membership perks</a>
</div>
<ul class="hero-stats" aria-label="Makerspace stats">
<li><span class="mono stat-num" data-count="1240">0</span><span class="stat-lbl">makers trained</span></li>
<li><span class="mono stat-num" data-count="38">0</span><span class="stat-lbl">classes / month</span></li>
<li><span class="mono stat-num" data-count="6">0</span><span class="stat-lbl">max seats per bench</span></li>
</ul>
</div>
<div class="hero-card" aria-hidden="true">
<div class="spec-plate">
<div class="spec-row"><span class="mono">PART NO.</span><span class="mono">BB-CLS-2026</span></div>
<div class="spec-row"><span class="mono">SCALE</span><span class="mono">1 : 1 (REAL LIFE)</span></div>
<div class="spec-row"><span class="mono">TOLERANCE</span><span class="mono">MISTAKES WELCOME</span></div>
<div class="spec-row"><span class="mono">FINISH</span><span class="mono">SANDED · 220 GRIT</span></div>
</div>
<div class="cut-line mono">✂ - - - - - - CUT HERE, KEEP THE STUB - - - - - -</div>
</div>
</section>
<!-- ============ SCHEDULE ============ -->
<main class="section" id="schedule">
<div class="section-head">
<div>
<p class="eyebrow mono">WEEK OF JUL 27 — AUG 02</p>
<h2 class="section-title">Upcoming workshops</h2>
</div>
<div class="filter-chips" role="group" aria-label="Filter classes by level">
<button class="chip is-active" type="button" data-level="all" aria-pressed="true">All levels</button>
<button class="chip" type="button" data-level="beginner" aria-pressed="false">Beginner</button>
<button class="chip" type="button" data-level="intermediate" aria-pressed="false">Intermediate</button>
</div>
</div>
<p class="filter-count mono" id="filterCount" aria-live="polite">SHOWING 4 OF 4 CLASSES</p>
<div class="class-list" id="classList">
<!-- Class 1 -->
<article class="class-card" data-level="beginner" data-id="solder">
<div class="date-block" aria-hidden="true">
<span class="date-dow mono">TUE</span>
<span class="date-day">28</span>
<span class="date-mon mono">JUL</span>
</div>
<div class="class-body">
<div class="class-top">
<h3 class="class-title">Intro to Soldering</h3>
<span class="badge badge-beginner">Beginner</span>
</div>
<p class="class-desc">Build a working LED badge from a bare kit. Iron handling, tinning, through-hole joints, and how to fix the blobby ones.</p>
<div class="class-meta mono">
<span>18:30–21:00</span><span class="dot" aria-hidden="true">·</span>
<span>ELECTRONICS BENCH A</span><span class="dot" aria-hidden="true">·</span>
<span>KIT INCLUDED</span>
</div>
<div class="seat-wrap">
<div class="seat-meter" role="img" aria-label="4 of 6 seats left">
<div class="seat-fill" data-seats="4" data-total="6"></div>
</div>
<span class="seat-label mono"><b class="seat-num">4</b>/6 SEATS LEFT</span>
</div>
</div>
<div class="class-side">
<span class="price mono">$45</span>
<button class="btn btn-orange btn-enroll" type="button" data-class="Intro to Soldering">Enroll</button>
</div>
</article>
<!-- Class 2 -->
<article class="class-card" data-level="intermediate" data-id="woodturn">
<div class="date-block" aria-hidden="true">
<span class="date-dow mono">WED</span>
<span class="date-day">29</span>
<span class="date-mon mono">JUL</span>
</div>
<div class="class-body">
<div class="class-top">
<h3 class="class-title">Woodturning Basics</h3>
<span class="badge badge-inter">Intermediate</span>
</div>
<p class="class-desc">Turn a maple honey dipper on the mini-lathe. Covers roughing gouges, speed selection, and not launching your workpiece.</p>
<div class="class-meta mono">
<span>17:00–20:30</span><span class="dot" aria-hidden="true">·</span>
<span>LATHE ROOM</span><span class="dot" aria-hidden="true">·</span>
<span>PPE PROVIDED</span>
</div>
<div class="seat-wrap">
<div class="seat-meter" role="img" aria-label="2 of 6 seats left">
<div class="seat-fill" data-seats="2" data-total="6"></div>
</div>
<span class="seat-label mono"><b class="seat-num">2</b>/6 SEATS LEFT</span>
</div>
</div>
<div class="class-side">
<span class="price mono">$68</span>
<button class="btn btn-orange btn-enroll" type="button" data-class="Woodturning Basics">Enroll</button>
</div>
</article>
<!-- Class 3 -->
<article class="class-card" data-level="intermediate" data-id="printer">
<div class="date-block" aria-hidden="true">
<span class="date-dow mono">THU</span>
<span class="date-day">30</span>
<span class="date-mon mono">JUL</span>
</div>
<div class="class-body">
<div class="class-top">
<h3 class="class-title">3D Printer Maintenance</h3>
<span class="badge badge-inter">Intermediate</span>
</div>
<p class="class-desc">Nozzle swaps, bed leveling, belt tension, and diagnosing the classic mid-print spaghetti. Bring your own machine or use ours.</p>
<div class="class-meta mono">
<span>19:00–21:30</span><span class="dot" aria-hidden="true">·</span>
<span>PRINT FARM</span><span class="dot" aria-hidden="true">·</span>
<span>BYO PRINTER OK</span>
</div>
<div class="seat-wrap">
<div class="seat-meter" role="img" aria-label="5 of 8 seats left">
<div class="seat-fill" data-seats="5" data-total="8"></div>
</div>
<span class="seat-label mono"><b class="seat-num">5</b>/8 SEATS LEFT</span>
</div>
</div>
<div class="class-side">
<span class="price mono">$52</span>
<button class="btn btn-orange btn-enroll" type="button" data-class="3D Printer Maintenance">Enroll</button>
</div>
</article>
<!-- Class 4 -->
<article class="class-card" data-level="beginner" data-id="laser">
<div class="date-block" aria-hidden="true">
<span class="date-dow mono">SAT</span>
<span class="date-day">01</span>
<span class="date-mon mono">AUG</span>
</div>
<div class="class-body">
<div class="class-top">
<h3 class="class-title">Laser Cutting 101</h3>
<span class="badge badge-beginner">Beginner</span>
</div>
<p class="class-desc">Design and cut a plywood desk organizer. File prep, kerf allowances, material settings, and safe operation of the 60W cutter.</p>
<div class="class-meta mono">
<span>10:00–13:00</span><span class="dot" aria-hidden="true">·</span>
<span>LASER BAY</span><span class="dot" aria-hidden="true">·</span>
<span>MATERIALS INCLUDED</span>
</div>
<div class="seat-wrap">
<div class="seat-meter" role="img" aria-label="6 of 6 seats left">
<div class="seat-fill" data-seats="6" data-total="6"></div>
</div>
<span class="seat-label mono"><b class="seat-num">6</b>/6 SEATS LEFT</span>
</div>
</div>
<div class="class-side">
<span class="price mono">$58</span>
<button class="btn btn-orange btn-enroll" type="button" data-class="Laser Cutting 101">Enroll</button>
</div>
</article>
</div>
<p class="empty-note mono" id="emptyNote" hidden>NO CLASSES MATCH THIS FILTER — CHECK BACK NEXT WEEK.</p>
</main>
<!-- ============ INSTRUCTORS ============ -->
<section class="section section-dark" id="instructors">
<div class="section-head">
<div>
<p class="eyebrow eyebrow-light mono">THE PEOPLE WITH THE KEYS</p>
<h2 class="section-title title-light">Your instructors</h2>
</div>
</div>
<div class="instructor-grid">
<article class="instructor-card">
<div class="avatar" aria-hidden="true" style="--av:#ff6b35">MO</div>
<h3 class="instructor-name">Mara Okafor</h3>
<p class="instructor-bio">Ex-repair-café lead. Believes every flux pen deserves a second chance.</p>
<div class="tag-row">
<span class="tag mono">SOLDERING</span>
<span class="tag mono">PCB REWORK</span>
</div>
</article>
<article class="instructor-card">
<div class="avatar" aria-hidden="true" style="--av:#2f9e6f">DK</div>
<h3 class="instructor-name">Dario Kessel</h3>
<p class="instructor-bio">Furniture maker turned lathe evangelist. Owns 41 chisels, needs none of them.</p>
<div class="tag-row">
<span class="tag mono">WOODTURNING</span>
<span class="tag mono">JOINERY</span>
</div>
</article>
<article class="instructor-card">
<div class="avatar" aria-hidden="true" style="--av:#d98a2b">PV</div>
<h3 class="instructor-name">Priya Venkat</h3>
<p class="instructor-bio">Runs the print farm. Can diagnose a clogged nozzle by sound alone.</p>
<div class="tag-row">
<span class="tag mono">FDM / RESIN</span>
<span class="tag mono">CAD PREP</span>
</div>
</article>
<article class="instructor-card">
<div class="avatar" aria-hidden="true" style="--av:#5b8dd6">JR</div>
<h3 class="instructor-name">Jonas Reyes</h3>
<p class="instructor-bio">Laser bay steward and kerf perfectionist. Ask him about living hinges.</p>
<div class="tag-row">
<span class="tag mono">LASER CUTTING</span>
<span class="tag mono">VECTOR PREP</span>
</div>
</article>
</div>
</section>
<!-- ============ MEMBERSHIP ============ -->
<section class="section" id="membership">
<div class="membership-banner">
<div class="membership-copy">
<p class="eyebrow mono">MEMBER CARD BB-0000 → BB-9999</p>
<h2 class="membership-title">Make it a habit.<br />Join the shop.</h2>
<p class="membership-sub">Monthly membership gets you past the front desk and onto the machines — classes are just the beginning.</p>
<ul class="perks">
<li><span class="perk-check" aria-hidden="true">✓</span> 20% off every workshop class</li>
<li><span class="perk-check" aria-hidden="true">✓</span> Open bench access, Tue–Sun until 22:00</li>
<li><span class="perk-check" aria-hidden="true">✓</span> Free filament & scrap-bin plywood allowance</li>
<li><span class="perk-check" aria-hidden="true">✓</span> Locker + tool checkout with your member card</li>
</ul>
<div class="membership-ctas">
<button class="btn btn-orange" type="button" id="joinBtn">Join for $49/mo</button>
<span class="membership-note mono">FIRST WEEK FREE · CANCEL ANYTIME</span>
</div>
</div>
<div class="member-card" aria-hidden="true">
<div class="member-card-top">
<span class="mono">BOLT & BEVEL</span>
<span class="mono">BB-4172</span>
</div>
<div class="member-card-name">A. MAKER</div>
<div class="member-card-row mono"><span>TIER</span><span>WORKSHOP+</span></div>
<div class="member-card-row mono"><span>VALID THRU</span><span>07 / 2027</span></div>
<div class="member-card-stripe"></div>
</div>
</div>
</section>
<!-- ============ FAQ ============ -->
<section class="section section-faq" id="faq">
<div class="section-head">
<div>
<p class="eyebrow mono">READ BEFORE YOU DRILL</p>
<h2 class="section-title">Frequently asked questions</h2>
</div>
</div>
<div class="faq-list">
<details class="faq-item">
<summary>
<span>Do I need any experience or my own tools?</span>
<span class="faq-icon mono" aria-hidden="true">+</span>
</summary>
<p>None at all. Beginner classes assume you have never touched the tool before, and everything — irons, gouges, PPE, materials — is provided. Intermediate classes list any prerequisites right on the card.</p>
</details>
<details class="faq-item">
<summary>
<span>What happens if a class is full?</span>
<span class="faq-icon mono" aria-hidden="true">+</span>
</summary>
<p>Every class has a waitlist. If a seat opens we email the first person on the list, who has 24 hours to claim it. Most classes repeat monthly, so a full week rarely means a long wait.</p>
</details>
<details class="faq-item">
<summary>
<span>Can I cancel or reschedule my enrollment?</span>
<span class="faq-icon mono" aria-hidden="true">+</span>
</summary>
<p>Yes — full refund up to 48 hours before class, or move to any future session of the same class for free. Inside 48 hours we can transfer your seat to a friend instead.</p>
</details>
<details class="faq-item">
<summary>
<span>Is the workshop accessible?</span>
<span class="faq-icon mono" aria-hidden="true">+</span>
</summary>
<p>The shop floor is step-free with an accessible entrance on the loading-dock side, height-adjustable benches at stations A and B, and reserved parking. Tell us what you need when enrolling and we will set up your bench before you arrive.</p>
</details>
</div>
</section>
<!-- ============ FOOTER ============ -->
<footer class="footer">
<div class="footer-inner">
<span class="mono">© 2026 BOLT & BEVEL MAKERSPACE · UNIT 7, FOUNDRY ROW</span>
<span class="mono">MEASURE TWICE · CUT ONCE</span>
</div>
</footer>
<div class="toast mono" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Workshop Classes
A landing page for a fictional makerspace (“Bolt & Bevel”) that sells hands-on workshop classes. The hero sits on a kraft graph-paper background with a stencil-style “Learn by making” headline, safety-orange CTAs, animated stat counters, and a rotated blueprint spec-plate card for flavor. A sticky top bar links to each section.
The heart of the page is the weekly schedule: four class cards (Intro to Soldering, Woodturning Basics, 3D Printer Maintenance, Laser Cutting 101), each with a navy date block, level badge, mono meta line, price, and a seats-left meter. The meter is green when plenty of seats remain, amber when a third or less are left, and red at zero. Clicking “Enroll” decrements the count, repaints the meter, fires a toast, and disables the button when the class fills. Filter chips above the list narrow it to Beginner or Intermediate and update a live “showing X of Y” counter.
Below, an instructors row on a dark blueprint-grid section shows avatar cards with mono specialty tags, a dashed-border membership banner lists perks with a join button, and a four-item FAQ accordion (built on <details>) closes siblings when one opens. Everything is vanilla JS and responsive down to ~360px.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.