Skip to content
Open
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
59 changes: 49 additions & 10 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,26 +1,65 @@
@import "tailwindcss";

:root {
--background: #ffffff;
--foreground: #171717;
--background: #050505;
--foreground: #ffffff;
--solvify-yellow: #FFD700;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
--font-mono: var(--font-mono);
}

body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
overflow-x: hidden;
}

/* Custom Font Utilities */
.font-bebas {
font-family: var(--font-bebas), sans-serif;
}

.font-montserrat {
font-family: var(--font-montserrat), sans-serif;
}

.font-titillium {
font-family: var(--font-titillium), sans-serif;
}

/* --- BACKGROUND PATTERN (Removed for clean state) --- */
/* ---------------------------------------------------- */

/* Animations */
@keyframes drawStroke {
0% { stroke-dashoffset: 700px; }
100% { stroke-dashoffset: 0px; }
}

@keyframes glitch {
0% { transform: translate(0); }
20% { transform: translate(-2px, 2px); }
40% { transform: translate(-2px, -2px); }
60% { transform: translate(2px, 2px); }
80% { transform: translate(2px, -2px); }
100% { transform: translate(0); }
}

.animate-glitch {
animation: glitch 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) both infinite;
}

@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

.animate-fadeIn {
animation: fadeIn 1s ease-out forwards;
}
46 changes: 31 additions & 15 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,58 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Bebas_Neue, Montserrat, Titillium_Web } from "next/font/google";
import "./globals.css";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer"
// import Navbar from "@/components/Navbar"; // Removed from here
// import Footer from "@/components/Footer"; // REMOVED: Must be rendered conditionally in page.tsx
import LogoIntro from "@/components/LogoIntro";

