Refactor: Convert to Single Page Application (SPA) and optimize assets
- Transformed the website into a Single Page Application (SPA) to provide instant, reload-free transitions. - Implemented dynamic UI updates in JavaScript, including title and favicon changes based on user interaction. - Enabled Gzip compression and browser caching in nginx.conf for significantly faster load times. - Optimized images by converting heavy GIFs to animated WebP format, reducing asset sizes by ~30%. - Streamlined project structure by removing redundant HTML files and implementing event delegation in script.js.
This commit is contained in:
@@ -6,6 +6,10 @@ http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Gzip compression for faster transfers
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
@@ -18,13 +22,18 @@ http {
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
# Proxy for the webhook to hide the secret
|
||||
# Browser caching for static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|webp)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
|
||||
# Proxy for the webhook
|
||||
location /api/webhook {
|
||||
proxy_pass https://n8n.mohandl3g.ly/webhook/cute;
|
||||
proxy_set_header MohandL3G-Auth "Ms@199903";
|
||||
proxy_set_header Content-Type "application/json";
|
||||
|
||||
# Basic proxy settings
|
||||
proxy_ssl_server_name on;
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<script src="./style/scripts/script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="container" id="mainContainer">
|
||||
<div class="text_box">
|
||||
<h1 class="header_text">
|
||||
شهد، اني معجب بيك ونبيك بالحلال، تقبلي بيا؟ 😊
|
||||
|
||||
33
html/no.html
33
html/no.html
@@ -1,33 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self';">
|
||||
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
||||
<title>رفض من شهد</title>
|
||||
<link rel="icon" type="image/png" href="./style/images/broken.png" />
|
||||
<link rel="stylesheet" href="./style/css/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Top text box with centered heading -->
|
||||
<div class="text_box">
|
||||
<h1 class="header_text centered_text">
|
||||
كلاااااا... 😔
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Centered gif -->
|
||||
<div class="gif_container">
|
||||
<img src="./style/images/sad-cat.gif" alt="Sad animated illustration" />
|
||||
</div>
|
||||
|
||||
<!-- Bottom text box -->
|
||||
<div class="text_box">
|
||||
<p class="text centered_text">
|
||||
لن استسلم وسوف تكونين لي 🌸
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
BIN
html/style/images/cute-cat.webp
Normal file
BIN
html/style/images/cute-cat.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
BIN
html/style/images/sad-cat.webp
Normal file
BIN
html/style/images/sad-cat.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -1,47 +1,66 @@
|
||||
const yesBtn = document.getElementById("yesButton");
|
||||
const noBtn = document.getElementById("noButton");
|
||||
const mainContainer = document.getElementById("mainContainer");
|
||||
|
||||
// The local endpoint handled by Nginx proxy
|
||||
// Your n8n webhook URL (handled by Nginx)
|
||||
const webhookUrl = "/api/webhook";
|
||||
|
||||
const SAFE_REDIRECTS = {
|
||||
Yes: "yes.html",
|
||||
No: "no.html",
|
||||
const contentMap = {
|
||||
Yes: {
|
||||
title: "هييييييي، شكرن بالنون! 🌹",
|
||||
image: "./style/images/cute-cat.webp",
|
||||
subtext: "هذا اليوم سيبقى في قلبي للأبد!",
|
||||
icon: "./style/images/red-heart.png"
|
||||
},
|
||||
No: {
|
||||
title: "كلاااااا... 😔",
|
||||
image: "./style/images/sad-cat.webp",
|
||||
subtext: "لن استسلم وسوف تكونين لي 🌸",
|
||||
icon: "./style/images/broken.png"
|
||||
}
|
||||
};
|
||||
|
||||
function sendAnswer(answer) {
|
||||
const redirectTo = SAFE_REDIRECTS[answer] || "index.html";
|
||||
function updateUI(answer) {
|
||||
const data = contentMap[answer];
|
||||
|
||||
// Update Favicon
|
||||
const link = document.querySelector("link[rel~='icon']");
|
||||
if (link) link.href = data.icon;
|
||||
|
||||
// Update Title
|
||||
document.title = answer === "Yes" ? "الموافقة من شهد" : "رفض من شهد";
|
||||
|
||||
// Re-build UI for SPA
|
||||
mainContainer.innerHTML = `
|
||||
<div class="text_box">
|
||||
<h1 class="header_text centered_text">${data.title}</h1>
|
||||
</div>
|
||||
<div class="gif_container">
|
||||
<img src="${data.image}" alt="Result illustration" />
|
||||
</div>
|
||||
<div class="text_box">
|
||||
<p class="text centered_text">${data.subtext}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function sendAnswer(answer) {
|
||||
fetch(webhookUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
// Secret is now handled by Nginx proxy
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
response: answer,
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
})
|
||||
.then(() => {
|
||||
// Success: Redirect to the page
|
||||
window.location.href = redirectTo;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Webhook failed:", error);
|
||||
// Even if the webhook fails, we still redirect the user so they don't get stuck
|
||||
window.location.href = redirectTo;
|
||||
});
|
||||
}
|
||||
|
||||
if (yesBtn) {
|
||||
yesBtn.addEventListener("click", () => {
|
||||
sendAnswer("Yes");
|
||||
.finally(() => {
|
||||
// Transition UI even if request fails
|
||||
updateUI(answer);
|
||||
});
|
||||
}
|
||||
|
||||
if (noBtn) {
|
||||
noBtn.addEventListener("click", () => {
|
||||
sendAnswer("No");
|
||||
});
|
||||
}
|
||||
// Delegate events to parent since initial buttons will be replaced
|
||||
mainContainer.addEventListener("click", (e) => {
|
||||
if (e.target.id === "yesButton") sendAnswer("Yes");
|
||||
if (e.target.id === "noButton") sendAnswer("No");
|
||||
});
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self';">
|
||||
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
||||
<title>الموافقة من شهد</title>
|
||||
<link rel="icon" type="image/png" href="./style/images/red-heart.png" />
|
||||
<link rel="stylesheet" href="./style/css/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Top text box -->
|
||||
<div class="text_box">
|
||||
<h1 class="header_text">هييييييي، شكرن بالنون! 🌹</h1>
|
||||
</div>
|
||||
|
||||
<!-- Centered gif -->
|
||||
<div class="gif_container">
|
||||
<img
|
||||
src="./style/images/cute-cat.gif"
|
||||
alt="Cute animated illustration"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Bottom text box with centered paragraph -->
|
||||
<div class="text_box">
|
||||
<p class="text centered_text">
|
||||
هذا اليوم سيبقى في قلبي للأبد!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user