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:
Concept Agent
2026-05-29 18:56:56 +02:00
parent 88ed4e6bda
commit 81feccdc1a
61 changed files with 2460 additions and 5747 deletions
+37 -3
View File
@@ -1,5 +1,5 @@
/* ============================================================
main.js Scroll animations, counters, FAQ, BA slider
main.js: Scroll animations, counters, FAQ, BA slider
============================================================ */
(function () {
@@ -128,7 +128,7 @@
const video = document.querySelector('.hero-video-wrap video');
if (!video) return;
video.play().catch(() => {
// autoplay blocked poster image is visible; nothing to do
// autoplay blocked: poster image is visible; nothing to do
});
}
@@ -145,8 +145,42 @@
track.setAttribute('tabindex', '0');
}
/* --- Boot ---------------------------------------------- */
/* --- Header scroll + mobile nav ------------------------- */
function initNav() {
var header = document.getElementById('site-header');
var mobileNav = document.getElementById('mobileNav');
var menuBtn = document.querySelector('.header-menu-btn');
var closeBtn = document.getElementById('mobileNavClose');
var overlay = document.getElementById('mobileNavOverlay');
if (!header) return;
window.addEventListener('scroll', function () {
header.classList.toggle('scrolled', window.scrollY > 40);
}, { passive: true });
function openNav() {
mobileNav.classList.add('open');
mobileNav.setAttribute('aria-hidden', 'false');
if (menuBtn) menuBtn.setAttribute('aria-expanded', 'true');
document.body.style.overflow = 'hidden';
}
function closeNav() {
mobileNav.classList.remove('open');
mobileNav.setAttribute('aria-hidden', 'true');
if (menuBtn) menuBtn.setAttribute('aria-expanded', 'false');
document.body.style.overflow = '';
}
if (menuBtn) menuBtn.addEventListener('click', openNav);
if (closeBtn) closeBtn.addEventListener('click', closeNav);
if (overlay) overlay.addEventListener('click', closeNav);
}
/* --- Boot: initialize all modules ------------------------ */
function boot() {
initNav();
initScrollAnimations();
initCounters();
initFAQ();