Salon — Classic Barbershop Landing
A heritage barbershop marketing landing for the fictional Iron and Oak Barber Co., cut from an oxblood, walnut, and cream palette with a strong Roboto Slab display over an Inter UI. It pairs a sticky nav with an animated CSS barber-pole, a bold slab-serif hero, count-up stats, a filterable services board with a live ticket builder and running total, tappable barber cards that pre-fill the booking form, a dark heritage band, a validating appointment request with an open-or-closed indicator, and tasteful toast confirmations.
MCP
コード
:root {
--oxblood: #6e2b2b;
--oxblood-d: #4f1d1d;
--walnut: #5a3e2b;
--walnut-d: #3c281a;
--cream: #f3e9d8;
--cream-d: #e6d8bf;
--ink: #241712;
--ink-2: #4a382c;
--muted: #8a755f;
--bg: #f6efe2;
--white: #fffdf8;
--gold: #b88a3e;
--gold-soft: #e7cf9e;
--line: rgba(36, 23, 18, 0.12);
--line-2: rgba(36, 23, 18, 0.22);
--ok: #4f7a55;
--danger: #b3503e;
--serif: "Roboto Slab", Georgia, "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 20px;
--sh-sm: 0 1px 2px rgba(36, 23, 18, 0.08), 0 2px 8px rgba(36, 23, 18, 0.06);
--sh-md: 0 6px 22px rgba(36, 23, 18, 0.12);
--sh-lg: 0 18px 48px rgba(36, 23, 18, 0.22);
--wrap: 1140px;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
font-size: 16px;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
overflow-x: hidden;
}
h1, h2, h3 { font-family: var(--serif); font-weight: 700; line-height: 1.08; margin: 0; color: var(--ink); }
p { margin: 0; }
img { max-width: 100%; display: block; }
a { color: inherit; }
.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }
.skip-link {
position: absolute; left: -9999px; top: 8px; z-index: 200;
background: var(--oxblood); color: var(--cream);
padding: 10px 16px; border-radius: var(--r-sm); font-weight: 600;
}
.skip-link:focus { left: 16px; }
.eyebrow {
font-family: var(--sans); font-weight: 700; font-size: 11px;
letter-spacing: 0.22em; text-transform: uppercase; color: var(--oxblood);
margin: 0 0 14px;
}
.eyebrow--cream { color: var(--gold-soft); }
:focus-visible { outline: 2px solid var(--oxblood); outline-offset: 3px; border-radius: 4px; }
/* ===== barber pole ===== */
.pole {
position: relative; display: inline-block;
width: 13px; height: 34px; border-radius: 7px;
background: #1b120c;
border: 2px solid #d8c298;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.4), var(--sh-sm);
overflow: hidden; flex: none;
}
.pole--sm { width: 11px; height: 28px; }
.pole__stripes {
position: absolute; inset: 2px;
border-radius: 5px; overflow: hidden;
}
.pole__stripes::before {
content: ""; position: absolute; inset: -50% -50%;
background: repeating-linear-gradient(
-55deg,
var(--cream) 0 6px,
var(--oxblood) 6px 12px,
#2a4a8c 12px 18px,
var(--cream) 18px 24px
);
animation: pole 2.2s linear infinite;
}
@keyframes pole { to { transform: translateY(33%); } }
@media (prefers-reduced-motion: reduce) { .pole__stripes::before { animation: none; } }
/* ===== buttons ===== */
.btn {
--bg-btn: var(--oxblood); --fg-btn: var(--cream);
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
font-family: var(--sans); font-weight: 600; font-size: 14px;
letter-spacing: 0.02em;
background: var(--bg-btn); color: var(--fg-btn);
border: 1px solid transparent; border-radius: var(--r-sm);
padding: 11px 20px; cursor: pointer; text-decoration: none;
transition: transform .15s ease, background .2s ease, box-shadow .2s ease, color .2s ease;
box-shadow: var(--sh-sm);
}
.btn:hover { background: var(--oxblood-d); transform: translateY(-1px); box-shadow: var(--sh-md); }
.btn:active { transform: translateY(0); }
.btn--lg { padding: 14px 26px; font-size: 15px; }
.btn--sm { padding: 8px 14px; font-size: 12.5px; }
.btn--block { width: 100%; }
.btn--ghost {
background: transparent; color: var(--oxblood);
border-color: var(--line-2); box-shadow: none;
}
.btn--ghost:hover { background: rgba(110,43,43,.07); color: var(--oxblood-d); box-shadow: none; }
.btn--outline {
background: transparent; color: var(--oxblood); border-color: var(--oxblood);
box-shadow: none;
}
.btn--outline:hover { background: var(--oxblood); color: var(--cream); }
.btn--cream {
color: var(--cream); border-color: var(--gold-soft);
}
.btn--cream:hover { background: var(--cream); color: var(--oxblood-d); }
.tag {
display: inline-flex; align-items: center;
font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
color: var(--walnut); background: rgba(90,62,43,.08);
border: 1px solid var(--line); border-radius: 999px;
padding: 4px 11px;
}
.tag--gold { color: var(--gold); background: rgba(184,138,62,.1); border-color: rgba(184,138,62,.3); }
/* ===== nav ===== */
.nav {
position: sticky; top: 0; z-index: 100;
background: rgba(246, 239, 226, 0.82);
backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
border-bottom: 1px solid transparent;
transition: border-color .25s ease, box-shadow .25s ease, background .25s ease;
}
.nav.is-stuck { border-color: var(--line); box-shadow: var(--sh-sm); background: rgba(246,239,226,.93); }
.nav__inner { display: flex; align-items: center; justify-content: space-between; height: 70px; }
.brand { display: inline-flex; align-items: center; gap: 12px; text-decoration: none; }
.brand__name {
font-family: var(--serif); font-weight: 800; font-size: 19px; line-height: 1;
color: var(--ink); display: flex; flex-direction: column; gap: 2px;
}
.brand__name small {
font-family: var(--sans); font-weight: 600; font-size: 9.5px;
letter-spacing: 0.26em; text-transform: uppercase; color: var(--muted);
}
.nav__links { display: flex; align-items: center; gap: 28px; }
.nav__links a {
text-decoration: none; font-weight: 600; font-size: 14px; color: var(--ink-2);
transition: color .18s ease;
}
.nav__links a:not(.btn):hover { color: var(--oxblood); }
.nav__links a.is-active:not(.btn) { color: var(--oxblood); }
.nav__toggle {
display: none; flex-direction: column; gap: 5px;
background: none; border: 0; cursor: pointer; padding: 8px;
}
.nav__toggle span { width: 24px; height: 2px; background: var(--ink); border-radius: 2px; transition: transform .2s ease, opacity .2s ease; }
.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); }
/* ===== reveal ===== */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity .6s ease, transform .6s ease; }
.reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; } }
/* ===== hero ===== */
.hero {
position: relative;
background:
radial-gradient(1100px 520px at 78% -10%, rgba(110,43,43,.16), transparent 60%),
radial-gradient(800px 460px at 8% 12%, rgba(90,62,43,.14), transparent 60%),
var(--bg);
padding: 88px 0 0;
overflow: hidden;
}
.hero__grain { position: absolute; inset: 0; pointer-events: none; opacity: .5; mix-blend-mode: multiply;
background-image:
repeating-linear-gradient(0deg, rgba(36,23,18,.025) 0 2px, transparent 2px 4px),
repeating-linear-gradient(90deg, rgba(36,23,18,.02) 0 3px, transparent 3px 6px);
}
.hero__inner { position: relative; text-align: center; padding-bottom: 64px; }
.hero__title {
font-size: clamp(54px, 11vw, 122px); font-weight: 800; letter-spacing: -0.02em;
margin: 6px 0 22px; text-transform: uppercase;
text-shadow: 0 1px 0 rgba(255,253,248,.6);
}
.hero__year {
position: relative; color: var(--oxblood);
}
.hero__year::after {
content: ""; position: absolute; left: 4%; right: 4%; bottom: 6%;
height: 6px; background: var(--gold); opacity: .9; border-radius: 3px;
}
.hero__lede {
max-width: 560px; margin: 0 auto; color: var(--ink-2); font-size: 18px;
}
.hero__cta { display: flex; gap: 14px; justify-content: center; margin: 30px 0 48px; flex-wrap: wrap; }
.hero__stats {
display: flex; justify-content: center; gap: 0; list-style: none; margin: 0; padding: 0;
border: 1px solid var(--line); border-radius: var(--r-md); background: var(--white);
box-shadow: var(--sh-sm); max-width: 640px; margin: 0 auto; overflow: hidden;
}
.hero__stats li { flex: 1; padding: 22px 18px; text-align: center; border-left: 1px solid var(--line); }
.hero__stats li:first-child { border-left: 0; }
.hero__stats b {
display: block; font-family: var(--serif); font-weight: 700;
font-size: clamp(28px, 5vw, 40px); color: var(--oxblood); line-height: 1;
}
.hero__stats span { display: block; margin-top: 8px; font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.hero__rule { height: 6px; background:
repeating-linear-gradient(-45deg, var(--oxblood) 0 16px, var(--cream) 16px 22px, var(--walnut) 22px 38px, var(--cream) 38px 44px);
opacity: .92; }
/* ===== band heads ===== */
.band-head { text-align: center; max-width: 640px; margin: 0 auto 38px; }
.band-head h2 { font-size: clamp(32px, 5vw, 46px); letter-spacing: -0.01em; }
.band-sub { color: var(--muted); margin-top: 12px; font-size: 16px; }
/* ===== services ===== */
.services { padding: 84px 0; }
.filters { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-bottom: 36px; }
.chip {
font-family: var(--sans); font-weight: 600; font-size: 13px;
padding: 9px 18px; border-radius: 999px; cursor: pointer;
background: var(--white); color: var(--ink-2);
border: 1px solid var(--line-2);
transition: background .18s ease, color .18s ease, border-color .18s ease;
}
.chip:hover { border-color: var(--oxblood); color: var(--oxblood); }
.chip.is-active { background: var(--oxblood); color: var(--cream); border-color: var(--oxblood); }
.svc-grid {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px;
}
.svc {
position: relative; background: var(--white);
border: 1px solid var(--line); border-radius: var(--r-md);
padding: 26px 24px 22px; box-shadow: var(--sh-sm);
transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
overflow: hidden;
}
.svc::before {
content: ""; position: absolute; left: 0; right: 0; top: 0; height: 4px;
background: linear-gradient(90deg, var(--oxblood), var(--gold));
transform: scaleX(0); transform-origin: left; transition: transform .28s ease;
}
.svc:hover { transform: translateY(-5px); box-shadow: var(--sh-md); border-color: var(--line-2); }
.svc:hover::before { transform: scaleX(1); }
.svc.is-hidden { display: none; }
.svc__top { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; margin-bottom: 10px; }
.svc__top h3 { font-size: 22px; }
.svc__price { font-family: var(--serif); font-weight: 700; font-size: 22px; color: var(--oxblood); white-space: nowrap; }
.svc p { color: var(--ink-2); font-size: 14.5px; margin-bottom: 18px; }
.svc__meta { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
/* ===== barbers ===== */
.barbers { padding: 0 0 84px; }
.barber-strip {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px;
}
.barber {
display: flex; align-items: center; gap: 16px;
background: var(--walnut-d); color: var(--cream);
border: 1px solid rgba(231,207,158,.18); border-radius: var(--r-md);
padding: 20px; cursor: pointer;
transition: transform .2s ease, box-shadow .2s ease, background .2s ease;
box-shadow: var(--sh-sm);
}
.barber:hover, .barber:focus-visible { transform: translateY(-4px); box-shadow: var(--sh-md); background: var(--walnut); }
.barber.is-picked { box-shadow: 0 0 0 2px var(--gold), var(--sh-md); background: var(--walnut); }
.barber__mono {
flex: none; width: 50px; height: 50px; border-radius: 50%;
display: grid; place-items: center;
font-family: var(--serif); font-weight: 700; font-size: 18px;
color: var(--cream); background: var(--oxblood);
border: 1px solid var(--gold-soft);
}
.barber h3 { font-size: 17px; color: var(--cream); }
.barber p { font-size: 12.5px; color: var(--gold-soft); margin: 2px 0 8px; }
/* ===== heritage ===== */
.heritage {
position: relative; overflow: hidden;
background:
radial-gradient(700px 400px at 85% 0%, rgba(184,138,62,.16), transparent 60%),
linear-gradient(180deg, var(--oxblood-d), #3a1414);
color: var(--cream); padding: 84px 0;
}
.heritage__grain { position: absolute; inset: 0; pointer-events: none; opacity: .35; mix-blend-mode: overlay;
background-image: repeating-linear-gradient(0deg, rgba(255,255,255,.04) 0 2px, transparent 2px 5px); }
.heritage__inner { position: relative; display: grid; grid-template-columns: 1.2fr 1fr; gap: 56px; align-items: center; }
.heritage h2 { color: var(--cream); font-size: clamp(30px, 4.4vw, 44px); margin-bottom: 18px; }
.heritage__copy p { color: rgba(243,233,216,.86); margin-bottom: 14px; font-size: 16px; max-width: 52ch; }
.heritage__copy .btn { margin-top: 14px; }
.heritage__quote {
margin: 0; padding: 30px 32px; border-left: 3px solid var(--gold);
background: rgba(0,0,0,.18); border-radius: var(--r-md);
}
.heritage__quote p { font-family: var(--serif); font-size: 26px; font-weight: 500; line-height: 1.3; color: var(--cream); }
.heritage__quote footer { margin-top: 16px; font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: var(--gold-soft); }
/* ===== book ===== */
.book { padding: 84px 0; }
.book__inner { display: grid; grid-template-columns: 1fr 1.05fr; gap: 56px; align-items: start; }
.book__copy h2 { font-size: clamp(32px, 5vw, 46px); }
.book__copy .band-sub { text-align: left; margin: 14px 0 26px; }
.book__facts { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.book__facts li {
display: flex; align-items: center; justify-content: space-between;
padding: 13px 0; border-bottom: 1px solid var(--line); font-size: 15px;
}
.book__facts b { font-weight: 600; }
.book__facts span { color: var(--muted); }
.book__open { gap: 10px; }
.book__open .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }
.book__open .dot.is-open { background: var(--ok); box-shadow: 0 0 0 4px rgba(79,122,85,.18); }
.book__open .dot.is-closed { background: var(--danger); box-shadow: 0 0 0 4px rgba(179,80,62,.18); }
.book__open b { color: var(--ink); }
.book__form {
background: var(--white); border: 1px solid var(--line);
border-radius: var(--r-lg); padding: 30px; box-shadow: var(--sh-md);
}
.ticket {
border: 1px dashed var(--line-2); border-radius: var(--r-md);
padding: 16px 18px; margin-bottom: 22px; background: var(--cream);
}
.ticket__head { font-family: var(--serif); font-weight: 700; font-size: 14px; letter-spacing: .14em; text-transform: uppercase; color: var(--walnut); margin-bottom: 10px; }
.ticket ul { list-style: none; margin: 0 0 10px; padding: 0; }
.ticket li { display: flex; justify-content: space-between; align-items: center; gap: 10px; font-size: 14px; padding: 5px 0; border-bottom: 1px dotted var(--line); }
.ticket li button {
background: none; border: 0; cursor: pointer; color: var(--danger);
font-size: 16px; line-height: 1; padding: 0 4px; font-weight: 700;
}
.ticket__total { display: flex; justify-content: space-between; align-items: baseline; padding-top: 8px; }
.ticket__total span { font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.ticket__total b { font-family: var(--serif); font-size: 22px; color: var(--oxblood); }
.field { margin-bottom: 16px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field label { display: block; font-size: 12px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--walnut); margin-bottom: 7px; }
.field input, .field select {
width: 100%; font-family: var(--sans); font-size: 15px; color: var(--ink);
background: var(--bg); border: 1px solid var(--line-2); border-radius: var(--r-sm);
padding: 11px 13px; transition: border-color .18s ease, box-shadow .18s ease;
}
.field input:focus, .field select:focus { outline: none; border-color: var(--oxblood); box-shadow: 0 0 0 3px rgba(110,43,43,.13); }
.field.has-error input, .field.has-error select { border-color: var(--danger); box-shadow: 0 0 0 3px rgba(179,80,62,.13); }
.err { display: block; min-height: 15px; margin-top: 5px; font-size: 12px; color: var(--danger); }
.book__form .btn { margin-top: 6px; }
.book__fine { text-align: center; font-size: 12px; color: var(--muted); margin-top: 14px; }
/* ===== footer ===== */
.foot { background: var(--walnut-d); color: var(--cream); padding: 44px 0; }
.foot__inner { display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; }
.foot__brand { display: inline-flex; align-items: center; gap: 12px; }
.foot__brand .brand__name { color: var(--cream); }
.foot__brand .brand__name small { color: var(--gold-soft); }
.foot__addr { font-size: 14px; color: var(--gold-soft); }
.foot__copy { font-size: 12.5px; color: rgba(243,233,216,.6); }
/* ===== toast ===== */
.toast {
position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 24px);
background: var(--ink); color: var(--cream);
padding: 14px 22px; border-radius: var(--r-sm);
font-size: 14px; font-weight: 500; box-shadow: var(--sh-lg);
border-left: 3px solid var(--gold);
opacity: 0; pointer-events: none; transition: opacity .3s ease, transform .3s ease;
z-index: 300; max-width: calc(100vw - 32px);
}
.toast.is-show { opacity: 1; transform: translate(-50%, 0); }
/* ===== responsive ===== */
@media (max-width: 900px) {
.svc-grid { grid-template-columns: repeat(2, 1fr); }
.barber-strip { grid-template-columns: repeat(2, 1fr); }
.heritage__inner, .book__inner { grid-template-columns: 1fr; gap: 36px; }
}
@media (max-width: 760px) {
.nav__toggle { display: flex; }
.nav__links {
position: absolute; top: 70px; left: 0; right: 0;
flex-direction: column; align-items: stretch; gap: 0;
background: var(--bg); border-bottom: 1px solid var(--line);
box-shadow: var(--sh-md);
padding: 8px 24px 18px;
transform: translateY(-12px); opacity: 0; pointer-events: none;
transition: opacity .22s ease, transform .22s ease;
}
.nav__links.is-open { transform: none; opacity: 1; pointer-events: auto; }
.nav__links a:not(.btn) { padding: 13px 0; border-bottom: 1px solid var(--line); }
.nav__links .btn { margin-top: 12px; }
}
@media (max-width: 520px) {
.wrap { padding: 0 18px; }
.hero { padding-top: 56px; }
.hero__title { font-size: clamp(46px, 16vw, 70px); }
.hero__lede { font-size: 16px; }
.hero__cta { gap: 10px; }
.hero__cta .btn { flex: 1; }
.hero__stats { flex-direction: column; }
.hero__stats li { border-left: 0; border-top: 1px solid var(--line); }
.hero__stats li:first-child { border-top: 0; }
.services, .barbers, .heritage, .book { padding-left: 0; padding-right: 0; }
.services { padding: 60px 0; }
.barbers { padding-bottom: 60px; }
.heritage, .book { padding: 60px 0; }
.svc-grid, .barber-strip { grid-template-columns: 1fr; }
.field-row { grid-template-columns: 1fr; }
.book__form { padding: 22px; }
.heritage__quote { padding: 22px; }
.heritage__quote p { font-size: 22px; }
}
/* Visibility guard: honor the [hidden] attribute over base display */
.ticket[hidden] {
display: none;
}(function () {
"use strict";
/* ---------- toast ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2800);
}
/* ---------- sticky nav shadow ---------- */
var nav = document.getElementById("nav");
function onScroll() {
if (window.scrollY > 8) nav.classList.add("is-stuck");
else nav.classList.remove("is-stuck");
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ---------- mobile nav ---------- */
var toggle = document.getElementById("navToggle");
var navLinks = document.getElementById("navLinks");
function closeNav() {
navLinks.classList.remove("is-open");
toggle.setAttribute("aria-expanded", "false");
toggle.setAttribute("aria-label", "Open menu");
}
toggle.addEventListener("click", function () {
var open = navLinks.classList.toggle("is-open");
toggle.setAttribute("aria-expanded", String(open));
toggle.setAttribute("aria-label", open ? "Close menu" : "Open menu");
});
navLinks.querySelectorAll("a").forEach(function (a) {
a.addEventListener("click", closeNav);
});
/* ---------- reveal on scroll ---------- */
var reveals = document.querySelectorAll(".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"); });
}
/* ---------- active nav link ---------- */
var sections = ["services", "barbers", "heritage", "book"]
.map(function (id) { return document.getElementById(id); })
.filter(Boolean);
var linkFor = {};
navLinks.querySelectorAll('a[href^="#"]:not(.btn)').forEach(function (a) {
linkFor[a.getAttribute("href").slice(1)] = a;
});
if ("IntersectionObserver" in window && sections.length) {
var spy = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
var link = linkFor[e.target.id];
if (!link) return;
if (e.isIntersecting) {
Object.keys(linkFor).forEach(function (k) { linkFor[k].classList.remove("is-active"); });
link.classList.add("is-active");
}
});
},
{ threshold: 0.4 }
);
sections.forEach(function (s) { spy.observe(s); });
}
/* ---------- count-up stats ---------- */
function animateCount(el) {
var target = parseInt(el.getAttribute("data-count"), 10) || 0;
var suffix = el.getAttribute("data-suffix") || "";
var dur = 1300, start = null;
function fmt(n) { return n.toLocaleString("en-US"); }
function step(ts) {
if (start === null) start = ts;
var p = Math.min((ts - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = fmt(Math.round(target * eased)) + suffix;
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
var counters = document.querySelectorAll("[data-count]");
if ("IntersectionObserver" in window) {
var cio = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { animateCount(e.target); cio.unobserve(e.target); }
});
},
{ threshold: 0.6 }
);
counters.forEach(function (c) { cio.observe(c); });
} else {
counters.forEach(animateCount);
}
/* ---------- service filters ---------- */
var chips = document.querySelectorAll(".chip");
var cards = document.querySelectorAll(".svc");
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
var f = chip.getAttribute("data-filter");
chips.forEach(function (c) {
var on = c === chip;
c.classList.toggle("is-active", on);
c.setAttribute("aria-selected", String(on));
});
cards.forEach(function (card) {
var match = f === "all" || card.getAttribute("data-cat") === f;
card.classList.toggle("is-hidden", !match);
});
});
});
/* ---------- ticket builder ---------- */
var ticket = [];
var ticketEl = document.getElementById("ticket");
var ticketList = document.getElementById("ticketList");
var ticketTotal = document.getElementById("ticketTotal");
function renderTicket() {
if (!ticket.length) {
ticketEl.hidden = true;
return;
}
ticketEl.hidden = false;
ticketList.innerHTML = "";
var total = 0;
ticket.forEach(function (item, i) {
total += item.cost;
var li = document.createElement("li");
var label = document.createElement("span");
label.textContent = item.name + " · $" + item.cost;
var rm = document.createElement("button");
rm.type = "button";
rm.setAttribute("aria-label", "Remove " + item.name);
rm.textContent = "×";
rm.addEventListener("click", function () {
ticket.splice(i, 1);
renderTicket();
});
li.appendChild(label);
li.appendChild(rm);
ticketList.appendChild(li);
});
ticketTotal.textContent = "$" + total;
}
document.querySelectorAll("[data-add]").forEach(function (btn) {
btn.addEventListener("click", function () {
var name = btn.getAttribute("data-add");
var cost = parseInt(btn.getAttribute("data-cost"), 10) || 0;
if (ticket.some(function (t) { return t.name === name; })) {
toast(name + " is already on your ticket.");
return;
}
ticket.push({ name: name, cost: cost });
renderTicket();
toast("Added " + name + " to your ticket.");
});
});
/* ---------- barber picker ---------- */
var barberSelect = document.getElementById("fBarber");
function pickBarber(name, node) {
document.querySelectorAll(".barber").forEach(function (b) { b.classList.remove("is-picked"); });
if (node) node.classList.add("is-picked");
if (barberSelect) {
var found = Array.prototype.some.call(barberSelect.options, function (o) {
if (o.value === name || o.text === name) { barberSelect.value = o.value || o.text; return true; }
return false;
});
if (!found) barberSelect.value = name;
}
toast(name + " is your barber. Finish booking below.");
}
document.querySelectorAll(".barber").forEach(function (b) {
function act() { pickBarber(b.getAttribute("data-barber"), b); }
b.addEventListener("click", act);
b.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); act(); }
});
});
/* ---------- open / closed indicator ---------- */
var openDot = document.getElementById("openDot");
var openLabel = document.getElementById("openLabel");
function updateOpen() {
var now = new Date();
var day = now.getDay(); // 0 Sun .. 6 Sat
var hour = now.getHours() + now.getMinutes() / 60;
var open = false;
if (day >= 1 && day <= 5) open = hour >= 9 && hour < 19;
else if (day === 6) open = hour >= 8 && hour < 17;
openDot.classList.remove("is-open", "is-closed");
if (open) {
openDot.classList.add("is-open");
openLabel.textContent = "Open now — the pole is spinning";
} else {
openDot.classList.add("is-closed");
openLabel.textContent = day === 0 ? "Closed Sundays — see you Monday" : "Closed now — back during shop hours";
}
}
if (openDot && openLabel) { updateOpen(); setInterval(updateOpen, 60000); }
/* ---------- date min = today ---------- */
var dateInput = document.getElementById("fDate");
if (dateInput) {
var t = new Date();
var iso = t.getFullYear() + "-" + String(t.getMonth() + 1).padStart(2, "0") + "-" + String(t.getDate()).padStart(2, "0");
dateInput.min = iso;
}
/* ---------- form validation ---------- */
var form = document.getElementById("bookForm");
function setError(id, msg) {
var field = document.getElementById(id).closest(".field");
var err = form.querySelector('.err[data-for="' + id + '"]');
field.classList.toggle("has-error", !!msg);
if (err) err.textContent = msg || "";
}
function validPhone(v) {
var digits = v.replace(/\D/g, "");
return digits.length >= 10;
}
form.addEventListener("submit", function (e) {
e.preventDefault();
var ok = true;
var name = document.getElementById("fName").value.trim();
var phone = document.getElementById("fPhone").value.trim();
var date = document.getElementById("fDate").value;
if (!name) { setError("fName", "Tell us your name."); ok = false; } else setError("fName", "");
if (!phone) { setError("fPhone", "We need a number to confirm."); ok = false; }
else if (!validPhone(phone)) { setError("fPhone", "Enter a valid phone number."); ok = false; }
else setError("fPhone", "");
if (!date) { setError("fDate", "Pick a date."); ok = false; }
else if (date < dateInput.min) { setError("fDate", "Choose today or later."); ok = false; }
else setError("fDate", "");
setError("fBarber", "");
if (!ok) {
toast("Please fix the highlighted fields.");
var firstErr = form.querySelector(".has-error input, .has-error select");
if (firstErr) firstErr.focus();
return;
}
var barber = barberSelect.value || "the next available barber";
var extra = ticket.length ? " (" + ticket.length + " item" + (ticket.length > 1 ? "s" : "") + " on ticket)" : "";
toast("Booked, " + name + "! " + barber + " on " + date + extra + ". We'll text to confirm.");
form.reset();
ticket = [];
renderTicket();
document.querySelectorAll(".barber").forEach(function (b) { b.classList.remove("is-picked"); });
if (dateInput) dateInput.min = iso;
});
/* clear field error as user types */
["fName", "fPhone", "fDate", "fBarber"].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.addEventListener("input", function () { setError(id, ""); });
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Home · Iron & Oak Barber Co.</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=Roboto+Slab:wght@400;500;600;700;800&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="nav">
<div class="wrap nav__inner">
<a class="brand" href="#top" aria-label="Iron and Oak Barber Co. home">
<span class="pole" aria-hidden="true"><span class="pole__stripes"></span></span>
<span class="brand__name">Iron & Oak<small>Barber Co.</small></span>
</a>
<nav class="nav__links" id="navLinks" aria-label="Primary">
<a href="#services">Services</a>
<a href="#barbers">Barbers</a>
<a href="#heritage">Heritage</a>
<a href="#book">Visit</a>
<a class="btn btn--sm btn--ghost" href="#book" data-book>Book a Chair</a>
</nav>
<button class="nav__toggle" id="navToggle" aria-expanded="false" aria-controls="navLinks" aria-label="Open menu">
<span></span><span></span><span></span>
</button>
</div>
</header>
<main id="main">
<span id="top"></span>
<!-- ===== HERO ===== -->
<section class="hero" id="hero">
<div class="hero__grain" aria-hidden="true"></div>
<div class="wrap hero__inner">
<p class="eyebrow reveal">Est. 1962 · Walnut Street</p>
<h1 class="hero__title reveal">Sharp<br />since <span class="hero__year">1962</span></h1>
<p class="hero__lede reveal">A traditional cut, a hot-towel shave, and a straight razor in steady hands. No apps at the chair — just the smell of bay rum and a good story.</p>
<div class="hero__cta reveal">
<a class="btn btn--lg" href="#book" data-book>Book a Chair</a>
<a class="btn btn--lg btn--outline" href="tel:+15551962620">Call the Shop</a>
</div>
<ul class="hero__stats reveal" aria-label="Shop facts">
<li><b data-count="62" data-suffix="">0</b><span>Years on the block</span></li>
<li><b data-count="6" data-suffix="">0</b><span>Master barbers</span></li>
<li><b data-count="41000" data-suffix="+">0</b><span>Cuts & shaves</span></li>
</ul>
</div>
<div class="hero__rule" aria-hidden="true"></div>
</section>
<!-- ===== SERVICES ===== -->
<section class="services" id="services">
<div class="wrap">
<header class="band-head reveal">
<p class="eyebrow">The Menu</p>
<h2>Cuts, shaves & the works</h2>
<p class="band-sub">Walk in or book ahead. Add an item to your ticket and we will have the chair warm.</p>
</header>
<div class="filters reveal" role="tablist" aria-label="Filter services">
<button class="chip is-active" role="tab" aria-selected="true" data-filter="all">All</button>
<button class="chip" role="tab" aria-selected="false" data-filter="cut">Cuts</button>
<button class="chip" role="tab" aria-selected="false" data-filter="shave">Shaves</button>
<button class="chip" role="tab" aria-selected="false" data-filter="beard">Beard</button>
</div>
<ul class="svc-grid" id="svcGrid">
<li class="svc reveal" data-cat="cut">
<div class="svc__top"><h3>The Classic Cut</h3><span class="svc__price" data-price="38">$38</span></div>
<p>Scissor-over-comb, clean neckline, hot towel finish. The one your grandfather got.</p>
<div class="svc__meta"><span class="tag">45 min</span><button class="btn btn--sm btn--ghost" data-add="The Classic Cut" data-cost="38">Add to ticket</button></div>
</li>
<li class="svc reveal" data-cat="cut">
<div class="svc__top"><h3>Skin Fade</h3><span class="svc__price" data-price="44">$44</span></div>
<p>Bald-to-blend taper, razor-finished edges, styled to your liking.</p>
<div class="svc__meta"><span class="tag">50 min</span><button class="btn btn--sm btn--ghost" data-add="Skin Fade" data-cost="44">Add to ticket</button></div>
</li>
<li class="svc reveal" data-cat="shave">
<div class="svc__top"><h3>Hot-Towel Shave</h3><span class="svc__price" data-price="42">$42</span></div>
<p>Steamed towels, badger-brush lather, straight razor, cold finish. Pure ceremony.</p>
<div class="svc__meta"><span class="tag">40 min</span><button class="btn btn--sm btn--ghost" data-add="Hot-Towel Shave" data-cost="42">Add to ticket</button></div>
</li>
<li class="svc reveal" data-cat="beard">
<div class="svc__top"><h3>Beard Sculpt</h3><span class="svc__price" data-price="29">$29</span></div>
<p>Lined, shaped and oiled with our house cedar-and-clove blend.</p>
<div class="svc__meta"><span class="tag">30 min</span><button class="btn btn--sm btn--ghost" data-add="Beard Sculpt" data-cost="29">Add to ticket</button></div>
</li>
<li class="svc reveal" data-cat="cut">
<div class="svc__top"><h3>Cut & Beard</h3><span class="svc__price" data-price="58">$58</span></div>
<p>Full reset — classic cut paired with a beard sculpt. Best value on the board.</p>
<div class="svc__meta"><span class="tag">70 min</span><button class="btn btn--sm btn--ghost" data-add="Cut & Beard" data-cost="58">Add to ticket</button></div>
</li>
<li class="svc reveal" data-cat="shave">
<div class="svc__top"><h3>The Father & Son</h3><span class="svc__price" data-price="64">$64</span></div>
<p>Two chairs, two cuts, one tradition. Bring the boy and start him right.</p>
<div class="svc__meta"><span class="tag">60 min</span><button class="btn btn--sm btn--ghost" data-add="The Father & Son" data-cost="64">Add to ticket</button></div>
</li>
</ul>
</div>
</section>
<!-- ===== BARBERS ===== -->
<section class="barbers" id="barbers">
<div class="wrap">
<header class="band-head reveal">
<p class="eyebrow">The Chair</p>
<h2>Steady hands</h2>
<p class="band-sub">Tap a barber to put their name on your ticket.</p>
</header>
<ul class="barber-strip" id="barberStrip">
<li class="barber reveal" tabindex="0" role="button" data-barber="Sal Moretti" aria-label="Choose Sal Moretti">
<span class="barber__mono" aria-hidden="true">SM</span>
<div><h3>Sal Moretti</h3><p>Master barber · 31 yrs</p><span class="tag tag--gold">Straight-razor</span></div>
</li>
<li class="barber reveal" tabindex="0" role="button" data-barber="Theo Burke" aria-label="Choose Theo Burke">
<span class="barber__mono" aria-hidden="true">TB</span>
<div><h3>Theo Burke</h3><p>Senior barber · 14 yrs</p><span class="tag tag--gold">Fades</span></div>
</li>
<li class="barber reveal" tabindex="0" role="button" data-barber="Marcus Hale" aria-label="Choose Marcus Hale">
<span class="barber__mono" aria-hidden="true">MH</span>
<div><h3>Marcus Hale</h3><p>Barber · 9 yrs</p><span class="tag tag--gold">Beards</span></div>
</li>
<li class="barber reveal" tabindex="0" role="button" data-barber="Eddie Cross" aria-label="Choose Eddie Cross">
<span class="barber__mono" aria-hidden="true">EC</span>
<div><h3>Eddie Cross</h3><p>Barber · 6 yrs</p><span class="tag tag--gold">Classic</span></div>
</li>
</ul>
</div>
</section>
<!-- ===== HERITAGE ===== -->
<section class="heritage" id="heritage">
<div class="heritage__grain" aria-hidden="true"></div>
<div class="wrap heritage__inner">
<div class="heritage__copy reveal">
<p class="eyebrow eyebrow--cream">Our Story</p>
<h2>Three generations of the right way.</h2>
<p>Vincent Moretti opened the shop with one chair and a borrowed mirror in 1962. Six decades on, his grandson still strops the razors at close and the same brass register sits by the door.</p>
<p>We never chased the trends. We kept the towels hot, the talk honest, and the prices fair — and the line out the door took care of itself.</p>
<a class="btn btn--lg btn--outline btn--cream" href="#book" data-book>Take a chair</a>
</div>
<blockquote class="heritage__quote reveal">
<p>“A man leaves this shop looking like himself — only sharper.”</p>
<footer>— Vincent Moretti, founder</footer>
</blockquote>
</div>
</section>
<!-- ===== BOOK ===== -->
<section class="book" id="book">
<div class="wrap book__inner">
<div class="book__copy reveal">
<p class="eyebrow">Reserve</p>
<h2>Claim a chair</h2>
<p class="band-sub">Drop your details and we will confirm by text. Walk-ins always welcome when the pole is spinning.</p>
<ul class="book__facts">
<li><b>Mon–Fri</b><span>9:00 — 7:00</span></li>
<li><b>Saturday</b><span>8:00 — 5:00</span></li>
<li><b>Sunday</b><span>Closed</span></li>
<li class="book__open"><span class="dot" id="openDot" aria-hidden="true"></span><b id="openLabel">Checking hours…</b></li>
</ul>
</div>
<form class="book__form reveal" id="bookForm" novalidate>
<div class="ticket" id="ticket" hidden>
<p class="ticket__head">Your ticket</p>
<ul id="ticketList"></ul>
<div class="ticket__total"><span>Estimated total</span><b id="ticketTotal">$0</b></div>
</div>
<div class="field">
<label for="fName">Name</label>
<input id="fName" name="name" type="text" autocomplete="name" required />
<small class="err" data-for="fName"></small>
</div>
<div class="field">
<label for="fPhone">Phone</label>
<input id="fPhone" name="phone" type="tel" inputmode="tel" autocomplete="tel" required placeholder="(555) 123-4567" />
<small class="err" data-for="fPhone"></small>
</div>
<div class="field-row">
<div class="field">
<label for="fBarber">Barber</label>
<select id="fBarber" name="barber" required>
<option value="">No preference</option>
<option>Sal Moretti</option>
<option>Theo Burke</option>
<option>Marcus Hale</option>
<option>Eddie Cross</option>
</select>
<small class="err" data-for="fBarber"></small>
</div>
<div class="field">
<label for="fDate">Date</label>
<input id="fDate" name="date" type="date" required />
<small class="err" data-for="fDate"></small>
</div>
</div>
<button class="btn btn--lg btn--block" type="submit">Request appointment</button>
<p class="book__fine">No deposit. Cancel anytime by text.</p>
</form>
</div>
</section>
</main>
<!-- ===== FOOTER ===== -->
<footer class="foot">
<div class="wrap foot__inner">
<div class="foot__brand">
<span class="pole pole--sm" aria-hidden="true"><span class="pole__stripes"></span></span>
<span class="brand__name">Iron & Oak<small>Barber Co. · Est. 1962</small></span>
</div>
<p class="foot__addr">214 Walnut Street, Cedar Hollow · (555) 196-2620</p>
<p class="foot__copy">© 1962—2026 Iron & Oak Barber Co. Built by hand.</p>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Classic Barbershop Landing
A full marketing landing for the fictional Iron and Oak Barber Co., built around a vintage, masculine palette of oxblood, walnut, and cream with a heavy Roboto Slab display set over a clean Inter UI. A translucent sticky nav carries a hand-built CSS barber-pole that endlessly spins, gains a hairline and shadow on scroll, and highlights the active section as you move down the page. The slab-serif hero shouts “Sharp since 1962” over a softly grained, oxblood-washed backdrop, with Book and Call calls-to-action and a row of stats that count up the first time they enter view.
Below the fold, a filterable services board lists cuts, fades, hot-towel shaves, and beard work with prices; chips switch between categories, and any service can be added to a live ticket that tracks line items and a running estimated total. A strip of dark walnut barber cards is fully interactive — tapping any barber marks them as picked and pre-selects them in the booking form. A dark heritage band tells the three-generation story with a founder’s pull-quote and angled barber-pole rules throughout.
Every section reveals on scroll via IntersectionObserver, the mobile nav collapses into an accessible toggle, and a live open-or-closed indicator reads the real clock against shop hours. The appointment request validates name, phone, and date inline, folds in your ticket and chosen barber, and confirms with a tasteful toast. It is pure vanilla HTML, CSS, and JavaScript — no frameworks, no build step, responsive down to 360px.