Brassmoor Gallery Gallery Block - Let's templates
Unlock every premium block and template with All-Access from $20 / 3 months, cancel anytime. Get All-Access
All blocks
Free Gallery

Brassmoor Gallery

A gallery block extracted from the Brassmoor template, available in Tailwind CSS and Bootstrap.

0 3
Brassmoor Gallery preview
brassmoor.html
<!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 Gallery | Tailwind CSS UI Block</title>
  <meta name="description" content="A gallery block extracted from the Brassmoor template, available in Tailwind CSS and Bootstrap.">
  <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" } %>
<% nav.each do |slug, label| %>
<% active = active_page == slug %>
<% end %>
<% nav.each do |slug, label| %>
<% active = active_page == slug %>
<% end %>
<% slides = [["Sharp cuts &amp; 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"]] %>
<% slides.each_with_index do |(line_one, line_two, photo, alt), index| %>
<% end %>
<% slides.each_with_index do |slide, index| %>
<% end %>
<% styles.each do |(name, photo, alt)| %>
<% end %>
<% [["18", "Years in the chair"], ["07", "Master barbers"]].each_with_index do |(value, label), index| %>
<% end %>
<% 5.times do %>
<% end %>
<% 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"]] %>
<% shops.each_with_index do |(name, address), index| %>
<% end %>
<% 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"]] %>
<% services.each_with_index do |(name, text, icon), index| %>
<% end %>
<% prices = [["Vintage cut", "$32"], ["Beard trim", "$18"], ["Cut + hot shave", "$48"], ["Modern fade", "$36"]] %>
<% prices.each_with_index do |(name, price), index| %>
<% end %>
<% 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"]] %>
<% team.each_with_index do |(name, role, bio), index| %>
<% end %>
<% team.each_with_index do |(name, role, bio, photo, alt), index| %>
<% end %>
<% strip.each do |(photo, alt)| %>
<% end %>
<% 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"]] %>
<% quotes.each_with_index do |(quote, name), index| %>
<% 5.times do %>
<% end %>
<% end %>
<% quotes.each_with_index do |quote, index| %>
<% end %>
<% 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 -->
<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>

From this template

Brassmoor screenshot

Business

Brassmoor

Unlimited access to all products

Unlock the entire library of premium templates and blocks. Renews automatically, cancel anytime.

$20 / 3 months or $60 / year
Get Unlimited Access