Emberlyn Hero Hero 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 Hero

Emberlyn Hero

A hero block extracted from the Emberlyn template, available in Tailwind CSS and Bootstrap.

0 3
Emberlyn Hero preview
emberlyn.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>Emberlyn Hero | Tailwind CSS UI Block</title>
  <meta name="description" content="A hero block extracted from the Emberlyn template, available in Tailwind CSS and Bootstrap.">
  <meta name="keywords" content="Emberlyn, Tailwind CSS template, HTML website template, portfolio template, wedding photographer website, photography portfolio, elopement photographer, photo studio template, editorial portfolio">
  <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="Emberlyn | Tailwind CSS Portfolio Template">
  <meta property="og:description" content="A quiet, editorial Tailwind CSS portfolio template for wedding photographers, with a black-and-white hero slider, a staggered journal and a date availability calendar.">
  <meta property="og:image" content="">
  <meta property="og:image:alt" content="Emberlyn template preview">

  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:url" content="">
  <meta name="twitter:title" content="Emberlyn | Tailwind CSS Portfolio Template">
  <meta name="twitter:description" content="A quiet, editorial Tailwind CSS portfolio template for wedding photographers, with a black-and-white hero slider, a staggered journal and a date availability calendar.">
  <meta name="twitter:image" content="">
  <meta name="twitter:image:alt" content="Emberlyn 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=Cormorant+Garamond:wght@400;500;600&family=Mrs+Saint+Delafield&family=Playfair+Display:wght@400;500&family=Poppins:wght@300;400;500&display=swap" rel="stylesheet">
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: { paper: '#f7f6f2', sand: '#efede8', sage: { DEFAULT: '#5f6c5f', deep: '#4b564b' }, ink: '#232323', mute: '#707070' },
          fontFamily: { sans: ['Poppins', 'sans-serif'], serif: ['Playfair Display', 'serif'], caps: ['Cormorant Garamond', 'serif'], script: ['Mrs Saint Delafield', 'cursive'] }
        }
      }
    }
  </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; }
    .slide { transition: opacity .7s ease, transform 6s ease; }
    .slide:not(.is-active) { opacity: 0; }
    .slide.is-active { transform: scale(1.04); }
    .notch { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 1.25rem), calc(50% + 1.25rem) calc(100% - 1.25rem), 50% 100%, calc(50% - 1.25rem) calc(100% - 1.25rem), 0 calc(100% - 1.25rem)); }
    .reveal-ready [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity .7s ease, transform .7s 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; }
      .slide.is-active { transform: none; }
      ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
    }
  </style>
