Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
'use client';
"use client";

import React, { useState } from "react";

export default function About() {
const [isHovered, setIsHovered] = useState(false);

return (
<>about</>
<div className="flex min-h-screen items-center justify-center bg-[#080d14] font-Michroma text-white">
<div
className="group relative flex flex-col items-center gap-6 rounded-lg border border-white/10 bg-black/20 p-12 backdrop-blur-sm transition-all duration-500 hover:border-white/20 hover:shadow-lg hover:shadow-white/5"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* Main text */}
<h1 className="text-center font-Michroma text-4xl font-bold tracking-wider">
COMING
</h1>
<h1 className="text-center font-Michroma text-5xl font-bold tracking-wider text-blue-400">
SOON!
</h1>

{/* Animated underline */}
<div className="relative mt-2">
<div
className={`h-0.5 bg-gradient-to-r from-transparent via-blue-400 to-transparent transition-all duration-500 ${
isHovered ? "w-48" : "w-32"
}`}
/>
</div>

{/* Subtitle */}
<p className="mt-4 text-center text-sm text-gray-400">
We're building something awesome.
<br />
Stay tuned!
</p>
</div>
</div>
);
}
75 changes: 31 additions & 44 deletions src/app/api/instagram-count/route.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,47 @@
import { NextResponse } from "next/server";
import puppeteer from "puppeteer";

export async function GET() {
let browser;
try {
browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
const response = await fetch("https://www.instagram.com/pcbuildinguf/", {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
Accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Cache-Control": "no-cache",
Pragma: "no-cache",
},
});

const page = await browser.newPage();

// Set a user agent to appear more like a regular browser
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
);
if (!response.ok) {
throw new Error("Failed to fetch Instagram page");
}

// Navigate to the Instagram profile
await page.goto("https://www.instagram.com/pcbuildinguf/", {
waitUntil: "networkidle0",
timeout: 30000,
});
const html = await response.text();

// Wait for the followers count to be visible
await page.waitForSelector('meta[property="og:description"]', {
timeout: 5000,
});
// Try to find the followers count in the JSON data that Instagram embeds
const jsonMatch = html.match(/"edge_followed_by":{"count":(\d+)}/);
if (jsonMatch && jsonMatch[1]) {
const followerCount = parseInt(jsonMatch[1]);
return NextResponse.json({ followerCount });
}

// Get the meta tag content
const metaContent = await page.$eval(
'meta[property="og:description"]',
(element) => element.content,
// Fallback: try to find it in meta tags
const metaMatch = html.match(
/<meta[^>]*?og:description[^>]*?content="[^"]*?(\d+)\s+Followers/i,
);

// Extract follower count from meta content
const followerMatch = metaContent.match(/([0-9,.]+)\s*Followers/i);
const followerCount = followerMatch
? parseInt(followerMatch[1].replace(/,/g, ""))
: null;

console.log("Fetched content:", metaContent); // For debugging

if (!followerCount) {
throw new Error("Could not extract follower count");
if (metaMatch && metaMatch[1]) {
const followerCount = parseInt(metaMatch[1]);
return NextResponse.json({ followerCount });
}

return NextResponse.json({ followerCount });
throw new Error("Could not find follower count");
} catch (error) {
console.error("Error fetching Instagram count:", error);
return NextResponse.json(
{ error: "Failed to fetch follower count" },
{ status: 500 },
);
} finally {
if (browser) {
await browser.close();
}

// Return a fallback count if scraping fails
// You can store this in an environment variable or database
return NextResponse.json({ followerCount: 908 });
}
}
38 changes: 37 additions & 1 deletion src/app/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
"use client";

import React, { useState } from "react";

