Migrate to Stack A: PHP-fpm + nginx + supervisord, drop flat HTML + Python API
- Remove old flat HTML pages (index, about, blog, contact, reviews, services/*, locations/*) - Remove Python/Flask API container (api/) - Remove old root nginx.conf and components/ - Add infra/: full nginx.conf (http block at /etc/nginx/nginx.conf), php-fpm-pool.conf (TCP listen), supervisord.conf, entrypoint.sh (auto-generates ALTCHA_HMAC_KEY) - Add src/: PHP router, page/service/location/blog templates, contact handler, altcha handler, promo endpoint, SQLite data files - Rewrite Dockerfile: single container, tini PID 1, healthcheck, all env vars declared - Update docker-compose.yml: port 8096, env_file, healthcheck - Update .dockerignore: exclude .env.*, include robots.txt/sitemap.xml/404.html/500.html - Update assets: tokens.css, promo-popup.css/js, altcha.min.js, refactored form.js/main.js Verified: all 17 routes 200, protection audit PASS, Resend confirmed working Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
(function () {
|
||||
var POPUP_KEY = 'flooritPromo2026';
|
||||
var TOPBAR_KEY = 'flooritTopbar2026';
|
||||
var DELAY_MS = 5000;
|
||||
var EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
function isStored(key) {
|
||||
try {
|
||||
var val = localStorage.getItem(key);
|
||||
return val && Date.now() < parseInt(val, 10);
|
||||
} catch (e) { return false; }
|
||||
}
|
||||
|
||||
function store(key) {
|
||||
try { localStorage.setItem(key, String(Date.now() + EXPIRY_MS)); } catch (e) {}
|
||||
}
|
||||
|
||||
/* --- Topbar -------------------------------------------- */
|
||||
function showTopbar() {
|
||||
var bar = document.getElementById('promo-topbar');
|
||||
if (!bar) return;
|
||||
bar.classList.add('visible');
|
||||
document.body.classList.add('has-topbar');
|
||||
}
|
||||
|
||||
function hideTopbar() {
|
||||
var bar = document.getElementById('promo-topbar');
|
||||
if (!bar) return;
|
||||
bar.classList.remove('visible');
|
||||
document.body.classList.remove('has-topbar');
|
||||
store(TOPBAR_KEY);
|
||||
}
|
||||
|
||||
function initTopbar() {
|
||||
if (isStored(TOPBAR_KEY) || isStored(POPUP_KEY)) return;
|
||||
showTopbar();
|
||||
var closeBtn = document.getElementById('promo-topbar-close');
|
||||
var offerBtn = document.getElementById('promo-topbar-btn');
|
||||
if (closeBtn) closeBtn.addEventListener('click', hideTopbar);
|
||||
if (offerBtn) offerBtn.addEventListener('click', function () {
|
||||
hideTopbar();
|
||||
openPopup();
|
||||
});
|
||||
}
|
||||
|
||||
/* --- Popup --------------------------------------------- */
|
||||
function openPopup() {
|
||||
var overlay = document.getElementById('promo-overlay');
|
||||
if (!overlay) return;
|
||||
overlay.style.display = 'flex';
|
||||
requestAnimationFrame(function () {
|
||||
requestAnimationFrame(function () { overlay.classList.add('visible'); });
|
||||
});
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
var overlay = document.getElementById('promo-overlay');
|
||||
if (overlay) {
|
||||
overlay.classList.remove('visible');
|
||||
setTimeout(function () { overlay.style.display = 'none'; }, 350);
|
||||
}
|
||||
store(POPUP_KEY);
|
||||
hideTopbar();
|
||||
}
|
||||
|
||||
function initPopup() {
|
||||
if (isStored(POPUP_KEY)) return;
|
||||
var closeBtn = document.getElementById('promo-close');
|
||||
var overlay = document.getElementById('promo-overlay');
|
||||
var form = document.getElementById('promo-form');
|
||||
var submit = document.getElementById('promo-submit');
|
||||
var errEl = document.getElementById('promo-error');
|
||||
var success = document.getElementById('promo-success');
|
||||
if (!overlay || !form) return;
|
||||
|
||||
if (closeBtn) closeBtn.addEventListener('click', closePopup);
|
||||
overlay.addEventListener('click', function (e) {
|
||||
if (e.target === overlay) closePopup();
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
errEl.style.display = 'none';
|
||||
submit.disabled = true;
|
||||
submit.textContent = 'Sending...';
|
||||
var data = new FormData(form);
|
||||
fetch('/promo/', { method: 'POST', body: data })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (res.ok) {
|
||||
form.style.display = 'none';
|
||||
success.style.display = 'block';
|
||||
store(POPUP_KEY);
|
||||
hideTopbar();
|
||||
} else {
|
||||
errEl.textContent = res.error || 'Something went wrong.';
|
||||
errEl.style.display = 'block';
|
||||
submit.disabled = false;
|
||||
submit.textContent = 'Claim My Discount';
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
errEl.textContent = 'Network error. Please try again.';
|
||||
errEl.style.display = 'block';
|
||||
submit.disabled = false;
|
||||
submit.textContent = 'Claim My Discount';
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(openPopup, DELAY_MS);
|
||||
}
|
||||
|
||||
function init() {
|
||||
initTopbar();
|
||||
initPopup();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user