diff --git a/SETUP.md b/SETUP.md
new file mode 100644
index 0000000..cfebe9b
--- /dev/null
+++ b/SETUP.md
@@ -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 (
+
+
{children}
+
+ );
+}
+```
+
+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.
diff --git a/public/images/club-advisor.jpg b/public/images/club-advisor.jpg
new file mode 100644
index 0000000..f75d5e1
Binary files /dev/null and b/public/images/club-advisor.jpg differ
diff --git a/public/images/guest-of-honour.jpg b/public/images/guest-of-honour.jpg
new file mode 100644
index 0000000..f712792
Binary files /dev/null and b/public/images/guest-of-honour.jpg differ
diff --git a/public/images/president.jpg b/public/images/president.jpg
new file mode 100644
index 0000000..9a60ee0
Binary files /dev/null and b/public/images/president.jpg differ
diff --git a/public/images/rotary-logo.png b/public/images/rotary-logo.png
new file mode 100644
index 0000000..f6ff269
Binary files /dev/null and b/public/images/rotary-logo.png differ
diff --git a/src/app/globals.css b/src/app/globals.css
index a2dc41e..c56f95f 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -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);
+}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 976eb90..7946cd3 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -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({
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 3f36f7c..b419c82 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -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 (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
-}
+}
\ No newline at end of file
diff --git a/src/components/Countdown.tsx b/src/components/Countdown.tsx
new file mode 100644
index 0000000..87119f6
--- /dev/null
+++ b/src/components/Countdown.tsx
@@ -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 | 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 (
+
+ {units.map((u) => (
+
+
+ {u.value !== undefined ? String(u.value).padStart(2, "0") : "--"}
+
+
+ {u.label}
+
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/CtaBand.tsx b/src/components/CtaBand.tsx
new file mode 100644
index 0000000..4c71ec1
--- /dev/null
+++ b/src/components/CtaBand.tsx
@@ -0,0 +1,19 @@
+import Reveal from "./Reveal";
+
+export default function CtaBand() {
+ return (
+
+
+
+
+ Dear Rotarians, your presence is most essential.
+
+
+ Join the President and Members of Rotary Club of Vellore, with
+ family, for the Installation Ceremony on 3rd July 2026.
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/EventDetails.tsx b/src/components/EventDetails.tsx
new file mode 100644
index 0000000..3e4dc3c
--- /dev/null
+++ b/src/components/EventDetails.tsx
@@ -0,0 +1,66 @@
+import Reveal from "./Reveal";
+
+const DETAILS = [
+ {
+ label: "Date",
+ value: "3rd July 2026",
+ sub: "Friday",
+ icon: (
+
+ ),
+ },
+ {
+ label: "Time",
+ value: "6:30 PM",
+ sub: "Doors open at 6:00 PM",
+ icon: <> >,
+ },
+ {
+ label: "Venue",
+ value: "Hotel Khanna, Laya Hall",
+ sub: "Anna Salai, Vellore",
+ icon: (
+ <> >
+ ),
+ },
+];
+
+export default function EventDetails() {
+ return (
+
+
+
+
+ Save the date
+
+
+ Join us for the evening
+
+
+
+
+ {DETAILS.map((d, i) => (
+
+
+
+
+ {d.icon}
+
+
+
+
+ {d.label}
+
+
+ {d.value}
+
+
{d.sub}
+
+
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
new file mode 100644
index 0000000..39d2389
--- /dev/null
+++ b/src/components/Footer.tsx
@@ -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 (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/GearMotif.tsx b/src/components/GearMotif.tsx
new file mode 100644
index 0000000..6c34915
--- /dev/null
+++ b/src/components/GearMotif.tsx
@@ -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 (
+
+
+
+ {Array.from({ length: 12 }).map((_, i) => {
+ const angle = (i * 360) / 12;
+ return (
+
+ );
+ })}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Guests.tsx b/src/components/Guests.tsx
new file mode 100644
index 0000000..74f8bcd
--- /dev/null
+++ b/src/components/Guests.tsx
@@ -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 (
+
+
+
+
+ With us this evening
+
+
+ Distinguished Guests
+
+
+
+
+ {GUESTS.map((g, i) => (
+
+
+
+
+
+
+
+ {g.role}
+
+
+ {g.name}
+
+
{g.subrole}
+
+
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Handover.tsx b/src/components/Handover.tsx
new file mode 100644
index 0000000..301944f
--- /dev/null
+++ b/src/components/Handover.tsx
@@ -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 (
+
+ {incoming && (
+
+ )}
+
+ {incoming ? "Incoming Board" : "Outgoing Board"}
+
+
+ {group.label}
+
+
+
+ {group.officers.map((o) => (
+
+
+ {o.role}
+
+
+ {o.name}
+
+
+ ))}
+
+
+ );
+}
+
+export default function Handover() {
+ return (
+
+
+
+
+ The gavel passes
+
+
+ A year of service, handed forward
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000..e035c89
--- /dev/null
+++ b/src/components/Header.tsx
@@ -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 (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx
new file mode 100644
index 0000000..25f2fdf
--- /dev/null
+++ b/src/components/Hero.tsx
@@ -0,0 +1,145 @@
+import Image from "next/image";
+import GearMotif from "./GearMotif";
+import Countdown from "./Countdown";
+
+export default function Hero() {
+ return (
+
+ {/* signature watermark */}
+
+
+
+
+ {/* Brand row — replaces the old header's logo now that it's gone */}
+
+
+
+
+
+
+
+ Rotary Club of Vellore
+
+
+ District 3231 · Est. 1948
+
+
+
+
+
+ {/* Copy */}
+
+ {/* Advisor + President thumbnails */}
+
+
+
+ Rotary Club of Vellore · Club No. 15900 · Charter 05.03.1948
+
+
+
+ Installation
+ Ceremony 2026 – 27
+
+
+
+ The President and Members cordially invite you, with family, to
+ witness the installation of the incoming Board and to
+ Create Lasting Impact together.
+
+
+ {/* date / venue chips */}
+
+
+
+
+
+
+ 3 July 2026 · Friday · 6:30 PM
+
+
+
+
+
+
+ Hotel Khanna, Laya Hall, Vellore
+
+
+
+
+
+ Counting down to installation
+
+
+
+
+
+ View Ceremony Details
+
+
+
+
+
+
+ {/* President portrait */}
+
+
+
+
+
+
+
+ President, 2026 – 27
+
+
+ Rtn. Er. G. Muralidharan
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/OfficeBearers.tsx b/src/components/OfficeBearers.tsx
new file mode 100644
index 0000000..695879d
--- /dev/null
+++ b/src/components/OfficeBearers.tsx
@@ -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 (
+
+
+
+
+ Office Bearers of 2026 – 27
+
+
+ The Directory
+
+
+
+ {/* Tabs */}
+
+
+ {GROUPS.map((g, i) => (
+ 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}
+
+ {g.roster.length}
+
+
+ ))}
+
+
+
+ {/* Roster */}
+
+ {GROUPS[active].roster.map((b, i) => (
+
+
+ {b.role}
+
+
+ {b.name}
+
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Reveal.tsx b/src/components/Reveal.tsx
new file mode 100644
index 0000000..4f38973
--- /dev/null
+++ b/src/components/Reveal.tsx
@@ -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(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 (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/tailwind.config.snippet.js b/tailwind.config.snippet.js
new file mode 100644
index 0000000..3ced245
--- /dev/null
+++ b/tailwind.config.snippet.js
@@ -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",
+ },
+ },
+ },
+};