export default function Projects() {
return <>Projects page</>;
const [isHovered, setIsHovered] = useState(false);

return (
<div className="flex min-h-screen items-center justify-center bg-[#080d14] font-Michroma text-white">
<div
className="group relative flex flex-col items-center gap-6 rounded-lg border border-white/10 bg-black/20 p-12 backdrop-blur-sm transition-all duration-500 hover:border-white/20 hover:shadow-lg hover:shadow-white/5"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* Main text */}
<h1 className="text-center font-Michroma text-4xl font-bold tracking-wider">
COMING
</h1>
<h1 className="text-center font-Michroma text-5xl font-bold tracking-wider text-blue-400">
SOON!
</h1>

{/* Animated underline */}
<div className="relative mt-2">
<div
className={`h-0.5 bg-gradient-to-r from-transparent via-blue-400 to-transparent transition-all duration-500 ${
isHovered ? "w-48" : "w-32"
}`}
/>
</div>

{/* Subtitle */}
<p className="mt-4 text-center text-sm text-gray-400">
We're building something awesome.
<br />
Stay tuned!
</p>
</div>
</div>
);
}
8 changes: 5 additions & 3 deletions src/components/MemberCounts/SocialStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const StatCard: React.FC<StatCardProps> = ({
rel="noopener noreferrer"
className="block transition-transform duration-300 hover:scale-105"
>
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-r from-black/80 to-black/60 p-4 shadow-[0_0_15px_rgba(0,0,0,0.5)] backdrop-blur-sm transition-all duration-300 hover:shadow-[0_0_25px_rgba(255,255,255,0.2)]">
<div className="group relative mt-6 overflow-hidden rounded-lg border border-[#B0B8FF] border-opacity-40 bg-gradient-to-r from-black/80 to-black/60 p-4 shadow-[0_0_15px_rgba(0,0,0,0.5)] backdrop-blur-sm transition-all duration-300 hover:shadow-[0_0_25px_rgba(255,255,255,0.2)]">
{/* Glow effect on hover */}
<div
className="absolute inset-0 -left-40 -top-40 h-[500px] w-[500px] bg-gradient-to-r from-transparent via-[rgba(255,255,255,0.1)] to-transparent opacity-0 transition-opacity duration-500 group-hover:opacity-100"
Expand Down Expand Up @@ -151,7 +151,9 @@ const SocialStats: React.FC = () => {
glowColor="rgba(88, 101, 242, 0.5)"
href="https://discord.com/invite/jfq9phWqTF"
/>
<StatCard

{/* Instagram stats are currently not working */}
{/* <StatCard
iconSrc="/landing/instagram.png"
label="Instagram Followers"
value={instagramCount}
Expand All @@ -160,7 +162,7 @@ const SocialStats: React.FC = () => {
bgColor="bg-gradient-to-tr from-[#F58529] via-[#DD2A7B] to-[#8134AF]"
glowColor="rgba(221, 42, 123, 0.5)"
href="https://www.instagram.com/pcbuildinguf/"
/>
/> */}
</div>
);
};
Expand Down
90 changes: 66 additions & 24 deletions src/components/landing/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,87 @@
import React from "react";
import React, { useState, useEffect } from "react";
import LogoCarousel from "./LogoCarousel";
import Image from "next/image";
import { motion } from "framer-motion";
import SocialStats from "@/components/MemberCounts/SocialStats";

export default function Hero() {
const [text, setText] = useState("");
const [isInitialBlinkComplete, setIsInitialBlinkComplete] = useState(false);
const fullText = "The Society of PC Building";
const [isTypingComplete, setIsTypingComplete] = useState(false);

// Handle initial cursor blink
useEffect(() => {
// Wait for 2 full blink cycles (2 seconds) before starting the typing
const initialBlinkTimeout = setTimeout(() => {
setIsInitialBlinkComplete(true);
}, 400); // 2 seconds = 2 full blink cycles at 1s per cycle

return () => clearTimeout(initialBlinkTimeout);
}, []);

// Handle typing animation
useEffect(() => {
if (!isInitialBlinkComplete) return; // Don't start typing until initial blink is complete

if (text.length < fullText.length) {
const timeout = setTimeout(() => {
setText(fullText.slice(0, text.length + 1));
}, 70); // Adjust typing speed here (milliseconds)

return () => clearTimeout(timeout);
} else {
setIsTypingComplete(true);
}
}, [text, isInitialBlinkComplete, fullText]);

return (
<div
className="relative pb-12 md:pb-32 pt-24 md:pt-52 text-[#eaeaea]"
className="relative pb-12 pt-24 font-Michroma text-[#eaeaea] md:pb-32 md:pt-52"
id="hero"
>
{/* Flex-row on desktop devices but flex-col-reverse for devices lg and smaller */}
<div className="mx-auto flex h-full max-w-7xl flex-col-reverse items-center px-6 pb-6 sm:px-10 lg:mt-0 lg:flex-row">
<div className="flex w-full flex-col items-center text-center lg:w-3/4 lg:items-start lg:text-left">
{/* Hidden on mobile */}
<span className="hidden rounded-full border border-[#B0B8FF] border-opacity-40 bg-black bg-opacity-50 px-4 py-1.5 text-xs lg:inline">
1000+ Active Members!
</span>
<h1 className="font-Michroma text-[22px] font-semibold leading-normal sm:text-4xl pt-1 md:pt-4 lg:text-[44px] lg:leading-normal">
<span className="hidden sm:inline">The</span> Society of PC Building
<h1 className="font-Michroma text-[22px] font-semibold leading-normal sm:text-4xl lg:text-[44px] lg:leading-normal">
{text}
<span className="ml-1 inline-block animate-[blink_1s_infinite]">
_
</span>
</h1>

{/* On mobile devices shorten the description to limit text-length */}
<p className="max-w-[620px] pt-3 text-[17px] font-medium leading-relaxed sm:pt-3 sm:text-lg px-1 sm:px-0">
<p
className="max-w-[620px] px-1 pt-3 text-[17px] font-medium leading-relaxed opacity-0 transition-opacity duration-500 sm:px-0 sm:pt-3 sm:text-lg"
style={{ opacity: isTypingComplete ? 1 : 0 }}
>
<span className="hidden sm:inline">
Join the Society of PC Building at UF—where
</span>
<span className="sm:hidden">Where</span> students passionate about
hardware and tech connect, innovate, and build custom PCs together.
</p>

<span
className="hidden py-1.5 text-xs opacity-0 transition-opacity duration-500 lg:inline"
style={{ opacity: isTypingComplete ? 1 : 0 }}
>
<SocialStats />
</span>

<a
href="https://discord.gg/CmqKbnBDBG"
href="https://linktr.ee/pcbuildinguf"
target="_blank"
rel="noopener noreferrer"
className="mt-6 rounded-md border border-[#B0B8FF] border-opacity-40 bg-black bg-opacity-80 px-8 py-2 font-medium text-white text-base"
className="mt-6 rounded-md border border-[#B0B8FF] border-opacity-40 bg-black bg-opacity-80 px-8 py-2 text-base font-medium text-white opacity-0 transition-opacity transition-transform duration-300 duration-500 hover:scale-105 hover:shadow-[0_0_25px_rgba(255,255,255,0.2)]"
style={{ opacity: isTypingComplete ? 1 : 0 }}
>
Join the Club!
Get Involved!
</a>

{/* On mobile devices show the infinite carousel but on desktop static list */}
<div className="mt-16 lg:mt-32">
<p className="text-xs sm:text-sm opacity-80">
<div
className="mt-16 opacity-0 transition-opacity duration-500 lg:mb-12 lg:mt-28"
style={{ opacity: isTypingComplete ? 1 : 0 }}
>
<p className="text-xs opacity-80 sm:text-sm">
Led by a team of officers with experience at:
</p>
<div className="-mt-5 hidden h-[103px] items-center gap-6 md:flex">
Expand All @@ -56,19 +97,20 @@ export default function Hero() {
</div>
</div>

{/* Animate SPCB logo upwards. Applied negative margin to center logo better */}
<motion.div
className="relative -mr-6 h-[220px] w-[220px] md:h-[300px] md:w-[300px] lg:-mr-12 lg:h-[460px] lg:w-[460px]"
className="relative -mr-6 h-[220px] w-[220px] md:h-[300px] md:w-[300px] lg:-mr-36 lg:h-[600px] lg:w-[600px]"
initial={{ opacity: 0, y: 60 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease: "easeOut" }}
viewport={{ once: true, amount: 0.3 }}
animate={{
opacity: isTypingComplete ? 1 : 0,
y: isTypingComplete ? 0 : 60,
}}
transition={{ duration: 0.4, ease: "easeOut", delay: 0.2 }}
>
<Image
src="/landing/logo.png"
alt="SPCB Logo"
width={460}
height={460}
width={600}
height={600}
className="object-contain"
style={{
filter: "drop-shadow(0 0 10px rgba(255,255,255,1))",
Expand Down
19 changes: 14 additions & 5 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ const config = {
},
},
extend: {
keyframes: {
blink: {
"0%, 49%": { opacity: "1" },
"50%, 100%": { opacity: "0" },
},
},
animation: {
blink: "blink 1s infinite",
},
colors: {
'accent-dark': "#1a1a1a",
'accent-orange': "#FA4616",
'accent-blue': "#0021A5",
'light-orange': "#FA4616",
'light-blue': "#8BC5F7",
"accent-dark": "#1a1a1a",
"accent-orange": "#FA4616",
"accent-blue": "#0021A5",
"light-orange": "#FA4616",
"light-blue": "#8BC5F7",
},
fontFamily: {
Michroma: ["Michroma", "sans-serif"],
Expand Down
Loading