@titas_mallick/wedding-site-gen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.eslintignore +20 -0
  2. package/.eslintrc.json +93 -0
  3. package/.recover +9 -0
  4. package/.vscode/settings.json +3 -0
  5. package/LICENSE +21 -0
  6. package/README.md +83 -0
  7. package/app/Neo-Lucentism/layout.tsx +7 -0
  8. package/app/Neo-Lucentism/page.tsx +259 -0
  9. package/app/couple/layout.tsx +7 -0
  10. package/app/couple/page.tsx +164 -0
  11. package/app/error.tsx +31 -0
  12. package/app/guestbook/page.tsx +470 -0
  13. package/app/invitation/[slug]/layout.tsx +36 -0
  14. package/app/invitation/[slug]/page.tsx +462 -0
  15. package/app/invitation/maker/auth.js +165 -0
  16. package/app/invitation/maker/dashboard.js +81 -0
  17. package/app/invitation/maker/guestAdder.js +204 -0
  18. package/app/invitation/maker/guestShower.js +287 -0
  19. package/app/invitation/maker/layout.tsx +11 -0
  20. package/app/invitation/maker/page.js +168 -0
  21. package/app/invitation/maker/rsvpViewer.js +122 -0
  22. package/app/layout.tsx +98 -0
  23. package/app/mark-the-dates/layout.tsx +7 -0
  24. package/app/mark-the-dates/page.tsx +196 -0
  25. package/app/memories/layout.tsx +7 -0
  26. package/app/memories/page.tsx +29 -0
  27. package/app/page.tsx +5 -0
  28. package/app/providers.tsx +33 -0
  29. package/app/sagun/layout.tsx +7 -0
  30. package/app/sagun/page.tsx +348 -0
  31. package/app/song-requests/page.tsx +354 -0
  32. package/app/sukanya/layout.tsx +7 -0
  33. package/app/sukanya/page.tsx +167 -0
  34. package/app/titas/layout.tsx +7 -0
  35. package/app/titas/page.tsx +175 -0
  36. package/app/travel-guide/page.tsx +400 -0
  37. package/app/updates/maker/page.js +323 -0
  38. package/app/updates/overlay/page.tsx +144 -0
  39. package/app/updates/page.js +207 -0
  40. package/cli.mjs +196 -0
  41. package/components/ConciergeBot.tsx +203 -0
  42. package/components/CountdownTimer.tsx +137 -0
  43. package/components/Gallery.tsx +372 -0
  44. package/components/LiveVideos.tsx +173 -0
  45. package/components/OurStory.tsx +160 -0
  46. package/components/certificate.jsx +300 -0
  47. package/components/counter.tsx +14 -0
  48. package/components/footer.tsx +89 -0
  49. package/components/hero.tsx +136 -0
  50. package/components/icons.tsx +283 -0
  51. package/components/importantNews.js +168 -0
  52. package/components/navbar.tsx +106 -0
  53. package/components/primitives.ts +53 -0
  54. package/components/sagun.js +22 -0
  55. package/components/theme-switch.tsx +81 -0
  56. package/components/updates.tsx +118 -0
  57. package/components/weddingcard.js +68 -0
  58. package/components/weddingcard2.js +58 -0
  59. package/config/firebase-admin.js +17 -0
  60. package/config/firebase.ts +36 -0
  61. package/config/fonts.ts +21 -0
  62. package/config/site.ts +74 -0
  63. package/next-env.d.ts +6 -0
  64. package/next.config.js +4 -0
  65. package/package.json +64 -0
  66. package/postcss.config.js +6 -0
  67. package/public/DCV.gif +0 -0
  68. package/public/DCV2.gif +0 -0
  69. package/public/DCV3.gif +0 -0
  70. package/public/Images/1.jpg +0 -0
  71. package/public/Images/11.jpg +0 -0
  72. package/public/Images/12.jpg +0 -0
  73. package/public/Images/13.jpg +0 -0
  74. package/public/Images/14.jpg +0 -0
  75. package/public/Images/15.jpg +0 -0
  76. package/public/Images/16.jpg +0 -0
  77. package/public/Images/17.jpg +0 -0
  78. package/public/Images/18.jpg +0 -0
  79. package/public/Images/19.jpg +0 -0
  80. package/public/Images/2.jpg +0 -0
  81. package/public/Images/20.jpg +0 -0
  82. package/public/Images/21.jpg +0 -0
  83. package/public/Images/22.jpg +0 -0
  84. package/public/Images/3.jpg +0 -0
  85. package/public/Images/4.jpg +0 -0
  86. package/public/Images/5.jpg +0 -0
  87. package/public/Images/6.jpg +0 -0
  88. package/public/Images/7.jpg +0 -0
  89. package/public/Images/8.jpg +0 -0
  90. package/public/Images/9.jpg +0 -0
  91. package/public/Images/9b.jpg +0 -0
  92. package/public/Images/Patipatra.jpeg +0 -0
  93. package/public/audio (1).mp3 +0 -0
  94. package/public/audio (2).mp3 +0 -0
  95. package/public/bride.jpg +0 -0
  96. package/public/corner1-01.svg +1 -0
  97. package/public/favicon.ico +0 -0
  98. package/public/groom.jpg +0 -0
  99. package/public/invite.png +0 -0
  100. package/public/love-birds.png +0 -0
  101. package/public/next.svg +1 -0
  102. package/public/pubqr.png +0 -0
  103. package/public/pw/001.jpg +0 -0
  104. package/public/pw/002.jpg +0 -0
  105. package/public/pw/003.jpg +0 -0
  106. package/public/pw/004.jpg +0 -0
  107. package/public/pw/005.jpg +0 -0
  108. package/public/pw/006.jpg +0 -0
  109. package/public/pw/007.jpg +0 -0
  110. package/public/pw/008.jpg +0 -0
  111. package/public/pw/009.jpg +0 -0
  112. package/public/pw/010.jpg +0 -0
  113. package/public/pw/011.jpg +0 -0
  114. package/public/pw/012.jpg +0 -0
  115. package/public/pw/013.jpg +0 -0
  116. package/public/pw/014.jpg +0 -0
  117. package/public/pw/015.jpg +0 -0
  118. package/public/pw/016.jpg +0 -0
  119. package/public/pw/017.jpg +0 -0
  120. package/public/pw/018.jpg +0 -0
  121. package/public/pw/019.jpg +0 -0
  122. package/public/pw/020.jpg +0 -0
  123. package/public/pw/021.jpg +0 -0
  124. package/public/pw/022.jpg +0 -0
  125. package/public/pw/023.jpg +0 -0
  126. package/public/pw/024.jpg +0 -0
  127. package/public/pw/025.jpg +0 -0
  128. package/public/pw/026.jpg +0 -0
  129. package/public/pw/027.jpg +0 -0
  130. package/public/pw/028.jpg +0 -0
  131. package/public/pw/029.jpg +0 -0
  132. package/public/pw/030.jpg +0 -0
  133. package/public/pw/031.jpg +0 -0
  134. package/public/pw/032.jpg +0 -0
  135. package/public/qr.png +0 -0
  136. package/public/vercel.svg +1 -0
  137. package/styles/globals.css +3 -0
  138. package/tailwind.config.js +51 -0
  139. package/tsconfig.json +45 -0
  140. package/tsconfig.tsbuildinfo +1 -0
  141. package/types/index.ts +5 -0
