+ {Array.from({ length: 5 }).map((_, index) => (
+
+ {/* Image Skeleton */}
+
+
+
+ {/* Title Skeleton */}
+
+
+
+ {/* Date Skeleton */}
+
+
+
+ {/* Description Skeleton */}
+
+
+ ))}
@@ -132,25 +161,40 @@ export default function ProjectsPage() {
setSelectedProject(project)}
- className="group relative cursor-pointer overflow-hidden rounded-lg bg-gray-950 p-4 shadow-white-glow transition-transform hover:scale-105"
+ className="group relative cursor-pointer overflow-hidden rounded-sm p-4"
>
{/* Project Image */}
-
+
+ {!imageLoaded[project.id] && (
+
+ )}

+ setImageLoaded((prev) => ({
+ ...prev,
+ [project.id]: true,
+ }))
+ }
+ className={`h-full w-full object-cover transition-transform group-hover:scale-105 ${
+ !imageLoaded[project.id] ? "opacity-0" : "opacity-100"
+ } transition-opacity`}
/>
+
{/* Project Info */}
-
- {project.Title}
+
+ {project.Title}
-
+
{project.semester.term + " " + project.semester.year}
+
+ {project.Description}
+
))}
@@ -158,7 +202,8 @@ export default function ProjectsPage() {
)}
- {/* Project Modal */}
+
+ {/* Modal */}
{selectedProject && (
);
}
+
+
+type Props = {
+ children: string;
+ lines?: number;
+ className?: string;
+};
+
+
+const TruncatedText: React.FC = ({
+ children,
+ lines = 2,
+ className = "",
+}) => {
+ const clampClass =
+ {
+ 1: "line-clamp-1",
+ 2: "line-clamp-2",
+ 3: "line-clamp-3",
+ 4: "line-clamp-4",
+ 5: "line-clamp-5",
+ 6: "line-clamp-6",
+ }[lines] || "line-clamp-2";
+
+
+ return (
+
+ {children}
+
+ );
+};
+
+
+
+
+