Products lookbook

import React from 'react';
import { View, Text, Image, Actionable, Flexbox } from '@evlop/native-components';
import { useProduct, PriceDisplay } from '@evlop/shopify';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withRepeat,
  Easing,
  interpolate,
} from 'react-native-reanimated';
import { StyleSheet, Pressable } from 'react-native';

interface ProductImage {
  id: string;
  originalSrc: string;
  altText: string;
}

interface Product {
  id: string;
  title: string;
  handle: string;
  images: ProductImage[];
  price?: string;
}

interface Annotation {
  id: string;
  x: number;
  y: number;
  width: number;
  height: number;
  product: Product;
}

interface ImageData {
  url: string;
  aspectRatio: number;
  annotations: Annotation[];
}

interface AppBlockProps {
  data: {
    images: ImageData[];
  };
}

const HOTSPOT_SIZE = 20;
const IMAGE_SIZE = 60;
const RING_COUNT = 3;
const ACTIVE_COLOR = '#4CD964'; // Green accent for active hotspot
const NAV_BUTTON_SIZE = 44;
const IMAGE_TRANSITION_MS = 400; // Duration for image crossfade

// Animated ring component for active hotspot highlight
interface PulseRingProps {
  delay: number;
  isActive: boolean;
}

const PulseRing: React.FC<PulseRingProps> = ({ delay, isActive }) => {
  const progress = useSharedValue(0);

  React.useEffect(() => {
    if (isActive) {
      // Use setTimeout for the delay instead of withDelay
      const timer = setTimeout(() => {
        progress.value = 0;
        progress.value = withRepeat(
          withTiming(1, { duration: 2000, easing: Easing.out(Easing.quad) }),
          -1,
          false
        );
      }, delay);
      return () => clearTimeout(timer);
    } else {
      progress.value = withTiming(0, { duration: 300, easing: Easing.out(Easing.quad) });
    }
  }, [isActive, delay]);

  const ringStyle = useAnimatedStyle(() => {
    const scale = interpolate(progress.value, [0, 1], [1, 3.5]);
    const opacity = interpolate(progress.value, [0, 0.3, 0.7, 1], [0.7, 0.5, 0.2, 0]);
    return {
      transform: [{ scale }],
      opacity,
    };
  });

  return (
    <Animated.View style={[styles.pulseRing, ringStyle]} />
  );
};

interface HotspotWithCardProps {
  annotation: Annotation;
  index: number;
  imageWidth: number;
  imageHeight: number;
  isActive: boolean;
  onHotspotPress: (index: number) => void;
}