@@ -0,0 +1,168 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import AddGuestModal from "./guestAdder";
5
+ import GuestTable from "./guestShower";
6
+ import Dash from "./dashboard";
7
+ import RSVPViewer from "./rsvpViewer";
8
+ import Auth from "./auth";
9
+ import firebaseApp from "@/config/firebase";
10
+ import {
11
+ getFirestore,
12
+ doc,
13
+ addDoc,
14
+ collection,
15
+ onSnapshot,
16
+ setDoc,
17
+ deleteDoc,
18
+ serverTimestamp,
19
+ query,
20
+ orderBy,
21
+ } from "firebase/firestore";
22
+ import { addToast } from "@heroui/react";
23
+ import { fontCursive } from "@/config/fonts";
24
+
25
+ const InvitationMaker = () => {
26
+ const [user, setUser] = useState(null);
27
+ const [guests, setGuests] = useState([]);
28
+ const [isModalOpen, setIsModalOpen] = useState(false);
29
+ const [editingGuest, setEditingGuest] = useState(null);
30
+
31
+ const db = getFirestore(firebaseApp());
32
+
33
+ useEffect(() => {
34
+ if (!user) return;
35
+
36
+ // const invitationRef = collection(db, "invitation");
37
+ const q = query(collection(db, "invitation"), orderBy("createdAt", "desc"));
38
+
39
+ const unsubscribe = onSnapshot(q, (snapshot) => {
40
+ const guests = snapshot.docs.map((doc) => ({
41
+ id: doc.id,
42
+ ...doc.data(),
43
+ }));
44
+ setGuests(guests);
45
+ });
46
+
47
+ return () => unsubscribe(); // cleanup on unmount
48
+ }, [db, user]);
49
+
50
+ const handleAddGuest = async (guestData) => {
51
+ console.log("Saving Guest:", guestData);
52
+ const cleanedData = {
53
+ ...guestData,
54
+ invitedGuests: Number(guestData.invitedGuests), // ensure number type
55
+ createdAt: serverTimestamp(),
56
+ };
57
+
58
+ try {
59
+ if (guestData.id) {
60
+ // Update existing guest by doc ID with merge
61
+ const guestRef = doc(db, "invitation", guestData.id);
62
+ await setDoc(guestRef, cleanedData, { merge: true });
63
+ console.log("🔁 Guest updated in Firestore");
64
+ addToast({
65
+ title: "Guest Updated",
66
+ description: "Guest details have been updated successfully.",
67
+ color: "success",
68
+ variant: "solid",
69
+ radius: "lg",
70
+ closeIcon: true,
71
+ timeout: 2000,
72
+ });
73
+ } else {
74
+ // Add new guest
75
+ await addDoc(collection(db, "invitation"), cleanedData);
76
+ console.log("✅ Guest added to Firestore");
77
+ addToast({
78
+ title: "Guest Added",
79
+ description: "Guest has been added successfully.",
80
+ color: "success",
81
+ variant: "solid",
82
+ radius: "lg",
83
+ closeIcon: true,
84
+ timeout: 2000,
85
+ });
86
+ }
87
+ } catch (error) {
88
+ console.error("❌ Error saving guest:", error);
89
+ addToast({
90
+ title: "Error",
91
+ description: "An error occurred while saving the guest.",
92
+ color: "danger",
93
+ variant: "solid",
94
+ radius: "lg",
95
+ closeIcon: true,
96
+ timeout: 2000,
97
+ });
98
+ }
99
+
100
+ setIsModalOpen(false);
101
+ setEditingGuest(null);
102
+ };
103
+
104
+ const handleDeleteGuest = async (guestToDelete) => {
105
+ try {
106
+ const guestRef = doc(db, "invitation", guestToDelete.id);
107
+ await deleteDoc(guestRef);
108
+ console.log("🗑️ Guest deleted from Firestore");
109
+ addToast({
110
+ title: "Guest deleted",
111
+ description: "Guest has been deleted successfully.",
112
+ color: "danger",
113
+ variant: "solid",
114
+ radius: "lg",
115
+ closeIcon: true,
116
+ timeout: 2000,
117
+ });
118
+ } catch (error) {
119
+ console.error("❌ Error deleting guest:", error);
120
+ }
121
+ };
122
+
123
+ const handleEditClick = (guest) => {
124
+ setEditingGuest(guest);
125
+ setIsModalOpen(true);
126
+ };
127
+
128
+ const handleAddClick = () => {
129
+ setEditingGuest(null);
130
+ setIsModalOpen(true);
131
+ };
132
+
133
+ return (
134
+ <>
135
+ <h1
136
+ className={`${fontCursive.className} text-center text-5xl leading-snug bg-cover bg-center bg-no-repeat pt-2 pb-10`}
137
+ >
138
+ Guest Management System
139
+ </h1>
140
+ <Auth userSet={setUser} />
141
+ {guests && <Dash guests={guests} />}
142
+ {user && guests && (
143
+ <div className="mt-10 px-4">
144
+ <RSVPViewer guests={guests} />
145
+ </div>
146
+ )}
147
+ {user && (
148
+ <AddGuestModal
149
+ isOpen={isModalOpen}
150
+ onClose={() => setIsModalOpen(false)}
151
+ onAdd={handleAddGuest}
152
+ existingGuest={editingGuest}
153
+ />
154
+ )}
155
+ <br />
156
+ {guests && user && (
157
+ <GuestTable
158
+ guests={guests}
159
+ onEdit={handleEditClick}
160
+ onDelete={handleDeleteGuest}
161
+ onAdd={handleAddClick}
162
+ />
163
+ )}
164
+ </>
165
+ );
166
+ };
167
+
168
+ export default InvitationMaker;
@@ -0,0 +1,122 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import {
5
+ Table,
6
+ TableHeader,
7
+ TableColumn,
8
+ TableBody,
9
+ TableRow,
10
+ TableCell,
11
+ Chip,
12
+ User,
13
+ Tooltip,
14
+ } from "@heroui/react";
15
+ import {
16
+ getFirestore,
17
+ collection,
18
+ onSnapshot,
19
+ query,
20
+ orderBy
21
+ } from "firebase/firestore";
22
+ import firebaseApp from "@/config/firebase";
23
+ import { fontMono } from "@/config/fonts";
24
+
25
+ const db = getFirestore(firebaseApp());
26
+
27
+ export default function RSVPViewer({ guests }) {
28
+ const [rsvps, setRsvps] = useState([]);
29
+ const [loading, setLoading] = useState(true);
30
+
31
+ useEffect(() => {
32
+ const q = query(collection(db, "rsvps"), orderBy("timestamp", "desc"));
33
+ const unsubscribe = onSnapshot(q, (snapshot) => {
34
+ const rsvpData = snapshot.docs.map((doc) => {
35
+ const data = doc.data();
36
+ // Match with the master guest list to get more details (side, relation)
37
+ const masterGuest = guests.find(g => g.id === data.guestId);
38
+ return {
39
+ id: doc.id,
40
+ ...data,
41
+ familySide: masterGuest?.familySide || "Unknown",
42
+ relation: masterGuest?.relation || "Guest",
43
+ };
44
+ });
45
+ setRsvps(rsvpData);
46
+ setLoading(false);
47
+ });
48
+
49
+ return () => unsubscribe();
50
+ }, [guests]);
51
+
52
+ const columns = [
53
+ { name: "GUEST", uid: "guest" },
54
+ { name: "ATTENDING", uid: "attending" },
55
+ { name: "FOOD", uid: "food" },
56
+ { name: "GUESTS", uid: "count" },
57
+ { name: "NOTE", uid: "note" },
58
+ ];
59
+
60
+ return (
61
+ <div className="space-y-4">
62
+ <div className="flex items-center justify-between px-2">
63
+ <h2 className="text-2xl font-bold text-default-800 dark:text-white">Live RSVP Responses</h2>
64
+ <Chip variant="flat" color="secondary" size="sm" className={fontMono.className}>
65
+ Total: {rsvps.length}
66
+ </Chip>
67
+ </div>
68
+
69
+ <Table aria-label="RSVP Response Table" shadow="sm" className="bg-white dark:bg-zinc-900/50">
70
+ <TableHeader columns={columns}>
71
+ {(column) => (
72
+ <TableColumn key={column.uid} className="bg-default-50 dark:bg-zinc-800 font-bold">
73
+ {column.name}
74
+ </TableColumn>
75
+ )}
76
+ </TableHeader>
77
+ <TableBody items={rsvps} emptyContent={"No RSVPs received yet."} isLoading={loading}>
78
+ {(item) => (
79
+ <TableRow key={item.id} className="border-b border-default-100 last:border-none">
80
+ <TableCell>
81
+ <User
82
+ name={item.guestName}
83
+ description={`${item.familySide === "bride" ? "Bride's" : "Groom's"} ${item.relation}`}
84
+ avatarProps={{
85
+ size: "sm",
86
+ className: item.familySide === "bride" ? "bg-wedding-pink-100 text-wedding-pink-600" : "bg-wedding-gold-100 text-wedding-gold-600",
87
+ name: item.guestName.charAt(0)
88
+ }}
89
+ />
90
+ </TableCell>
91
+ <TableCell>
92
+ <Chip
93
+ color={item.attending === "yes" ? "success" : "danger"}
94
+ variant="flat"
95
+ size="sm"
96
+ className="font-bold uppercase"
97
+ >
98
+ {item.attending === "yes" ? "Attending" : "Declined"}
99
+ </Chip>
100
+ </TableCell>
101
+ <TableCell>
102
+ <Chip color={item.food === "veg" ? "success" : "warning"} variant="dot" size="sm">
103
+ {item.food === "veg" ? "Vegetarian" : "Non-Veg"}
104
+ </Chip>
105
+ </TableCell>
106
+ <TableCell>
107
+ <span className="font-mono font-bold text-lg">{item.guests}</span>
108
+ </TableCell>
109
+ <TableCell>
110
+ <Tooltip content={item.note || "No special note"}>
111
+ <p className="text-xs text-default-500 truncate max-w-[150px] italic">
112
+ {item.note || "—"}
113
+ </p>
114
+ </Tooltip>
115
+ </TableCell>
116
+ </TableRow>
117
+ )}
118
+ </TableBody>
119
+ </Table>
120
+ </div>
121
+ );
122
+ }
package/app/layout.tsx ADDED
@@ -0,0 +1,98 @@
1
+ import "@/styles/globals.css";
2
+ import { Metadata, Viewport } from "next";
3
+ import clsx from "clsx";
4
+
5
+ import { Providers } from "./providers";
6
+
7
+ import { siteConfig } from "@/config/site";
8
+ import { fontSans, fontMono, fontCursive } from "@/config/fonts"; // include all fonts
9
+ import { Navbar } from "@/components/navbar";
10
+ import { Footer } from "@/components/footer";
11
+ import { Analytics } from "@vercel/analytics/next";
12
+ import ConciergeBot from "@/components/ConciergeBot";
13
+
14
+ export const metadata: Metadata = {
15
+ metadataBase: new URL("https://www.titas-sukanya-for.life/"),
16
+
17
+ title: {
18
+ default: siteConfig.name,
19
+ template: `%s - ${siteConfig.name}`,
20
+ },
21
+ description: siteConfig.description,
22
+ keywords: ["Wedding", "Titas", "Sukanya", "Marriage", "Celebration", "Event"],
23
+ authors: [
24
+ {
25
+ name: "Titas Mallick",
26
+ url: "https://www.titas-sukanya-for.life",
27
+ },
28
+ ],
29
+ creator: "Titas Mallick",
30
+ icons: {
31
+ icon: "/love-birds.png",
32
+ },
33
+ openGraph: {
34
+ type: "website",
35
+ locale: "en_US",
36
+ url: "https://www.titas-sukanya-for.life/",
37
+ title: siteConfig.name,
38
+ description: siteConfig.description,
39
+ siteName: siteConfig.name,
40
+ images: [
41
+ {
42
+ url: "/pw/020.jpg",
43
+ width: 1200,
44
+ height: 630,
45
+ alt: "Titas & Sukanya Wedding",
46
+ },
47
+ ],
48
+ },
49
+ twitter: {
50
+ card: "summary_large_image",
51
+ title: siteConfig.name,
52
+ description: siteConfig.description,
53
+ images: ["/pw/020.jpg"],
54
+ creator: "@titas",
55
+ },
56
+ };
57
+
58
+ export const viewport: Viewport = {
59
+ themeColor: [
60
+ { media: "(prefers-color-scheme: light)", color: "white" },
61
+ { media: "(prefers-color-scheme: dark)", color: "black" },
62
+ ],
63
+ };
64
+
65
+ export default function RootLayout({
66
+ children,
67
+ }: {
68
+ children: React.ReactNode;
69
+ }) {
70
+ return (
71
+ <html
72
+ suppressHydrationWarning
73
+ className={clsx(
74
+ fontSans.variable,
75
+ fontMono.variable,
76
+ fontCursive.variable,
77
+ )}
78
+ lang="en"
79
+ >
80
+ <head />
81
+ <body
82
+ className={clsx("min-h-screen bg-background font-sans antialiased")}
83
+ >
84
+ <Providers themeProps={{ attribute: "class", defaultTheme: "light" }}>
85
+ <div className="relative flex flex-col h-screen">
86
+ <Navbar />
87
+ <main className="container mx-auto max-w-7xl pt-16 px-6 flex-grow">
88
+ {children}
89
+ </main>
90
+ <Footer />
91
+ <Analytics />
92
+ <ConciergeBot />
93
+ </div>
94
+ </Providers>
95
+ </body>
96
+ </html>
97
+ );
98
+ }
@@ -0,0 +1,7 @@
1
+ export default function MarkTheDatesLayout({
2
+ children,
3
+ }: {
4
+ children: React.ReactNode;
5
+ }) {
6
+ return <section className="w-full">{children}</section>;
7
+ }
@@ -0,0 +1,196 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import { Card, CardBody, Image, Chip } from "@heroui/react";
5
+ import { useEffect, useState } from "react";
6
+
7
+ import { fontCursive, fontSans, fontMono } from "@/config/fonts";
8
+ import { HeartFilledIcon, CalendarIcon } from "@/components/icons";
9
+
10
+ const importantDates = [
11
+ {
12
+ title: "The Proposal",
13
+ date: "15th March 2025",
14
+ note: "The day Titas finally asked Sukanya out with a ring.",
15
+ image: "/Images/19.jpg",
16
+ },
17
+ {
18
+ title: "The Patipatra",
19
+ date: "18th April 2025",
20
+ note: "Our families came up with a holy date.",
21
+ image: "/Images/Patipatra.jpeg",
22
+ },
23
+ {
24
+ title: "Our Engagement",
25
+ date: "23rd November 2025",
26
+ note: "A promise sealed with love and laughter.",
27
+ image: "/Images/22.jpg",
28
+ },
29
+ {
30
+ title: "Wedding Bells",
31
+ date: "23rd January 2026",
32
+ note: "Two souls, one journey begins.",
33
+ image: "/Images/21.jpg",
34
+ },
35
+ {
36
+ title: "Reception",
37
+ date: "25th January 2026",
38
+ note: "Celebrating our union with friends and family.",
39
+ image: "/Images/20.jpg",
40
+ },
41
+ ];
42
+
43
+ export default function DatesPage() {
44
+ const [now, setNow] = useState<Date | null>(null);
45
+
46
+ useEffect(() => {
47
+ setNow(new Date());
48
+ }, []);
49
+
50
+ const parseDate = (dateStr: string) => {
51
+ const cleaned = dateStr.replace(/(\d+)(st|nd|rd|th)/, "$1");
52
+
53
+ return new Date(cleaned);
54
+ };
55
+
56
+ return (
57
+ <div className="min-h-screen pb-20 pt-10 px-4">
58
+ {/* Header */}
59
+ <section className="max-w-4xl mx-auto text-center mb-20">
60
+ <motion.div
61
+ animate={{ opacity: 1, y: 0 }}
62
+ initial={{ opacity: 0, y: -20 }}
63
+ transition={{ duration: 0.8 }}
64
+ >
65
+ <span className="inline-block px-3 py-1 rounded-full bg-wedding-pink-50 dark:bg-wedding-pink-900/30 text-wedding-pink-600 dark:text-wedding-pink-300 text-xs font-bold tracking-widest uppercase mb-6 border border-wedding-pink-100 dark:border-wedding-pink-800">
66
+ Our Journey
67
+ </span>
68
+ <h1
69
+ className={`${fontCursive.className} py-2 text-5xl md:text-7xl bg-gradient-to-r from-wedding-pink-600 to-wedding-gold-600 dark:from-wedding-pink-400 dark:to-wedding-gold-400 bg-clip-text text-transparent mb-6 leading-normal`}
70
+ >
71
+ Mark the Dates
72
+ </h1>
73
+ <p className="text-default-500 dark:text-default-400 max-w-xl mx-auto italic">
74
+ &quot;Every moment led us here. From the first proposal to the final
75
+ vows, follow the milestones of our love story.&quot;
76
+ </p>
77
+ </motion.div>
78
+ </section>
79
+
80
+ {/* Timeline Grid */}
81
+ <div className="max-w-5xl mx-auto relative">
82
+ {/* Timeline Line (Center - Desktop only) */}
83
+ <div className="absolute left-1/2 top-0 bottom-0 w-0.5 bg-gradient-to-b from-wedding-pink-200 via-wedding-gold-200 to-transparent -translate-x-1/2 hidden md:block opacity-30" />
84
+
85
+ <div className="flex flex-col gap-12 md:gap-24">
86
+ {importantDates.map((item, index) => {
87
+ const eventDate = parseDate(item.date);
88
+ const isPast = now ? now > eventDate : false;
89
+ const isLeft = index % 2 === 0;
90
+
91
+ return (
92
+ <motion.div
93
+ key={index}
94
+ className={`flex flex-col ${isLeft ? "md:flex-row" : "md:flex-row-reverse"} items-center gap-8 md:gap-0`}
95
+ initial={{ opacity: 0, x: isLeft ? -50 : 50 }}
96
+ transition={{ duration: 0.7, ease: "easeOut" }}
97
+ viewport={{ once: true, margin: "-100px" }}
98
+ whileInView={{ opacity: 1, x: 0 }}
99
+ >
100
+ {/* Content Side */}
101
+ <div
102
+ className={`w-full md:w-1/2 ${isLeft ? "md:pr-16 md:text-right" : "md:pl-16 md:text-left"}`}
103
+ >
104
+ <div
105
+ className={`flex flex-col ${isLeft ? "md:items-end" : "md:items-start"} items-center space-y-3`}
106
+ >
107
+ <div className="flex items-center gap-2">
108
+ {isPast && (
109
+ <Chip
110
+ className="bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 border-green-100 dark:border-green-800/30"
111
+ color="success"
112
+ size="sm"
113
+ variant="flat"
114
+ >
115
+ Completed
116
+ </Chip>
117
+ )}
118
+ <p
119
+ className={`${fontMono.className} text-wedding-pink-500 font-bold tracking-tighter text-sm`}
120
+ >
121
+ {item.date}
122
+ </p>
123
+ </div>
124
+
125
+ <h3
126
+ className={`${fontSans.className} text-2xl md:text-3xl font-bold text-default-900`}
127
+ >
128
+ {item.title}
129
+ </h3>
130
+
131
+ <p className="text-default-500 dark:text-default-400 text-sm md:text-base leading-relaxed max-w-sm">
132
+ {item.note}
133
+ </p>
134
+ </div>
135
+ </div>
136
+
137
+ {/* Center Icon (Desktop only) */}
138
+ <div className="absolute left-1/2 -translate-x-1/2 w-10 h-10 rounded-full bg-white dark:bg-zinc-900 border-4 border-wedding-pink-100 dark:border-wedding-pink-900 hidden md:flex items-center justify-center z-10 shadow-lg">
139
+ {isPast ? (
140
+ <HeartFilledIcon className="w-4 h-4 text-wedding-pink-500" />
141
+ ) : (
142
+ <CalendarIcon className="w-4 h-4 text-wedding-gold-500" />
143
+ )}
144
+ </div>
145
+
146
+ {/* Image Side */}
147
+ <div
148
+ className={`w-full md:w-1/2 ${isLeft ? "md:pl-16" : "md:pr-16"}`}
149
+ >
150
+ <motion.div
151
+ className="w-full"
152
+ transition={{ type: "spring", stiffness: 300 }}
153
+ whileHover={{ scale: 1.02 }}
154
+ >
155
+ <Card
156
+ className={`overflow-hidden border-none shadow-xl aspect-[4/5] w-full ${isPast ? "opacity-90" : ""}`}
157
+ >
158
+ <CardBody className="p-0 h-full w-full overflow-hidden relative">
159
+ <Image
160
+ removeWrapper
161
+ alt={item.title}
162
+ className="z-0 w-full h-full object-cover hover:scale-105 transition-transform duration-700 block"
163
+ height="100%"
164
+ radius="none"
165
+ src={item.image}
166
+ width="100%"
167
+ />
168
+ </CardBody>
169
+ </Card>
170
+ </motion.div>
171
+ </div>
172
+ </motion.div>
173
+ );
174
+ })}
175
+ </div>
176
+ </div>
177
+
178
+ {/* Bottom Call to Action */}
179
+ <motion.div
180
+ className="mt-32 text-center"
181
+ initial={{ opacity: 0 }}
182
+ whileInView={{ opacity: 1 }}
183
+ >
184
+ <div className="inline-block p-[2px] rounded-full bg-gradient-to-r from-wedding-pink-500 to-wedding-gold-500 mb-6">
185
+ <div className="bg-white dark:bg-zinc-900 px-8 py-3 rounded-full">
186
+ <p
187
+ className={`${fontCursive.className} text-3xl bg-gradient-to-r from-wedding-pink-600 to-wedding-gold-600 dark:from-wedding-pink-400 dark:to-wedding-gold-400 bg-clip-text text-transparent`}
188
+ >
189
+ Counting down the days...
190
+ </p>
191
+ </div>
192
+ </div>
193
+ </motion.div>
194
+ </div>
195
+ );
196
+ }
@@ -0,0 +1,7 @@
1
+ export default function MemoriesLayout({
2
+ children,
3
+ }: {
4
+ children: React.ReactNode;
5
+ }) {
6
+ return <div className="w-full">{children}</div>;
7
+ }
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import { fontCursive } from "@/config/fonts";
4
+ import Gallery from "@/components/Gallery";
5
+
6
+ export default function MemoriesPage() {
7
+ return (
8
+ <div className="w-full min-h-screen pb-20">
9
+ <section className="relative py-16 md:py-24 text-center px-4 overflow-hidden">
10
+ {/* Decorative Background */}
11
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-[400px] bg-wedding-pink-100/40 dark:bg-wedding-pink-900/10 rounded-full blur-[100px] pointer-events-none" />
12
+
13
+ <div className="relative z-10">
14
+ <h1
15
+ className={`${fontCursive.className} text-5xl md:text-7xl lg:text-8xl bg-gradient-to-r from-wedding-pink-600 to-wedding-gold-600 bg-clip-text text-transparent mb-6 py-4 leading-normal`}
16
+ >
17
+ Days of Future Past
18
+ </h1>
19
+ <p className="text-default-500 dark:text-gray-300 text-lg md:text-xl max-w-2xl mx-auto italic">
20
+ &quot;Frames that echo where we began, rather than where we
21
+ stand.&quot;
22
+ </p>
23
+ </div>
24
+ </section>
25
+
26
+ <Gallery />
27
+ </div>
28
+ );
29
+ }
package/app/page.tsx ADDED
@@ -0,0 +1,5 @@
1
+ import Herohome from "@/components/hero";
2
+
3
+ export default function Home() {
4
+ return <Herohome />;
5
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import type { ThemeProviderProps } from "next-themes";
4
+
5
+ import * as React from "react";
6
+ import { HeroUIProvider } from "@heroui/system";
7
+ import { useRouter } from "next/navigation";
8
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
9
+ import { ToastProvider } from "@heroui/react";
10
+
11
+ export interface ProvidersProps {
12
+ children: React.ReactNode;
13
+ themeProps?: ThemeProviderProps;
14
+ }
15
+
16
+ declare module "@react-types/shared" {
17
+ interface RouterConfig {
18
+ routerOptions: NonNullable<
19
+ Parameters<ReturnType<typeof useRouter>["push"]>[1]
20
+ >;
21
+ }
22
+ }
23
+
24
+ export function Providers({ children, themeProps }: ProvidersProps) {
25
+ const router = useRouter();
26
+
27
+ return (
28
+ <HeroUIProvider navigate={router.push}>
29
+ <ToastProvider />
30
+ <NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
31
+ </HeroUIProvider>
32
+ );
33
+ }
@@ -0,0 +1,7 @@
1
+ export default function SagunLayout({
2
+ children,
3
+ }: {
4
+ children: React.ReactNode;
5
+ }) {
6
+ return <div className="w-full">{children}</div>;
7
+ }