first commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
|||||||
|
# Setup notes
|
||||||
|
|
||||||
|
## 1. Copy files
|
||||||
|
- `components/*` → your `components/` folder
|
||||||
|
- `app/page.tsx` → replace your existing `app/page.tsx`
|
||||||
|
- `public/images/*` → your `public/images/` folder
|
||||||
|
|
||||||
|
## 2. Tailwind config
|
||||||
|
Merge `tailwind.config.snippet.js` into your existing `tailwind.config.js`
|
||||||
|
(the `colors`, `fontFamily`, `keyframes`, and `animation` keys under
|
||||||
|
`theme.extend`). Don't overwrite the whole file — just merge these keys in.
|
||||||
|
|
||||||
|
## 3. Fonts (optional but recommended — matches the design as built)
|
||||||
|
In `app/layout.tsx`, add next/font and expose them as CSS variables that the
|
||||||
|
tailwind config's `fontFamily` reads from:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Playfair_Display, Inter } from "next/font/google";
|
||||||
|
|
||||||
|
const display = Playfair_Display({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["500", "600", "700"],
|
||||||
|
variable: "--font-display",
|
||||||
|
});
|
||||||
|
const body = Inter({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-body",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className={`${display.variable} ${body.variable}`}>
|
||||||
|
<body className="font-sans">{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you'd rather skip next/font, everything still works — `font-serif` and
|
||||||
|
`font-sans` just fall back to system serif/sans stacks.
|
||||||
|
|
||||||
|
## 4. Path alias
|
||||||
|
Components import via `@/components/...`. Confirm your `tsconfig.json` has:
|
||||||
|
```json
|
||||||
|
"paths": { "@/*": ["./*"] }
|
||||||
|
```
|
||||||
|
If your project already uses a different alias (e.g. `src/`), adjust the
|
||||||
|
import paths in `app/page.tsx` accordingly.
|
||||||
|
|
||||||
|
## What's included
|
||||||
|
- **Header.tsx** — sticky, scroll-aware shrink/shadow, animated nav underline, mobile slide-menu
|
||||||
|
- **Hero.tsx** — masthead + live Countdown + rotating gear watermark (signature element)
|
||||||
|
- **Countdown.tsx** — client-side ticking countdown to the ceremony
|
||||||
|
- **EventDetails.tsx** — date / time / venue cards
|
||||||
|
- **Handover.tsx** — outgoing vs incoming board, gavel-pass layout
|
||||||
|
- **Guests.tsx** — Chief Guest & Guest of Honour cards
|
||||||
|
- **OfficeBearers.tsx** — full 46-role roster, tabbed (Leadership / Directors / Committee Chairs), dotted-leader rows
|
||||||
|
- **CtaBand.tsx** — gold RSVP band with shimmer animation
|
||||||
|
- **Footer.tsx** — club info, links, back-to-top
|
||||||
|
- **GearMotif.tsx** / **Reveal.tsx** — shared primitives (rotating SVG gear, scroll-reveal wrapper)
|
||||||
|
|
||||||
|
All animation is Tailwind-only: utility transitions, `animate-*` classes
|
||||||
|
from the extended config, and one small IntersectionObserver hook (`Reveal`)
|
||||||
|
for scroll-triggered fades — no animation library used.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
+68
-15
@@ -1,26 +1,79 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
@theme {
|
||||||
--background: #ffffff;
|
/* Rotary brand colors — generates bg-rotary-navy, text-rotary-gold, etc. */
|
||||||
--foreground: #171717;
|
--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 {
|
@keyframes spin-slow {
|
||||||
--color-background: var(--background);
|
to {
|
||||||
--color-foreground: var(--foreground);
|
transform: rotate(360deg);
|
||||||
--font-sans: var(--font-geist-sans);
|
}
|
||||||
--font-mono: var(--font-geist-mono);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@keyframes fade-up {
|
||||||
:root {
|
0% {
|
||||||
--background: #0a0a0a;
|
opacity: 0;
|
||||||
--foreground: #ededed;
|
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 {
|
body {
|
||||||
background: var(--background);
|
background-color: var(--color-rotary-paper);
|
||||||
color: var(--foreground);
|
color: var(--color-rotary-ink);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
}
|
||||||
+2
-2
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Rotary Club of Vellore — Installation Ceremony 2026",
|
||||||
description: "Generated by create next app",
|
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({
|
export default function RootLayout({
|
||||||
|
|||||||
+17
-61
@@ -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 (
|
return (
|
||||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<main className="bg-rotary-paper">
|
||||||
<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">
|
<Hero />
|
||||||
<Image
|
<EventDetails />
|
||||||
className="dark:invert"
|
<Handover />
|
||||||
src="/next.svg"
|
<Guests />
|
||||||
alt="Next.js logo"
|
<OfficeBearers />
|
||||||
width={100}
|
<CtaBand />
|
||||||
height={20}
|
<Footer />
|
||||||
priority
|
</main>
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* MERGE THIS into your existing tailwind.config.js/ts — do not replace the file.
|
||||||
|
* Add the `colors`, `fontFamily`, `keyframes`, and `animation` keys below
|
||||||
|
* into your existing `theme.extend` block.
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
rotary: {
|
||||||
|
navy: "#0B2545", // primary — headers, deep panels
|
||||||
|
blue: "#12468C", // Rotary International blue — brand primary
|
||||||
|
azure: "#2E7CD6", // secondary accent, links, gradients
|
||||||
|
gold: "#F2A900", // Rotary gold — CTAs, dividers, highlights
|
||||||
|
goldlight: "#FFD166",
|
||||||
|
paper: "#F4F6FA", // page background (cool paper, not cream)
|
||||||
|
ink: "#101B2D", // body text
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
serif: ["var(--font-display)", "Georgia", "serif"],
|
||||||
|
sans: ["var(--font-body)", "ui-sans-serif", "system-ui", "sans-serif"],
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"spin-slow": { to: { transform: "rotate(360deg)" } },
|
||||||
|
"fade-up": {
|
||||||
|
"0%": { opacity: "0", transform: "translateY(16px)" },
|
||||||
|
"100%": { opacity: "1", transform: "translateY(0)" },
|
||||||
|
},
|
||||||
|
shimmer: {
|
||||||
|
"0%": { backgroundPosition: "-200% 0" },
|
||||||
|
"100%": { backgroundPosition: "200% 0" },
|
||||||
|
},
|
||||||
|
"ring-pulse": {
|
||||||
|
"0%, 100%": { boxShadow: "0 0 0 0 rgba(242,169,0,0.45)" },
|
||||||
|
"50%": { boxShadow: "0 0 0 10px rgba(242,169,0,0)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"spin-slow": "spin-slow 50s linear infinite",
|
||||||
|
"spin-slower": "spin-slow 90s linear infinite",
|
||||||
|
"fade-up": "fade-up 0.7s cubic-bezier(0.16,1,0.3,1) both",
|
||||||
|
shimmer: "shimmer 2.5s linear infinite",
|
||||||
|
"ring-pulse": "ring-pulse 2.4s ease-out infinite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user