Download index.html from willytpfw/pixelflow-carousel-magic: direct link, hf CLI and curl.
- Browser
- Download file 14.1 kB
-
https://huggingface.co/spaces/willytpfw/pixelflow-carousel-magic/resolve/main/index.html
- Command line
-
hf download hf://spaces/willytpfw/pixelflow-carousel-magic/index.html
-
curl -L -o index.html https://huggingface.co/spaces/willytpfw/pixelflow-carousel-magic/resolve/main/index.html
14.1 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PixelFlow Carousel</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/react@18/umd/react.development.js"></script> | |
| <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> | |
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <style> | |
| .zoom-effect { | |
| transition: transform 0.3s ease; | |
| } | |
| .zoom-effect:hover { | |
| transform: scale(1.03); | |
| } | |
| .skeleton { | |
| background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); | |
| background-size: 200% 100%; | |
| animation: shimmer 1.5s infinite; | |
| } | |
| @keyframes shimmer { | |
| 0% { background-position: 200% 0; } | |
| 100% { background-position: -200% 0; } | |
| } | |
| .dark .skeleton { | |
| background: linear-gradient(90deg, #2d3748 25%, #4a5568 50%, #2d3748 75%); | |
| } | |
| .carousel-transition { | |
| transition: opacity 0.5s ease, transform 0.5s ease; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-white dark:bg-gray-900 transition-colors duration-300"> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| const { useState, useEffect, useRef } = React; | |
| const ImageCarousel = () => { | |
| const [images, setImages] = useState([]); | |
| const [currentIndex, setCurrentIndex] = useState(0); | |
| const [isLoading, setIsLoading] = useState(true); | |
| const [error, setError] = useState(null); | |
| const [isAutoPlaying, setIsAutoPlaying] = useState(true); | |
| const [apiSource, setApiSource] = useState('unsplash'); | |
| const [darkMode, setDarkMode] = useState(false); | |
| const timerRef = useRef(null); | |
| const carouselRef = useRef(null); | |
| const API_CONFIG = { | |
| unsplash: { | |
| url: '', | |
| params: {}, | |
| transform: () => [] | |
| }, | |
| pexels: { | |
| url: '', | |
| params: {}, | |
| transform: () => [] | |
| }, | |
| pixabay: { | |
| url: '', | |
| params: {}, | |
| transform: () => [] | |
| } | |
| }; | |
| const fetchImages = async () => { | |
| setIsLoading(true); | |
| setError(null); | |
| try { | |
| const config = API_CONFIG[apiSource]; | |
| // Skip API calls and use placeholders directly since API keys aren't provided | |
| setImages(Array(5).fill().map((_, i) => ({ | |
| url: `http://static.photos/nature/1024x576/${i+1}`, | |
| alt: `Placeholder Image ${i+1}`, | |
| author: 'Placeholder Author', | |
| authorUrl: '#' | |
| }))); | |
| return; | |
| const data = await response.json(); | |
| if (!response.ok) throw new Error(data.message || 'Failed to fetch images'); | |
| setImages(config.transform(data)); | |
| } catch (err) { | |
| console.error('Error fetching images:', err); | |
| setError(err.message || 'Failed to load images. Please try again.'); | |
| // Fallback to placeholder images | |
| setImages(Array(5).fill().map((_, i) => ({ | |
| url: `http://static.photos/nature/1024x576/${i+1}`, | |
| alt: `Placeholder Image ${i+1}`, | |
| author: 'Placeholder Author', | |
| authorUrl: '#' | |
| }))); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| useEffect(() => { | |
| fetchImages(); | |
| }, [apiSource]); | |
| useEffect(() => { | |
| if (isAutoPlaying && images.length > 0) { | |
| timerRef.current = setInterval(() => { | |
| setCurrentIndex(prev => (prev + 1) % images.length); | |
| }, 5000); | |
| } | |
| return () => clearInterval(timerRef.current); | |
| }, [isAutoPlaying, images.length]); | |
| const goToPrev = () => { | |
| setCurrentIndex(prev => (prev - 1 + images.length) % images.length); | |
| resetTimer(); | |
| }; | |
| const goToNext = () => { | |
| setCurrentIndex(prev => (prev + 1) % images.length); | |
| resetTimer(); | |
| }; | |
| const goToIndex = (index) => { | |
| setCurrentIndex(index); | |
| resetTimer(); | |
| }; | |
| const resetTimer = () => { | |
| if (isAutoPlaying) { | |
| clearInterval(timerRef.current); | |
| timerRef.current = setInterval(() => { | |
| setCurrentIndex(prev => (prev + 1) % images.length); | |
| }, 5000); | |
| } | |
| }; | |
| const toggleAutoPlay = () => { | |
| setIsAutoPlaying(prev => !prev); | |
| }; | |
| const toggleDarkMode = () => { | |
| setDarkMode(prev => !prev); | |
| document.body.classList.toggle('dark'); | |
| }; | |
| if (error) { | |
| return ( | |
| <div className="flex flex-col items-center justify-center min-h-screen p-4"> | |
| <div className="bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 p-4 rounded-lg max-w-md"> | |
| <div className="flex items-center"> | |
| <i data-feather="alert-triangle" className="mr-2"></i> | |
| <h3 className="font-bold">Error Loading Images</h3> | |
| </div> | |
| <p className="mt-2">{error}</p> | |
| <p className="mt-2 text-sm">Using static placeholder images.</p> | |
| <button | |
| onClick={fetchImages} | |
| className="mt-4 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md transition-colors" | |
| > | |
| Retry | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div className="w-full max-w-4xl"> | |
| <div className="flex justify-between items-center mb-6"> | |
| <h1 className="text-3xl font-bold text-gray-800 dark:text-white">PixelFlow Carousel</h1> | |
| <div className="flex space-x-4"> | |
| <div className="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-white px-3 py-2 rounded-md"> | |
| Static Photos | |
| </div> | |
| <button | |
| onClick={toggleDarkMode} | |
| className="p-2 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200" | |
| > | |
| <i data-feather={darkMode ? "sun" : "moon"}></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div className="relative overflow-hidden rounded-xl shadow-xl"> | |
| {isLoading ? ( | |
| <div className="aspect-video w-full skeleton rounded-xl"></div> | |
| ) : ( | |
| <div | |
| ref={carouselRef} | |
| className="relative aspect-video w-full overflow-hidden" | |
| > | |
| {images.map((image, index) => ( | |
| <div | |
| key={index} | |
| className={`absolute inset-0 flex items-center justify-center carousel-transition ${index === currentIndex ? 'opacity-100' : 'opacity-0 pointer-events-none'}`} | |
| > | |
| <div className="relative w-full h-full group"> | |
| <img | |
| src={image.url} | |
| alt={image.alt} | |
| className="w-full h-full object-cover zoom-effect" | |
| /> | |
| <div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-all duration-300 flex items-end p-6"> | |
| <div className="transform translate-y-8 group-hover:translate-y-0 transition-transform duration-300"> | |
| <h3 className="text-white text-xl font-bold">{image.alt}</h3> | |
| <a | |
| href={image.authorUrl} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-gray-200 hover:text-white text-sm" | |
| > | |
| Photo by {image.author} | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| {/* Navigation Arrows */} | |
| <button | |
| onClick={goToPrev} | |
| className="absolute left-4 top-1/2 -translate-y-1/2 bg-white dark:bg-gray-800 bg-opacity-80 dark:bg-opacity-80 p-2 rounded-full shadow-md hover:bg-opacity-100 transition-all" | |
| disabled={isLoading} | |
| > | |
| <i data-feather="chevron-left" className="text-gray-800 dark:text-white"></i> | |
| </button> | |
| <button | |
| onClick={goToNext} | |
| className="absolute right-4 top-1/2 -translate-y-1/2 bg-white dark:bg-gray-800 bg-opacity-80 dark:bg-opacity-80 p-2 rounded-full shadow-md hover:bg-opacity-100 transition-all" | |
| disabled={isLoading} | |
| > | |
| <i data-feather="chevron-right" className="text-gray-800 dark:text-white"></i> | |
| </button> | |
| {/* Indicators */} | |
| <div className="absolute bottom-4 left-0 right-0 flex justify-center space-x-2"> | |
| {images.map((_, index) => ( | |
| <button | |
| key={index} | |
| onClick={() => goToIndex(index)} | |
| className={`w-3 h-3 rounded-full ${index === currentIndex ? 'bg-white' : 'bg-white bg-opacity-50'} transition-all`} | |
| disabled={isLoading} | |
| ></button> | |
| ))} | |
| </div> | |
| {/* Play/Pause Button */} | |
| <button | |
| onClick={toggleAutoPlay} | |
| className="absolute top-4 right-4 bg-white dark:bg-gray-800 bg-opacity-80 dark:bg-opacity-80 p-2 rounded-full shadow-md hover:bg-opacity-100 transition-all" | |
| > | |
| <i data-feather={isAutoPlaying ? "pause" : "play"} className="text-gray-800 dark:text-white"></i> | |
| </button> | |
| </div> | |
| <div className="mt-6 text-center text-gray-600 dark:text-gray-400"> | |
| <p>Browse the static image carousel using navigation controls</p> | |
| <div className="mt-2 flex justify-center space-x-4"> | |
| <button | |
| onClick={fetchImages} | |
| className="flex items-center text-sm bg-gray-100 dark:bg-gray-700 px-3 py-1 rounded-md" | |
| > | |
| <i data-feather="refresh-cw" className="w-4 h-4 mr-1"></i> | |
| Refresh Images | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| const App = () => { | |
| return ( | |
| <div> | |
| <ImageCarousel /> | |
| </div> | |
| ); | |
| }; | |
| const root = ReactDOM.createRoot(document.getElementById('root')); | |
| root.render(<App />); | |
| // Initialize feather icons | |
| feather.replace(); | |
| </script> | |
| </body> | |
| </html> | |