init
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
.env
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const app = express();
|
||||||
|
const cardRouter = require('./routes/card');
|
||||||
|
const guestbookRouter = require('./routes/guestbook');
|
||||||
|
const countdownRouter = require('./routes/countdown');
|
||||||
|
|
||||||
|
// Set the view engine to EJS
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
app.use(express.static('public')); // To serve static files (CSS, images, etc.)
|
||||||
|
|
||||||
|
// Route for handling cards
|
||||||
|
app.use('/card', cardRouter);
|
||||||
|
app.use('/guestbook', guestbookRouter);
|
||||||
|
app.use('/countdown', countdownRouter);
|
||||||
|
|
||||||
|
|
||||||
|
app.get('*', function(req, res){
|
||||||
|
res.redirect('/card/en');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
const port = 3000;
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server running on http://localhost:${port}`);
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"lang": "de",
|
||||||
|
"menu_card": "Karte",
|
||||||
|
"menu_guestbook": "Gästebuch",
|
||||||
|
"menu_countdown": "Countdown",
|
||||||
|
"title": "Karte",
|
||||||
|
"description": "Einladung zur Hochzeit von Milenya & Bunbun",
|
||||||
|
"hover_hint":"Klick hier!",
|
||||||
|
"front_invation_1": "In den Reichen von Eorzea haben wir uns gefunden und nun, von den Sternen gesegnet, treten wir in den Bund der Ewigkeit.",
|
||||||
|
"front_invation_2": "Diesen Moment möchten wir mit euch teilen am 28.09.2024 um 15 Uhr im Ostwald am Dom der Einkehr.",
|
||||||
|
"front_invation_3": "Im Anschluss, wer möchte, kann sich gerne uns anschließen, um in Shirogane, Bezirk 23, Nr. 15 dieses Bündniss zu feiern.",
|
||||||
|
"back_info": "Bitte beachtet:",
|
||||||
|
"back_style": "Stil: Formell",
|
||||||
|
"back_rules": "Keine Minions, Keine Meme Outfits",
|
||||||
|
"back_afterword": "Bei weiteren Fragen könnt ihr euch gerne jederzeit bei uns melden",
|
||||||
|
"back_signed": "LG Milenya & Bunbun"
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"menu_card": "Card",
|
||||||
|
"menu_guestbook": "Guestbook",
|
||||||
|
"menu_countdown": "Countdown",
|
||||||
|
"title": "Card",
|
||||||
|
"description": "Invitation to the wedding of Milenya & Bunbun",
|
||||||
|
"hover_hint":"Click here!",
|
||||||
|
"front_invation_1": "In the realms of Eorzea, we have found each other, and now, blessed by the stars, we enter into the ceremony of eternal bonding.",
|
||||||
|
"front_invation_2": "We would like to share this moment with you on September 28th 2024, at 13 o'clock Server Time in the Sanctum of the Twelve in East Shroud.",
|
||||||
|
"front_invation_3": "Afterwards, anyone who wishes can gladly join the festivities in Shirogane, Ward 23, Plot 15.",
|
||||||
|
"back_info": "Please note:",
|
||||||
|
"back_style": "Style: Formal",
|
||||||
|
"back_rules": "No Minions, No Meme Outfits",
|
||||||
|
"back_afterword": "If you have any questions, feel free to reach out to us",
|
||||||
|
"back_signed": "Kind regards, Milenya & Bunbun"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"lang": "de",
|
||||||
|
"menu_card": "Karte",
|
||||||
|
"menu_guestbook": "Gästebuch",
|
||||||
|
"menu_countdown": "Countdown",
|
||||||
|
"title": "Hochzeit Timer",
|
||||||
|
"description": "Unser Hochzeit Timer",
|
||||||
|
"countdown_text": "Hochzeit Timer :",
|
||||||
|
"ountdown_finished": "Es ist soweit!"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"menu_card": "Card",
|
||||||
|
"menu_guestbook": "Guestbook",
|
||||||
|
"menu_countdown": "Countdown",
|
||||||
|
"title": "Wedding Timer",
|
||||||
|
"description": "our wedding timer",
|
||||||
|
"countdown_text": "Wedding Timer:",
|
||||||
|
"countdown_finished": "It is time!"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"lang": "de",
|
||||||
|
"menu_card": "Karte",
|
||||||
|
"menu_guestbook": "Gästebuch",
|
||||||
|
"menu_countdown": "Countdown",
|
||||||
|
"title": "Gästebuch",
|
||||||
|
"description": "Unser Gästebuch",
|
||||||
|
"guestbook_welcome": "Willkommen im Gästebuch!",
|
||||||
|
"guestbook_name": "Dein Name:",
|
||||||
|
"guestbook_name_placeholder": "Wer bist du?",
|
||||||
|
"guestbook_comment": "Deine Nachricht:",
|
||||||
|
"guestbook_comment_placeholder": "Was möchtest du mit uns teilen?",
|
||||||
|
"guestbook_submit": "Absenden",
|
||||||
|
"messages_wrote": "schrieb"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"menu_card": "Card",
|
||||||
|
"menu_guestbook": "Guestbook",
|
||||||
|
"menu_countdown": "Countdown",
|
||||||
|
"title": "Guestbook",
|
||||||
|
"description": "our guestbook",
|
||||||
|
"guestbook_welcome": "Welcome to our Guestbook!",
|
||||||
|
"guestbook_name":"Your Name:",
|
||||||
|
"guestbook_name_placeholder":"Who are you?",
|
||||||
|
"guestbook_comment":"Your Message:",
|
||||||
|
"guestbook_comment_placeholder": "What would you like to share with us?",
|
||||||
|
"guestbook_submit": "Submit",
|
||||||
|
"messages_wrote": "wrote"
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Aquanort",
|
||||||
|
"comment": "Plenty of silly jokes *wink*",
|
||||||
|
"imagePath": null,
|
||||||
|
"originalFilename": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Strange Person",
|
||||||
|
"comment": "“May your choices reflect your hopes, not your fears.” ",
|
||||||
|
"imagePath": null,
|
||||||
|
"originalFilename": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dorythos Tuulai",
|
||||||
|
"comment": "Freue mich schon! ",
|
||||||
|
"imagePath": null,
|
||||||
|
"originalFilename": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Milaysa Federschein",
|
||||||
|
"comment": "May your Hearts and Souls always beat in the same Rhythm ♥♥♥",
|
||||||
|
"imagePath": "/upload/Screenshot 2024-09-25 181537-1727281135769-e31074a6.png",
|
||||||
|
"originalFilename": "Screenshot 2024-09-25 181537.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "milenbun",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "app.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"ejs": "^3.1.10",
|
||||||
|
"express": "^4.21.0",
|
||||||
|
"jquery": "^3.7.1",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"path": "^0.12.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
/* Basic styling for the entire page */
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Great Vibes', cursive; /* Use Great Vibes font */
|
||||||
|
background-image: url('../images/background.gif');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
/* Apply blur effect */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Invitation card container with 3D perspective */
|
||||||
|
.invitation-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 650px;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 850px;
|
||||||
|
perspective: 900px;
|
||||||
|
/* Creates the 3D effect */
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
animation: pulse 5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card:hover {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inner container for the flip effect */
|
||||||
|
.card-inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.8s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card.flipped .card-inner {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for the front and back of the card */
|
||||||
|
.card-front,
|
||||||
|
.card-back {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
/* Adds a shadow for a better visual appearance */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
/* Ensures the image covers the entire card */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flip the back side of the card */
|
||||||
|
.card-back {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text on top of the card */
|
||||||
|
.card-text {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
color: rgba(249, 104, 14, 0.671);
|
||||||
|
-webkit-text-stroke-width: 1px;
|
||||||
|
-webkit-text-stroke-color: rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
display: inline-block; /* This makes the underline match the text width */
|
||||||
|
color: rgba(249, 104, 14, 0.671);
|
||||||
|
font-size: 40px;
|
||||||
|
font-style: italic;
|
||||||
|
-webkit-text-stroke-width: 1px;
|
||||||
|
-webkit-text-stroke-color: rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, p {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-text-align {
|
||||||
|
max-width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-top: 20%;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outer glow effect for text */
|
||||||
|
.card-text h1, .card-text p {
|
||||||
|
text-shadow: 0 0 10px rgba(170, 103, 2, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-text-align p {
|
||||||
|
text-shadow: 0 0 40px rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover hint styling */
|
||||||
|
.hover-hint {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20%;
|
||||||
|
left: 50%;
|
||||||
|
font-size: 20px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
color: white;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 20px 40px;
|
||||||
|
border-radius: 5px;
|
||||||
|
opacity: 0; /* Initially hidden */
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none; /* Avoid interaction when hidden */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover hint disappears on hover */
|
||||||
|
.invitation-card:hover .hover-hint {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Language switcher styles */
|
||||||
|
.language-switcher {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 0 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher a:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
/* Menu styles similar to language switcher */
|
||||||
|
.menu-btn {
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
left: 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu.open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: block;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li a:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
/* Mobile responsiveness */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
height: auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card {
|
||||||
|
max-width: 95vw;
|
||||||
|
max-height: 80vh;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
/* Basic styling for the entire page */
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Great Vibes', cursive; /* Use Great Vibes font */
|
||||||
|
background-image: url('../images/background.gif');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
/* Apply blur effect */
|
||||||
|
}
|
||||||
|
.invitation-card {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-hint {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
color: white;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
opacity: 0; /* Initially hidden */
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none; /* Avoid interaction when hidden */
|
||||||
|
animation: showHint 0.3s ease forwards; /* Add an animation */
|
||||||
|
animation-delay: 5s; /* Delay the appearance by 5 seconds */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes showHint {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1; /* Become visible */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card:hover .hover-hint {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: none; /* Instantly hide when hovered */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Invitation card container with 3D perspective */
|
||||||
|
.invitation-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 650px;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 850px;
|
||||||
|
perspective: 900px;
|
||||||
|
/* Creates the 3D effect */
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inner container for the flip effect */
|
||||||
|
.card-inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.8s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flip the card on hover */
|
||||||
|
.invitation-card:hover .card-inner {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for the front and back of the card */
|
||||||
|
.card-front,
|
||||||
|
.card-back {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
/* Adds a shadow for a better visual appearance */
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
/* Ensures the image covers the entire card */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flip the back side of the card */
|
||||||
|
.card-back {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text on top of the card */
|
||||||
|
.card-text {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
/* Slight overlay for better readability of text */
|
||||||
|
color: rgba(249, 104, 14, 0.671);
|
||||||
|
-webkit-text-stroke-width: 1px;
|
||||||
|
-webkit-text-stroke-color: rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
display: inline-block; /* This makes the underline match the text width */
|
||||||
|
/* Slight overlay for better readability of text */
|
||||||
|
color: rgba(249, 104, 14, 0.671);
|
||||||
|
font-size: 40px;
|
||||||
|
font-style: italic;
|
||||||
|
-webkit-text-stroke-width: 1px;
|
||||||
|
-webkit-text-stroke-color: rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
p {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-text-align {
|
||||||
|
max-width: 80%;
|
||||||
|
/* Set a max width */
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-top: 20%;
|
||||||
|
/* Center the container */
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outer glow effect for text */
|
||||||
|
.card-text h1,
|
||||||
|
.card-text p {
|
||||||
|
text-shadow: 0 0 10px rgba(170, 103, 2, 0.8)
|
||||||
|
}
|
||||||
|
.custom-text-align p {
|
||||||
|
text-shadow: 0 0 40px rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Language switcher styles */
|
||||||
|
.language-switcher {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 0 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher a:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu styles similar to language switcher */
|
||||||
|
.menu-btn {
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
left: 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu.open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: block;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li a:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Mobile responsiveness */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
height: auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card {
|
||||||
|
max-width: 95vw;
|
||||||
|
/* Adjust the card width for mobile devices */
|
||||||
|
max-height: 80vh;
|
||||||
|
/* Adjust the card height for mobile devices */
|
||||||
|
margin: 0 auto;
|
||||||
|
/* Center the card */
|
||||||
|
padding: 10px;
|
||||||
|
/* Add some padding around the card */
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Pulse animation to the invitation card */
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card {
|
||||||
|
animation: pulse 5s infinite;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card:hover {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
.countdown-card {
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown {
|
||||||
|
font-size: 5em; /* Larger font for visibility */
|
||||||
|
color: rgba(249, 104, 14, 0.671); /* Color for the countdown */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Basic styling for the entire page */
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-image: url('../images/background.gif');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
font-family: 'Great Vibes', cursive; /* Use Great Vibes font */
|
||||||
|
font-style: italic;
|
||||||
|
backdrop-filter: blur(4px); /* Apply blur effect */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: rgba(255, 255, 255, 0.8); /* Semi-transparent white background */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-top {
|
||||||
|
border-radius: 0; /* Remove rounded edges for the image */
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007bff; /* Bootstrap primary color */
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0056b3; /* Darker shade on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Language switcher styles */
|
||||||
|
.language-switcher {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 0 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher a:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
/* Menu styles similar to language switcher */
|
||||||
|
.menu-button {
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
left: 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu.open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: block;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li a:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-title {
|
||||||
|
font-size: xx-large;
|
||||||
|
color: rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Mobile responsiveness */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
height: auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card {
|
||||||
|
max-width: 95vw; /* Adjust the card width for mobile devices */
|
||||||
|
max-height: 80vh; /* Adjust the card height for mobile devices */
|
||||||
|
margin: 0 auto; /* Center the card */
|
||||||
|
padding: 10px; /* Add some padding around the card */
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/* Basic styling for the entire page */
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-image: url('../images/background.gif');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
font-family: 'Great Vibes', cursive; /* Use Great Vibes font */
|
||||||
|
font-style: italic;
|
||||||
|
backdrop-filter: blur(4px); /* Apply blur effect */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: rgba(255, 255, 255, 0.8); /* Semi-transparent white background */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-top {
|
||||||
|
border-radius: 0; /* Remove rounded edges for the image */
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007bff; /* Bootstrap primary color */
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0056b3; /* Darker shade on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Language switcher styles */
|
||||||
|
.language-switcher {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 0 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher a:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu styles similar to language switcher */
|
||||||
|
.menu-button {
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
left: 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu.open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: block;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retractable-menu ul li a:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollable messages section */
|
||||||
|
.scrollable-messages {
|
||||||
|
max-height: 400px; /* Set the maximum height */
|
||||||
|
overflow-y: auto; /* Enable vertical scroll */
|
||||||
|
padding: 10px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8); /* Same background for consistency */
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for each individual message */
|
||||||
|
.uploaded-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item {
|
||||||
|
margin-bottom: 15px; /* Space between messages */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile responsiveness */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
height: auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitation-card {
|
||||||
|
max-width: 95vw; /* Adjust the card width for mobile devices */
|
||||||
|
max-height: 80vh; /* Adjust the card height for mobile */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the modal image scales properly */
|
||||||
|
.modal-body img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 6.7 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 4.9 MiB |
@@ -0,0 +1,27 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Function to load the appropriate language file
|
||||||
|
function getLanguageFile(language) {
|
||||||
|
const filePath = path.join(__dirname, `../locales/card/${language}.json`);
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route for the card, with language support
|
||||||
|
router.get('/:lang', (req, res) => {
|
||||||
|
const lang = req.params.lang || 'en'; // Default to English
|
||||||
|
let languageData;
|
||||||
|
|
||||||
|
// Try to load the correct language file, fall back to English
|
||||||
|
try {
|
||||||
|
languageData = getLanguageFile(lang);
|
||||||
|
} catch (err) {
|
||||||
|
languageData = getLanguageFile('en');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render('card', { langData: languageData});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Function to load the appropriate language file
|
||||||
|
function getLanguageFile(language) {
|
||||||
|
const filePath = path.join(__dirname, `../locales/countdown/${language}.json`);
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route for the card, with language support
|
||||||
|
router.get('/:lang', (req, res) => {
|
||||||
|
const lang = req.params.lang || 'en'; // Default to English
|
||||||
|
let languageData;
|
||||||
|
|
||||||
|
// Try to load the correct language file, fall back to English
|
||||||
|
try {
|
||||||
|
languageData = getLanguageFile(lang);
|
||||||
|
} catch (err) {
|
||||||
|
languageData = getLanguageFile('en');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render('countdown', { langData: languageData});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const multer = require('multer');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
// Configure multer storage to create unique filenames
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: 'public/upload',
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
const originalName = path.parse(file.originalname);
|
||||||
|
const uniqueSuffix = Date.now() + '-' + crypto.randomBytes(4).toString('hex'); // Generate a unique suffix
|
||||||
|
const newFilename = `${originalName.name}-${uniqueSuffix}${originalName.ext}`; // Create new filename
|
||||||
|
cb(null, newFilename); // Save the file with the new name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload = multer({ storage }); // Use the custom storage configuration
|
||||||
|
const messagesFilePath = path.join(__dirname, '../messages.json');
|
||||||
|
|
||||||
|
// Function to load the appropriate language file
|
||||||
|
function getLanguageFile(language) {
|
||||||
|
const filePath = path.join(__dirname, `../locales/guestbook/${language}.json`);
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load previous messages from a JSON file
|
||||||
|
let messages = [];
|
||||||
|
if (fs.existsSync(messagesFilePath)) {
|
||||||
|
messages = JSON.parse(fs.readFileSync(messagesFilePath, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route for the guestbook, with language support
|
||||||
|
router.get('/:lang', (req, res) => {
|
||||||
|
const lang = req.params.lang || 'en'; // Default to English
|
||||||
|
let languageData;
|
||||||
|
|
||||||
|
// Try to load the correct language file, fall back to English
|
||||||
|
try {
|
||||||
|
languageData = getLanguageFile(lang);
|
||||||
|
} catch (err) {
|
||||||
|
languageData = getLanguageFile('en');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the guestbook with messages
|
||||||
|
res.render('guestbook', { langData: languageData, messages });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Endpoint to handle guestbook form submission
|
||||||
|
router.post('/new_comment', upload.single('file'), (req, res) => {
|
||||||
|
const lang = req.params.lang || 'en'; // Get the language from the URL
|
||||||
|
const comment = req.body.comment;
|
||||||
|
const name = req.body.name;
|
||||||
|
const imagePath = req.file ? path.join('/upload', req.file.filename) : null;
|
||||||
|
const originalFilename = req.file ? req.file.originalname : null; // Get the original filename
|
||||||
|
|
||||||
|
// Add the new message to the messages array
|
||||||
|
messages.push({ name, comment, imagePath, originalFilename });
|
||||||
|
|
||||||
|
// Save messages to a JSON file
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(messagesFilePath, JSON.stringify(messages, null, 2)); // Indent for readability
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error writing to messages.json:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect(`/guestbook/${lang}`); // Redirect to the guestbook page with language
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= langData.lang %>">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=1024">
|
||||||
|
<meta property="og:title" content="<%= langData.title %>" />
|
||||||
|
<meta property="og:description" content="<%= langData.description %>" />
|
||||||
|
<meta property="og:image" content="/previews/card.png" />
|
||||||
|
<meta property="og:url" content="/card/<%= langData.lang %>" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<title>
|
||||||
|
<%= langData.title %>
|
||||||
|
</title>
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/card.css">
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Language Switcher -->
|
||||||
|
<div class="language-switcher">
|
||||||
|
<a href="/card/en">English</a> |
|
||||||
|
<a href="/card/de">Deutsch</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Button and Menu -->
|
||||||
|
<header>
|
||||||
|
<button class="menu-btn" id="menu-btn" onclick="toggleMenu()">Menu</button>
|
||||||
|
<nav id="menu" class="retractable-menu">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/card/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_card %>
|
||||||
|
</a></li>
|
||||||
|
|
||||||
|
<li><a href="/guestbook/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_guestbook %>
|
||||||
|
</a></li>
|
||||||
|
|
||||||
|
|
||||||
|
<li><a href="/countdown/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_countdown %>
|
||||||
|
</a></li>
|
||||||
|
|
||||||
|
<!-- Add more menu items as needed -->
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleMenu() {
|
||||||
|
const menu = document.getElementById('menu');
|
||||||
|
menu.classList.toggle('open');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="hover-hint"> <%= langData.hover_hint %> </div>
|
||||||
|
<div class="invitation-card">
|
||||||
|
<div class="card-inner">
|
||||||
|
<!-- Front of the card -->
|
||||||
|
<div class="card-front">
|
||||||
|
<img src="/images/card_front.png" alt="Front of Invitation" class="card-image">
|
||||||
|
<div class="title-text">
|
||||||
|
<h1>
|
||||||
|
<u style="text-decoration-color: rgba(249, 104, 14, 0.671); text-decoration-thickness: 2px">Save the Date</u>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Back of the card -->
|
||||||
|
<div class="card-back">
|
||||||
|
<img src="/images/card_back.png" alt="Back of Invitation" class="card-image">
|
||||||
|
<div class="card-text custom-text-align">
|
||||||
|
<p>
|
||||||
|
<%= langData.front_invation_1 %>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<%= langData.front_invation_2 %>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<%= langData.front_invation_3 %>
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<h2>
|
||||||
|
<%= langData.back_info %>
|
||||||
|
</h2>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
<%= langData.back_style %>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<%= langData.back_rules %>
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
<%= langData.back_afterword %>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<%= langData.back_signed %>
|
||||||
|
</p>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// <h2>
|
||||||
|
// <p>
|
||||||
|
// <%= langData.back_countdown %>
|
||||||
|
// <div id="countdown"></div>
|
||||||
|
// </p>
|
||||||
|
// </h2>
|
||||||
|
// // Convert the Unix timestamp (seconds) to milliseconds
|
||||||
|
// const targetDate = new Date(1727528400 * 1000);
|
||||||
|
|
||||||
|
// function updateCountdown() {
|
||||||
|
// const now = new Date();
|
||||||
|
// const timeDifference = targetDate - now;
|
||||||
|
|
||||||
|
// if (timeDifference <= 0) {
|
||||||
|
// document.getElementById('countdown').innerHTML = 'Time\'s up!';
|
||||||
|
// clearInterval(countdownInterval); // Stop the interval when the countdown reaches 0
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const days = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
|
||||||
|
// const hours = Math.floor((timeDifference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||||
|
// const minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
// const seconds = Math.floor((timeDifference % (1000 * 60)) / 1000);
|
||||||
|
|
||||||
|
// document.getElementById('countdown').innerHTML =
|
||||||
|
// days + "d " + hours + "h " + minutes + "m " + seconds + "s ";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Update the countdown every second
|
||||||
|
// const countdownInterval = setInterval(updateCountdown, 1000);
|
||||||
|
// updateCountdown(); // Initial call to set the countdown immediately
|
||||||
|
|
||||||
|
document.querySelector('.invitation-card').addEventListener('mouseenter', () => {
|
||||||
|
var duration = 1 * 1000;
|
||||||
|
var animationEnd = Date.now() + duration;
|
||||||
|
var skew = 1;
|
||||||
|
|
||||||
|
function randomInRange(min, max) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
(function frame() {
|
||||||
|
var timeLeft = animationEnd - Date.now();
|
||||||
|
var ticks = Math.max(200, 500 * (timeLeft / duration));
|
||||||
|
skew = Math.max(0.8, skew - 0.001);
|
||||||
|
|
||||||
|
confetti({
|
||||||
|
particleCount: 1,
|
||||||
|
startVelocity: 0,
|
||||||
|
ticks: ticks,
|
||||||
|
origin: {
|
||||||
|
x: Math.random(),
|
||||||
|
// since particles fall down, skew start toward the top
|
||||||
|
y: (Math.random() * skew) - 0.2
|
||||||
|
},
|
||||||
|
colors: ['#da7ff0'],
|
||||||
|
shapes: ['circle'],
|
||||||
|
gravity: randomInRange(0.4, 0.6),
|
||||||
|
scalar: randomInRange(0.4, 2),
|
||||||
|
drift: randomInRange(-0.4, 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (timeLeft > 0) {
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const card = document.querySelector('.invitation-card');
|
||||||
|
const hoverHint = document.querySelector('.hover-hint');
|
||||||
|
|
||||||
|
let hoverTimeout;
|
||||||
|
|
||||||
|
function showHint() {
|
||||||
|
hoverHint.style.opacity = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideHint() {
|
||||||
|
hoverHint.style.opacity = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the hover hint after 5 seconds
|
||||||
|
hoverTimeout = setTimeout(showHint, 5000);
|
||||||
|
|
||||||
|
// On hover, flip the card and hide the hover hint
|
||||||
|
card.addEventListener('mouseenter', function() {
|
||||||
|
hideHint();
|
||||||
|
clearTimeout(hoverTimeout);
|
||||||
|
card.classList.add('flipped');
|
||||||
|
});
|
||||||
|
|
||||||
|
// On mouse leave, flip the card back and reset hint timer
|
||||||
|
card.addEventListener('mouseleave', function() {
|
||||||
|
card.classList.remove('flipped');
|
||||||
|
hoverTimeout = setTimeout(showHint, 5000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= langData.lang %>">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=1024">
|
||||||
|
<meta property="og:title" content="<%= langData.title %>" />
|
||||||
|
<meta property="og:description" content="<%= langData.description %>" />
|
||||||
|
<meta property="og:image" content="/previews/countdown.png" />
|
||||||
|
<meta property="og:url" content="/countdown/<%= langData.lang %>" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<title>
|
||||||
|
<%= langData.title %>
|
||||||
|
</title>
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/countdown.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Language Switcher -->
|
||||||
|
<div class="language-switcher">
|
||||||
|
<a href="/countdown/en">English</a> |
|
||||||
|
<a href="/countdown/de">Deutsch</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Button and Menu -->
|
||||||
|
<header>
|
||||||
|
<button class="menu-button" id="menu-button" onclick="toggleMenu()">Menu</button>
|
||||||
|
<nav id="menu" class="retractable-menu">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/card/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_card %>
|
||||||
|
</a></li>
|
||||||
|
<li><a href="/guestbook/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_guestbook %>
|
||||||
|
</a></li>
|
||||||
|
|
||||||
|
<li><a href="/countdown/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_countdown %>
|
||||||
|
</a></li>
|
||||||
|
<!-- Add more menu items as needed -->
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleMenu() {
|
||||||
|
const menu = document.getElementById('menu');
|
||||||
|
menu.classList.toggle('open');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="countdown-card">
|
||||||
|
<p class="countdown-title"></p>
|
||||||
|
<h2 class="countdown">
|
||||||
|
<u style="color: rgba(0, 0, 0, 0.5); text-decoration-color: rgba(0, 0, 0, 0.5); text-decoration-thickness: 2px"><%= langData.countdown_text %></u>
|
||||||
|
<p>
|
||||||
|
<div id="countdown"></div>
|
||||||
|
</p>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||||
|
<script>
|
||||||
|
//Convert the Unix timestamp (seconds) to milliseconds
|
||||||
|
const targetDate = new Date(1727528400 * 1000);
|
||||||
|
const finishMessage = "<%= langData.countdown_finished %>";
|
||||||
|
function updateCountdown() {
|
||||||
|
const now = new Date();
|
||||||
|
const timeDifference = targetDate - now;
|
||||||
|
|
||||||
|
if (timeDifference <= 0) {
|
||||||
|
document.getElementById('countdown').innerHTML = finishMessage;
|
||||||
|
clearInterval(countdownInterval); //Stop the interval when the countdown reaches 0
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const days = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
|
||||||
|
const hours = Math.floor((timeDifference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
const seconds = Math.floor((timeDifference % (1000 * 60)) / 1000);
|
||||||
|
|
||||||
|
document.getElementById('countdown').innerHTML =
|
||||||
|
days + "d " + hours + "h " + minutes + "m " + seconds + "s ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the countdown every second
|
||||||
|
const countdownInterval = setInterval(updateCountdown, 1000);
|
||||||
|
updateCountdown(); //Initial call to set the countdown immediately
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= langData.lang %>">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>
|
||||||
|
<%= langData.title %>
|
||||||
|
</title>
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/guestbook.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Language Switcher -->
|
||||||
|
<div class="language-switcher">
|
||||||
|
<a href="/guestbook/en">English</a> |
|
||||||
|
<a href="/guestbook/de">Deutsch</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Button and Menu -->
|
||||||
|
<header>
|
||||||
|
<button class="menu-button" id="menu-button" onclick="toggleMenu()">Menu</button>
|
||||||
|
<nav id="menu" class="retractable-menu">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/card/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_card %>
|
||||||
|
</a></li>
|
||||||
|
<li><a href="/guestbook/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_guestbook %>
|
||||||
|
</a></li>
|
||||||
|
<li><a href="/countdown/<%= langData.lang %>">
|
||||||
|
<%= langData.menu_countdown %>
|
||||||
|
</a></li>
|
||||||
|
<!-- Add more menu items as needed -->
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleMenu() {
|
||||||
|
const menu = document.getElementById('menu');
|
||||||
|
menu.classList.toggle('open');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h1 class="display-4 text-white">
|
||||||
|
<%= langData.guestbook_welcome %>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<form action="/guestbook/new_comment" method="POST" enctype="multipart/form-data">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">
|
||||||
|
<%= langData.guestbook_name %>
|
||||||
|
</label>
|
||||||
|
<input type="text" name="name" id="name" class="form-control" required
|
||||||
|
placeholder="<%= langData.guestbook_name_placeholder %>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="comment">
|
||||||
|
<%= langData.guestbook_comment %>
|
||||||
|
</label>
|
||||||
|
<textarea name="comment" id="comment" class="form-control" required
|
||||||
|
placeholder="<%= langData.guestbook_comment_placeholder %>"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="file">
|
||||||
|
<%= langData.guestbook_upload %>
|
||||||
|
</label>
|
||||||
|
<input type="file" name="file" id="file" class="form-control p-1">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<%= langData.guestbook_submit %>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scrollable Previous Messages Section -->
|
||||||
|
<div class="previous-messages">
|
||||||
|
<h2>
|
||||||
|
<%= langData.previous_messages %>
|
||||||
|
</h2>
|
||||||
|
<!-- Modal Trigger in your previous-messages section -->
|
||||||
|
<div class="list-group scrollable-messages">
|
||||||
|
<% messages.forEach(message=> { %>
|
||||||
|
<div class="list-group-item">
|
||||||
|
<h5 class="mb-1">
|
||||||
|
<%= message.name %>
|
||||||
|
<%= langData.messages_wrote %>
|
||||||
|
</h5>
|
||||||
|
<p class="mb-1">
|
||||||
|
<%= message.comment %>
|
||||||
|
</p>
|
||||||
|
<% if (message.imagePath) { %>
|
||||||
|
<!-- Modal Trigger: Click on the image to open the modal -->
|
||||||
|
<img src="<%= message.imagePath %>" class="img-fluid uploaded-image"
|
||||||
|
alt="User Uploaded Image" data-toggle="modal" data-target="#imageModal"
|
||||||
|
data-image="<%= message.imagePath %>">
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<% }); %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Structure -->
|
||||||
|
<div class="modal fade" id="imageModal" tabindex="-1" aria-labelledby="imageModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="imageModalLabel">Image Preview</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<img src="" id="modalImage" class="img-fluid" alt="Selected Image">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- jQuery and Bootstrap JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$('#imageModal').on('show.bs.modal', function (event) {
|
||||||
|
var button = $(event.relatedTarget); // Button that triggered the modal
|
||||||
|
var imageUrl = button.data('image'); // Extract image URL from data-image attribute
|
||||||
|
var modal = $(this);
|
||||||
|
modal.find('.modal-body #modalImage').attr('src', imageUrl); // Update the image in the modal
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||