diff --git a/src/assets/2014 (2).jpg b/src/assets/2014 (2).jpg new file mode 100644 index 0000000..aa2b04d Binary files /dev/null and b/src/assets/2014 (2).jpg differ diff --git a/src/assets/2014 (3).jpg b/src/assets/2014 (3).jpg new file mode 100644 index 0000000..5a3ffe1 Binary files /dev/null and b/src/assets/2014 (3).jpg differ diff --git a/src/assets/2014.jpg b/src/assets/2014.jpg new file mode 100644 index 0000000..2610f7a Binary files /dev/null and b/src/assets/2014.jpg differ diff --git a/src/assets/2016 (2).jpg b/src/assets/2016 (2).jpg new file mode 100644 index 0000000..39fb9c9 Binary files /dev/null and b/src/assets/2016 (2).jpg differ diff --git a/src/assets/2016.jpg b/src/assets/2016.jpg new file mode 100644 index 0000000..fd1c6f0 Binary files /dev/null and b/src/assets/2016.jpg differ diff --git a/src/assets/2017 (2).jpg b/src/assets/2017 (2).jpg new file mode 100644 index 0000000..2001042 Binary files /dev/null and b/src/assets/2017 (2).jpg differ diff --git a/src/assets/2017 (3).jpg b/src/assets/2017 (3).jpg new file mode 100644 index 0000000..6ed0b03 Binary files /dev/null and b/src/assets/2017 (3).jpg differ diff --git a/src/assets/2017.jpg b/src/assets/2017.jpg new file mode 100644 index 0000000..ed9491b Binary files /dev/null and b/src/assets/2017.jpg differ diff --git a/src/assets/2022.jpg b/src/assets/2022.jpg new file mode 100644 index 0000000..33b3a00 Binary files /dev/null and b/src/assets/2022.jpg differ diff --git a/src/assets/2023 (2).jpg b/src/assets/2023 (2).jpg new file mode 100644 index 0000000..1b44936 Binary files /dev/null and b/src/assets/2023 (2).jpg differ diff --git a/src/assets/2023.jpg b/src/assets/2023.jpg new file mode 100644 index 0000000..7597cba Binary files /dev/null and b/src/assets/2023.jpg differ diff --git a/src/assets/2024.jpg b/src/assets/2024.jpg new file mode 100644 index 0000000..514c0dd Binary files /dev/null and b/src/assets/2024.jpg differ diff --git a/src/assets/2025 (2).jpg b/src/assets/2025 (2).jpg new file mode 100644 index 0000000..8cf2209 Binary files /dev/null and b/src/assets/2025 (2).jpg differ diff --git a/src/assets/2025 (3).jpg b/src/assets/2025 (3).jpg new file mode 100644 index 0000000..c7236d2 Binary files /dev/null and b/src/assets/2025 (3).jpg differ diff --git a/src/assets/2025 (4).jpg b/src/assets/2025 (4).jpg new file mode 100644 index 0000000..81d637b Binary files /dev/null and b/src/assets/2025 (4).jpg differ diff --git a/src/assets/2025 (5).jpg b/src/assets/2025 (5).jpg new file mode 100644 index 0000000..147c356 Binary files /dev/null and b/src/assets/2025 (5).jpg differ diff --git a/src/assets/2025 (6).jpg b/src/assets/2025 (6).jpg new file mode 100644 index 0000000..a7a2e1e Binary files /dev/null and b/src/assets/2025 (6).jpg differ diff --git a/src/assets/2025.jpg b/src/assets/2025.jpg new file mode 100644 index 0000000..6181edc Binary files /dev/null and b/src/assets/2025.jpg differ diff --git a/src/assets/Frame 136.jpg b/src/assets/Frame 136.jpg new file mode 100644 index 0000000..beec50c Binary files /dev/null and b/src/assets/Frame 136.jpg differ diff --git a/src/assets/Frame 142.jpg b/src/assets/Frame 142.jpg new file mode 100644 index 0000000..b25056f Binary files /dev/null and b/src/assets/Frame 142.jpg differ diff --git a/src/components/AboutSection.jsx b/src/components/AboutSection.jsx index c55c76a..bfa4170 100644 --- a/src/components/AboutSection.jsx +++ b/src/components/AboutSection.jsx @@ -14,8 +14,11 @@ export default function AboutSection() { About ASME NIT Rourkela +
diff --git a/src/components/AchievementCard.jsx b/src/components/AchievementCard.jsx index ea84bd7..99437a9 100644 --- a/src/components/AchievementCard.jsx +++ b/src/components/AchievementCard.jsx @@ -6,6 +6,7 @@ import { ChevronRight, } from "lucide-react"; import { useState, useEffect } from "react"; +import { getOptimizedImageUrl } from "../utils/imageOptimizer"; const ImageCarousel = ({ images, title }) => { const [currentIndex, setCurrentIndex] = useState(0); @@ -28,8 +29,10 @@ const ImageCarousel = ({ images, title }) => {
{`${title} { e.target.style.display = "none"; diff --git a/src/components/EventCard.jsx b/src/components/EventCard.jsx index 56cad8e..3cd21cf 100644 --- a/src/components/EventCard.jsx +++ b/src/components/EventCard.jsx @@ -5,6 +5,7 @@ import { FaCalendar, FaMapMarkerAlt, } from "react-icons/fa"; +import { getOptimizedImageUrl } from "../utils/imageOptimizer"; const EventCard = ({ title, description, images, date, location, status }) => { const [currentImageIndex, setCurrentImageIndex] = useState(0); @@ -67,10 +68,13 @@ const EventCard = ({ title, description, images, date, location, status }) => { {images && images.length > 0 ? ( <> {`${title}
diff --git a/src/components/GallerySection.jsx b/src/components/GallerySection.jsx index e0dffed..9502c05 100644 --- a/src/components/GallerySection.jsx +++ b/src/components/GallerySection.jsx @@ -58,7 +58,9 @@ export default function GallerySection() { alt={image.alt} className="w-full h-64 object-cover transition-transform duration-300 group-hover:scale-105" loading="lazy" + decoding="async" /> +
{/* Overlay on hover */} diff --git a/src/components/HeroSection.jsx b/src/components/HeroSection.jsx index 292b5a3..a3b51cd 100644 --- a/src/components/HeroSection.jsx +++ b/src/components/HeroSection.jsx @@ -71,8 +71,12 @@ export default function HeroSection() { ASME NIT Rourkela - Engineering Excellence +
diff --git a/src/components/Sponsor.jsx b/src/components/Sponsor.jsx index f71f0b3..191a287 100644 --- a/src/components/Sponsor.jsx +++ b/src/components/Sponsor.jsx @@ -68,6 +68,8 @@ const SponsorsSection = () => { {sponsor.name} diff --git a/src/components/Team.jsx b/src/components/Team.jsx index 250e070..90bfa51 100644 --- a/src/components/Team.jsx +++ b/src/components/Team.jsx @@ -150,8 +150,11 @@ const TeamSlider = () => { "https://via.placeholder.com/300x300?text=Photo+Coming+Soon" } alt={member.name} + loading="lazy" + decoding="async" className="w-full h-64 object-cover transition-transform duration-300 group-hover:scale-105" /> +
diff --git a/src/components/shared/OptimizedImage.jsx b/src/components/shared/OptimizedImage.jsx new file mode 100644 index 0000000..239a339 --- /dev/null +++ b/src/components/shared/OptimizedImage.jsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { getOptimizedImageUrl } from "../../utils/imageOptimizer"; + +/** + * Reusable Optimized Image Component + * Handles Cloudinary auto-format/quality optimization, lazy loading, async decoding, + * and smooth fade-in loading state. + */ +export default function OptimizedImage({ + src, + alt = "", + width, + className = "", + loading = "lazy", + decoding = "async", + fetchPriority, + onError, + onLoad, + showSkeleton = true, + ...props +}) { + const [isLoaded, setIsLoaded] = useState(false); + const [hasError, setHasError] = useState(false); + + const optimizedSrc = getOptimizedImageUrl(src, { width }); + + const handleLoad = (e) => { + setIsLoaded(true); + if (onLoad) onLoad(e); + }; + + const handleError = (e) => { + setHasError(true); + if (onError) onError(e); + }; + + if (hasError) { + return ( +
+ {alt || "Image unavailable"} +
+ ); + } + + return ( +
+ {/* Optional skeleton loader placeholder */} + {showSkeleton && !isLoaded && ( +
+ )} + {alt} +
+ ); +} diff --git a/src/config/achievement.js b/src/config/achievement.js index 3993d15..c9816e6 100644 --- a/src/config/achievement.js +++ b/src/config/achievement.js @@ -1,25 +1,26 @@ // Import all year-based images -import img2014 from "../assets/2014.png"; -import img2014_2 from "../assets/2014 (2).png"; -import img2014_3 from "../assets/2014 (3).png"; -import img2016 from "../assets/2016.png"; -import img2016_2 from "../assets/2016 (2).png"; -import img2017 from "../assets/2017.png"; -import img2017_2 from "../assets/2017 (2).png"; -import img2017_3 from "../assets/2017 (3).png"; -import img2022 from "../assets/2022.png"; -import img2023 from "../assets/2023.png"; -import img2023_2 from "../assets/2023 (2).png"; -import img2024 from "../assets/2024.png"; -import img2025 from "../assets/2025.png"; -import img2025_2 from "../assets/2025 (2).png"; -import img2025_3 from "../assets/2025 (3).png"; -import img2025_4 from "../assets/2025 (4).png"; -import img2025_5 from "../assets/2025 (5).png"; -import img2025_6 from "../assets/2025 (6).png"; +import img2014 from "../assets/2014.jpg"; +import img2014_2 from "../assets/2014 (2).jpg"; +import img2014_3 from "../assets/2014 (3).jpg"; +import img2016 from "../assets/2016.jpg"; +import img2016_2 from "../assets/2016 (2).jpg"; +import img2017 from "../assets/2017.jpg"; +import img2017_2 from "../assets/2017 (2).jpg"; +import img2017_3 from "../assets/2017 (3).jpg"; +import img2022 from "../assets/2022.jpg"; +import img2023 from "../assets/2023.jpg"; +import img2023_2 from "../assets/2023 (2).jpg"; +import img2024 from "../assets/2024.jpg"; +import img2025 from "../assets/2025.jpg"; +import img2025_2 from "../assets/2025 (2).jpg"; +import img2025_3 from "../assets/2025 (3).jpg"; +import img2025_4 from "../assets/2025 (4).jpg"; +import img2025_5 from "../assets/2025 (5).jpg"; +import img2025_6 from "../assets/2025 (6).jpg"; import img2015 from "../assets/2015.jpeg"; import img2026 from "../assets/2026.jpg"; import img2026_2 from "../assets/2026 (2).jpg"; +import { optimizeCloudinaryUrl } from "../utils/imageOptimizer"; // Organize images by year const yearImages = { @@ -177,7 +178,10 @@ const achievementsData = [ ]; export const achivementTop = { - link: "https://res.cloudinary.com/dc7tu3hzv/image/upload/v1785234333/WhatsApp_Image_2026-06-29_at_18.22.43_mjqlt2.jpg", + link: optimizeCloudinaryUrl( + "https://res.cloudinary.com/dc7tu3hzv/image/upload/v1785234333/WhatsApp_Image_2026-06-29_at_18.22.43_mjqlt2.jpg", + { width: 1200 }, + ), }; export default achievementsData; diff --git a/src/config/gallary.js b/src/config/gallary.js index 8d66047..c0fc9b1 100644 --- a/src/config/gallary.js +++ b/src/config/gallary.js @@ -1,18 +1,18 @@ import img2026_1 from "../assets/2026.jpg"; import img2026_2 from "../assets/2026 (2).jpg"; -import img2014 from "../assets/2014.png"; -import img2014_2 from "../assets/2014 (2).png"; -import img2014_3 from "../assets/2014 (3).png"; -import img2016 from "../assets/2016.png"; -import img2016_2 from "../assets/2016 (2).png"; -import img2017 from "../assets/2017.png"; -import img2017_2 from "../assets/2017 (2).png"; -import img2017_3 from "../assets/2017 (3).png"; -import img2022 from "../assets/2022.png"; -import img2023 from "../assets/2023.png"; -import img2023_2 from "../assets/2023 (2).png"; -import img2024 from "../assets/2024.png"; +import img2014 from "../assets/2014.jpg"; +import img2014_2 from "../assets/2014 (2).jpg"; +import img2014_3 from "../assets/2014 (3).jpg"; +import img2016 from "../assets/2016.jpg"; +import img2016_2 from "../assets/2016 (2).jpg"; +import img2017 from "../assets/2017.jpg"; +import img2017_2 from "../assets/2017 (2).jpg"; +import img2017_3 from "../assets/2017 (3).jpg"; +import img2022 from "../assets/2022.jpg"; +import img2023 from "../assets/2023.jpg"; +import img2023_2 from "../assets/2023 (2).jpg"; import img2015 from "../assets/2015.jpeg"; +import { optimizeCloudinaryUrl } from "../utils/imageOptimizer"; const images = [ { @@ -231,18 +231,14 @@ const images = [ alt: "ASME Image 7", year: "others", }, - { - id: 34, - src: img2024, - alt: "ASME Image 8", - year: "others", - }, - { - id: 35, - src: "https://res.cloudinary.com/dsjxx976j/image/upload/v1756627303/asme93_gbo3e7.jpg", - alt: "ASME Image 9", - year: "others", - }, ]; -export default images; +const optimizedImages = images.map((img) => ({ + ...img, + src: + typeof img.src === "string" + ? optimizeCloudinaryUrl(img.src, { width: 1000 }) + : img.src, +})); + +export default optimizedImages; diff --git a/src/config/navbarHero.js b/src/config/navbarHero.js index 05b5eb4..16c57c5 100644 --- a/src/config/navbarHero.js +++ b/src/config/navbarHero.js @@ -1,6 +1,11 @@ +import { optimizeCloudinaryUrl } from "../utils/imageOptimizer"; + export const siteConfig = { navigation: { - logo: "https://res.cloudinary.com/dswk9scro/image/upload/v1755092284/ASME_NIT_Rourkela_Student_Section_Blue_ldskye.png", + logo: optimizeCloudinaryUrl( + "https://res.cloudinary.com/dswk9scro/image/upload/v1755092284/ASME_NIT_Rourkela_Student_Section_Blue_ldskye.png", + { width: 400 }, + ), links: [ { name: "Home", href: "/" }, { name: "Team", href: "/team" }, @@ -18,8 +23,10 @@ export const siteConfig = { "The ASME (American Society of Mechanical Engineers) NIT Rourkela Student Section is an official technical society under the Student Activity Centre (SAC) of NIT Rourkela. It serves as a vibrant platform for undergraduate students to enhance both technical and professional capabilities, preparing them for future industrial and corporate roles while aiming to bring laurels to the institute.", primaryButton: "Join Us", secondaryLink: "Learn More", - image: + image: optimizeCloudinaryUrl( "https://res.cloudinary.com/ddoeahbpv/image/upload/IMG-20260118-WA0067_aofrxp.jpg", + { width: 1200 }, + ), image1: "https://res.cloudinary.com/dwjcki9ey/image/upload/v1752159681/Ellipse_1_sxc0u7.png", image2: @@ -31,8 +38,10 @@ export const siteConfig = { description: "ASME NIT Rourkela is dedicated to fostering engineering innovation among students. We serve as a pivotal connecting link between academia and industry, tackling technical obstacles with enthusiasm through our various initiatives and events. ASME NIT Rourkela is committed to fostering engineering innovation and practical application among students. All of us are free to make a positive impact through our various technical and non-events.", button: "Learn More", - image: + image: optimizeCloudinaryUrl( "https://res.cloudinary.com/ddoeahbpv/image/upload/IMG-20260118-WA0109_ft7m8u.jpg", + { width: 1000 }, + ), }, missions: [ diff --git a/src/config/sponsors.js b/src/config/sponsors.js index 5f0b4eb..1c264d6 100644 --- a/src/config/sponsors.js +++ b/src/config/sponsors.js @@ -1,4 +1,5 @@ -// sponsors.js +import { optimizeCloudinaryUrl } from "../utils/imageOptimizer"; + const sponsors = [ { name: "NIT Rourkela Alumini ", @@ -44,4 +45,9 @@ const sponsors = [ }, ]; -export default sponsors; +const optimizedSponsors = sponsors.map((item) => ({ + ...item, + image: optimizeCloudinaryUrl(item.image, { width: 400 }), +})); + +export default optimizedSponsors; diff --git a/src/config/teammate.js b/src/config/teammate.js index c54d816..1aca0cb 100644 --- a/src/config/teammate.js +++ b/src/config/teammate.js @@ -1,3 +1,5 @@ +import { optimizeCloudinaryUrl } from "../utils/imageOptimizer"; + const teamMembers = [ { name: "Radha Choudhary", @@ -147,4 +149,9 @@ const teamMembers = [ }, ]; -export default teamMembers; +const optimizedTeamMembers = teamMembers.map((member) => ({ + ...member, + img: optimizeCloudinaryUrl(member.img, { width: 500 }), +})); + +export default optimizedTeamMembers; diff --git a/src/pages/gallery/Gallery.jsx b/src/pages/gallery/Gallery.jsx index 9863d42..3c621de 100644 --- a/src/pages/gallery/Gallery.jsx +++ b/src/pages/gallery/Gallery.jsx @@ -1,6 +1,12 @@ import { useState, useEffect, useCallback, useMemo } from "react"; import images from "../../config/gallary"; -import { ChevronLeft, ChevronRight, Maximize2, X } from "lucide-react"; +import { + ChevronLeft, + ChevronRight, + Maximize2, + X, + Image as ImageIcon, +} from "lucide-react"; export default function GalleryPage() { const [selectedYear, setSelectedYear] = useState("all"); @@ -44,17 +50,6 @@ export default function GalleryPage() { return images.filter((img) => img.year === Number(selectedYear)); }, [selectedYear]); - // Fallback image helper - const getImageSrc = useCallback( - (image) => { - if (failedImages[image.id]) { - return "https://res.cloudinary.com/dbnfkkfov/image/upload/v1759606221/WhatsApp_Image_2025-10-04_at_20.38.01_n5yqjt.jpg"; - } - return image.src; - }, - [failedImages], - ); - const handleImageError = (id) => { setFailedImages((prev) => ({ ...prev, [id]: true })); }; @@ -98,21 +93,33 @@ export default function GalleryPage() { {/* Clean Large Hero Photo (Textless, clearly visible like Achievement section) */}
- {heroSlides.map((slide, index) => ( -
- {slide.alt handleImageError(slide.id)} - className="w-full h-full object-cover transition-transform duration-[8000ms] ease-out scale-105" - /> -
- ))} + {heroSlides.map((slide, index) => { + const isSlideFailed = failedImages[slide.id]; + return ( +
+ {!isSlideFailed ? ( + {slide.alt handleImageError(slide.id)} + loading={index === 0 ? "eager" : "lazy"} + decoding="async" + {...(index === 0 ? { fetchpriority: "high" } : {})} + className="w-full h-full object-cover transition-transform duration-[8000ms] ease-out scale-105" + /> + ) : ( +
+ +
+ )} +
+ ); + })} {/* Minimal Bottom Slide Indicators */}
@@ -208,28 +215,40 @@ export default function GalleryPage() { {galleryImages.length > 0 ? (
{galleryImages.map((image, index) => { - const imgSrc = getImageSrc(image); + const isFailed = failedImages[image.id]; return (
openLightbox(index)} + onClick={() => !isFailed && openLightbox(index)} className="group relative cursor-pointer overflow-hidden rounded-2xl bg-gray-100 dark:bg-slate-800 shadow-sm hover:shadow-xl transition-all duration-300 hover:-translate-y-1.5 aspect-[4/3] w-full border border-gray-200/80 dark:border-slate-700/60" > - {image.alt handleImageError(image.id)} - loading="lazy" - className="w-full h-full object-cover transition-transform duration-500 ease-out group-hover:scale-105" - /> + {!isFailed ? ( + <> + {image.alt handleImageError(image.id)} + loading="lazy" + decoding="async" + className="w-full h-full object-cover transition-transform duration-500 ease-out group-hover:scale-105" + /> - {/* Subtle Hover Overlay */} -
-
- + {/* Subtle Hover Overlay */} +
+
+ +
+
+ + ) : ( +
+ + + {image.alt || `Image ${index + 1}`} +
-
+ )}
); })} @@ -282,7 +301,7 @@ export default function GalleryPage() {
{`Gallery diff --git a/src/pages/team/TeamPage.jsx b/src/pages/team/TeamPage.jsx index 61ec2bb..a92a0b1 100644 --- a/src/pages/team/TeamPage.jsx +++ b/src/pages/team/TeamPage.jsx @@ -43,6 +43,8 @@ const TeamMemberImage = ({ src, alt }) => { "https://via.placeholder.com/300x300?text=Photo+Coming+Soon" } alt={alt} + loading="lazy" + decoding="async" className={`w-full h-64 object-cover object-center rounded-none border-[8px] border-white shadow-[inset_0_0_0_1px_rgba(0,0,0,0.04)] transition-transform duration-300 group-hover:scale-105 ${ isLoading ? "opacity-0" : "opacity-100" }`} diff --git a/src/utils/imageOptimizer.js b/src/utils/imageOptimizer.js new file mode 100644 index 0000000..f84f74f --- /dev/null +++ b/src/utils/imageOptimizer.js @@ -0,0 +1,55 @@ +/** + * Utility to optimize image URLs, specifically Cloudinary hosted images, + * by appending auto-format (f_auto), auto-quality (q_auto), and optional width constraints. + */ + +/** + * Optimizes a Cloudinary image URL by injecting f_auto, q_auto, and optional width parameters. + * @param {string} url - The original image URL + * @param {Object} options - Optimization options + * @param {number} [options.width] - Optional target width in pixels + * @param {number} [options.quality='auto'] - Quality level or 'auto' + * @param {string} [options.format='auto'] - Format or 'auto' + * @param {string} [options.crop='limit'] - Crop mode (e.g. 'limit', 'scale', 'fill') + * @returns {string} The optimized image URL + */ +export function optimizeCloudinaryUrl(url, options = {}) { + if (!url || typeof url !== "string") return url; + + // Only transform Cloudinary URLs + if (!url.includes("res.cloudinary.com") || !url.includes("/upload/")) { + return url; + } + + // If already contains optimization flags, return as is + if (url.includes("f_auto") && url.includes("q_auto")) { + return url; + } + + const { width, quality = "auto", format = "auto", crop = "limit" } = options; + + const params = [`f_${format}`, `q_${quality}`]; + if (width && typeof width === "number") { + params.push(`w_${width}`); + params.push(`c_${crop}`); + } + + const paramString = params.join(","); + + // Insert params immediately after '/upload/' + return url.replace("/upload/", `/upload/${paramString}/`); +} + +/** + * Returns an optimized image URL for any image source (Cloudinary or local). + * @param {string} url - Image source URL + * @param {Object} options - Options for optimization + * @returns {string} Optimized URL + */ +export function getOptimizedImageUrl(url, options = {}) { + if (!url) return ""; + if (typeof url === "string" && url.includes("res.cloudinary.com")) { + return optimizeCloudinaryUrl(url, options); + } + return url; +}