Free
0
2
Brassmoor
By Luca Moretti
Brassmoor is a dark, masculine business template for barbershops and grooming studios, with a hero slider, cut styles, locations, services, a price list with opening hours, a team slider, testimonials, a gallery with lightbox and an appointment form.
Download Codebase
Commercial License<%# letstemplates: access=premium category=business %>
<!DOCTYPE html>
<html lang="en">
<head>
<script>
window.FontAwesomeConfig = { autoReplaceSvg: 'nest' };
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brassmoor | Tailwind CSS Business Template</title>
<meta name="description" content="Brassmoor is a dark, masculine Tailwind CSS business template for barbershops and grooming studios, with a hero slider, cut styles, locations, services, a price list with opening hours, a team slider, testimonials, a gallery with lightbox and an appointment form.">
<meta name="keywords" content="Brassmoor, Tailwind CSS template, HTML website template, business template, barbershop website, barber template, grooming salon, haircut prices, booking form, men's hair studio">
<meta name="author" content="Let's templates">
<link rel="canonical" href="">
<meta property="og:type" content="product">
<meta property="og:site_name" content="Let's templates">
<meta property="og:locale" content="en_US">
<meta property="og:url" content="">
<meta property="og:title" content="Brassmoor | Tailwind CSS Business Template">
<meta property="og:description" content="A dark Tailwind CSS barbershop template with a hero slider, a price list with opening hours, a team slider, a gallery and an appointment form.">
<meta property="og:image" content="">
<meta property="og:image:alt" content="Brassmoor template preview">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="">
<meta name="twitter:title" content="Brassmoor | Tailwind CSS Business Template">
<meta name="twitter:description" content="A dark Tailwind CSS barbershop template with a hero slider, a price list with opening hours, a team slider, a gallery and an appointment form.">
<meta name="twitter:image" content="">
<meta name="twitter:image:alt" content="Brassmoor template preview">
<script src="https://cdn.tailwindcss.com/3.4.17"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Teko:wght@400;500;600&family=Hind:wght@400;500;600&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: { coal: { DEFAULT: '#121416', card: '#1e2023', soft: '#26292d', line: '#34383d' }, ember: { DEFAULT: '#e85a3d', dark: '#c9452b' }, gold: '#f2a541' },
fontFamily: { sans: ['Hind', 'sans-serif'], display: ['Teko', 'sans-serif'] }
}
}
}
</script>
<style>
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: 180ms ease-out both page-fade-out; }
::view-transition-new(root) { animation: 220ms ease-in both page-fade-in; }
@keyframes page-fade-out { to { opacity: 0; } }
@keyframes page-fade-in { from { opacity: 0; } }
html { scroll-behavior: smooth; }
[hidden] { display: none !important; }
.mark { background: linear-gradient(transparent 58%, #e85a3d 58%, #e85a3d 92%, transparent 92%); padding: 0 .1em; }
.slide { transition: opacity .6s ease; }
.reveal-ready [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity .6s ease, transform .6s ease; transition-delay: var(--reveal-delay, 0ms); }
.reveal-ready [data-reveal].is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.slide { transition: none; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
</style>
</head>
<body class="bg-coal font-sans text-white antialiased">
<% active_page = "index" %>
<% nav = { "index" => "Home", "about" => "About Us", "services" => "Services", "gallery" => "Gallery", "contact" => "Contact" } %>
<!-- block:start navbar -->
<header class="relative z-40">
<div class="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-3 px-5 pt-4 text-xs text-white/70 md:px-10">
<p class="flex flex-wrap gap-6"><span><i class="fa-solid fa-clock mr-2 text-ember" aria-hidden="true"></i>09:00 – 19:00</span><span class="hidden sm:inline"><i class="fa-solid fa-calendar-days mr-2 text-ember" aria-hidden="true"></i>Tuesday – Saturday</span></p>
<a href="tel:+16175550142" class="hover:text-ember"><i class="fa-solid fa-phone mr-2 text-ember" aria-hidden="true"></i>(617) 555-0142</a>
</div>
<nav class="mx-auto flex max-w-7xl items-center justify-between gap-4 px-5 py-5 md:px-10" aria-label="Main">
<a href="index.html" class="font-display text-3xl font-semibold uppercase leading-none tracking-wide">Brass<span class="text-ember">moor</span>.</a>
<ul class="hidden items-center gap-8 text-sm md:flex">
<% nav.each do |slug, label| %>
<% active = active_page == slug %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active %> class="transition <%= active ? 'font-semibold text-white' : 'text-white/70 hover:text-ember' %>"><%= label %></a></li>
<% end %>
</ul>
<button type="button" data-menu-toggle aria-controls="mobile-menu" aria-expanded="false" class="grid h-10 w-10 place-items-center border border-white/30 md:hidden" aria-label="Open menu"><i class="fa-solid fa-bars" aria-hidden="true"></i></button>
</nav>
<div id="mobile-menu" class="hidden border-y border-coal-line bg-coal-card px-5 py-4 md:hidden">
<ul class="grid gap-1 font-display text-3xl">
<% nav.each do |slug, label| %>
<% active = active_page == slug %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active %> class="block py-1 <%= active ? 'text-ember' : 'hover:text-ember' %>"><%= label %></a></li>
<% end %>
</ul>
</div>
</header>
<!-- block:end navbar -->
<% slides = [["Sharp cuts & hot shaves for", "every gentleman", "1506794778202-cad84cf45f1d", "Man with tousled hair looking straight ahead"], ["Classic fades with a", "modern edge", "1593702275687-f8b402bf1fb5", "Barber shaping a fade with clippers"], ["Beards trimmed the", "old-school way", "1517832606299-7ae9b720a186", "Barber trimming a full beard with scissors"], ["A chair, a coffee and", "an hour for you", "1585747860715-2ba37e788b70", "Row of leather barber chairs under warm lights"], ["Walk in tired,", "walk out sharp", "1534297635766-a262cdcb8ee4", "Barber with glasses cutting a client's hair"]] %>
<% styles = [["Textured crop", "1622286342621-4bd786c2447c", "Barber styling a textured crop"], ["Skin fade", "1593702275687-f8b402bf1fb5", "Fresh skin fade from the side"], ["Side part", "1508341591423-4347099e1f19", "Young man with a neat side part"], ["Curly taper", "1567894340315-735d7c361db0", "Barber tapering curly hair"]] %>
<!-- block:start hero -->
<section class="relative isolate -mt-32 pt-32" data-hero>
<svg class="pointer-events-none absolute inset-0 h-full w-full text-white/10" viewBox="0 0 1440 700" preserveAspectRatio="none" fill="none" aria-hidden="true"><path d="M0 420C260 300 420 520 720 380S1180 180 1440 260" stroke="currentColor"/><path d="M0 500C300 380 460 600 760 460S1200 260 1440 340" stroke="currentColor"/><path d="M0 120C320 220 560 40 860 140S1240 60 1440 120" stroke="currentColor"/></svg>
<div class="relative mx-auto max-w-7xl px-5 md:px-10">
<div class="relative grid min-h-[34rem] items-center lg:min-h-[38rem]">
<% slides.each_with_index do |(line_one, line_two, photo, alt), index| %>
<div class="slide absolute inset-0 grid items-center <%= index.zero? ? 'opacity-100' : 'pointer-events-none opacity-0' %>" data-slide aria-hidden="<%= !index.zero? %>">
<img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=1100&h=900&q=80" alt="<%= alt %>" class="absolute bottom-0 right-0 -z-10 h-full w-full object-cover opacity-50 [mask-image:linear-gradient(to_left,black_45%,transparent)] md:w-3/4 md:opacity-80">
<div class="max-w-xl">
<<%= index.zero? ? 'h1' : 'p' %> class="font-display text-5xl font-medium leading-[1.05] md:text-7xl"><%= line_one.html_safe %> <span class="mark"><%= line_two %></span></<%= index.zero? ? 'h1' : 'p' %>>
<p class="mt-6 max-w-md text-white/70">Precision cuts, hot towels and a proper conversation. Book your chair and leave looking like the best version of you.</p>
<a href="contact.html" class="mt-8 inline-block bg-ember px-7 py-4 text-xs font-semibold uppercase tracking-widest transition hover:bg-white hover:text-coal" tabindex="<%= index.zero? ? 0 : -1 %>">Book appointment</a>
</div>
</div>
<% end %>
<p class="absolute right-16 top-1/2 hidden -translate-y-1/2 font-display text-7xl font-semibold md:block" aria-live="polite"><span data-slide-index>01</span> <span class="text-3xl text-white/50">/ 0<%= slides.size %></span></p>
<div class="absolute right-0 top-1/2 flex -translate-y-1/2 flex-col gap-5" role="group" aria-label="Choose a slide">
<% slides.each_with_index do |slide, index| %>
<button type="button" data-slide-dot="<%= index %>" aria-pressed="<%= index.zero? %>" class="group grid h-5 w-5 place-items-center rounded-full transition aria-pressed:ring-2 aria-pressed:ring-white" aria-label="Show slide <%= index + 1 %>"><span class="h-2 w-2 rounded-full bg-white/50 group-aria-pressed:bg-ember"></span></button>
<% end %>
</div>
</div>
<ul class="relative grid grid-cols-2 gap-x-6 gap-y-4 bg-coal-soft/90 p-5 backdrop-blur md:grid-cols-4 md:p-6 lg:mx-10">
<% styles.each do |(name, photo, alt)| %>
<li><a href="gallery.html" class="group flex items-center gap-4"><img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=200&h=200&q=70" alt="<%= alt %>" class="h-16 w-16 object-cover transition group-hover:ring-2 group-hover:ring-ember md:h-20 md:w-20"><span class="text-sm transition group-hover:text-ember"><%= name %></span></a></li>
<% end %>
</ul>
</div>
</section>
<!-- block:end hero -->
<!-- block:start stats -->
<section class="mx-auto max-w-7xl px-5 py-20 md:px-10 md:py-24">
<div class="grid gap-12 lg:grid-cols-[1.4fr_1fr] lg:items-center">
<div data-reveal>
<h2 class="font-display text-5xl font-medium leading-[1.05] md:text-6xl">A barbershop is more than a trade, it's our <span class="mark">calling!</span></h2>
<p class="mt-6 max-w-lg text-white/70">Three generations of barbers taught us the same lesson: a good cut is a craft, and a good chair is a place where people feel heard.</p>
</div>
<dl class="grid gap-6">
<% [["18", "Years in the chair"], ["07", "Master barbers"]].each_with_index do |(value, label), index| %>
<div class="flex items-center gap-5" data-reveal style="--reveal-delay: <%= index * 100 %>ms"><dt class="order-2 font-display text-2xl"><%= label %></dt><dd class="font-display text-7xl font-semibold leading-none text-ember"><span data-count="<%= value %>"><%= value %></span></dd></div>
<% end %>
</dl>
</div>
<div class="mt-16 flex items-center gap-6">
<span class="h-px flex-1 bg-white/15"></span>
<div class="text-center">
<p class="flex justify-center gap-2 text-3xl text-gold"><% 5.times do %><i class="fa-solid fa-star" aria-hidden="true"></i><% end %><span class="sr-only">Rated 5 out of 5</span></p>
<p class="mt-2 font-display text-xl text-white/70"><span data-count="4280">4280</span> reviews</p>
</div>
<span class="h-px flex-1 bg-white/15"></span>
</div>
</section>
<!-- block:end stats -->
<% shops = [["Downtown chair", "212 Hanover St, Boston, MA 02113"], ["Southside studio", "48 Dorchester Ave, Boston, MA 02127"], ["Harbor corner", "9 Seaport Blvd, Boston, MA 02210"]] %>
<!-- block:start about -->
<section class="mx-auto max-w-7xl px-5 md:px-10">
<div class="relative isolate overflow-hidden px-6 pb-8 pt-20 text-center md:px-12 md:pt-28">
<img src="https://images.unsplash.com/photo-1503951914875-452162b0f3f1?auto=format&fit=crop&w=1600&h=900&q=75" alt="Barber giving a straight-razor shave in a dim shop" class="absolute inset-0 -z-10 h-full w-full object-cover opacity-50">
<div class="absolute inset-0 -z-10 bg-gradient-to-b from-coal/40 to-coal/80"></div>
<h2 class="font-display text-5xl font-medium">Since 2008</h2>
<p class="mx-auto mt-3 max-w-xl text-white/80">What started as one chair in a borrowed basement is now three shops across the city — same clippers, same coffee, same welcome.</p>
<a href="about.html" class="mt-8 inline-block bg-ember px-7 py-4 text-xs font-semibold uppercase tracking-widest transition hover:bg-white hover:text-coal">Read our story</a>
<ul class="mt-16 grid gap-4 md:grid-cols-3">
<% shops.each_with_index do |(name, address), index| %>
<li class="bg-coal-soft/90 p-6 text-left backdrop-blur" data-reveal style="--reveal-delay: <%= index * 100 %>ms"><p class="flex items-center gap-3 font-display text-2xl"><i class="fa-solid fa-location-dot text-ember" aria-hidden="true"></i><%= name %></p><p class="mt-2 text-sm text-white/70"><%= address %></p></li>
<% end %>
</ul>
</div>
</section>
<!-- block:end about -->
<% services = [["Shaving", "Hot towel, straight razor and a cool balm to finish.", "fa-pump-soap"], ["Styling", "Blow-dry, texture and the right product for your hair.", "fa-wind"], ["Trimming", "Beard shaping and line-ups to keep the edges clean.", "fa-scissors"], ["Haircut", "From skin fades to long scissor cuts, done properly.", "fa-user"]] %>
<!-- block:start features -->
<section class="mx-auto max-w-7xl px-5 py-20 md:px-10 md:py-28">
<h2 class="text-center font-display text-5xl font-medium md:text-6xl">Service beyond <span class="mark">expectations</span></h2>
<ul class="mt-14 grid gap-4 sm:grid-cols-2 lg:grid-cols-4 lg:items-center">
<% services.each_with_index do |(name, text, icon), index| %>
<li class="group border border-transparent bg-coal-card p-8 text-center transition hover:-translate-y-2 hover:border-ember <%= 'lg:-translate-y-6 border-ember' if index == 1 %>" data-reveal style="--reveal-delay: <%= index * 80 %>ms">
<i class="fa-solid <%= icon %> text-4xl text-ember" aria-hidden="true"></i>
<h3 class="mt-6 font-display text-3xl uppercase"><%= name %></h3>
<p class="mt-2 text-sm text-white/70"><%= text %></p>
<a href="services.html" class="mt-6 inline-block border border-ember px-4 py-2 text-xs font-semibold uppercase tracking-widest transition hover:bg-ember <%= 'bg-ember' if index == 1 %>">Read more</a>
</li>
<% end %>
</ul>
</section>
<!-- block:end features -->
<% prices = [["Vintage cut", "$32"], ["Beard trim", "$18"], ["Cut + hot shave", "$48"], ["Modern fade", "$36"]] %>
<!-- block:start pricing -->
<section class="mx-auto grid max-w-7xl gap-12 px-5 pb-20 md:px-10 md:pb-28 lg:grid-cols-[1fr_1.2fr] lg:items-center">
<div>
<h2 class="font-display text-5xl font-medium md:text-6xl">Price list</h2>
<ul class="mt-10 grid gap-2">
<% prices.each_with_index do |(name, price), index| %>
<li class="flex items-baseline gap-3 py-3" data-reveal style="--reveal-delay: <%= index * 80 %>ms"><span class="font-display text-2xl"><%= name %></span><span class="flex-1 border-b-2 border-dashed border-white/20"></span><span class="font-display text-2xl text-ember"><%= price %></span></li>
<% end %>
</ul>
<a href="services.html" class="mt-10 inline-block bg-ember px-7 py-4 text-xs font-semibold uppercase tracking-widest transition hover:bg-white hover:text-coal">View all</a>
</div>
<div class="relative pb-24 md:pb-0">
<img src="https://images.unsplash.com/photo-1585747860715-2ba37e788b70?auto=format&fit=crop&w=1000&h=750&q=80" alt="Row of leather barber chairs under warm lights" class="ml-auto aspect-[4/3] w-full object-cover md:w-[78%]">
<div class="absolute bottom-0 left-4 w-56 bg-coal-soft/95 p-7 text-center backdrop-blur md:bottom-auto md:left-0 md:top-1/4 md:w-60">
<h3 class="font-display text-4xl">Opening hours</h3>
<p class="mt-6 text-sm text-white/70">Tuesday – Saturday</p>
<p class="mt-1 border-b border-white/15 pb-5 text-sm">09:00 – 19:00</p>
<p class="mt-5 text-sm text-white/70">Sunday & Monday closed</p>
</div>
</div>
</section>
<!-- block:end pricing -->
<% team = [["Marcus Hale", "Master barber", "Twenty years with the clippers and still the first one in every morning. Ask him about pompadours.", "1534297635766-a262cdcb8ee4", "Barber with glasses cutting hair"], ["Dario Vance", "Beard specialist", "Turns any beard into a plan. Known for hot towels and long, honest chats.", "1622287162716-f311baa1a2b8", "Bearded barber working in a colourful shop"], ["Leo Park", "Fade expert", "Clean lines and zero-to-two fades in under thirty minutes, without rushing a thing.", "1605497788044-5a32c7078486", "Barber blow-drying a smiling client"]] %>
<% strip = [["1517832606299-7ae9b720a186", "Beard trim in black and white"], ["1532710093739-9470acff878f", "Barber brushing shaving foam onto a beard"], ["1596728325488-58c87691e9af", "Barber shaving a client with a straight razor"]] %>
<!-- block:start team -->
<section class="pb-20 md:pb-28" data-team>
<h2 class="px-5 text-center font-display text-5xl font-medium md:text-6xl">Meet our master <span class="mark">barbers</span></h2>
<div class="mx-auto mt-14 grid max-w-7xl gap-8 px-5 md:px-10 lg:grid-cols-[1.3fr_1fr] lg:items-center">
<div aria-live="polite">
<% team.each_with_index do |(name, role, bio), index| %>
<div data-member="<%= index %>" <%= 'hidden' unless index.zero? %>>
<p class="font-display text-xl text-white/60"><%= role %></p>
<h3 class="font-display text-5xl"><%= name %></h3>
<p class="mt-4 max-w-xl text-white/70"><%= bio %></p>
<div class="mt-6 flex gap-4 text-white/70">
<a href="contact.html" class="hover:text-ember" aria-label="<%= name %> on Instagram"><i class="fa-brands fa-instagram" aria-hidden="true"></i></a>
<a href="contact.html" class="hover:text-ember" aria-label="<%= name %> on Facebook"><i class="fa-brands fa-facebook-f" aria-hidden="true"></i></a>
</div>
</div>
<% end %>
<div class="mt-8 flex gap-3">
<button type="button" data-member-prev class="grid h-11 w-11 place-items-center bg-ember transition hover:bg-white hover:text-coal" aria-label="Previous barber"><i class="fa-solid fa-chevron-left" aria-hidden="true"></i></button>
<button type="button" data-member-next class="grid h-11 w-11 place-items-center bg-ember transition hover:bg-white hover:text-coal" aria-label="Next barber"><i class="fa-solid fa-chevron-right" aria-hidden="true"></i></button>
</div>
</div>
<div class="relative aspect-[4/5] overflow-hidden">
<% team.each_with_index do |(name, role, bio, photo, alt), index| %>
<img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=800&h=1000&q=80" alt="<%= alt %>" class="slide absolute inset-0 h-full w-full object-cover <%= index.zero? ? 'opacity-100' : 'opacity-0' %>" data-member-photo="<%= index %>">
<% end %>
</div>
</div>
<ul class="mx-auto mt-6 grid max-w-7xl grid-cols-3 gap-3 px-5 md:px-10 lg:w-[60%] lg:max-w-none lg:pr-0">
<% strip.each do |(photo, alt)| %>
<li><img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=400&h=420&q=70" alt="<%= alt %>" class="aspect-square w-full object-cover" loading="lazy"></li>
<% end %>
</ul>
</section>
<!-- block:end team -->
<% quotes = [["The best cut I've had in years — relaxed, sharp and the hot shave was quick too.", "Alex Moreno"], ["They listened, suggested something better and nailed it. I'm not going anywhere else.", "Jordan Blake"], ["Proper old-school service with a modern eye. The beard trim alone is worth it.", "Sam Whitfield"]] %>
<!-- block:start reviews -->
<section class="mx-auto max-w-7xl px-5 pb-20 md:px-10 md:pb-28" data-carousel>
<div class="relative isolate overflow-hidden px-6 py-20 text-center md:px-24 md:py-28">
<img src="https://images.unsplash.com/photo-1560066984-138dadb4c035?auto=format&fit=crop&w=1600&h=900&q=75" alt="" class="absolute inset-0 -z-10 h-full w-full object-cover">
<div class="absolute inset-0 -z-10 bg-coal/70"></div>
<i class="fa-solid fa-quote-left absolute left-8 top-4 text-7xl text-white/80 md:left-16 md:text-8xl" aria-hidden="true"></i>
<% quotes.each_with_index do |(quote, name), index| %>
<figure data-slide <%= 'hidden' unless index.zero? %>>
<blockquote class="font-display text-3xl font-medium leading-tight md:text-5xl"><%= quote %></blockquote>
<figcaption class="mt-6"><span class="block font-display text-xl"><%= name %></span><span class="text-gold"><% 5.times do %><i class="fa-solid fa-star" aria-hidden="true"></i><% end %></span></figcaption>
</figure>
<% end %>
<button type="button" data-carousel-prev class="absolute left-4 top-1/2 grid h-10 w-10 -translate-y-1/2 place-items-center bg-ember transition hover:bg-white hover:text-coal md:left-8" aria-label="Previous review"><i class="fa-solid fa-chevron-left" aria-hidden="true"></i></button>
<button type="button" data-carousel-next class="absolute right-4 top-1/2 grid h-10 w-10 -translate-y-1/2 place-items-center bg-ember transition hover:bg-white hover:text-coal md:right-8" aria-label="Next review"><i class="fa-solid fa-chevron-right" aria-hidden="true"></i></button>
<div class="mt-8 flex justify-center gap-2">
<% quotes.each_with_index do |quote, index| %>
<button type="button" data-carousel-dot aria-current="<%= index.zero? %>" class="h-2 w-6 bg-white/30 transition aria-[current=true]:w-10 aria-[current=true]:bg-ember" aria-label="Show review <%= index + 1 %>"></button>
<% end %>
</div>
</div>
</section>
<!-- block:end reviews -->
<% gallery = [["1585747860715-2ba37e788b70", "Long row of barber chairs and mirrors", "row-span-2"], ["1605497788044-5a32c7078486", "Barber blow-drying a client's hair", "row-span-2"], ["1512690459411-b9245aed614b", "Vintage leather barber chair", ""], ["1622287162716-f311baa1a2b8", "Barber at work in a colourful shop", "row-span-2"], ["1503951914875-452162b0f3f1", "Straight-razor shave in a dim room", ""], ["1596728325488-58c87691e9af", "Close-up of a beard being shaved", ""]] %>
<!-- block:start gallery -->
<section class="mx-auto max-w-7xl px-5 pb-20 md:px-10 md:pb-28" data-lightbox-root>
<h2 class="font-display text-5xl font-medium md:text-6xl">Barbershop gallery</h2>
<ul class="mt-10 grid auto-rows-[10rem] grid-cols-2 gap-3 md:auto-rows-[12rem] md:grid-cols-3">
<% gallery.each_with_index do |(photo, alt, span), index| %>
<li class="<%= span %>"><button type="button" class="group relative block h-full w-full overflow-hidden" data-lightbox-open="<%= photo %>" data-alt="<%= alt %>" aria-label="Open photo: <%= alt %>"><img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=700&h=800&q=70" alt="<%= alt %>" class="h-full w-full object-cover transition duration-500 group-hover:scale-105" loading="lazy"><span class="absolute inset-0 grid place-items-center bg-coal/60 opacity-0 transition group-hover:opacity-100"><i class="fa-solid fa-plus grid h-12 w-12 place-items-center bg-ember text-lg" aria-hidden="true"></i></span></button></li>
<% end %>
</ul>
<dialog data-lightbox class="w-[min(60rem,92vw)] bg-coal-card p-0 text-white backdrop:bg-black/80">
<img data-lightbox-image src="https://images.unsplash.com/photo-1585747860715-2ba37e788b70?auto=format&fit=crop&w=1400&h=900&q=80" alt="" class="aspect-[14/9] w-full object-cover">
<div class="flex items-center justify-between gap-4 p-4"><p class="text-sm text-white/70" data-lightbox-caption></p><button type="button" data-lightbox-close class="grid h-10 w-10 place-items-center bg-ember" aria-label="Close"><i class="fa-solid fa-xmark" aria-hidden="true"></i></button></div>
</dialog>
</section>
<!-- block:end gallery -->
<!-- block:start contact -->
<section class="relative isolate overflow-hidden py-20 md:py-28">
<img src="https://images.unsplash.com/photo-1512690459411-b9245aed614b?auto=format&fit=crop&w=1800&h=900&q=70" alt="" class="absolute inset-0 -z-10 h-full w-full object-cover opacity-30">
<div class="mx-auto max-w-7xl px-5 md:px-10">
<h2 class="font-display text-5xl font-medium md:text-6xl">Book an appointment</h2>
<p class="mt-3 max-w-lg text-white/70">Pick a day and we'll hold a chair for you. We'll text to confirm within the hour.</p>
<form class="mt-10 grid gap-3 md:grid-cols-[1fr_1fr_1fr_auto]" data-booking novalidate>
<label class="sr-only" for="book-name">Your name</label>
<input id="book-name" name="name" required autocomplete="name" placeholder="Your name" class="bg-white px-5 py-4 text-coal placeholder:text-coal/50 focus:outline-none focus:ring-4 focus:ring-ember/50">
<label class="sr-only" for="book-phone">Phone</label>
<input id="book-phone" name="phone" type="tel" required autocomplete="tel" placeholder="Phone number" class="bg-white px-5 py-4 text-coal placeholder:text-coal/50 focus:outline-none focus:ring-4 focus:ring-ember/50">
<label class="sr-only" for="book-date">Date</label>
<input id="book-date" name="date" type="date" required class="bg-white px-5 py-4 text-coal focus:outline-none focus:ring-4 focus:ring-ember/50">
<button type="submit" class="bg-ember px-8 py-4 text-xs font-semibold uppercase tracking-widest transition hover:bg-white hover:text-coal">Book now</button>
</form>
<p class="mt-4 text-sm text-white/80" data-form-status role="status"></p>
</div>
</section>
<!-- block:end contact -->
<!-- block:start footer -->
<footer class="bg-coal">
<div class="mx-auto max-w-7xl px-5 pt-16 md:px-10">
<a href="index.html" class="block text-center font-display text-4xl font-semibold uppercase">Brass<span class="text-ember">moor</span>.</a>
<div class="mt-12 grid gap-10 sm:grid-cols-2 lg:grid-cols-4">
<div>
<h2 class="font-display text-2xl">Pages</h2>
<ul class="mt-4 grid gap-2 text-sm text-white/70">
<% nav.each do |slug, label| %><li><a href="<%= slug %>.html" class="hover:text-ember"><%= label %></a></li><% end %>
</ul>
</div>
<div>
<h2 class="font-display text-2xl">Services</h2>
<ul class="mt-4 grid gap-2 text-sm text-white/70">
<% services.each do |(name)| %><li><a href="services.html" class="hover:text-ember"><%= name %></a></li><% end %>
</ul>
</div>
<div>
<h2 class="font-display text-2xl">Contact</h2>
<ul class="mt-4 grid gap-2 text-sm text-white/70">
<li><i class="fa-solid fa-phone mr-2 text-ember" aria-hidden="true"></i>(617) 555-0142</li>
<li><i class="fa-solid fa-envelope mr-2 text-ember" aria-hidden="true"></i><a href="mailto:chair@brassmoor.com" class="hover:text-ember">chair@brassmoor.com</a></li>
<li><i class="fa-solid fa-clock mr-2 text-ember" aria-hidden="true"></i>Tue – Sat, 09:00 – 19:00</li>
</ul>
</div>
<div>
<h2 class="font-display text-2xl">Stay in the loop</h2>
<p class="mt-4 text-sm text-white/70">Offers and new barbers, once a month.</p>
<form class="mt-4 flex" data-subscribe novalidate>
<label for="footer-email" class="sr-only">Email</label>
<input id="footer-email" name="email" type="email" required placeholder="Email address" class="min-w-0 flex-1 bg-coal-soft px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-ember">
<button type="submit" class="grid w-12 place-items-center bg-ember transition hover:bg-white hover:text-coal" aria-label="Subscribe"><i class="fa-solid fa-paper-plane" aria-hidden="true"></i></button>
</form>
<p class="mt-2 text-xs text-white/70" data-subscribe-status role="status"></p>
</div>
</div>
<ul class="mt-14 grid gap-6 border-t border-coal-line pt-10 text-sm text-white/70 md:grid-cols-3">
<% shops.each do |(name, address)| %>
<li class="flex gap-3"><i class="fa-solid fa-location-dot mt-1 text-ember" aria-hidden="true"></i><span><span class="block font-display text-xl text-white"><%= name %></span><%= address %></span></li>
<% end %>
</ul>
</div>
<p class="mt-10 border-t border-coal-line px-5 py-6 text-center text-xs text-white/50">© 2026 Brassmoor Barbers. All rights reserved.</p>
</footer>
<!-- block:end footer -->
<script>
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
const watchMotion = !reducedMotion && !navigator.webdriver && 'IntersectionObserver' in window;
document.querySelectorAll('[data-menu-toggle]').forEach((button) => {
const menu = document.getElementById(button.getAttribute('aria-controls'));
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!open));
menu.classList.toggle('hidden', open);
});
});
document.querySelectorAll('[data-hero]').forEach((root) => {
const slides = [...root.querySelectorAll('[data-slide]')];
const dots = [...root.querySelectorAll('[data-slide-dot]')];
let current = 0;
const show = (index) => {
current = (index + slides.length) % slides.length;
slides.forEach((slide, i) => {
const on = i === current;
slide.classList.toggle('opacity-100', on); slide.classList.toggle('opacity-0', !on); slide.classList.toggle('pointer-events-none', !on);
slide.setAttribute('aria-hidden', String(!on));
slide.querySelectorAll('a').forEach((link) => { link.tabIndex = on ? 0 : -1; });
});
dots.forEach((dot, i) => dot.setAttribute('aria-pressed', String(i === current)));
root.querySelector('[data-slide-index]').textContent = String(current + 1).padStart(2, '0');
};
dots.forEach((dot, i) => dot.addEventListener('click', () => show(i)));
root.addEventListener('keydown', (event) => { if (event.key === 'ArrowDown') show(current + 1); if (event.key === 'ArrowUp') show(current - 1); });
});
document.querySelectorAll('[data-team]').forEach((root) => {
const members = [...root.querySelectorAll('[data-member]')];
const photos = [...root.querySelectorAll('[data-member-photo]')];
let current = 0;
const show = (index) => {
current = (index + members.length) % members.length;
members.forEach((member, i) => { member.hidden = i !== current; });
photos.forEach((photo, i) => { photo.classList.toggle('opacity-100', i === current); photo.classList.toggle('opacity-0', i !== current); });
};
root.querySelector('[data-member-prev]').addEventListener('click', () => show(current - 1));
root.querySelector('[data-member-next]').addEventListener('click', () => show(current + 1));
});
document.querySelectorAll('[data-carousel]').forEach((root) => {
const slides = [...root.querySelectorAll('[data-slide]')];
const dots = [...root.querySelectorAll('[data-carousel-dot]')];
let current = 0;
const show = (index) => {
current = (index + slides.length) % slides.length;
slides.forEach((slide, i) => { slide.hidden = i !== current; });
dots.forEach((dot, i) => dot.setAttribute('aria-current', String(i === current)));
};
root.querySelector('[data-carousel-prev]').addEventListener('click', () => show(current - 1));
root.querySelector('[data-carousel-next]').addEventListener('click', () => show(current + 1));
dots.forEach((dot, i) => dot.addEventListener('click', () => show(i)));
});
document.querySelectorAll('[data-lightbox-root]').forEach((root) => {
const dialog = root.querySelector('[data-lightbox]');
root.querySelectorAll('[data-lightbox-open]').forEach((button) => button.addEventListener('click', () => {
const image = dialog.querySelector('[data-lightbox-image]');
image.src = `https://images.unsplash.com/photo-${button.dataset.lightboxOpen}?auto=format&fit=crop&w=1400&h=900&q=80`;
image.alt = button.dataset.alt;
dialog.querySelector('[data-lightbox-caption]').textContent = button.dataset.alt;
dialog.showModal();
}));
dialog.querySelector('[data-lightbox-close]').addEventListener('click', () => dialog.close());
dialog.addEventListener('click', (event) => { if (event.target === dialog) dialog.close(); });
});
document.querySelectorAll('[data-booking]').forEach((form) => {
const status = form.parentElement.querySelector('[data-form-status]');
const date = form.querySelector('[name="date"]');
if (date) date.min = new Date().toISOString().slice(0, 10);
form.addEventListener('submit', (event) => {
event.preventDefault();
const missing = [...form.querySelectorAll('[required]')].find((input) => !input.checkValidity());
if (missing) { status.textContent = 'Please add your name, phone and a date from today onwards.'; missing.focus(); return; }
status.textContent = `Thanks, ${form.elements.name.value}! Your chair is held for ${new Date(date.value).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}.`;
form.reset();
});
});
document.querySelectorAll('[data-subscribe]').forEach((form) => {
const status = form.parentElement.querySelector('[data-subscribe-status]');
form.addEventListener('submit', (event) => {
event.preventDefault();
if (!form.elements.email.checkValidity()) { status.textContent = 'Please enter a valid email.'; return; }
status.textContent = 'Subscribed — see you next month.';
form.reset();
});
});
document.querySelectorAll('[data-count]').forEach((el) => {
if (!watchMotion) return;
const target = Number(el.dataset.count);
const pad = el.dataset.count.length;
const observer = new IntersectionObserver(([entry]) => {
if (!entry.isIntersecting) return;
observer.disconnect();
const start = performance.now();
const tick = (now) => { const t = Math.min(1, (now - start) / 700); el.textContent = String(Math.round(target * t)).padStart(pad > 2 ? 0 : pad, '0'); if (t < 1) requestAnimationFrame(tick); };
requestAnimationFrame(tick);
});
observer.observe(el);
});
if (watchMotion) {
document.documentElement.classList.add('reveal-ready');
const observer = new IntersectionObserver((entries) => entries.forEach((entry) => {
if (entry.isIntersecting) { entry.target.classList.add('is-visible'); observer.unobserve(entry.target); }
}), { threshold: 0.15 });
document.querySelectorAll('[data-reveal]').forEach((el) => observer.observe(el));
}
</script>
</body>
</html>
Unlimited access to all templates
Unlock our entire library of premium UI kits, dashboards, and landing pages. Renews automatically, cancel anytime.
$20
/ 3 months
or $60 / year
Get Unlimited Access