diff --git a/app/page.tsx b/app/page.tsx
index f048eae..9151a52 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -13,6 +13,7 @@ import { getEventsSafe } from '@/lib/data/event';
import { getClubsSafe } from '@/lib/data/club';
import { getHubsSafe } from '@/lib/data/hub';
import { getGalleriesSafe } from '@/lib/data/gallery';
+import { getBlogsSafe } from '@/lib/data/blog';
import type { Metadata } from 'next';
export const metadata: Metadata = {
@@ -20,13 +21,14 @@ export const metadata: Metadata = {
};
const Home = async () => {
- const [hero, potw, events, clubs, hubs, galleries] = await Promise.all([
+ const [hero, potw, events, clubs, hubs, galleries, blogs] = await Promise.all([
getPageHero('home'),
getPersonalityOfTheWeek(),
getEventsSafe(),
getClubsSafe(),
getHubsSafe(),
getGalleriesSafe(),
+ getBlogsSafe(),
]);
return (
@@ -47,7 +49,7 @@ const Home = async () => {
-
+
)
diff --git a/components/home/Contribute.tsx b/components/home/Contribute.tsx
index bbd83f7..342aed0 100644
--- a/components/home/Contribute.tsx
+++ b/components/home/Contribute.tsx
@@ -3,15 +3,15 @@ import Container from '../custom/Container';
import { Button } from '../ui/button';
import Link from 'next/link';
import { ArrowRight } from 'lucide-react';
-import useBlogCollection from '@/hooks/useBlogCollection';
+import useBlogCollection, { type Blog } from '@/hooks/useBlogCollection';
import BlogCard from '../blog/BlogCard';
import FetchError from '../custom/FetchError';
import { motion } from "framer-motion";
-const Contribute = () => {
+const Contribute = ({ initialBlogs }: { initialBlogs?: Blog[] }) => {
- const { data: blogs, error } = useBlogCollection()
+ const { data: blogs, error } = useBlogCollection(initialBlogs)
if (error) {
return
diff --git a/components/home/HeroSection.tsx b/components/home/HeroSection.tsx
index 82d7938..f06fe76 100644
--- a/components/home/HeroSection.tsx
+++ b/components/home/HeroSection.tsx
@@ -24,6 +24,7 @@ const BAND = {
curve: 80,
fade: 18,
fill: 0.72,
+ sizes: '(max-width: 768px) 55vw, 30vw',
};
function arc(bend: number) {
@@ -261,6 +262,22 @@ const HeroSection = ({
};
}, [variant, bend]);
+ const readyRef = useRef(false);
+
+ const reveal = (node: HTMLImageElement | null) => {
+ if (!node || !readyRef.current) return;
+ if (!node.complete || node.naturalWidth === 0) return;
+ node.setAttribute('data-shown', '');
+ };
+
+ useEffect(() => {
+ readyRef.current = ready;
+ if (!ready) return;
+ for (const row of shots.current.values()) {
+ for (const node of row) reveal(node);
+ }
+ }, [ready]);
+
const band = layout ?? measureBand(1200, 560, 200, 560, false, bend);
const cycles = tiles.length > band.columns;
@@ -268,6 +285,7 @@ const HeroSection = ({
if (variant !== 'band') return;
for (const tile of tiles) {
const preload = new window.Image();
+ preload.sizes = BAND.sizes;
if (tile.srcSet) preload.srcset = tile.srcSet;
preload.src = tile.src;
preload.decode?.().catch(() => {});
@@ -294,6 +312,7 @@ const HeroSection = ({
const orbit = `hero-orbit-${bandId}`;
const peek = `hero-peek-${bandId}`;
const tileClass = `hero-tile-${bandId}`;
+ const shotClass = `hero-shot-${bandId}`;
const radiusPct = 100 * arc(bend);
const unitOf = (n: number) => `calc(${+n.toFixed(4)} * var(--band-tile))`;
const vanish = +(((band.sweep - band.limit) / (2 * band.sweep || 1)) * 100).toFixed(1);
@@ -307,7 +326,9 @@ const HeroSection = ({
`@keyframes ${orbit}{` +
`from{transform:translateZ(var(--band-r)) rotateY(var(--band-from)) translateZ(calc(-1 * var(--band-r)))}` +
`to{transform:translateZ(var(--band-r)) rotateY(var(--band-to)) translateZ(calc(-1 * var(--band-r)))}}` +
- `@media(prefers-reduced-motion:reduce){.${tileClass}{animation-play-state:paused}}`;
+ `@media(prefers-reduced-motion:reduce){.${tileClass}{animation-play-state:paused}}` +
+ `.${shotClass}{opacity:0;transition:opacity 700ms ease}` +
+ `.${shotClass}[data-shown]{opacity:1}`;
const peekCss =
`@keyframes ${peek}{` +
@@ -341,7 +362,7 @@ const HeroSection = ({
className='pointer-events-none absolute inset-0 transition-opacity duration-500'
style={{
...bandVars,
- opacity: layout && ready ? 1 : 0,
+ opacity: layout ? 1 : 0,
perspective: `${+(radiusPct * CAMERA).toFixed(3)}cqw`,
perspectiveOrigin: `50% ${band.axis}%`,
maskImage: bandMask,
@@ -392,6 +413,7 @@ const HeroSection = ({
maskImage: sliceMask,
WebkitMaskImage: sliceMask,
backfaceVisibility: 'hidden',
+ backgroundColor: '#e9ecef',
left: unitOf((BAND.aspect - share) / 2),
width:
k === band.slices - 1
@@ -410,14 +432,16 @@ const HeroSection = ({
const row = shots.current.get(t) ?? [];
if (node) row[k] = node;
shots.current.set(t, row);
+ reveal(node);
}}
+ onLoad={(event) => reveal(event.currentTarget)}
src={tile.src}
srcSet={tile.srcSet || undefined}
- sizes='(max-width: 768px) 55vw, 30vw'
+ sizes={BAND.sizes}
alt=''
draggable={false}
decoding='async'
- className='absolute top-0 max-w-none object-cover'
+ className={`${shotClass} absolute top-0 max-w-none object-cover`}
style={{
left: unitOf(-k * share),
width: unitOf(BAND.aspect),
diff --git a/components/home/RecentEvent.tsx b/components/home/RecentEvent.tsx
index adcc984..0deb0da 100644
--- a/components/home/RecentEvent.tsx
+++ b/components/home/RecentEvent.tsx
@@ -2,6 +2,7 @@
import Image from "next/image";
import { ctfSrc } from '@/lib/contentful-src';
import { format } from "date-fns";
+import { useEffect, useRef, useState } from "react";
import { ArrowRight, CalendarDays, Clock } from "lucide-react";
import Link from "next/link";
import useEventCollection, { type EventItem } from "@/hooks/useEventCollection";
@@ -12,6 +13,31 @@ import * as Tabs from "@radix-ui/react-tabs";
const RecentEvent = ({ initialEvents }: { initialEvents?: EventItem[] }) => {
const { data: events, isLoading, error } = useEventCollection(initialEvents);
+ const sectionRef = useRef(null);
+ const [showVideo, setShowVideo] = useState(false);
+
+ useEffect(() => {
+ const node = sectionRef.current;
+ if (!node || showVideo) return;
+ if (typeof IntersectionObserver === 'undefined') {
+ setShowVideo(true);
+ return;
+ }
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ if (entries.some((entry) => entry.isIntersecting)) {
+ setShowVideo(true);
+ observer.disconnect();
+ }
+ },
+ { rootMargin: '600px 0px' }
+ );
+
+ observer.observe(node);
+ return () => observer.disconnect();
+ }, [showVideo]);
+
const now = new Date();
const upcoming = events
@@ -39,10 +65,12 @@ const RecentEvent = ({ initialEvents }: { initialEvents?: EventItem[] }) => {
}
return (
-
+