const HotspotWithCard: React.FC<HotspotWithCardProps> = ({
  annotation,
  index,
  imageWidth,
  imageHeight,
  isActive,
  onHotspotPress,
}) => {
  // Track if product should be visible (stays true during exit animation)
  const [showProduct, setShowProduct] = React.useState(false);
  
  // Load actual product data using handle or id
  const product = useProduct(
    annotation.product.handle 
      ? { productHandle: annotation.product.handle }
      : { productId: annotation.product.id }
  );
  
  const contentX = (annotation.x + annotation.width / 2) * imageWidth;
  const contentY = (annotation.y + annotation.height / 2) * imageHeight;
  
  // Annotation dimensions in pixels
  const annotationWidth = annotation.width * imageWidth;
  const annotationHeight = annotation.height * imageHeight;

  // Smart positioning - check all edges
  const leftSpace = contentX;
  const rightSpace = imageWidth - contentX;
  
  const isLeft = leftSpace > rightSpace;
  const gap = 8; // Gap between annotation edge and product
  
  // Distance from hotspot center to annotation edge
  const halfAnnotationWidth = annotationWidth / 2;
  
  // Position product outside the annotation area
  const productX = isLeft 
    ? -(halfAnnotationWidth + gap + IMAGE_SIZE)  // Left: outside left edge of annotation
    : halfAnnotationWidth + gap;                  // Right: outside right edge of annotation
  
  // Align center of product image with center of hotspot
  const productY = -IMAGE_SIZE / 2;

  // Subtle idle pulse for all hotspots
  const idlePulse = useSharedValue(0);
  React.useEffect(() => {
    idlePulse.value = withRepeat(
      withTiming(1, { duration: 1500, easing: Easing.inOut(Easing.quad) }),
      -1,
      true // reverse to create pulse effect
    );
  }, []);

  // Active state animation
  const activeScale = useSharedValue(1);
  const activeGlow = useSharedValue(0);
  
  React.useEffect(() => {
    if (isActive) {
      activeScale.value = withTiming(1.2, { duration: 300, easing: Easing.out(Easing.quad) });
      activeGlow.value = withTiming(1, { duration: 400, easing: Easing.out(Easing.quad) });
    } else {
      activeScale.value = withTiming(1, { duration: 250, easing: Easing.out(Easing.quad) });
      activeGlow.value = withTiming(0, { duration: 300, easing: Easing.out(Easing.quad) });
    }
  }, [isActive]);

  const hotspotOuterStyle = useAnimatedStyle(() => {
    const idleScale = interpolate(idlePulse.value, [0, 1], [1, 1.08]);
    const combinedScale = idleScale * activeScale.value;
    return { 
      transform: [{ scale: combinedScale }],
    };
  });

  const hotspotInnerStyle = useAnimatedStyle(() => {
    const glowScale = interpolate(activeGlow.value, [0, 1], [1, 1.1]);
    return { 
      transform: [{ scale: glowScale }],
    };
  });

  const hotspotGlowStyle = useAnimatedStyle(() => {
    const glowOpacity = interpolate(activeGlow.value, [0, 1], [0, 0.4]);
    return {
      opacity: glowOpacity,
      transform: [{ scale: interpolate(activeGlow.value, [0, 1], [0.8, 1]) }],
    };
  });

  // Line animation - delayed after hotspot glow
  const lineProgress = useSharedValue(0);
  
  // Product animation - delayed after line draws
  const productProgress = useSharedValue(0);
  
  React.useEffect(() => {
    if (isActive) {
      // Show elements immediately (for mounting)
      setShowProduct(true);
      
      // Reset animations
      lineProgress.value = 0;
      productProgress.value = 0;
      
      // Sequence: hotspot glow (0-300ms) → line draws (300-550ms) → product shows (550-950ms)
      const lineTimer = setTimeout(() => {
        lineProgress.value = withTiming(1, { duration: 250, easing: Easing.out(Easing.quad) });
      }, 300); // Start after hotspot glow
      
      const productTimer = setTimeout(() => {
        productProgress.value = withTiming(1, { duration: 400, easing: Easing.out(Easing.quad) });
      }, 550); // Start after line finishes
      
      return () => {
        clearTimeout(lineTimer);
        clearTimeout(productTimer);
      };
    } else {
      // Exit: product fades first, then line retracts
      productProgress.value = withTiming(0, { duration: 200, easing: Easing.inOut(Easing.quad) });
      
      const lineTimer = setTimeout(() => {
        lineProgress.value = withTiming(0, { duration: 180, easing: Easing.inOut(Easing.quad) });
      }, 100); // Line starts retracting slightly after product starts fading
      
      // Hide after all animations complete
      const hideTimer = setTimeout(() => {
        setShowProduct(false);
      }, 300);
      
      return () => {
        clearTimeout(lineTimer);
        clearTimeout(hideTimer);
      };
    }
  }, [isActive]);

  const productAnimStyle = useAnimatedStyle(() => {
    // Scale: 0.3 (exit) → 1 (visible)
    const productScale = interpolate(productProgress.value, [0, 1], [0.3, 1]);
    // Fade from 0 to 1
    const productOpacity = interpolate(productProgress.value, [0, 0.3, 1], [0, 0.4, 1]);
    return { 
      opacity: productOpacity,
      transform: [
        { scale: productScale }
      ],
    };
  });

  // Connection line animation - draws from hotspot towards product
  const lineStyle = useAnimatedStyle(() => {
    // Scale from 0 to full width (drawing effect)
    const scaleX = interpolate(lineProgress.value, [0, 1], [0, 1]);
    const lineOpacity = interpolate(lineProgress.value, [0, 0.2, 1], [0, 1, 1]);
    return {
      opacity: lineOpacity,
      transform: [
        { scaleX }
      ],
    };
  });

  return (
    <View
      style={[
        styles.overlayContainer,
        {
          left: contentX,
          top: contentY,
        },
      ]}
      pointerEvents="box-none"
    >
      {/* Hotspot */}
      <Pressable 
        onPress={() => onHotspotPress(index)}
        style={styles.hotspotPressable}
      >
        <View style={styles.hotspotWrapper}>
          {/* Multi-layer pulse rings for active state */}
          <View style={styles.ringsContainer}>
            {[...Array(RING_COUNT)].map((_, i) => (
              <PulseRing 
                key={i} 
                delay={i * 400} 
                isActive={isActive} 
              />
            ))}
          </View>
          
          {/* Active glow backdrop */}
          <Animated.View style={[styles.hotspotGlow, hotspotGlowStyle]} />
          
          <Animated.View style={[
            styles.hotspotOuter, 
            hotspotOuterStyle,
            isActive && styles.hotspotOuterActive
          ]}>
            <Animated.View style={[
              styles.hotspotInner, 
              hotspotInnerStyle,
              isActive && styles.hotspotInnerActive
            ]} />
          </Animated.View>
        </View>
      </Pressable>

      {/* Connection line */}
      {showProduct && (
        <Animated.View 
          style={[
            styles.connectionLine,
            lineStyle,
            {
              width: halfAnnotationWidth + gap,
              left: isLeft ? -(halfAnnotationWidth + gap) : 0,
              // Transform origin: line draws from hotspot (right side if left, left side if right)
              transformOrigin: isLeft ? 'right center' : 'left center',
            }
          ]} 
        />
      )}

      {/* Product Info */}
      {showProduct && (
        <Animated.View
          style={[
            styles.productContainer,
            productAnimStyle,
            {
              left: productX,
              top: productY,
            }
          ]}
        >
          <Actionable action={product?.actions?.openDetailsPage} hapticFeedback="impactLight" pressEffect="pop">
            <Flexbox flexDirection="column" alignItems="center" gap={5}>
              <Image
                src={product?.images?.[0]}
                style={styles.productImage}
                resizeMode="cover"
              />
              {product?.priceRange?.minVariantPrice && (
                <View  bg="gray-1100/600" px="4xs" py="6xs" borderRadius={10}>
                  <PriceDisplay adjustsFontSizeToFit numberOfLines={1} color="gray-0" fontSize="2xs" price={product.priceRange.minVariantPrice} />
                </View>
              )}
            </Flexbox>
          </Actionable>
        </Animated.View>
      )}
    </View>
  );
};

