first commit

This commit is contained in:
2026-07-02 14:41:36 +05:30
parent 7d4727755a
commit e2cf8f803b
20 changed files with 1087 additions and 80 deletions
+69 -16
View File
@@ -1,26 +1,79 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
@theme {
/* Rotary brand colors — generates bg-rotary-navy, text-rotary-gold, etc. */
--color-rotary-navy: #0b2545;
--color-rotary-blue: #12468c;
--color-rotary-azure: #2e7cd6;
--color-rotary-gold: #f2a900;
--color-rotary-goldlight: #ffd166;
--color-rotary-amber: #c2660c;
--color-rotary-paper: #f4f6fa;
--color-rotary-ink: #101b2d;
/* Fonts — generates font-serif / font-sans overrides.
Works even without next/font: falls back to system stacks. */
--font-serif: var(--font-display, ui-serif), Georgia, serif;
--font-sans: var(--font-body, ui-sans-serif), system-ui, sans-serif;
/* Custom animation utilities — generates animate-spin-slow, animate-fade-up, etc. */
--animate-spin-slow: spin-slow 50s linear infinite;
--animate-spin-slower: spin-slow 90s linear infinite;
--animate-fade-up: fade-up 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
--animate-shimmer: shimmer 2.5s linear infinite;
--animate-ring-pulse: ring-pulse 2.4s ease-out infinite;
--animate-shine: shine 3.5s ease-in-out infinite;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
@keyframes spin-slow {
to {
transform: rotate(360deg);
}
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
@keyframes fade-up {
0% {
opacity: 0;
transform: translateY(16px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
@keyframes ring-pulse {
0%,
100% {
box-shadow: 0 0 0 0 rgba(242, 169, 0, 0.45);
}
50% {
box-shadow: 0 0 0 10px rgba(242, 169, 0, 0);
}
}
@keyframes shine {
0% {
transform: translateX(-120%);
}
50% {
transform: translateX(220%);
}
100% {
transform: translateX(220%);
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
background-color: var(--color-rotary-paper);
color: var(--color-rotary-ink);
}
+2 -2
View File
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Rotary Club of Vellore — Installation Ceremony 2026",
description: "The President and Members cordially invite you, with family, to witness the installation of the incoming Board and to Create Lasting Impact together.",
};
export default function RootLayout({
+18 -62
View File
@@ -1,65 +1,21 @@
import Image from "next/image";
import Hero from "@/components/Hero";
import EventDetails from "@/components/EventDetails";
import Handover from "@/components/Handover";
import Guests from "@/components/Guests";
import OfficeBearers from "@/components/OfficeBearers";
import CtaBand from "@/components/CtaBand";
import Footer from "@/components/Footer";
export default function Home() {
export default function Page() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
</div>
<main className="bg-rotary-paper">
<Hero />
<EventDetails />
<Handover />
<Guests />
<OfficeBearers />
<CtaBand />
<Footer />
</main>
);
}
}
+51
View File
@@ -0,0 +1,51 @@
"use client";
import { useEffect, useState } from "react";
const TARGET = new Date("2026-07-03T18:30:00+05:30").getTime();
function getParts(msLeft: number) {
const clamped = Math.max(msLeft, 0);
return {
days: Math.floor(clamped / (1000 * 60 * 60 * 24)),
hours: Math.floor((clamped / (1000 * 60 * 60)) % 24),
minutes: Math.floor((clamped / (1000 * 60)) % 60),
seconds: Math.floor((clamped / 1000) % 60),
};
}
export default function Countdown() {
const [parts, setParts] = useState<ReturnType<typeof getParts> | null>(null);
useEffect(() => {
const tick = () => setParts(getParts(TARGET - Date.now()));
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
const units = [
{ label: "Days", value: parts?.days },
{ label: "Hours", value: parts?.hours },
{ label: "Min", value: parts?.minutes },
{ label: "Sec", value: parts?.seconds },
];
return (
<div className="flex gap-2.5 sm:gap-3" aria-live="polite">
{units.map((u) => (
<div
key={u.label}
className="flex w-14 flex-col items-center rounded-xl border border-white/10 bg-white/5 py-2 backdrop-blur-sm sm:w-16"
>
<span className="font-serif text-xl font-semibold tabular-nums text-white sm:text-2xl">
{u.value !== undefined ? String(u.value).padStart(2, "0") : "--"}
</span>
<span className="text-[10px] uppercase tracking-wider text-white/60">
{u.label}
</span>
</div>
))}
</div>
);
}
+19
View File
@@ -0,0 +1,19 @@
import Reveal from "./Reveal";
export default function CtaBand() {
return (
<section className="bg-rotary-navy px-4 py-14 sm:px-6 lg:px-8">
<Reveal>
<div className="relative mx-auto max-w-4xl overflow-hidden rounded-3xl bg-gradient-to-r from-rotary-gold via-rotary-goldlight to-rotary-gold bg-[length:200%_100%] p-8 text-center shadow-xl animate-shimmer sm:p-10">
<p className="font-serif text-xl font-semibold text-rotary-navy sm:text-2xl">
Dear Rotarians, your presence is most essential.
</p>
<p className="mx-auto mt-2 max-w-md text-sm text-rotary-navy/70">
Join the President and Members of Rotary Club of Vellore, with
family, for the Installation Ceremony on 3rd July 2026.
</p>
</div>
</Reveal>
</section>
);
}
+66
View File
@@ -0,0 +1,66 @@
import Reveal from "./Reveal";
const DETAILS = [
{
label: "Date",
value: "3rd July 2026",
sub: "Friday",
icon: (
<path d="M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z" strokeLinecap="round" strokeLinejoin="round" />
),
},
{
label: "Time",
value: "6:30 PM",
sub: "Doors open at 6:00 PM",
icon: <><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 3" strokeLinecap="round" strokeLinejoin="round" /></>,
},
{
label: "Venue",
value: "Hotel Khanna, Laya Hall",
sub: "Anna Salai, Vellore",
icon: (
<><path d="M12 21s7-6.2 7-11.5A7 7 0 0 0 5 9.5C5 14.8 12 21 12 21Z" strokeLinecap="round" strokeLinejoin="round" /><circle cx="12" cy="9.5" r="2.5" /></>
),
},
];
export default function EventDetails() {
return (
<section id="event" className="bg-rotary-paper px-4 py-6 sm:px-6 sm:py-8 lg:px-8">
<div className="mx-auto max-w-7xl">
<Reveal>
<p className="text-center text-xs font-semibold uppercase tracking-[0.28em] text-rotary-azure">
Save the date
</p>
<h2 className="mt-2 text-center font-serif text-3xl font-semibold text-rotary-navy sm:text-4xl">
Join us for the evening
</h2>
</Reveal>
<div className="mt-8 grid gap-3 sm:grid-cols-3">
{DETAILS.map((d, i) => (
<Reveal key={d.label} delay={i * 100}>
<div className="group flex h-full items-center gap-3 rounded-xl border border-rotary-navy/[0.06] bg-white p-3.5 shadow-sm transition-all duration-300 hover:-translate-y-0.5 hover:shadow-md hover:shadow-rotary-blue/10">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-rotary-blue/[0.08] text-rotary-blue transition-colors duration-300 group-hover:bg-rotary-gold/15 group-hover:text-rotary-gold">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-4 w-4">
{d.icon}
</svg>
</div>
<div className="min-w-0">
<p className="text-[10px] font-semibold uppercase tracking-wider text-rotary-azure">
{d.label}
</p>
<p className="truncate font-serif text-base font-semibold leading-tight text-rotary-navy">
{d.value}
</p>
<p className="truncate text-xs text-rotary-ink/55">{d.sub}</p>
</div>
</div>
</Reveal>
))}
</div>
</div>
</section>
);
}
+93
View File
@@ -0,0 +1,93 @@
import Image from "next/image";
import GearMotif from "./GearMotif";
const LINKS = [
{ href: "#event", label: "The Ceremony" },
{ href: "#handover", label: "Handover" },
{ href: "#guests", label: "Guests" },
{ href: "#bearers", label: "Office Bearers" },
];
export default function Footer() {
return (
<footer className="relative overflow-hidden bg-rotary-navy px-4 pb-8 pt-16 text-white sm:px-6 lg:px-8">
<GearMotif className="pointer-events-none absolute -bottom-16 -right-16 h-64 w-64 text-white/[0.04]" />
<div className="relative mx-auto grid max-w-7xl gap-10 border-b border-white/10 pb-10 sm:grid-cols-3">
{/* Brand */}
<div>
<div className="flex items-center gap-3">
<div className="rounded-lg bg-white px-2.5 py-1.5 shadow-md shadow-black/20">
<Image
src="/images/rotary-logo.png"
alt="Rotary International — Create Lasting Impact"
width={180}
height={52}
className="h-7 w-auto object-contain"
/>
</div>
<span className="font-serif text-base font-semibold">
Rotary Club of Vellore
</span>
</div>
<p className="mt-4 max-w-xs text-sm leading-relaxed text-white/60">
Club No. 15900 · Chartered 5th March 1948 · District 3231.
Serving Vellore since generations, one project at a time.
</p>
</div>
{/* Quick links */}
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-rotary-goldlight">
Quick links
</p>
<ul className="mt-4 space-y-2.5">
{LINKS.map((l) => (
<li key={l.href}>
<a
href={l.href}
className="text-sm text-white/65 transition-colors duration-200 hover:text-white"
>
{l.label}
</a>
</li>
))}
</ul>
</div>
{/* Contact */}
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-rotary-goldlight">
The evening
</p>
<ul className="mt-4 space-y-2.5 text-sm text-white/65">
<li>3rd July 2026 · Friday · 6:30 PM</li>
<li>Hotel Khanna, Laya Hall</li>
<li>Anna Salai, Vellore</li>
</ul>
</div>
</div>
<div className="relative mx-auto flex max-w-7xl flex-col-reverse items-center gap-3 pt-6 text-xs text-white/45 sm:flex-row sm:justify-between">
<p>© 2026 Rotary Club of Vellore. All are welcome.</p>
<div className="flex items-center gap-5">
<p className="text-white/35">
Powered by{" "}
<span className="font-medium text-white/60 transition-colors duration-200 hover:text-rotary-goldlight">
Rootxwire
</span>
</p>
<a
href="#top"
className="group flex items-center gap-1.5 text-white/60 transition-colors duration-200 hover:text-white"
>
Back to top
<svg className="h-3.5 w-3.5 transition-transform duration-300 group-hover:-translate-y-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 19V5M6 11l6-6 6 6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
</div>
</div>
</footer>
);
}
+35
View File
@@ -0,0 +1,35 @@
type Props = {
className?: string;
duration?: "spin-slow" | "spin-slower";
};
/** A single-color outline gear, meant to sit behind content at low opacity. */
export default function GearMotif({ className = "", duration = "spin-slow" }: Props) {
const spin = duration === "spin-slower" ? "animate-spin-slower" : "animate-spin-slow";
return (
<svg
viewBox="0 0 200 200"
className={`${className} ${spin}`}
fill="none"
aria-hidden="true"
>
<circle cx="100" cy="100" r="46" stroke="currentColor" strokeWidth="6" />
<circle cx="100" cy="100" r="14" stroke="currentColor" strokeWidth="6" />
{Array.from({ length: 12 }).map((_, i) => {
const angle = (i * 360) / 12;
return (
<rect
key={i}
x="94"
y="4"
width="12"
height="26"
rx="2"
fill="currentColor"
transform={`rotate(${angle} 100 100)`}
/>
);
})}
</svg>
);
}
+61
View File
@@ -0,0 +1,61 @@
import Image from "next/image";
import Reveal from "./Reveal";
const GUESTS = [
{
name: "Rtn. PDG. M. Rajan Babu",
role: "Chief Guest",
subrole: "Past District Governor, 3231",
photo: "/images/chief-guest-couple.png",
},
{
name: "Rtn. Yelagiri V. Selvam",
role: "Guest of Honour",
subrole: "Chairman, Kodai Vizha 2026 – 27",
photo: "/images/guest-of-honour.jpg",
},
];
export default function Guests() {
return (
<section id="guests" className="bg-rotary-paper px-4 py-6 sm:px-6 sm:py-8 lg:px-8">
<div className="mx-auto max-w-7xl">
<Reveal>
<p className="text-center text-xs font-semibold uppercase tracking-[0.28em] text-rotary-azure">
With us this evening
</p>
<h2 className="mt-2 text-center font-serif text-3xl font-semibold text-rotary-navy sm:text-4xl">
Distinguished Guests
</h2>
</Reveal>
<div className="mt-10 grid gap-6 sm:grid-cols-2">
{GUESTS.map((g, i) => (
<Reveal key={g.name} delay={i * 120}>
<div className="group flex items-center gap-5 rounded-2xl border border-rotary-navy/[0.06] bg-white p-5 shadow-sm transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-rotary-blue/10 sm:p-6">
<div className="relative h-20 w-20 shrink-0 overflow-hidden rounded-full ring-4 ring-rotary-gold/20 transition-all duration-300 group-hover:ring-rotary-gold/50 sm:h-24 sm:w-24">
<Image
src={g.photo}
alt={g.name}
fill
sizes="96px"
className="object-cover"
/>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-rotary-gold">
{g.role}
</p>
<p className="mt-1 font-serif text-lg font-semibold text-rotary-navy sm:text-xl">
{g.name}
</p>
<p className="mt-0.5 text-sm text-rotary-ink/60">{g.subrole}</p>
</div>
</div>
</Reveal>
))}
</div>
</div>
</section>
);
}
+109
View File
@@ -0,0 +1,109 @@
import Reveal from "./Reveal";
const OUTGOING = {
label: "2025 – 26",
officers: [
{ role: "President", name: "Rtn. Prof. S. Thiyagukumar" },
{ role: "Secretary", name: "Rtn. A.S. Abdul Rahaman" },
{ role: "Treasurer", name: "Rtn. Er. G. Muralidharan" },
],
};
const INCOMING = {
label: "2026 – 27",
officers: [
{ role: "President", name: "Rtn. Er. G. Muralidharan" },
{ role: "Secretary", name: "Rtn. M. Bhimarao" },
{ role: "Treasurer", name: "Rtn. K. Kumar" },
],
};
function OfficerCard({
group,
tone,
}: {
group: typeof OUTGOING;
tone: "outgoing" | "incoming";
}) {
const incoming = tone === "incoming";
return (
<div
className={`relative overflow-hidden rounded-2xl border p-6 sm:p-7 ${
incoming
? "border-rotary-gold/30 bg-rotary-navy text-white shadow-lg shadow-rotary-navy/20"
: "border-rotary-navy/[0.08] bg-white"
}`}
>
{incoming && (
<div className="pointer-events-none absolute inset-0 overflow-hidden rounded-2xl">
<div className="absolute inset-y-0 -left-1/4 w-1/3 -skew-x-12 bg-gradient-to-r from-transparent via-rotary-amber/35 to-transparent blur-2xl animate-shine" />
</div>
)}
<p
className={`text-xs font-semibold uppercase tracking-[0.2em] ${
incoming ? "text-rotary-goldlight" : "text-rotary-azure"
}`}
>
{incoming ? "Incoming Board" : "Outgoing Board"}
</p>
<p className={`mt-1 font-serif text-2xl font-semibold ${incoming ? "text-white" : "text-rotary-navy"}`}>
{group.label}
</p>
<ul className="mt-5 space-y-3.5">
{group.officers.map((o) => (
<li
key={o.role}
className={`flex items-center justify-between gap-4 border-b pb-3.5 last:border-b-0 last:pb-0 ${
incoming ? "border-white/10" : "border-rotary-navy/[0.06]"
}`}
>
<span className={`text-sm ${incoming ? "text-white/60" : "text-rotary-ink/55"}`}>
{o.role}
</span>
<span className={`text-right text-sm font-medium ${incoming ? "text-white" : "text-rotary-navy"}`}>
{o.name}
</span>
</li>
))}
</ul>
</div>
);
}
export default function Handover() {
return (
<section id="handover" className="bg-white px-4 py-6 sm:px-6 sm:py-8 lg:px-8">
<div className="mx-auto max-w-7xl">
<Reveal>
<p className="text-center text-xs font-semibold uppercase tracking-[0.28em] text-rotary-azure">
The gavel passes
</p>
<h2 className="mt-2 text-center font-serif text-3xl font-semibold text-rotary-navy sm:text-4xl">
A year of service, handed forward
</h2>
</Reveal>
<div className="mt-10 grid items-center gap-6 sm:grid-cols-[1fr_auto_1fr]">
<Reveal>
<OfficerCard group={OUTGOING} tone="outgoing" />
</Reveal>
<Reveal delay={100}>
<div className="flex justify-center py-2 sm:py-0">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-rotary-gold/15 text-rotary-gold transition-transform duration-500 hover:rotate-180 sm:h-14 sm:w-14">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-6 w-6 rotate-90 sm:rotate-0">
<path d="M5 12h14M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
</Reveal>
<Reveal delay={200}>
<OfficerCard group={INCOMING} tone="incoming" />
</Reveal>
</div>
</div>
</section>
);
}
+127
View File
@@ -0,0 +1,127 @@
"use client";
import { useEffect, useState } from "react";
import Image from "next/image";
const NAV_LINKS = [
{ href: "#event", label: "The Ceremony" },
{ href: "#handover", label: "Handover" },
{ href: "#guests", label: "Guests" },
{ href: "#bearers", label: "Office Bearers" },
];
export default function Header() {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 12);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
return (
<header
className={`sticky top-0 z-50 w-full transition-all duration-300 ${
scrolled
? "bg-rotary-navy/95 shadow-lg shadow-rotary-navy/20 backdrop-blur"
: "bg-rotary-navy"
}`}
>
<div
className={`mx-auto flex max-w-7xl items-center justify-between px-4 transition-all duration-300 ${
scrolled ? "py-2.5" : "py-4"
}`}
>
{/* Brand */}
<a href="#top" className="group flex items-center gap-3">
<span className="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-rotary-gold/15 ring-1 ring-rotary-gold/40 transition-transform duration-300 group-hover:scale-105 sm:h-10 sm:w-10">
<Image
src="/images/rotary-logo.png"
alt="Rotary International District 3231"
width={28}
height={28}
className="h-6 w-6 object-contain sm:h-7 sm:w-7"
/>
</span>
<span className="flex flex-col leading-tight">
<span className="font-serif text-[15px] font-semibold tracking-tight text-white sm:text-base">
Rotary Club of Vellore
</span>
<span className="text-[11px] uppercase tracking-[0.18em] text-rotary-goldlight/90">
District 3231 · Est. 1948
</span>
</span>
</a>
{/* Desktop nav (hidden) */}
<nav className="hidden">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
className="group relative py-1 text-sm font-medium text-white/85 transition-colors hover:text-white"
>
{link.label}
<span className="absolute -bottom-0.5 left-0 h-[2px] w-full origin-left scale-x-0 bg-rotary-gold transition-transform duration-300 ease-out group-hover:scale-x-100" />
</a>
))}
<a
href="#event"
className="rounded-full bg-rotary-gold px-4 py-2 text-sm font-semibold text-rotary-navy shadow-sm transition-all duration-300 hover:-translate-y-0.5 hover:bg-rotary-goldlight hover:shadow-md active:translate-y-0"
>
RSVP
</a>
</nav>
{/* Mobile toggle (hidden) */}
<button
onClick={() => setOpen((v) => !v)}
aria-label="Toggle menu"
aria-expanded={open}
className="hidden"
>
<span
className={`h-0.5 w-6 bg-white transition-transform duration-300 ${
open ? "translate-y-2 rotate-45" : ""
}`}
/>
<span
className={`h-0.5 w-6 bg-white transition-opacity duration-300 ${
open ? "opacity-0" : "opacity-100"
}`}
/>
<span
className={`h-0.5 w-6 bg-white transition-transform duration-300 ${
open ? "-translate-y-2 -rotate-45" : ""
}`}
/>
</button>
</div>
{/* Mobile menu (hidden) */}
<div className="hidden">
<nav className="flex flex-col gap-1 border-t border-white/10 bg-rotary-navy px-4 pb-4 pt-2 sm:px-6">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
onClick={() => setOpen(false)}
className="rounded-lg px-3 py-2.5 text-sm font-medium text-white/85 transition-colors hover:bg-white/5 hover:text-white"
>
{link.label}
</a>
))}
<a
href="#event"
onClick={() => setOpen(false)}
className="mt-2 rounded-full bg-rotary-gold px-4 py-2.5 text-center text-sm font-semibold text-rotary-navy"
>
RSVP
</a>
</nav>
</div>
</header>
);
}
+145
View File
@@ -0,0 +1,145 @@
import Image from "next/image";
import GearMotif from "./GearMotif";
import Countdown from "./Countdown";
export default function Hero() {
return (
<section
id="top"
className="relative overflow-hidden bg-gradient-to-b from-rotary-navy via-rotary-navy to-rotary-blue px-4 pb-6 pt-4 text-white sm:px-6 sm:pb-6 sm:pt-6 lg:px-8"
>
{/* signature watermark */}
<GearMotif className="pointer-events-none absolute -right-24 -top-24 h-[420px] w-[420px] text-white/[0.05] sm:h-[520px] sm:w-[520px]" />
<GearMotif
duration="spin-slower"
className="pointer-events-none absolute -bottom-32 -left-20 h-72 w-72 text-rotary-gold/[0.06]"
/>
<div className="relative mx-auto max-w-6xl">
{/* Brand row — replaces the old header's logo now that it's gone */}
<div className="mb-12 flex flex-wrap items-center gap-4 sm:mb-16">
<div className="rounded-xl bg-white px-3 py-1.5 shadow-lg shadow-black/20">
<Image
src="/images/rotary-logo.png"
alt="Rotary International — Create Lasting Impact"
width={220}
height={64}
className="h-9 w-auto object-contain sm:h-11"
/>
</div>
<span className="hidden h-8 w-px bg-white/15 sm:block" />
<span className="flex flex-col leading-tight">
<span className="font-serif text-base font-semibold tracking-tight text-white sm:text-lg">
Rotary Club of Vellore
</span>
<span className="text-[11px] uppercase tracking-[0.18em] text-rotary-goldlight/90">
District 3231 · Est. 1948
</span>
</span>
</div>
<div className="grid items-center gap-12 lg:grid-cols-[1.15fr_0.85fr]">
{/* Copy */}
<div className="animate-fade-up">
{/* Advisor + President thumbnails */}
<div className="mb-6 flex items-center gap-4">
<div className="relative h-20 w-20 overflow-hidden rounded-2xl ring-2 ring-white/15 sm:h-24 sm:w-24">
<Image
src="/images/club-advisor.jpg"
alt="Rtn. Dr. T. Jacob John, Club Advisor"
fill
sizes="96px"
className="object-cover"
/>
</div>
<div className="relative h-20 w-20 overflow-hidden rounded-2xl ring-2 ring-rotary-gold/60 sm:h-24 sm:w-24">
<Image
src="/images/president.png"
alt="Rtn. Er. G. Muralidharan, President 2026-27"
fill
sizes="96px"
className="object-cover"
/>
</div>
</div>
<p className="text-xs font-semibold uppercase tracking-[0.28em] text-rotary-goldlight">
Rotary Club of Vellore · Club No. 15900 · Charter 05.03.1948
</p>
<h1 className="mt-4 font-serif text-4xl font-semibold leading-[1.08] tracking-tight sm:text-5xl lg:text-6xl">
Installation
<span className="block text-rotary-goldlight">Ceremony 2026 – 27</span>
</h1>
<p className="mt-5 max-w-md text-[15px] leading-relaxed text-white/75 sm:text-base">
The President and Members cordially invite you, with family, to
witness the installation of the incoming Board and to
<span className="text-white"> Create Lasting Impact</span> together.
</p>
{/* date / venue chips */}
<div className="mt-7 flex flex-wrap gap-3">
<div className="flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-4 py-2 text-sm">
<svg className="h-4 w-4 text-rotary-gold" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="5" width="18" height="16" rx="2" />
<path d="M16 3v4M8 3v4M3 10h18" strokeLinecap="round" />
</svg>
3 July 2026 · Friday · 6:30 PM
</div>
<div className="flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-4 py-2 text-sm">
<svg className="h-4 w-4 text-rotary-gold" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 21s7-6.2 7-11.5A7 7 0 0 0 5 9.5C5 14.8 12 21 12 21Z" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="12" cy="9.5" r="2.5" />
</svg>
Hotel Khanna, Laya Hall, Vellore
</div>
</div>
<div className="mt-8">
<p className="mb-2.5 text-xs font-medium uppercase tracking-wider text-white/50">
Counting down to installation
</p>
<Countdown />
</div>
<a
href="#event"
className="group mt-9 inline-flex items-center gap-2 rounded-full bg-rotary-gold px-6 py-3 text-sm font-semibold text-rotary-navy shadow-[0_8px_24px_-6px_rgba(242,169,0,0.5)] transition-all duration-300 hover:-translate-y-0.5 hover:bg-rotary-goldlight animate-ring-pulse"
>
View Ceremony Details
<svg className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M5 12h14M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
</div>
{/* President portrait */}
<div className="animate-fade-up [animation-delay:150ms] justify-self-center lg:justify-self-end">
<div className="relative">
<div className="absolute -inset-3 rounded-[2rem] border border-rotary-gold/30" />
<div className="relative h-92 w-80 overflow-hidden rounded-[1.75rem] ring-4 ring-white/10 sm:h-100 sm:w-84">
<Image
src="/images/president.jpg"
alt="Rtn. Er. G. Muralidharan, President 2026-27"
fill
sizes="256px"
className="object-cover"
priority
/>
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-rotary-navy/90 to-transparent px-4 pb-4 pt-10">
<p className="text-[11px] uppercase tracking-widest text-rotary-goldlight">
President, 2026 – 27
</p>
<p className="font-serif text-lg font-semibold leading-tight">
Rtn. Er. G. Muralidharan
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
+127
View File
@@ -0,0 +1,127 @@
"use client";
import { useState } from "react";
import Reveal from "./Reveal";
const GROUPS = [
{
label: "Leadership",
roster: [
{ role: "Club Advisor", name: "Rtn. Dr. T. Jacob John" },
{ role: "Club Mentor", name: "Rtn. Dr. T. Daniel Ezhilarasu" },
{ role: "Club Trainer", name: "Rtn. Dr. T. Jacob John" },
{ role: "President", name: "Rtn. Er. G. Muralidharan" },
{ role: "Secretary", name: "Rtn. M. Bhimarao" },
{ role: "Immediate Past President", name: "Rtn. Prof. S. Thiyagukumar" },
{ role: "President Elect (2027-28)", name: "Rtn. B. Nirmala Gopinath" },
{ role: "President Nominee (2028-29)", name: "Rtn. A.S. Abdul Rahiman" },
{ role: "Treasurer", name: "Rtn. K. Kumar" },
{ role: "Sergeant at Arms – 1", name: "Rtn. Dr. D. Daniel Ezhilarasu" },
],
},
{
label: "Directors",
roster: [
{ role: "Club Administration", name: "Rtn. P. Srinivasan" },
{ role: "Vocational Service", name: "Rtn. Joseph Annaiah" },
{ role: "Community Service — Health", name: "Rtn. Dr. Thiagarajan" },
{ role: "Community Service — Development", name: "Rtn. Emperumal M." },
{ role: "International Service", name: "Rtn. Suthakarraj" },
{ role: "Youth Service", name: "Rtn. A.S. Abdul Rahiman" },
],
},
{
label: "Committee Chairs",
roster: [
{ role: "Rotary Foundation", name: "Rtn. Subash Chandaran" },
{ role: "Membership Development", name: "Rtn. Dr. M. Thangamani" },
{ role: "Public Image", name: "Rtn. Boominatthan" },
{ role: "Family Participation", name: "Rtn. P. Srinivasan" },
{ role: "DE-RYLA", name: "Rtn. Shyam Sundar Jaising" },
{ role: "Blood Camp", name: "Rtn. Prof. D. Thirumaran" },
{ role: "School into Smiles", name: "Rtn. Gopinath" },
{ role: "Award Team", name: "Rtn. ASA. Shanmugam" },
{ role: "Polio Plus", name: "Rtn. Viswanathan" },
{ role: "Water and Sanitation", name: "Rtn. R. Ravi" },
{ role: "Literacy", name: "Rtn. Dr. K. Saravanan" },
{ role: "Bulletin", name: "Rtn. Dr. M. Thangamani" },
{ role: "Eye Camps", name: "Rtn. A.S. Abdul Rahiman" },
{ role: "Dental Camps", name: "Rtn. Dr. Bavithara" },
{ role: "General Medical Camps", name: "Rtn. Emperumal M." },
{ role: "Dist. Special Project", name: "Rtn. Emperumal M." },
{ role: "Pongal Vizha & Festivals", name: "Rtn. M. Bhima Rao" },
{ role: "Environment", name: "Rtn. TKT Srinivasan" },
{ role: "Skill India", name: "Rtn. K.V. Elangovan" },
{ role: "Rotract", name: "Rtn. Prof. B. Nirmal Prasad" },
{ role: "Interact", name: "Rtn. P. Duraisamy Padmanaban" },
{ role: "Go Green", name: "Rtn. Dr. M. Kumaran" },
{ role: "WinS", name: "Rtn. Balaji" },
{ role: "Low Cost Shelter", name: "Rtn. Chandru" },
{ role: "Be Safety Town", name: "Rtn. R. Ravi" },
{ role: "Clean Town — Swachh Bharat", name: "Rtn. M. Bhima Rao" },
{ role: "Job Fair", name: "Rtn. TKT Srinivasan" },
{ role: "Rally (Car, Bike, Walk)", name: "Rtn. Joseph Annaiah" },
{ role: "IWC Ann Club", name: "Rtn. Nirmala Gopinath" },
{ role: "Competition & Sports", name: "Rtn. P. Duraisamy" },
],
},
];
export default function OfficeBearers() {
const [active, setActive] = useState(0);
return (
<section id="bearers" className="bg-white px-4 py-6 sm:px-6 sm:py-8 lg:px-8">
<div className="mx-auto max-w-7xl">
<Reveal>
<p className="text-center text-xs font-semibold uppercase tracking-[0.28em] text-rotary-azure">
Office Bearers of 2026 – 27
</p>
<h2 className="mt-2 text-center font-serif text-3xl font-semibold text-rotary-navy sm:text-4xl">
The Directory
</h2>
</Reveal>
{/* Tabs */}
<Reveal delay={100}>
<div className="mt-9 flex justify-center gap-1 rounded-full border border-rotary-navy/[0.08] bg-rotary-paper p-1.5 sm:inline-flex sm:gap-1">
{GROUPS.map((g, i) => (
<button
key={g.label}
onClick={() => setActive(i)}
className={`relative flex-1 rounded-full px-3 py-2 text-xs font-semibold transition-all duration-300 sm:flex-none sm:px-5 sm:text-sm ${
active === i
? "bg-rotary-navy text-white shadow-sm"
: "text-rotary-ink/60 hover:text-rotary-navy"
}`}
>
{g.label}
<span className="ml-1.5 text-[10px] opacity-60">
{g.roster.length}
</span>
</button>
))}
</div>
</Reveal>
{/* Roster */}
<div className="mt-8 grid gap-x-10 sm:grid-cols-2">
{GROUPS[active].roster.map((b, i) => (
<div
key={b.role}
style={{ animationDelay: `${(i % 15) * 40}ms` }}
className="group flex animate-fade-up items-baseline justify-between gap-3 border-b border-dotted border-rotary-navy/15 py-3 transition-colors duration-200 hover:border-rotary-gold"
>
<span className="text-[13px] text-rotary-ink/55 transition-colors duration-200 group-hover:text-rotary-ink/80 sm:text-sm">
{b.role}
</span>
<span className="shrink-0 text-right text-[13px] font-medium text-rotary-navy sm:text-sm">
{b.name}
</span>
</div>
))}
</div>
</div>
</section>
);
}
+53
View File
@@ -0,0 +1,53 @@
"use client";
import { useEffect, useRef, useState } from "react";
type Props = {
children: React.ReactNode;
delay?: number; // ms
className?: string;
variant?: "fade" | "glow";
};
export default function Reveal({ children, delay = 0, className = "", variant = "fade" }: Props) {
const ref = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const node = ref.current;
if (!node) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setVisible(true);
observer.disconnect();
}
},
{ threshold: 0.15 }
);
observer.observe(node);
return () => observer.disconnect();
}, []);
const hiddenState =
variant === "glow"
? "opacity-0 translate-y-6 scale-95 drop-shadow-none"
: "opacity-0 translate-y-6";
const visibleState =
variant === "glow"
? "opacity-100 translate-y-0 scale-100 drop-shadow-[0_0_36px_rgba(242,169,0,0.28)]"
: "opacity-100 translate-y-0";
return (
<div
ref={ref}
style={{ transitionDelay: visible ? `${delay}ms` : "0ms" }}
className={`transition-all duration-700 ease-out motion-reduce:transition-none ${
visible ? visibleState : hiddenState
} ${className}`}
>
{children}
</div>
);
}