</head>
<body class="bg-paper font-sans font-light text-ink antialiased">
<% active_page = "index" %>
<% left_nav = { "about" => "meet us", "story" => "our story", "services" => "services" } %>
<% right_nav = { "portfolio" => "portfolio", "blog" => "the journal", "contact" => "hire us" } %>
<% left_nav.each do |slug, label| %>
<% active = active_page == slug %>
<% end %>
<% right_nav.each do |slug, label| %>
<% active = active_page == slug %>
<% end %>
<% { "index" => "home" }.merge(left_nav).merge(right_nav).each do |slug, label| %>
<% active = active_page == slug %>
<% end %>
<% slides = [["1460978812857-470ed1c77af0", "Couple kissing on a windy shore, the bride's veil lifting in black and white"], ["1537633552985-df8429e8048b", "Bride and groom embracing by the sea, her long veil trailing on the pebbles"], ["1532712938310-34cb3982ef74", "Newlyweds walking hand in hand across a hilltop at dusk"]] %>
<!-- block:start hero -->
  <section class="notch relative h-[92vh] max-h-[52rem] min-h-[34rem] overflow-hidden bg-ink text-white" data-slider aria-roledescription="carousel" aria-label="Featured weddings">
    <% slides.each_with_index do |(photo, alt), index| %>
      <img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=1900&q=80" alt="<%= alt %>" data-slide class="slide absolute inset-0 h-full w-full object-cover grayscale <%= 'is-active' if index.zero? %>" <%= 'aria-hidden="true"'.html_safe unless index.zero? %>>
    <% end %>
    <div class="absolute inset-0 bg-black/45" aria-hidden="true"></div>
    <div class="relative flex h-full flex-col items-center justify-center px-6 text-center">
      <p class="text-[11px] uppercase tracking-[0.6em] text-white/85">wedding photography</p>
      <h1 class="mt-4 font-serif text-5xl md:text-7xl">Elias Marlowe</h1>
      <p class="mt-6 max-w-md text-sm leading-relaxed text-white/85 md:text-base">Keep the brightest days of your life in more than memory. Keep them in print.</p>
      <a href="portfolio.html" class="mt-9 border border-white/50 bg-white/15 px-7 py-2.5 text-[11px] uppercase tracking-[0.4em] backdrop-blur transition hover:bg-white hover:text-ink">Discover</a>
    </div>
    <button type="button" data-slider-prev class="group absolute left-0 top-1/2 hidden w-40 -translate-y-1/2 text-left text-[11px] tracking-widest text-white/80 md:block" aria-label="Previous wedding"><span class="block h-px w-full bg-white/70 transition-all group-hover:w-32"></span><span class="mt-2 block pl-16">Prev</span></button>
    <button type="button" data-slider-next class="group absolute right-0 top-1/2 hidden w-40 -translate-y-1/2 text-right text-[11px] tracking-widest text-white/80 md:block" aria-label="Next wedding"><span class="ml-auto block h-px w-full bg-white/70 transition-all group-hover:w-32"></span><span class="mt-2 block pr-16">Next</span></button>
    <div class="absolute inset-x-0 bottom-10 flex items-end justify-between px-5 text-[11px] md:px-10">
      <div class="flex gap-2" role="group" aria-label="Choose a slide">
        <% slides.each_index do |index| %>
          <button type="button" data-slider-dot="<%= index %>" aria-label="Show wedding <%= index + 1 %>" aria-pressed="<%= index.zero? %>" class="h-px w-8 bg-white/40 transition aria-pressed:w-14 aria-pressed:bg-white"></button>
        <% end %>
      </div>
      <a href="#welcome" class="hidden text-white/80 md:block">Scroll down</a>
      <div class="flex items-center gap-5">
        <span class="text-white/80">Follow</span>
        <% [["fa-instagram", "Instagram", "https://www.instagram.com"], ["fa-facebook-f", "Facebook", "https://www.facebook.com"], ["fa-pinterest-p", "Pinterest", "https://www.pinterest.com"]].each do |icon, label, url| %>
          <a href="<%= url %>" aria-label="<%= label %>" class="text-sm transition hover:text-white/60"><i class="fa-brands <%= icon %>" aria-hidden="true"></i></a>
        <% end %>
      </div>
    </div>
  </section>
  <!-- block:end hero -->