// Navigation Arrow Button
interface NavButtonProps {
  direction: 'prev' | 'next';
  onPress: () => void;
  disabled?: boolean;
}

const NavButton: React.FC<NavButtonProps> = ({ direction, onPress, disabled }) => {
  const scale = useSharedValue(1);
  
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  const handlePressIn = () => {
    scale.value = withTiming(0.9, { duration: 100 });
  };

  const handlePressOut = () => {
    scale.value = withTiming(1, { duration: 100 });
  };

  return (
    <Pressable
      onPress={onPress}
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      disabled={disabled}
      style={[styles.navButton, disabled && styles.navButtonDisabled]}
    >
      <Animated.View style={[styles.navButtonInner, animatedStyle]}>
        <Text style={styles.navButtonText}>
          {direction === 'prev' ? '‹' : '›'}
        </Text>
      </Animated.View>
    </Pressable>
  );
};

// Animated Look Image component for smooth transitions
interface LookImageProps {
  image: ImageData;
  imageIndex: number;
  layout: { width: number; height: number };
  activeHotspot: number;
  showCard: boolean;
  onHotspotPress: (index: number) => void;
  opacity: Animated.SharedValue<number>;
  isVisible: boolean;
  isOverlay?: boolean; // If true, renders as absolute overlay
}

const LookImage: React.FC<LookImageProps> = ({
  image,
  imageIndex,
  layout,
  activeHotspot,
  showCard,
  onHotspotPress,
  opacity,
  isVisible,
  isOverlay = false,
}) => {
  const animatedStyle = useAnimatedStyle(() => ({
    opacity: opacity.value,
  }));

  if (!isVisible) return null;

  return (
    <Animated.View style={[isOverlay ? styles.lookImageOverlay : styles.lookImageBase, animatedStyle]}>
      <Image
        source={{ uri: image.url }}
        style={{ width: '100%', aspectRatio: image.aspectRatio }}
        resizeMode="cover"
      />
      {/* Subtle vignette overlay */}
      <View style={styles.vignetteOverlay} />
      
      {/* Hotspots */}
      {layout.width > 0 && image.annotations?.map((annotation, index) => (
        <HotspotWithCard
          key={`${imageIndex}-${annotation.id}`}
          annotation={annotation}
          index={index}
          imageWidth={layout.width}
          imageHeight={layout.height}
          isActive={index === activeHotspot && showCard}
          onHotspotPress={onHotspotPress}
        />
      ))}
    </Animated.View>
  );
};

