{# Image Reveal Module - Performance-optimized animated images #}

{# Shorthand variables for cleaner code #}
{% set c = module.content %}
{% set anim = module.animation %}
{% set s = module.style %}
{% set a = module.advanced %}

{% set animation_class = "reveal-" ~ name %}
{% set has_animation = anim.animation_type != 'none' %}
{% set has_displacement = s.enable_displacement %}
{% set displacement_x = s.displacement_x|default(0) %}
{% set displacement_y = s.displacement_y|default(0) %}
{% set displacement_unit = s.displacement_unit|default('px') %}

<figure class="image-reveal {{ animation_class }} {{ anim.animation_type }} {{ has_animation ? 'will-animate' : '' }}{% if a.custom_class %} {{ a.custom_class }}{% endif %}"
        {% if a.custom_id %}id="{{ a.custom_id }}"{% endif %}
        style="text-align: {{ s.alignment }};">
  {% if c.image.src %}
  <img src="{{ c.image.src }}"
       alt="{{ c.image.alt }}"
       width="{{ c.image.width }}"
       height="{{ c.image.height }}"
       loading="{{ a.loading_type }}"
       class="reveal-image"
       {% if has_animation %}
         data-animation="{{ anim.animation_type }}"
         data-duration="{{ s.animation_duration }}"
         data-delay="{{ s.animation_delay }}"
       {% endif %}
       {% if a.sizes %}sizes="{{ a.sizes }}"{% endif %}
       {% if c.image.srcset %}srcset="{{ c.image.srcset }}"{% endif %}>
  {% endif %}

  {% if c.show_caption and c.caption %}
    <figcaption class="reveal-caption">{{ c.caption }}</figcaption>
  {% endif %}
</figure>

{# NOTE: do NOT add a <noscript><style>.reveal-image{opacity:1!important;
   animation:none!important}</style></noscript> "no-JS reveal" here. HubSpot's CSS
   aggregation hoists the <style> OUT of the <noscript>, so the !important override
   applies for everyone and kills the animations (regression 2026-06-12). The
   image starts at opacity:0 by design and is revealed by the observer below. #}

{# Module-specific CSS - Only included for this instance #}
<style>
  .{{ animation_class }} {
    overflow: hidden;
    position: relative;
    display: inline-block;
    max-width: 100%;
  }

  .{{ animation_class }} .reveal-image {
    display: block;
    max-width: 100%;
    height: auto;
    {% if has_animation %}
      opacity: 0;
      animation-duration: {{ s.animation_duration }}ms;
      animation-delay: {{ s.animation_delay }}ms;
      animation-fill-mode: forwards;
      animation-timing-function: {{ s.animation_easing }};
    {% endif %}
  }

  {% if has_animation %}
    {# Animation is triggered when 'animated' class is added by observer #}
    .{{ animation_class }}.animated .reveal-image,
    .{{ animation_class }}.no-js .reveal-image {
      animation-name: {{ anim.animation_type }}-{{ name }};
    }

    {# Define animations for this specific module instance #}
    {% set final_x = has_displacement ? displacement_x ~ displacement_unit : '0' %}
    {% set final_y = has_displacement ? displacement_y ~ displacement_unit : '0' %}

    @keyframes slide-left-{{ name }} {
      from {
        opacity: 0;
        transform: translateX(-{{ s.animation_distance }}px) translateY({{ final_y }});
      }
      to {
        opacity: 1;
        transform: translateX({{ final_x }}) translateY({{ final_y }});
      }
    }

    @keyframes slide-right-{{ name }} {
      from {
        opacity: 0;
        transform: translateX({{ s.animation_distance }}px) translateY({{ final_y }});
      }
      to {
        opacity: 1;
        transform: translateX({{ final_x }}) translateY({{ final_y }});
      }
    }

    @keyframes slide-up-{{ name }} {
      from {
        opacity: 0;
        transform: translateX({{ final_x }}) translateY(calc({{ s.animation_distance }}px + {{ final_y }}));
      }
      to {
        opacity: 1;
        transform: translateX({{ final_x }}) translateY({{ final_y }});
      }
    }

    @keyframes slide-down-{{ name }} {
      from {
        opacity: 0;
        transform: translateX({{ final_x }}) translateY(calc(-{{ s.animation_distance }}px + {{ final_y }}));
      }
      to {
        opacity: 1;
        transform: translateX({{ final_x }}) translateY({{ final_y }});
      }
    }

    @keyframes fade-{{ name }} {
      from {
        opacity: 0;
        transform: translateX({{ final_x }}) translateY({{ final_y }});
      }
      to {
        opacity: 1;
        transform: translateX({{ final_x }}) translateY({{ final_y }});
      }
    }

    @keyframes zoom-{{ name }} {
      from {
        opacity: 0;
        transform: scale({{ s.zoom_start }}) translateX({{ final_x }}) translateY({{ final_y }});
      }
      to {
        opacity: 1;
        transform: scale(1) translateX({{ final_x }}) translateY({{ final_y }});
      }
    }

    @keyframes rotate-{{ name }} {
      from {
        opacity: 0;
        transform: rotate({{ s.rotate_degrees }}deg) scale(0.8) translateX({{ final_x }}) translateY({{ final_y }});
      }
      to {
        opacity: 1;
        transform: rotate(0) scale(1) translateX({{ final_x }}) translateY({{ final_y }});
      }
    }
  {% endif %}

  {% if c.show_caption %}
    /* Base caption — always visible (no opacity:0 here, or it stays hidden
       forever when animation_type = none and the fade keyframe isn't emitted). */
    .reveal-caption {
      margin-top: 0.5rem;
      font-size: 0.875rem;
      {% if s.caption_color.color %}
        color: {{ s.caption_color.color }};
      {% else %}
        color: var(--color-text-muted, #6b7280);
      {% endif %}
    }
    {% if has_animation %}
    .{{ animation_class }} .reveal-caption {
      opacity: 0;
      animation: fade-{{ name }} 400ms ease-out forwards;
      animation-delay: {{ s.animation_duration + s.animation_delay + 100 }}ms;
    }
    {% endif %}
  {% endif %}

  {# Respect reduced motion preference #}
  @media (prefers-reduced-motion: reduce) {
    .{{ animation_class }} .reveal-image,
    .{{ animation_class }} .reveal-caption {
      animation: none !important;
      opacity: 1;
      transform: translateX({{ has_displacement ? displacement_x ~ displacement_unit : '0' }}) translateY({{ has_displacement ? displacement_y ~ displacement_unit : '0' }});
    }
  }
</style>

{# Minimal Intersection Observer for triggering animations on viewport entry #}
{% if has_animation %}
<script>
  (function() {
    function initReveal() {
      const element = document.querySelector('.{{ animation_class }}');
      if (!element) return;

      if ('IntersectionObserver' in window) {
        const imageObserver = new IntersectionObserver(function(entries, observer) {
          entries.forEach(function(entry) {
            if (entry.isIntersecting) {
              entry.target.classList.add('animated');
              observer.unobserve(entry.target);
            }
          });
        }, {
          rootMargin: '0px 0px {{ anim.trigger_offset }}px 0px',
          threshold: {{ anim.trigger_threshold }}
        });

        imageObserver.observe(element);
      } else {
        element.classList.add('no-js');
      }
    }

    // Delay observer setup so page renders first, then animations trigger
    // This ensures user sees the animation even if element is already in viewport
    setTimeout(function() {
      if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initReveal);
      } else {
        initReveal();
      }
    }, 150);
  })();
</script>
{% endif %}