<script>
    document.querySelectorAll('[data-menu-toggle]').forEach((button) => {
      const menu = document.getElementById(button.getAttribute('aria-controls'));
      if (!menu) return;
      button.addEventListener('click', () => {
        const open = button.getAttribute('aria-expanded') === 'true';
        button.setAttribute('aria-expanded', String(!open));
        menu.classList.toggle('hidden', open);
      });
    });

    document.querySelectorAll('[data-slider]').forEach((slider) => {
      const slides = [...slider.querySelectorAll('[data-slide]')];
      const dots = [...slider.querySelectorAll('[data-slider-dot]')];
      let current = 0;
      let timer;
      const show = (index) => {
        current = (index + slides.length) % slides.length;
        slides.forEach((slide, i) => {
          slide.classList.toggle('is-active', i === current);
          slide.toggleAttribute('aria-hidden', i !== current);
        });
        dots.forEach((dot, i) => dot.setAttribute('aria-pressed', String(i === current)));
      };
      const restart = () => {
        clearInterval(timer);
        if (!matchMedia('(prefers-reduced-motion: reduce)').matches) timer = setInterval(() => show(current + 1), 6000);
      };
      slider.querySelector('[data-slider-prev]')?.addEventListener('click', () => { show(current - 1); restart(); });
      slider.querySelector('[data-slider-next]')?.addEventListener('click', () => { show(current + 1); restart(); });
      dots.forEach((dot, i) => dot.addEventListener('click', () => { show(i); restart(); }));
      restart();
    });

    document.querySelectorAll('[data-carousel]').forEach((carousel) => {
      const slides = [...carousel.querySelectorAll('[data-carousel-slide]')];
      const index = carousel.querySelector('[data-carousel-index]');
      let current = 0;
      const show = (next) => {
        current = (next + slides.length) % slides.length;
        slides.forEach((slide, i) => { slide.hidden = i !== current; });
        if (index) index.textContent = current + 1;
      };
      carousel.querySelector('[data-carousel-prev]')?.addEventListener('click', () => show(current - 1));
      carousel.querySelector('[data-carousel-next]')?.addEventListener('click', () => show(current + 1));
    });

    document.querySelectorAll('[data-hover-list]').forEach((list) => {
      const preview = list.querySelector('[data-hover-preview]');
      if (!preview || !matchMedia('(hover: hover)').matches) return;
      list.querySelectorAll('[data-hover-item]').forEach((item) => {
        item.addEventListener('mouseenter', () => { preview.src = item.dataset.image; preview.style.opacity = '1'; });
        item.addEventListener('mouseleave', () => { preview.style.opacity = '0'; });
        item.addEventListener('mousemove', (event) => { preview.style.transform = `translate(${event.clientX + 28}px, ${event.clientY - 90}px) rotate(-3deg)`; });
      });
    });

    document.querySelectorAll('[data-availability]').forEach((root) => {
      const months = [...root.querySelectorAll('[data-month]')];
      const label = root.querySelector('[data-month-label]');
      const prev = root.querySelector('[data-month-prev]');
      const next = root.querySelector('[data-month-next]');
      const summary = root.querySelector('[data-availability-summary]');
      const note = root.querySelector('[data-availability-note]');
      let current = 0;
      const showMonth = (index) => {
        current = Math.max(0, Math.min(months.length - 1, index));
        months.forEach((month, i) => { month.hidden = i !== current; });
        label.textContent = months[current].dataset.month;
        prev.disabled = current === 0;
        next.disabled = current === months.length - 1;
      };
      prev.addEventListener('click', () => showMonth(current - 1));
      next.addEventListener('click', () => showMonth(current + 1));
      root.addEventListener('click', (event) => {
        const day = event.target.closest('[data-day]');
        if (!day || day.disabled) return;
        root.querySelectorAll('[data-day]').forEach((other) => other.setAttribute('aria-pressed', String(other === day)));
        summary.textContent = `${day.dataset.day} is free`;
        note.textContent = 'Lovely choice. Hold it now and I will keep it for you for seven days.';
      });
      showMonth(0);
    });

    document.querySelectorAll('[data-filter-group]').forEach((group) => {
      const items = document.querySelectorAll(group.dataset.filterGroup);
      group.querySelectorAll('[data-filter]').forEach((chip) => {
        chip.addEventListener('click', () => {
          group.querySelectorAll('[data-filter]').forEach((other) => other.setAttribute('aria-pressed', String(other === chip)));
          items.forEach((item) => { item.hidden = chip.dataset.filter !== 'all' && item.dataset.role !== chip.dataset.filter; });
        });
      });
    });

    document.querySelectorAll('[data-lightbox]').forEach((dialog) => {
      const image = dialog.querySelector('img');
      const caption = dialog.querySelector('[data-lightbox-caption]');
      document.querySelectorAll('[data-lightbox-open]').forEach((button) => {
        button.addEventListener('click', () => {
          image.src = button.dataset.full;
          image.alt = button.querySelector('img')?.alt || '';
          caption.textContent = button.dataset.caption;
          dialog.showModal();
        });
      });
      dialog.addEventListener('click', (event) => { if (event.target === dialog) dialog.close(); });
    });

    document.querySelectorAll('[data-contact-form]').forEach((form) => {
      form.addEventListener('submit', (event) => {
        event.preventDefault();
        const status = form.querySelector('[data-form-status]');
        if (!form.checkValidity()) {
          status.textContent = form.dataset.error;
          form.querySelector(':invalid')?.focus();
          return;
        }
        status.textContent = form.dataset.success;
        form.reset();
      });
    });

    document.querySelectorAll('[data-current-year]').forEach((el) => { el.textContent = new Date().getFullYear(); });

    (() => {
      const items = document.querySelectorAll('[data-reveal]');
      const skip = navigator.webdriver || !('IntersectionObserver' in window) || matchMedia('(prefers-reduced-motion: reduce)').matches;
      if (!items.length || skip) return;
      document.documentElement.classList.add('reveal-ready');
      const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          if (!entry.isIntersecting) return;
          entry.target.classList.add('is-visible');
          observer.unobserve(entry.target);
        });
      }, { rootMargin: '0px 0px -10% 0px' });
      items.forEach((item) => observer.observe(item));
    })();
  </script>
</body>
</html>

From this template

Emberlyn screenshot

Portfolio

Emberlyn

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