const AppBlock: React.FC<AppBlockProps> = ({ data }) => {
  const { images } = data || {};

  if (!images || images.length === 0) {
    return (
      <View style={styles.emptyContainer}>
        <Text style={styles.emptyText}>No images available</Text>
      </View>
    );
  }

  const [currentImageIndex, setCurrentImageIndex] = React.useState(0);
  const [nextImageIndex, setNextImageIndex] = React.useState<number | null>(null);
  const [containerWidth, setContainerWidth] = React.useState(0);
  const [activeHotspot, setActiveHotspot] = React.useState(0);
  const [showCard, setShowCard] = React.useState(true);
  const [isTransitioning, setIsTransitioning] = React.useState(false);
  
  // Animated opacity values for crossfade
  const currentOpacity = useSharedValue(1);
  const nextOpacity = useSharedValue(0);
  
  // Animated height for smooth height transitions
  const animatedHeight = useSharedValue(0);
  
  const currentImage = images[currentImageIndex];
  const nextImage = nextImageIndex !== null ? images[nextImageIndex] : null;
  const totalImages = images.length;
  const totalHotspots = currentImage?.annotations?.length || 0;
  
  // Calculate height from width and aspect ratio
  const getHeightForImage = (image: ImageData, width: number) => {
    if (!width || !image?.aspectRatio) return 0;
    return width / image.aspectRatio;
  };
  
  // Initialize height when container width is set
  React.useEffect(() => {
    if (containerWidth > 0 && currentImage) {
      const initialHeight = getHeightForImage(currentImage, containerWidth);
      animatedHeight.value = initialHeight;
    }
  }, [containerWidth, currentImage?.aspectRatio]);
  
  // Animated style for container height
  const containerHeightStyle = useAnimatedStyle(() => ({
    height: animatedHeight.value,
  }));
  
  // Layout object derived from containerWidth and animated height
  const layout = React.useMemo(() => ({
    width: containerWidth,
    height: containerWidth > 0 && currentImage ? getHeightForImage(currentImage, containerWidth) : 0,
  }), [containerWidth, currentImage?.aspectRatio]);
  
  const DISPLAY_MS = 3500; // dwell time for each hotspot
  const TRANSITION_MS = 300; // smooth transition gap for hotspots
  const cycleRef = React.useRef<NodeJS.Timeout | null>(null);
  const transitionRef = React.useRef<NodeJS.Timeout | null>(null);

  const clearTimers = () => {
    if (cycleRef.current) clearTimeout(cycleRef.current);
    if (transitionRef.current) clearTimeout(transitionRef.current);
  };

  const startCycle = React.useCallback(() => {
    clearTimers();
    
    cycleRef.current = setTimeout(() => {
      setShowCard(false);
      
      transitionRef.current = setTimeout(() => {
        setActiveHotspot((prev) => (prev + 1) % totalHotspots);
        setShowCard(true);
        startCycle();
      }, TRANSITION_MS);
    }, DISPLAY_MS);
  }, [totalHotspots]);

  React.useEffect(() => {
    if (!totalHotspots || isTransitioning) return;
    startCycle();
    return () => clearTimers();
  }, [totalHotspots, startCycle, currentImageIndex, isTransitioning]);

  // Handle hotspot press - switch to selected hotspot
  const handleHotspotPress = React.useCallback((index: number) => {
    if (index === activeHotspot || isTransitioning) return;
    
    clearTimers();
    setShowCard(false);
    
    transitionRef.current = setTimeout(() => {
      setActiveHotspot(index);
      setShowCard(true);
      startCycle();
    }, TRANSITION_MS);
  }, [activeHotspot, startCycle, isTransitioning]);

  // Smooth image transition
  const transitionToImage = React.useCallback((targetIndex: number) => {
    if (isTransitioning || targetIndex === currentImageIndex) return;
    if (targetIndex < 0 || targetIndex >= totalImages) return;
    
    clearTimers();
    setShowCard(false);
    setIsTransitioning(true);
    setNextImageIndex(targetIndex);
    
    const targetImage = images[targetIndex];
    const targetHeight = getHeightForImage(targetImage, containerWidth);
    
    // Fade in the next image on top (current stays visible underneath)
    // This avoids the flicker from opacity swapping
    currentOpacity.value = 1; // Keep current visible as background
    nextOpacity.value = 0;
    nextOpacity.value = withTiming(1, { 
      duration: IMAGE_TRANSITION_MS, 
      easing: Easing.inOut(Easing.quad) 
    });
    
    // Animate height change
    animatedHeight.value = withTiming(targetHeight, {
      duration: IMAGE_TRANSITION_MS,
      easing: Easing.inOut(Easing.quad),
    });
    
    // After transition completes, update state in stages to avoid flicker
    const completeTimer = setTimeout(() => {
      // Stage 1: Update the current image index
      // The next image is still visible as overlay, covering the transition
      setCurrentImageIndex(targetIndex);
      setActiveHotspot(0);
      setShowCard(true);
      setIsTransitioning(false);
    }, IMAGE_TRANSITION_MS);
    
    // Stage 2: Clear the overlay after the state has settled
    // This delay ensures React has fully rendered the new current image
    const clearOverlayTimer = setTimeout(() => {
      setNextImageIndex(null);
      nextOpacity.value = 0;
    }, IMAGE_TRANSITION_MS + 100); // Extra 100ms to ensure render is complete
    
    return () => {
      clearTimeout(completeTimer);
      clearTimeout(clearOverlayTimer);
    };
  }, [currentImageIndex, totalImages, isTransitioning, containerWidth, images]);

  // Navigate to previous image (loops to last when at first)
  const handlePrevImage = React.useCallback(() => {
    const prevIndex = currentImageIndex === 0 ? totalImages - 1 : currentImageIndex - 1;
    transitionToImage(prevIndex);
  }, [currentImageIndex, totalImages, transitionToImage]);

  // Navigate to next image (loops to first when at last)
  const handleNextImage = React.useCallback(() => {
    const nextIndex = currentImageIndex === totalImages - 1 ? 0 : currentImageIndex + 1;
    transitionToImage(nextIndex);
  }, [currentImageIndex, totalImages, transitionToImage]);

  // Handle container layout to get width
  const handleLayout = React.useCallback((e: any) => {
    const { width } = e.nativeEvent.layout;
    if (width !== containerWidth) {
      setContainerWidth(width);
      // Set initial height immediately
      if (currentImage && animatedHeight.value === 0) {
        animatedHeight.value = getHeightForImage(currentImage, width);
      }
    }
  }, [containerWidth, currentImage]);

  return (
    <View style={styles.container}>
      <Animated.View
        style={[styles.imageWrapper, containerHeightStyle]}
        onLayout={handleLayout}
      >
        {/* Current image (fades out during transition) */}
        <LookImage
          image={currentImage}
          imageIndex={currentImageIndex}
          layout={layout}
          activeHotspot={activeHotspot}
          showCard={showCard && !isTransitioning}
          onHotspotPress={handleHotspotPress}
          opacity={currentOpacity}
          isVisible={true}
          isOverlay={false}
        />

        {/* Next image (fades in during transition, rendered as overlay) */}
        {nextImage && (
          <LookImage
            image={nextImage}
            imageIndex={nextImageIndex!}
            layout={{ width: containerWidth, height: getHeightForImage(nextImage, containerWidth) }}
            activeHotspot={0}
            showCard={false}
            onHotspotPress={() => {}}
            opacity={nextOpacity}
            isVisible={true}
            isOverlay={true}
          />
        )}

        {/* Navigation Controls */}
        {totalImages > 1 && (
          <>
            {/* Previous Button */}
            <View style={styles.navButtonLeft}>
              <NavButton
                direction="prev"
                onPress={handlePrevImage}
                disabled={isTransitioning}
              />
            </View>

            {/* Next Button */}
            <View style={styles.navButtonRight}>
              <NavButton
                direction="next"
                onPress={handleNextImage}
                disabled={isTransitioning}
              />
            </View>
          </>
        )}
      </Animated.View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'transparent',
  },
  emptyContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 24,
    backgroundColor: 'transparent',
  },
  emptyText: {
    fontSize: 14,
    color: '#666',
    textAlign: 'center',
  },
  imageWrapper: {
    width: '100%',
    position: 'relative',
    zIndex: 1,
    overflow: 'hidden',
  },
  lookImageBase: {
    position: 'relative',
    width: '100%',
  },
  lookImageOverlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    width: '100%',
  },
  vignetteOverlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'transparent',
    // Subtle edge darkening for depth
    borderWidth: 0,
  },
  overlayContainer: {
    position: 'absolute',
    alignItems: 'center',
    justifyContent: 'center',
    zIndex: 10,
    overflow: 'visible',
  },
  ringsContainer: {
    position: 'absolute',
    width: HOTSPOT_SIZE,
    height: HOTSPOT_SIZE,
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 5,
  },
  pulseRing: {
    position: 'absolute',
    width: HOTSPOT_SIZE,
    height: HOTSPOT_SIZE,
    borderRadius: HOTSPOT_SIZE / 2,
    borderWidth: 2,
    borderColor: ACTIVE_COLOR,
    backgroundColor: 'transparent',
  },
  hotspotPressable: {
    width: HOTSPOT_SIZE * 2,
    height: HOTSPOT_SIZE * 2,
    marginLeft: -HOTSPOT_SIZE,
    marginTop: -HOTSPOT_SIZE,
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 15,
  },
  hotspotWrapper: {
    width: HOTSPOT_SIZE,
    height: HOTSPOT_SIZE,
    justifyContent: 'center',
    alignItems: 'center',
  },
  hotspotGlow: {
    position: 'absolute',
    width: HOTSPOT_SIZE * 2,
    height: HOTSPOT_SIZE * 2,
    borderRadius: HOTSPOT_SIZE,
    backgroundColor: ACTIVE_COLOR,
  },
  hotspotOuter: {
    width: HOTSPOT_SIZE,
    height: HOTSPOT_SIZE,
    borderRadius: HOTSPOT_SIZE / 2,
    backgroundColor: 'rgba(255, 255, 255, 0.2)',
    justifyContent: 'center',
    alignItems: 'center',
    borderWidth: 1.5,
    borderColor: 'rgba(255, 255, 255, 0.6)',
    // Soft shadow
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.15,
    shadowRadius: 4,
    elevation: 4,
  },
  hotspotInner: {
    width: HOTSPOT_SIZE * 0.45,
    height: HOTSPOT_SIZE * 0.45,
    borderRadius: HOTSPOT_SIZE * 0.225,
    backgroundColor: '#fff',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.2,
    shadowRadius: 2,
    elevation: 2,
  },
  hotspotOuterActive: {
    borderColor: ACTIVE_COLOR,
    backgroundColor: 'rgba(76, 217, 100, 0.15)',
  },
  hotspotInnerActive: {
    backgroundColor: ACTIVE_COLOR,
  },
  connectionLine: {
    position: 'absolute',
    height: 2,
    backgroundColor: ACTIVE_COLOR,
    borderRadius: 1,
    top: -1,
    zIndex: 12,
  },
  productContainer: {
    position: 'absolute',
    width: IMAGE_SIZE,
    alignItems: 'center',
    zIndex: 20,
  },
  productImage: {
    width: IMAGE_SIZE,
    height: IMAGE_SIZE,
    borderRadius: IMAGE_SIZE / 2,
    borderWidth: 2,
    borderColor: '#fff',
    backgroundColor: '#f0f0f0',
    // Shadow for the circular image
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.25,
    shadowRadius: 6,
    elevation: 8,
  },
  // Navigation styles
  navButtonLeft: {
    position: 'absolute',
    left: 12,
    top: '50%',
    marginTop: -NAV_BUTTON_SIZE / 2,
    zIndex: 30,
  },
  navButtonRight: {
    position: 'absolute',
    right: 12,
    top: '50%',
    marginTop: -NAV_BUTTON_SIZE / 2,
    zIndex: 30,
  },
  navButton: {
    width: NAV_BUTTON_SIZE,
    height: NAV_BUTTON_SIZE,
    borderRadius: NAV_BUTTON_SIZE / 2,
    justifyContent: 'center',
    alignItems: 'center',
  },
  navButtonDisabled: {
    opacity: 0.3,
  },
  navButtonInner: {
    width: NAV_BUTTON_SIZE,
    height: NAV_BUTTON_SIZE,
    borderRadius: NAV_BUTTON_SIZE / 2,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
    borderWidth: 1,
    borderColor: 'rgba(255, 255, 255, 0.3)',
  },
  navButtonText: {
    color: '#fff',
    fontSize: 28,
    fontWeight: '300',
    marginTop: -2,
  },
});

export default AppBlock;