const geistSans = Geist({
variable: "--font-geist-sans",
// 1. Headline Font: Bebas Neue
const bebasNeue = Bebas_Neue({
weight: "400",
subsets: ["latin"],
variable: "--font-bebas",
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
// 2. Subtitle Font: Montserrat
const montserrat = Montserrat({
subsets: ["latin"],
variable: "--font-montserrat",
});

// 3. Terminal Font: Titillium Web
const titillium = Titillium_Web({
weight: ["400", "600", "700"],
subsets: ["latin"],
variable: "--font-titillium",
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Solvify - Bridging Gaps",
description: "The official website of Solvify Club",
};

// This component MUST return the <html> and <body> tags.
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
// Mandatory Root Tags
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
className={`${bebasNeue.variable} ${montserrat.variable} ${titillium.variable} antialiased bg-[#050505] text-white overflow-x-hidden`}
>
{/* LogoIntro is rendered first. It is fixed and hides the entire screen until the animation is complete. */}
<LogoIntro />
<div className="opacity-0 animate-fadeIn delay-2000">
<Navbar />
{children}
<Footer />

{/* Main Content Area: Now completely reliant on children (page.tsx) */}
<div className="relative z-0">
<main>
{children}
</main>
{/* Footer removed from here. It must be rendered inside the conditional block in page.tsx */}
</div>
</body>
</html>
);
}
}
184 changes: 173 additions & 11 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,182 @@
"use client";

import AboutUs from "@/components/AboutUs";
import Instagram from "@/components/Instagram";
import Event from "@/components/Event";
import TeamHome from "@/components/TeamHome"
import ContactUsHome from "@/components/ContactUsHome";
import HomePage from "@/components/HomePage";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
import { useState, useEffect, useCallback } from "react";

// --- COMPONENT: SEQUENTIAL DECIPHER LOGIC (Functional Implementation) ---
const SequentialDecipher = ({ text, startTrigger, onComplete }) => {
// Start with blank spaces matching the length of the text
const [displayText, setDisplayText] = useState(text.replace(/./g, ' '));
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345567890$#@%&";

// The animation logic is defined here
const decipherLogic = useCallback(() => {
if (!startTrigger) {
setDisplayText(text.replace(/./g, ' '));
return;
}

let revealIndex = 0;
const interval = setInterval(() => {
// Speed control (takes roughly 3 seconds to reveal the longest subtitle)
revealIndex += 0.3;

const currentString = text
.split("")
.map((char, i) => {
if (i < Math.floor(revealIndex)) return char; // Revealed char
if (i === Math.floor(revealIndex)) return chars[Math.floor(Math.random() * chars.length)]; // Glitch char
return " "; // Unrevealed space
})
.join("");

setDisplayText(currentString);

if (revealIndex >= text.length) {
clearInterval(interval);
setDisplayText(text); // Show final text
if (onComplete) oncomplete();
}
}, 50);

return () => clearInterval(interval);
}, [text, startTrigger, onComplete]);

useEffect(() => {
const cleanup = decipherLogic();
return cleanup;
}, [decipherLogic]);

return <span className="inline-block min-h-[1em]">{displayText}</span>;
};


// --- COMPONENT: STATIC HOME PAGE CONTENT ---
// This renders the final, non-animated state of the title and subtitles, matching the TIGHTLY GROUPED CENTER alignment.
const StaticHomeContent = ({ startDecipher }) => (
// This container reserves the full screen height (h-screen)
<div className="h-screen w-full relative flex flex-col items-center justify-start bg-[#050505]">

{/* 1. CONTENT BLOCK (Anchored to the top with responsive padding) */}
<div
className="z-10 text-center flex flex-col items-center w-full"
// Responsive vertical position: 25vh for desktop/tablet, slightly higher (20vh) for mobile
style={{ paddingTop: '25vh' }}
>

{/* 1. MASSIVE MAIN TITLE (Final State) */}
<div className="relative w-full flex justify-center items-center mb-2">
<svg viewBox="0 0 1320 350" className="w-[95vw] max-w-[1600px] h-auto overflow-visible">
<text
x="50%"
y="50%"
dy=".35em"
textAnchor="middle"
className={`
font-bebas text-[19vw] md:text-[200px] tracking-wide
fill-white drop-shadow-[0_0_10px_rgba(255,255,255,0.1)]
stroke-none
`}
>
SOLVIFY NMIT
</text>
</svg>
</div>

{/* 2. SUBTITLES (Final Decipher Trigger) */}
<div className="flex flex-col md:flex-row items-center justify-center gap-2 md:gap-4 text-base md:text-3xl font-montserrat font-semibold tracking-[0.2em] md:tracking-[0.3em] uppercase">

{/* Part 1: BRIDGING GAPS (Now triggered by startDecipher) */}
<span className="text-[#FFD700] drop-shadow-[0_0_15px_rgba(255,215,0,0.4)] min-w-[150px] md:min-w-[200px] text-center">
<SequentialDecipher text="BRIDGING GAPS" startTrigger={startDecipher} />
</span>

<span className="hidden md:inline text-gray-500 text-xl">•</span>

{/* Part 2: SOLVING CHALLENGES (Now triggered by startDecipher) */}
<span className="text-white min-w-[200px] md:min-w-[250px] text-center">
<SequentialDecipher text="SOLVING CHALLENGES" startTrigger={startDecipher} />
</span>
</div>
</div>

{/* Background Element - Cleaned to be just an empty div on the dark background */}
<div className="absolute inset-0 pointer-events-none"></div>
</div>
);


export default function Home() {
return (
<>
<HomePage/>
<AboutUs/>
<Event/>
<TeamHome/>
<Instagram/>
<ContactUsHome/>
</>
);
}
const [introComplete, setIntroComplete] = useState(false);
const [contentVisible, setContentVisible] = useState(false);
const [startDecipher, setStartDecipher] = useState(false); // NEW STATE for Decipher Trigger

const handleIntroComplete = () => {
// Step 1: Hide the fixed intro overlay
setIntroComplete(true);

// Step 2: Force scroll to the top of the content block (Y=0)
window.scrollTo({ top: 0, left: 0, behavior: 'instant' });

// Step 3: Start Decipher animation and make content visible AFTER the scroll reset
setTimeout(() => {
setStartDecipher(true); // FINAL STEP: Start the animation on the static content
setContentVisible(true);
}, 100); // Small delay for browser to render initial StaticHomeContent
};

// Use useEffect to manage body overflow state
useEffect(() => {
if (!introComplete) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'auto';
}
}, [introComplete]);

return (
<>

{/* 1. Render HomePage (Fixed overlay) while the intro is running */}
{!introComplete && (
<HomePage onIntroComplete={handleIntroComplete} />
)}

{/* 2. Conditionally mount the rest of the page only after the intro is done */}
<div
id="content-container"
// Fade in the whole content block
className={`relative z-10 transition-opacity duration-500 ${contentVisible ? "opacity-100" : "opacity-0"}`}
>
{/* Navbar is FIXED, so it floats over all content */}
<Navbar />

<div id="main-content-wrapper" className="relative z-10">

{/* CRITICAL FIX: The Home Page Section (Page 1) */}
<div className="h-screen w-full relative bg-[#050505]">
<StaticHomeContent startDecipher={startDecipher} />
</div>


{/* The rest of the content starts here, flowing below the 100vh Home Section (Page 2) */}
<div id="subsequent-sections">
<AboutUs/>
<Event/>
<TeamHome/>
<Instagram/>
<ContactUsHome/>
<Footer />
</div>
</div>
</div>
</>
);
}
Loading