From b93de83519a1d0f13bf7930a4ab6781f4d9485be Mon Sep 17 00:00:00 2001 From: cclohmar Date: Thu, 25 Jun 2026 14:12:46 +0000 Subject: [PATCH] =?UTF-8?q?bare-site=20v1.0=20=E2=80=94=20zero-dependency?= =?UTF-8?q?=20PHP=20micro-framework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 7 languages, 3 pages (What/How/Start), 80-line router, 98KB total. Features: - No database, no Composer, no build step — just files on disk - nginx PHP isolation: only index.php executes - Bot detection with browser heuristic bypass - Rate limiting, structured weekly logs, geo-location, Telegram alerts - Multi-language: Accept-Language auto-detect, nav dropdown - Article sub-page routing via /start/{slug} - Apache + Caddy config alternatives documented in Start page - Sample config — no real keys or secrets --- .gitignore | 22 + html/app/controler/request.php | 16 + html/app/controler/start.php | 63 +++ html/app/model/menu.php | 19 + html/app/view/_dev.php | 11 + html/app/view/_error.phtml | 20 + html/app/view/_footer.phtml | 46 ++ html/app/view/_header.phtml | 27 + html/cdn/css/default.css | 867 +++++++++++++++++++++++++++++++++ html/cdn/css/mobile.css | 235 +++++++++ html/class/config.php | 89 ++++ html/class/functions.php | 332 +++++++++++++ html/class/site.php | 15 + html/index.php | 19 + html/lng/de/about.phtml | 1 + html/lng/de/home.phtml | 1 + html/lng/de/menu.php | 13 + html/lng/de/start.phtml | 5 + html/lng/en/about.phtml | 31 ++ html/lng/en/home.phtml | 25 + html/lng/en/menu.php | 26 + html/lng/en/start.phtml | 85 ++++ html/lng/es/about.phtml | 1 + html/lng/es/home.phtml | 1 + html/lng/es/menu.php | 13 + html/lng/es/start.phtml | 5 + html/lng/fr/about.phtml | 1 + html/lng/fr/home.phtml | 1 + html/lng/fr/menu.php | 13 + html/lng/fr/start.phtml | 5 + html/lng/it/about.phtml | 1 + html/lng/it/home.phtml | 1 + html/lng/it/menu.php | 13 + html/lng/it/start.phtml | 5 + html/lng/nl/about.phtml | 1 + html/lng/nl/home.phtml | 1 + html/lng/nl/menu.php | 13 + html/lng/nl/start.phtml | 5 + html/lng/pt/about.phtml | 1 + html/lng/pt/home.phtml | 1 + html/lng/pt/menu.php | 13 + html/lng/pt/start.phtml | 5 + nginx/default.conf | 62 +++ nginx/security.conf | 12 + 44 files changed, 2142 insertions(+) create mode 100644 .gitignore create mode 100644 html/app/controler/request.php create mode 100644 html/app/controler/start.php create mode 100644 html/app/model/menu.php create mode 100644 html/app/view/_dev.php create mode 100644 html/app/view/_error.phtml create mode 100644 html/app/view/_footer.phtml create mode 100644 html/app/view/_header.phtml create mode 100644 html/cdn/css/default.css create mode 100644 html/cdn/css/mobile.css create mode 100644 html/class/config.php create mode 100644 html/class/functions.php create mode 100644 html/class/site.php create mode 100644 html/index.php create mode 100644 html/lng/de/about.phtml create mode 100644 html/lng/de/home.phtml create mode 100644 html/lng/de/menu.php create mode 100644 html/lng/de/start.phtml create mode 100644 html/lng/en/about.phtml create mode 100644 html/lng/en/home.phtml create mode 100644 html/lng/en/menu.php create mode 100644 html/lng/en/start.phtml create mode 100644 html/lng/es/about.phtml create mode 100644 html/lng/es/home.phtml create mode 100644 html/lng/es/menu.php create mode 100644 html/lng/es/start.phtml create mode 100644 html/lng/fr/about.phtml create mode 100644 html/lng/fr/home.phtml create mode 100644 html/lng/fr/menu.php create mode 100644 html/lng/fr/start.phtml create mode 100644 html/lng/it/about.phtml create mode 100644 html/lng/it/home.phtml create mode 100644 html/lng/it/menu.php create mode 100644 html/lng/it/start.phtml create mode 100644 html/lng/nl/about.phtml create mode 100644 html/lng/nl/home.phtml create mode 100644 html/lng/nl/menu.php create mode 100644 html/lng/nl/start.phtml create mode 100644 html/lng/pt/about.phtml create mode 100644 html/lng/pt/home.phtml create mode 100644 html/lng/pt/menu.php create mode 100644 html/lng/pt/start.phtml create mode 100755 nginx/default.conf create mode 100755 nginx/security.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c4faaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Logs +logs/*.log +logs/*.csv + +# Session files +.sessions/ + +# Environment +.env + +# OS files +.DS_Store +**/.DS_Store +Thumbs.db + +# Backups +*.backup +*.bak + +# IDE +.vscode/ +.idea/ diff --git a/html/app/controler/request.php b/html/app/controler/request.php new file mode 100644 index 0000000..756f2b4 --- /dev/null +++ b/html/app/controler/request.php @@ -0,0 +1,16 @@ +

Development

'; + include(__ROOT__.'/app/view/_dev.php'); + + } elseif ($slug === 'clear') { + $_SESSION = array(); + header('Location: '.__URL__); + exit; + + } elseif ($slug === 'set') { + $supported = array_keys(LANGUAGES); + if (!empty($PARAMS['2']) && in_array($PARAMS['2'], $supported, true)) { + $_SESSION['SITE']['language'] = $PARAMS['2']; + header('Location: '.__URL__.$_SESSION['SITE']['slug']); + } else { + $_SESSION = array(); + header('Location: '.__URL__); + } + exit; + + } else { + header('Location: '.__URL__.'error/404'); + exit; + } + +} else { + $_SESSION['SITE']['slug'] = PAGE_00_SLUG; + include(__ROOT__.'/app/view/_header.phtml'); + include(__ROOT__.'/lng/'.$_SESSION['SITE']['language'].'/'.$_SESSION['SITE']['slug'].'.phtml'); + include(__ROOT__.'/app/view/_footer.phtml'); +} diff --git a/html/app/model/menu.php b/html/app/model/menu.php new file mode 100644 index 0000000..e806e9c --- /dev/null +++ b/html/app/model/menu.php @@ -0,0 +1,19 @@ +'.PAGE_00_TXT.'';} +if (!empty(PAGE_01_TXT)) {$menue .= ''.PAGE_01_TXT.'';} +if (!empty(PAGE_02_TXT)) {$menue .= ''.PAGE_02_TXT.'';} +if (!empty(PAGE_03_TXT)) {$menue .= ''.PAGE_03_TXT.'';} +if (!empty(PAGE_04_TXT)) {$menue .= ''.PAGE_04_TXT.'';} +if (!empty(PAGE_05_TXT)) {$menue .= ''.PAGE_05_TXT.'';} + +$menue .= ''; diff --git a/html/app/view/_dev.php b/html/app/view/_dev.php new file mode 100644 index 0000000..c16be70 --- /dev/null +++ b/html/app/view/_dev.php @@ -0,0 +1,11 @@ +'; +print_r($_SESSION['VISITOR']); +echo '
'; +echo "

All Headers

"; +foreach ($_SERVER as $key => $value) { + echo "$key: " . htmlspecialchars($value) . "
"; +} +echo '
'; +echo "workbench"; diff --git a/html/app/view/_error.phtml b/html/app/view/_error.phtml new file mode 100644 index 0000000..0016e83 --- /dev/null +++ b/html/app/view/_error.phtml @@ -0,0 +1,20 @@ + + + + + +
+ + + + + + diff --git a/html/app/view/_header.phtml b/html/app/view/_header.phtml new file mode 100644 index 0000000..0ad6dac --- /dev/null +++ b/html/app/view/_header.phtml @@ -0,0 +1,27 @@ + + + + + + + + + <?php echo SITE_NAME; ?> + + + + + + +
diff --git a/html/cdn/css/default.css b/html/cdn/css/default.css new file mode 100644 index 0000000..b908559 --- /dev/null +++ b/html/cdn/css/default.css @@ -0,0 +1,867 @@ +@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;500;600;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); + +/* ===== BASE STYLES ===== */ +html, body { + height: 100%; + font-family: 'Lato', 'Helvetica Neue', Arial, sans-serif; + color: #333; + background-color: #F5F5F5; + margin: 0; + padding: 0; +} + +.content-wrapper { + /* Add min-height to ensure it spans the content */ + min-height: calc(100vh - 70px - 150px); /* Adjust 150px to match your footer height */ +} + +/* ===== NAVIGATION ===== */ +.navbar { + background-color: #08755B; + border-bottom: none; + padding: 0.5rem 1rem; + margin: 0; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + width: 100%; + position: sticky; + top: 0; + z-index: 1000; +} + +.nav-container { + display: flex; + justify-content: flex-start; + align-items: center; + width: 100%; + max-width: 1200px; + margin: 0 auto; + gap: 1rem; +} + +.nav-logo-title { + display: flex; + align-items: center; + gap: 1rem; +} + +.nav-logo img { + height: 60px; + width: auto; + transition: transform 0.3s ease; +} + +.nav-logo img:hover { + transform: scale(1.05); +} + +.nav-title { + color: white; + font-size: 1.5rem; + font-weight: 600; + margin: 0; + white-space: nowrap; +} + +.nav-menu { + display: flex; + align-items: center; + gap: 1.5rem; + margin-left: auto; +} + +.nav-menu a { + color: white; + text-decoration: none; + font-size: 1.1rem; + font-weight: 500; + padding: 0.5rem 1rem; + border-radius: 4px; + transition: all 0.3s ease; + position: relative; +} + +.nav-menu a:hover { + background-color: rgba(255, 255, 255, 0.2); + transform: translateY(-2px); +} + +.nav-menu a.active { + background-color: rgba(255, 255, 255, 0.3); +} + +.hamburger { + display: none; + flex-direction: column; + cursor: pointer; + padding: 0.5rem; +} + +.bar { + width: 25px; + height: 3px; + background-color: #FFF; + margin: 3px 0; + transition: 0.3s; + border-radius: 2px; +} + +/* Menu container */ +.dropdown { + position: relative; + display: inline-block; +} + +.dropbtn { + background-color: #08755B; + color: white; + padding: 16px; + font-size: 16px; + border: none; + cursor: pointer; +} + +.dropdown-content { + display: none; + position: absolute; + background-color: #f9f9f9; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +.dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} + +.dropdown-content a:hover { + background-color: #f1f1f1 +} + +/* ===== HERO SECTION ===== */ +.hero-banner { + position: relative; + width: 100%; + background-size: cover; + background-position: center; + margin-bottom: 2rem; +} + +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(rgba(15, 45, 82, 0.8), rgba(8, 117, 91, 0.7)); +} + +.hero-content { + position: relative; + z-index: 2; + text-align: center; + flex-direction: column; + justify-content: center; + min-height: 260px; +} + +.hero-title { + text-align: center; + color: #f8da78; + font-size: 2.1rem; + padding-top: 2rem; + max-width: 70%; + margin: 0 auto; + font-weight: 700; + line-height: 1.2; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); +} + +.hero-subtitle { + text-align: center; + color: #f8da78; + font-size: 1.4rem; + padding-top: 1.5rem; + max-width: 70%; + margin: 0 auto; + font-weight: 400; + line-height: 1.6; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); +} + +.hero-cta { + background: linear-gradient(135deg, #0F2D52 0%, #08755B 100%); + margin: 3rem auto; + max-width: 60%; + border-radius: 12px; + padding: 2.5rem 0; + text-align: center; +} + +/* ===== CONTENT SECTIONS ===== */ +.content-section { + margin-bottom: 3rem; + padding: 4rem 1rem; + max-width: 800px; + margin: auto; + color: #2d3748; +} + +.section-title { + text-align: center; + color: #0a775b; + font-size: 2rem; + margin-bottom: 2rem; + font-weight: 600; +} + +.section-subtitle { + text-align: center; + color: #7f948f; + font-size: 2.2rem; + margin-bottom: 2rem; + font-weight: 600; +} + +/* SECTION CONTENT STYLES */ +.section-content { + text-align: left; + max-width: 800px; + margin: 0 auto; + font-size: 1.1rem; + line-height: 1.6; +} + +.section-content p { + text-align: left; + margin-bottom: 1.5rem; + color: #333; + font-size: inherit; +} + +.section-content ul { + text-align: left; + margin: 1.5rem 0; + padding-left: 1.5rem; + list-style: disc; +} + +.section-content li { + margin-bottom: 1rem; + line-height: 1.5; + text-align: left; + background: none; + padding: 0; + border-left: none; + box-shadow: none; +} + +.section-content strong { + color: #0F2D52; + font-weight: 600; +} + +/* ===== CONTACT PAGE SPECIFIC STYLES ===== */ +.contact-section { + padding: 4rem 1rem; + max-width: 800px; + margin: auto; + color: #2d3748; +} + +.contact-title { + font-size: 2.5rem; + font-weight: 700; + color: #2c5282; + text-align: center; + margin-bottom: 2rem; +} + +.form-container { + background-color: white; + padding: 2.5rem; + border-radius: 12px; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +/* Form input styles */ +input, textarea { + width: 100%; + padding: 0.75rem 1rem; + border: 1px solid #e2e8f0; + border-radius: 8px; + margin-bottom: 1.5rem; + transition: all 0.2s ease-in-out; + font-family: 'Inter', sans-serif; + font-size: 1rem; +} + +input:focus, textarea:focus { + outline: none; + border-color: #4299e1; + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); +} + +.btn-submit { + display: block; + width: 100%; + padding: 1rem; + border-radius: 8px; + background-color: #2b6cb0; + color: white; + font-weight: 600; + text-align: center; + transition: background-color 0.2s; + border: none; + cursor: pointer; + font-family: 'Inter', sans-serif; + font-size: 1rem; +} + +.btn-submit:hover { + background-color: #2c5282; +} + +.qr-code { + text-align: center; + margin-bottom: 2rem; +} + +.qr-code h3 { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 1rem; + color: #2d3748; +} + +.qr-code p { + color: #718096; + margin-bottom: 1.5rem; + line-height: 1.6; +} + +.qr-code img { + margin: 0 auto; + border-radius: 8px; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +/* Form labels */ +.form-label { + display: block; + color: #4a5568; + font-weight: 500; + margin-bottom: 0.5rem; + font-family: 'Inter', sans-serif; +} + +/* ===== CTA BUTTONS ===== */ +.cta-button { + display: inline-block; + padding: 1rem 2rem; + margin: 0 1rem; + background-color: #08755B; + color: white; + text-decoration: none; + border-radius: 5px; + font-weight: 600; + transition: all 0.3s ease; + border: 2px solid #08755B; + font-size: 1.1rem; +} + +.cta-button.secondary { + background-color: transparent; + color: #08755B; + border: 2px solid #08755B; +} + +.cta-button:hover { + background-color: #065a46; + transform: translateY(-2px); +} + +.cta-button.secondary:hover { + background-color: #08755B; + color: white; +} + +/* CTA Section Styling */ +.cta-content { + max-width: 600px; + margin: 0 auto; +} + +.cta-title { + color: white; + font-size: 1.8rem; + margin-bottom: 1rem; + font-weight: 600; +} + +.cta-subtitle { + color: #f8da78; + font-size: 1.2rem; + margin-bottom: 2rem; + line-height: 1.5; +} + +.google-calendar-container { + margin-top: 1.5rem; +} + +/* Force Google button styling */ +.gc-button { + display: inline-block !important; + padding: 1rem 2rem !important; + background-color: #f8da78 !important; + color: #0F2D52 !important; + text-decoration: none !important; + border-radius: 5px !important; + font-weight: 600 !important; + transition: all 0.3s ease !important; + border: 2px solid #f8da78 !important; + font-size: 1.1rem !important; + font-family: 'Lato', 'Helvetica Neue', Arial, sans-serif !important; + min-width: 200px !important; +} + +.gc-button:hover { + background-color: #e6c966 !important; + transform: translateY(-2px) !important; + border-color: #e6c966 !important; +} + +/* ===== FOOTER STYLES ===== */ +.footer { + background-color: #08755B; + color: white; + text-align: center; + padding: 2rem 1rem; + margin-top: auto; + width: 100%; +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; +} + +.footer p { + margin: 0.5rem 0; + font-size: 1rem; +} + +.footer-links { + margin-top: 1rem; +} + +.footer-links a { + color: #f8da78; + text-decoration: none; + margin: 0 1rem; + transition: color 0.3s ease; +} + +.footer-links a:hover { + color: white; + text-decoration: underline; +} + +.footer-legal { + margin-top: 0.5rem; + font-size: 0.8rem; + opacity: 0.7; + line-height: 1.6; +} +.footer-legal p { margin: 0; } + +.footer-copyright { + margin-top: 1.5rem; + font-size: 0.9rem; + opacity: 0.8; +} + +/* ===== HERO BUTTONS ===== */ +.hero-actions { + padding-bottom: 2rem; + display: flex; + justify-content: center; + gap: 1.25rem; + flex-wrap: wrap; +} +.hero-banner .cta-button.secondary { + color: white; + border-color: white; +} +.hero-banner .cta-button.secondary:hover { + background: rgba(255,255,255,0.15); +} + +/* ===== WIDE CONTENT SECTIONS (for grids) ===== */ +.content-wide { + max-width: 1100px; + margin: 0 auto; + padding: 4rem 1rem; +} + +/* ===== GRID ===== */ +.grid { display: grid; gap: 2rem; } +.grid-2 { grid-template-columns: 1fr 1fr; } +.grid-3 { grid-template-columns: 1fr 1fr 1fr; } + +/* ===== SECTION LEAD ===== */ +.section-lead { + text-align: center; + max-width: 720px; + margin: 0 auto 2.5rem; + font-size: 1.1rem; + line-height: 1.7; + color: #4a5568; +} + +/* ===== CARDS ===== */ +.card { + background: white; + border-radius: 10px; + padding: 2rem 1.5rem; + box-shadow: 0 4px 12px rgba(0,0,0,0.06); + border-top: 4px solid #08755B; + transition: transform 0.25s ease, box-shadow 0.25s ease; +} +.card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgba(0,0,0,0.1); } +.card-title { color: #0F2D52; font-size: 1.2rem; font-weight: 700; margin-bottom: 0.75rem; } +.card-text { color: #4a5568; font-size: 0.95rem; line-height: 1.6; margin: 0; } + +/* ===== FEATURE BLOCKS ===== */ +.feature-block { + background: white; + border-radius: 10px; + padding: 2rem 1.5rem; + box-shadow: 0 2px 8px rgba(0,0,0,0.05); +} +.feature-title { color: #08755B; font-size: 1.15rem; font-weight: 700; margin-bottom: 0.75rem; } +.feature-text { color: #4a5568; font-size: 0.95rem; line-height: 1.6; margin: 0; } + +/* ===== TIER CARDS ===== */ +.tier-card { + background: white; + border-radius: 10px; + padding: 2rem 1.5rem; + box-shadow: 0 4px 12px rgba(0,0,0,0.06); + transition: transform 0.25s ease; +} +.tier-card:hover { transform: translateY(-4px); } +.tier-card.highlight { border: 2px solid #08755B; box-shadow: 0 6px 20px rgba(8,117,91,0.15); } +.tier-badge { + display: inline-block; + background: #e8f5f0; + color: #08755B; + font-size: 0.8rem; + font-weight: 700; + padding: 0.3rem 0.8rem; + border-radius: 20px; + margin-bottom: 1rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.tier-card.highlight .tier-badge { background: #08755B; color: white; } +.tier-title { color: #0F2D52; font-size: 1.1rem; font-weight: 700; margin-bottom: 0.75rem; } +.tier-text { color: #4a5568; font-size: 0.95rem; line-height: 1.6; margin: 0; } + +/* ===== ALT SECTION BACKGROUND ===== */ +.section-alt { background: #f0f4f8; } + +/* ===== MOBILE GRID OVERRIDES ===== */ +@media screen and (max-width: 768px) { + .grid-2, + .grid-3 { grid-template-columns: 1fr; } + .hero-actions { flex-direction: column; align-items: center; } + .hero-actions .cta-button { width: 100%; max-width: 320px; text-align: center; } + .content-wide { padding: 2.5rem 1rem; } +} + +/* ===== MODEL CARDS (Services page) ===== */ +.model-card { + background: white; + border-radius: 10px; + padding: 2rem 1.5rem; + box-shadow: 0 4px 12px rgba(0,0,0,0.06); + transition: transform 0.25s ease; +} +.model-card:hover { transform: translateY(-4px); } +.model-card.highlight { border: 2px solid #08755B; box-shadow: 0 6px 20px rgba(8,117,91,0.15); } +.model-icon { + color: #08755B; + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 1rem; +} +.model-title { color: #0F2D52; font-size: 1.15rem; font-weight: 700; margin-bottom: 0.25rem; } +.model-subtitle { color: #7f948f; font-size: 0.95rem; font-weight: 400; display: block; margin-top: 0.25rem; } +.model-text { color: #4a5568; font-size: 0.95rem; line-height: 1.6; margin: 1rem 0; } +.model-list { padding-left: 1.25rem; margin: 0; } +.model-list li { color: #4a5568; font-size: 0.9rem; line-height: 1.6; margin-bottom: 0.5rem; } + +/* ===== PAGE HERO (no background image) ===== */ +.page-hero { + background: linear-gradient(135deg, #0F2D52 0%, #08755B 100%); + padding: 4rem 2rem 3rem; + text-align: center; +} +.page-hero .hero-title { padding-top: 0; font-size: 2.2rem; } +.page-hero .hero-subtitle { padding-top: 1rem; font-size: 1.2rem; } + +/* ===== INSIGHTS FILTER BAR ===== */ +.insights-filter { + display: flex; + justify-content: center; + gap: 0.5rem; + flex-wrap: wrap; + padding: 1.5rem 1rem 2rem; + border-bottom: 1px solid #e2e8f0; +} +.filter-item { + padding: 0.45rem 1rem; + border-radius: 20px; + font-size: 0.85rem; + color: #4a5568; + cursor: default; + transition: background 0.2s; +} +.filter-item.active { + background: #08755B; + color: white; + font-weight: 600; +} + +/* ===== INSIGHT CARDS ===== */ +.insight-card { + background: white; + border-radius: 10px; + padding: 2rem 1.5rem; + box-shadow: 0 4px 12px rgba(0,0,0,0.06); + transition: transform 0.25s ease; +} +.insight-card:hover { transform: translateY(-4px); } +.insight-card.featured { border: 2px solid #08755B; } +.card-meta { + color: #08755B; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 0.75rem; +} +.card-title { color: #0F2D52; font-size: 1.2rem; font-weight: 700; margin-bottom: 0.75rem; line-height: 1.35; } +.card-title a { color: inherit; text-decoration: none; } +.card-title a:hover { color: #08755B; } +.card-excerpt { color: #4a5568; font-size: 0.95rem; line-height: 1.65; margin-bottom: 1rem; } +.read-more { + color: #08755B; + font-weight: 600; + font-size: 0.9rem; + text-decoration: none; +} +.read-more:hover { text-decoration: underline; } + +/* ===== SUBSCRIBE SECTION ===== */ +.insights-subscribe { + background: #f0f4f8; + text-align: center; + padding: 4rem 2rem; + margin-top: 3rem; +} +.insights-subscribe h3 { color: #0F2D52; font-size: 1.5rem; margin-bottom: 0.75rem; } +.insights-subscribe p { color: #4a5568; font-size: 1rem; max-width: 500px; margin: 0 auto 1.5rem; } +.subscribe-form { + display: flex; + justify-content: center; + gap: 0.75rem; + max-width: 440px; + margin: 0 auto; +} +.subscribe-form input { + flex: 1; + margin-bottom: 0; + padding: 0.65rem 1rem; + border: 1px solid #cbd5e0; + border-radius: 6px; + font-family: 'Inter', sans-serif; + font-size: 0.95rem; +} +.subscribe-form button { + padding: 0.65rem 1.5rem; + background: #08755B; + color: white; + border: none; + border-radius: 6px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + white-space: nowrap; + font-family: 'Inter', sans-serif; +} +.subscribe-form button:hover { background: #065a46; } + +@media screen and (max-width: 768px) { + .subscribe-form { flex-direction: column; } +} + +/* ===== ARTICLE PAGE ===== */ +.article-header { max-width: 800px; margin: 3rem auto 1.5rem; padding: 0 1rem; } +.back-link { color: #08755B; font-weight: 600; font-size: 0.9rem; text-decoration: none; } +.back-link:hover { text-decoration: underline; } +.article-header h1 { + color: #0F2D52; + font-size: 2rem; + line-height: 1.3; + margin: 1rem 0 0.5rem; + font-weight: 700; +} +.article-meta { color: #7f948f; font-size: 0.85rem; } +.article-body { max-width: 800px; margin: 0 auto 3rem; padding: 0 1rem; } +.article-body .lead { font-size: 1.15rem; color: #0F2D52; font-weight: 500; line-height: 1.7; } +.article-body h2 { color: #08755B; font-size: 1.4rem; margin: 2.5rem 0 0.75rem; } +.article-body h3 { color: #0F2D52; font-size: 1.15rem; margin: 1.5rem 0 0.5rem; } +.article-body p { color: #4a5568; font-size: 1.05rem; line-height: 1.75; margin-bottom: 1.25rem; } +.article-body ul { padding-left: 1.5rem; margin-bottom: 1.25rem; } +.article-body li { color: #4a5568; font-size: 1rem; line-height: 1.7; margin-bottom: 0.5rem; } +.article-body code { background: #f0f4f8; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.9em; color: #08755B; } +.article-footer-cta { + background: linear-gradient(135deg, #0F2D52 0%, #08755B 100%); + border-radius: 12px; + padding: 2.5rem 2rem; + text-align: center; + margin-top: 3rem; +} +.article-footer-cta h3 { color: white; font-size: 1.4rem; margin-bottom: 0.75rem; } +.article-footer-cta p { color: #f8da78; font-size: 1rem; margin-bottom: 1.5rem !important; } +.article-footer-cta .cta-button { margin: 0 auto; } + +/* ===== ENTERPRISE LANDING PAGE ===== */ +.enterprise-hero { + background: linear-gradient(135deg, #1a0a2e 0%, #0F2D52 40%, #08755B 100%); + padding: 4rem 2rem 3rem; + text-align: center; +} +.enterprise-hero h1 { color: white; font-size: 2.2rem; max-width: 800px; margin: 0 auto 1rem; line-height: 1.3; } +.hero-desc { color: #cbd5e0; font-size: 1.15rem; max-width: 700px; margin: 0 auto 2rem; line-height: 1.6; } +.badge.red { background: #c53030; color: white; } + +/* ===== CRISIS SECTION ===== */ +.crisis-section { max-width: 800px; margin: 3rem auto 2rem; padding: 0 1rem; } + +/* ===== PILLAR CARDS ===== */ +.pillar-card { + background: white; + border-radius: 10px; + padding: 2rem 1.5rem; + box-shadow: 0 4px 12px rgba(0,0,0,0.06); + text-align: center; + transition: transform 0.25s ease; +} +.pillar-card:hover { transform: translateY(-4px); } +.pillar-icon { + font-size: 2rem; + color: #08755B; + margin-bottom: 0.75rem; + font-weight: 700; +} +.pillar-card h3 { color: #0F2D52; font-size: 1.1rem; margin-bottom: 0.75rem; } +.pillar-card p { color: #4a5568; font-size: 0.95rem; line-height: 1.6; margin: 0; } + +/* ===== TIMELINE ===== */ +.timeline { max-width: 700px; margin: 2rem auto 0; } +.step { + border-left: 3px solid #08755B; + padding: 0 0 2rem 1.5rem; + margin-left: 0.5rem; + position: relative; +} +.step:last-child { padding-bottom: 0; } +.step::before { + content: ''; + position: absolute; + left: -8px; + top: 4px; + width: 13px; + height: 13px; + background: #08755B; + border-radius: 50%; +} +.step h4 { color: #0F2D52; font-size: 1.05rem; margin: 0 0 0.4rem; } +.step p { color: #4a5568; font-size: 0.95rem; line-height: 1.6; margin: 0; } + +/* ===== ABOUT PAGE ===== */ +.about-grid { align-items: start; gap: 3rem; } +.bio-text { color: #2d3748; } +.badge { + display: inline-block; + background: #e8f5f0; + color: #08755B; + font-size: 0.8rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 0.3rem 0.8rem; + border-radius: 20px; + margin-bottom: 1rem; +} +.bio-body { font-size: 1.05rem; line-height: 1.7; margin-bottom: 1.25rem; color: #4a5568; } +.profile-frame { text-align: center; } +.profile-photo { width: 100%; max-width: 320px; border-radius: 12px; box-shadow: 0 6px 20px rgba(0,0,0,0.12); } +.frame-caption { color: #7f948f; font-size: 0.85rem; margin-top: 0.75rem; } + +/* ===== EVOLUTION PILLARS ===== */ +.evolution-pillars { + max-width: 1100px; + margin: 3rem auto 2rem; + padding: 2rem 1rem; +} +.evolution-card { + background: white; + border-radius: 10px; + padding: 2rem 1.5rem; + box-shadow: 0 4px 12px rgba(0,0,0,0.06); + transition: transform 0.25s ease; +} +.evolution-card:hover { transform: translateY(-4px); } +.evolution-card.highlight { border: 2px solid #08755B; box-shadow: 0 6px 20px rgba(8,117,91,0.15); } +.evolution-card h3 { color: #0F2D52; font-size: 1.1rem; margin: 0.5rem 0 0.75rem; } +.evolution-card p { color: #4a5568; font-size: 0.95rem; line-height: 1.6; margin: 0; } +.era-date { + display: inline-block; + background: #f0f4f8; + color: #4a5568; + font-size: 0.8rem; + font-weight: 600; + padding: 0.25rem 0.75rem; + border-radius: 4px; +} +.evolution-card.highlight .era-date { background: #08755B; color: white; } + +/* ===== RESUME CTA ===== */ +.resume-cta { + background: linear-gradient(135deg, #0F2D52 0%, #08755B 100%); + margin: 3rem auto; + max-width: 60%; + border-radius: 12px; + padding: 2.5rem 2rem; + text-align: center; +} +.resume-cta h2 { color: white; font-size: 1.8rem; margin-bottom: 1rem; } +.resume-cta p { color: #f8da78; font-size: 1rem; max-width: 600px; margin: 0 auto 2rem; line-height: 1.6; } +.cta-buttons { display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap; } + +@media screen and (max-width: 768px) { + .about-grid { grid-template-columns: 1fr; gap: 1.5rem; } + .profile-photo { max-width: 240px; } + .resume-cta { max-width: 90%; padding: 2rem 1.25rem; } + .cta-buttons { flex-direction: column; align-items: center; } +} diff --git a/html/cdn/css/mobile.css b/html/cdn/css/mobile.css new file mode 100644 index 0000000..befb178 --- /dev/null +++ b/html/cdn/css/mobile.css @@ -0,0 +1,235 @@ +/* ===== MOBILE RESPONSIVE DESIGN ===== */ +@media screen and (max-width: 768px) { + .content-wrapper { + min-height: calc(100vh - 60px - 200px); + } + /* Navigation */ + .hamburger { + display: flex; + } + + .nav-container { + justify-content: space-between; + gap: 0; + } + + .nav-logo-title { + gap: 0.5rem; + } + + .nav-title { + font-size: 1.3rem; + } + + .nav-logo img { + height: 50px; + } + + .nav-menu { + position: fixed; + left: -100%; + top: 70px; + background-color: #08755B; + width: 100%; + flex-direction: column; + text-align: center; + transition: 0.3s; + box-shadow: 0 10px 27px rgba(0,0,0,0.1); + padding: 2rem 0; + gap: 0; + z-index: 999; + max-height: calc(100vh - 70px); + overflow-y: auto; + margin-left: 0; + } + + .nav-menu.active { + left: 0; + } + + .nav-menu a { + width: 100%; + display: block; + padding: 1rem; + font-size: 1.2rem; + border-bottom: 1px solid rgba(255,255,255,0.1); + } + + .nav-menu a:hover { + background-color: rgba(255, 255, 255, 0.1); + transform: none; + } + + .hamburger.active .bar:nth-child(2) { + opacity: 0; + } + + .hamburger.active .bar:nth-child(1) { + transform: translateY(8px) rotate(45deg); + } + + .hamburger.active .bar:nth-child(3) { + transform: translateY(-8px) rotate(-45deg); + } + + /* Dropdown mobile styles */ + .dropdown-content { + display: none; + position: relative; + background-color: transparent; + min-width: auto; + box-shadow: none; + } + + /* Hero Section */ + .hero-title { + font-size: 2rem; + max-width: 90%; + padding-top: 1.5rem; + } + + .hero-subtitle { + font-size: 1.1rem; + max-width: 90%; + padding-top: 1rem; + } + + .hero-content { + padding: 2rem 1rem; + min-height: 300px; + } + + .hero-cta { + max-width: 90%; + padding: 2rem 1rem; + margin: 2rem auto; + } + + /* Content Sections */ + .content-section { + max-width: 90%; + padding: 1.5rem 0; + } + + .section-content { + padding: 0 1rem; + } + + .section-content ul { + padding-left: 1rem; + } + + .section-title { + font-size: 1.8rem; + } + + .cta-button { + display: block; + margin: 1rem auto; + width: 80%; + max-width: 250px; + } + + /* CTA Section */ + .cta-title { + font-size: 1.5rem; + } + + .cta-subtitle { + font-size: 1.1rem; + } + + .gc-button { + padding: 0.8rem 1.5rem !important; + font-size: 1rem !important; + min-width: 180px !important; + } + + /* Footer */ + .footer { + padding: 1.5rem 1rem; + } + + .footer-links { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + .footer-links a { + margin: 0.3rem 0; + } + + .footer-info p, + .footer-copyright p { + font-size: 0.9rem; + } +} + +@media screen and (max-width: 480px) { + .content-wrapper { + min-height: calc(100vh - 60px - 180px); /* Adjust for smaller screens */ + } + /* Navigation */ + .navbar { + position: sticky; + top: 0; + padding: 0.5rem; + } + + .nav-title { + font-size: 1.1rem; + margin: 0 0.3rem; + } + + .nav-logo img { + height: 45px; + } + + .nav-logo-title { + gap: 0.3rem; + } + + .nav-menu { + top: 60px; + } + + /* Hero Section */ + .hero-title { + font-size: 1.8rem; + max-width: 95%; + } + + .hero-subtitle { + font-size: 1rem; + max-width: 95%; + } + + .hero-cta { + max-width: 95%; + } + + /* Content Sections */ + .content-section { + max-width: 90%; + padding: 1.5rem 0; + } + + /* Footer */ + .footer { + padding: 1rem 0.5rem; + } + + .footer-info p { + font-size: 0.85rem; + } + + .footer-copyright p { + font-size: 0.8rem; + } + + .footer-links a { + font-size: 0.9rem; + margin: 0.2rem 0; + } +} diff --git a/html/class/config.php b/html/class/config.php new file mode 100644 index 0000000..241aeba --- /dev/null +++ b/html/class/config.php @@ -0,0 +1,89 @@ += 3) { + $subdomain = $parts[0]; + } +} + +if ($subdomain === 'www' || $subdomain === '') { + define('ENVIRONMENT', 'production'); + define('DEBUG', false); +} else { + define('ENVIRONMENT', 'development'); + define('DEBUG', true); +} + +// PHP error reporting +if (DEBUG) { + error_reporting(E_ALL); + ini_set('display_errors', '1'); +} else { + error_reporting(0); + ini_set('display_errors', '0'); +} + +//== BASICS + define('__ROOT__', dirname(dirname(__FILE__))); + $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; + define('__URL__', $scheme . '://' . $host . '/'); + +//== SITE META — customize these for your project + define('SITE_NAME', 'bare-site'); + define('SITE_DESCRIPTION', 'A zero-dependency PHP micro-framework for static multi-language sites'); + define('SITE_AUTHOR', ''); + define('SITE_KEYWORDS', ''); + +//== API KEYS — optional, leave empty to disable features + define('GEO_KEY',''); // ipgeolocation.io — for visitor geo-location + define('TELEGRAM_BOT_TOKEN',''); // Telegram bot token — for visitor notifications + define('TELEGRAM_CHAT_ID', ''); // Telegram chat ID + +//== Bot Detection + define('BLOCK_THRESHOLD', '5'); + define('SUSPICIOUS_THRESHOLD', '3'); + define('ALERT_THRESHOLD', '10'); + +//== SUPPORTED LANGUAGES — add/remove as needed + $languages = ['en' => 'English', 'de' => 'Deutsch', 'fr' => 'Française', 'it' => 'Italiano', 'nl' => 'Nederlands', 'es' => 'Español', 'pt' => 'Português']; + define('LANGUAGES', $languages); + +//== DEFINE GLOBAL SLUGS — map URL paths to page templates + define('PAGE_00_SLUG','home'); + define('PAGE_01_SLUG','about'); + define('PAGE_02_SLUG','start'); + define('PAGE_03_SLUG',''); + define('PAGE_04_SLUG',''); + define('PAGE_05_SLUG','not-used'); + +//== SECURITY CONSTANTS + define('BOT_USER_AGENT_PATTERNS', [ + '/bot/i', '/crawl/i', '/spider/i', '/scanner/i', '/monitor/i', + '/python-requests/i', '/python-urllib/i', '/curl/i', '/wget/i', + '/zgrab/i', '/httpx/i', '/go-http-client/i', '/java/i', + '/masscan/i', '/aiohttp/i', '/censysinspect/i', '/amazonbot/i', + '/dotbot/i', '/360spider/i', '/dnbcrawler/i', '/semrushbot/i', + '/ahrefsbot/i', '/yandexbot/i', '/facebookexternalhit/i', + '/facebot/i', '/twitterbot/i', '/bingbot/i', '/duckduckbot/i', + '/nmap/i', '/nikto/i', '/nessus/i', + '/(?:libwww-perl|lwp-request)/i', '/screaming frog/i', '/postman/i' + ]); + + define('BLOCKED_PATHS', [ + '/.env', '/.git', '/wp-admin', '/administrator', '/config', + '/backup', '/sql', '/database', '/admin', '/cgi-bin', + '/wp-login.php', '/readme.html', '/license.txt', '/phpinfo.php', + '/pma', '/phpmyadmin', '/install.php', '/shell.php', + '/vendor', '/composer.json', '/passwd', '/etc/passwd', + '/logs', '/velocity.csv' + ]); diff --git a/html/class/functions.php b/html/class/functions.php new file mode 100644 index 0000000..deac105 --- /dev/null +++ b/html/class/functions.php @@ -0,0 +1,332 @@ + $_SESSION['ID'] ?? 'unknown', + 'timestamp' => gmdate(DATE_ATOM), + 'error_code' => $error_code, + 'log_data' => $data, + 'ip' => $visitor['ip'] ?? 'unknown', + 'state' => $visitor['state'] ?? 'unknown', + 'country' => $visitor['country'] ?? 'unknown' + ]; + + $log_entry = json_encode($log_data, JSON_UNESCAPED_SLASHES); + $week = gmdate('o-\WW'); + file_put_contents(__ROOT__ . '/logs/errors_' . $week . '.log', $log_entry . PHP_EOL, FILE_APPEND | LOCK_EX); + return $redirect; +} + +function logging_requests() { + $requests_per_second = calculateRequestVelocity($_SESSION['ID']); + $visitor = !empty($_SESSION['VISITOR']) && is_array($_SESSION['VISITOR']) ? $_SESSION['VISITOR'] : []; + + if($requests_per_second < BLOCK_THRESHOLD) { + $log_data = [ + 'correlation_id' => $_SESSION['ID'], + 'timestamp' => gmdate(DATE_ATOM), + ]; + + if(isRealVisitor() == true) { + $log_data['type'] = 'visitor'; + sendVisitorNotification(); + } else { + $log_data['type'] = 'bot'; + } + + $log_data = array_merge($log_data, [ + 'method' => $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN', + 'uri' => $_SERVER['REQUEST_URI'] ?? 'UNKNOWN', + 'ip' => $visitor['ip'] ?? 'unknown', + 'state' => $visitor['state'] ?? 'unknown', + 'country' => $visitor['country'] ?? 'unknown', + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'UNKNOWN', + 'requests_per_second' => $requests_per_second + ]); + + $log_entry = json_encode($log_data, JSON_UNESCAPED_SLASHES); + $week = gmdate('o-\WW'); + file_put_contents(__ROOT__ . '/logs/requests_' . $week . '.log', $log_entry . PHP_EOL, FILE_APPEND | LOCK_EX); + } else { + $redirect = logging_errors("429", $_SERVER['REQUEST_URI']); + header('Location: ' . __URL__ . $redirect); + exit; + } +} + +//==================================================== +// SECURITY FUNCTIONS +//==================================================== + +function calculateRequestVelocity($session_id) { + $velocity_file = __ROOT__ . '/logs/velocity.csv'; + $now = time(); + $csv_line = $session_id . ',' . $now . PHP_EOL; + file_put_contents($velocity_file, $csv_line, FILE_APPEND | LOCK_EX); + + $time_window = 1; + $requests_per_second = calculateRecentRequests($session_id, $time_window); + return $requests_per_second; +} + +function calculateRecentRequests($session_id, $time_window = 1) { + $velocity_file = __ROOT__ . '/logs/velocity.csv'; + $now = time(); + $count = 0; + + if (!file_exists($velocity_file)) { + return 0; + } + + $file = fopen($velocity_file, 'r'); + while (($line = fgetcsv($file)) !== FALSE) { + if (count($line) >= 2) { + $stored_session = $line[0]; + $timestamp = (int)$line[1]; + if ($stored_session === $session_id && ($now - $timestamp) <= $time_window) { + $count++; + } + } + } + fclose($file); + return $count; +} + +function calculateTotalSessionRequests($session_id) { + $velocity_file = __ROOT__ . '/logs/velocity.csv'; + $count = 0; + + if (!file_exists($velocity_file)) { + return 0; + } + + $file = fopen($velocity_file, 'r'); + while (($line = fgetcsv($file)) !== FALSE) { + if (count($line) >= 2 && $line[0] === $session_id) { + $count++; + } + } + fclose($file); + return $count; +} + +function sendVisitorNotification() { + if (!defined('TELEGRAM_BOT_TOKEN') || empty(TELEGRAM_BOT_TOKEN)) return; + if (isset($_SESSION['visitor_notification_sent'])) return; + + $total_requests = calculateTotalSessionRequests($_SESSION['ID']); + $visitor = !empty($_SESSION['VISITOR']) && is_array($_SESSION['VISITOR']) ? $_SESSION['VISITOR'] : []; + + if ($total_requests >= 2) { + $_SESSION['visitor_notification_sent'] = true; + + $ua = $_SERVER['HTTP_USER_AGENT'] ?? 'UNKNOWN'; + $ua_short = strlen($ua) > 80 ? substr($ua, 0, 77) . '...' : $ua; + $referrer = $_SERVER['HTTP_REFERER'] ?? 'direct'; + $timestamp = gmdate('Y-m-d H:i:s') . ' UTC'; + + $message = "👤 *Visitor — " . ($visitor['country'] ?? 'Unknown') . "*\n" + . "• *Time:* $timestamp\n" + . "• *Page:* " . ($_SERVER['REQUEST_URI'] ?? '/') . "\n" + . "• *Referrer:* $referrer\n" + . "• *IP:* " . ($visitor['ip'] ?? 'unknown') . "\n" + . "• *Location:* " . ($visitor['city'] ?? '') . ", " . ($visitor['country'] ?? '') . "\n" + . "• *Pages:* " . $total_requests . "\n" + . "• *Browser:* $ua_short\n" + . "• *Session:* `" . $_SESSION['ID'] . "`"; + + send2telegram($message); + } +} + +function isRealVisitor() { + $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? ''; + $uri = $_SERVER['REQUEST_URI'] ?? ''; + $method = $_SERVER['REQUEST_METHOD'] ?? ''; + $accept = $_SERVER['HTTP_ACCEPT'] ?? ''; + $accept_lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''; + + $browser_patterns = [ + '/Mozilla\/5\.0.*(Chrome|Firefox|Safari|Edg)\//i', + '/Mozilla\/5\.0.*Mobile.*Safari/i', + '/Mozilla\/5\.0.*(iPhone|Android).*(Chrome|Safari)/i', + ]; + foreach ($browser_patterns as $pattern) { + if (preg_match($pattern, $user_agent)) return true; + } + + foreach (BOT_USER_AGENT_PATTERNS as $pattern) { + if (preg_match($pattern, $user_agent)) return false; + } + + if (empty($accept) || empty($accept_lang)) return false; + if (empty($user_agent) || $user_agent === 'UNKNOWN' || strlen($user_agent) < 20) return false; + + foreach (BLOCKED_PATHS as $path) { + if (strpos($uri, $path) !== false) return false; + } + + $allowed_methods = ['GET', 'POST', 'HEAD']; + if (!in_array($method, $allowed_methods)) return false; + + return true; +} + +//==================================================== +// UTILITY FUNCTIONS +//==================================================== + +function getVisitor() { + if (!defined('GEO_KEY') || empty(GEO_KEY)) return false; + + if (!empty($_SERVER['HTTP_X_REAL_IP'])) { + $ip = $_SERVER['HTTP_X_REAL_IP']; + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ipList = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $ip = trim($ipList[0]); + } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } else { + $ip = $_SERVER['REMOTE_ADDR']; + } + + $curl = curl_init(); + curl_setopt_array($curl, [ + CURLOPT_URL => 'https://api.ipgeolocation.io/v2/ipgeo?apiKey='.GEO_KEY.'&ip='.$ip, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 5, + CURLOPT_CONNECTTIMEOUT => 3, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 2, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + ]); + + $response = curl_exec($curl); + $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $curlError = curl_error($curl); + curl_close($curl); + + if ($response === false || $httpCode !== 200) { + $err = $curlError ?: "HTTP $httpCode"; + error_log("Geo API failed ($err) for IP $ip"); + return false; + } + + $data = json_decode($response, true); + if (isset($data)) { + return [ + 'ip' => $data['ip'], + 'city' => $data['location']['city'], + 'state' => $data['location']['state_prov'], + 'country' => $data['location']['country_name'] + ]; + } + + return false; +} + +function loadLanguage() { + $language = !empty($_SESSION['SITE']['language']) ? $_SESSION['SITE']['language'] : 'en'; + $languageFile = __ROOT__ . "/lng/{$language}/menu.php"; + + if (file_exists($languageFile)) { + require_once($languageFile); + } else { + require_once(__ROOT__ . "/lng/en/menu.php"); + } +} + +function generateCorrelationId(): string { + $data = random_bytes(16); + $data[6] = chr(ord($data[6]) & 0x0f | 0x40); + $data[8] = chr(ord($data[8]) & 0x3f | 0x80); + return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); +} + +function cleanupVelocityLog() { + $velocity_file = __ROOT__ . '/logs/velocity.csv'; + $temp_file = __ROOT__ . '/logs/velocity_temp.csv'; + $now = time(); + $max_age = 86400; + + if (!file_exists($velocity_file)) return; + + $input = fopen($velocity_file, 'r'); + $output = fopen($temp_file, 'w'); + + while (($line = fgetcsv($input)) !== FALSE) { + if (count($line) >= 2) { + $timestamp = (int)$line[1]; + if (($now - $timestamp) <= $max_age) { + fputcsv($output, $line); + } + } + } + + fclose($input); + fclose($output); + rename($temp_file, $velocity_file); +} + +//==================================================== +// TELEGRAM FUNCTIONS — optional, requires TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID +//==================================================== + +function send2telegram($message) { + if (!defined('TELEGRAM_BOT_TOKEN') || !defined('TELEGRAM_CHAT_ID') || + empty(TELEGRAM_BOT_TOKEN) || empty(TELEGRAM_CHAT_ID)) { + return false; + } + + $url = "https://api.telegram.org/bot" . TELEGRAM_BOT_TOKEN . "/sendMessage"; + $data = [ + 'chat_id' => TELEGRAM_CHAT_ID, + 'text' => $message, + 'parse_mode' => 'Markdown' + ]; + + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $data, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 3, + CURLOPT_CONNECTTIMEOUT => 2 + ]); + + $result = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); + + if ($error || $httpCode !== 200) { + error_log("Telegram API failed: " . ($error ?: "HTTP $httpCode")); + return false; + } + return true; +} diff --git a/html/class/site.php b/html/class/site.php new file mode 100644 index 0000000..ab5ebe0 --- /dev/null +++ b/html/class/site.php @@ -0,0 +1,15 @@ + 86400, + 'cookie_httponly' => true, + 'cookie_samesite' => 'Lax', + 'use_strict_mode' => true +]); + +require_once('./class/config.php'); +require_once('./class/functions.php'); +require_once('./class/site.php'); + +if (!empty($_POST)) { $HTML_REQUEST = filter_input_array(INPUT_POST, FILTER_SANITIZE_FULL_SPECIAL_CHARS);} +if (!empty($_GET)) { $HTML_REQUEST = filter_input_array(INPUT_GET, FILTER_SANITIZE_FULL_SPECIAL_CHARS);} +if (!empty($HTML_REQUEST)) { require_once(__ROOT__.'/app/controler/request.php'); } +else { + $PARAMS = explode("/", $_SERVER['REQUEST_URI']); + require_once(__ROOT__.'/app/controler/start.php'); +} diff --git a/html/lng/de/about.phtml b/html/lng/de/about.phtml new file mode 100644 index 0000000..e065e00 --- /dev/null +++ b/html/lng/de/about.phtml @@ -0,0 +1 @@ +

Wie es funktioniert

Anfragefluss

Jede Anfrage trifft index.php. Das ist die einzige PHP-Datei, die nginx ausführen lässt — alle anderen geben 404 zurück.

Routing

URLs werden über Slug-Konstanten in class/config.php auf Seitenvorlagen abgebildet. Neue Seite hinzufügen: Slug definieren, Menütext in lng/{lang}/menu.php, Vorlage in lng/{lang}/{slug}.phtml.

Sprachen

Jede Sprache hat ein eigenes Verzeichnis unter lng/ mit menu.php und .phtml-Vorlagen. Der Browser Accept-Language-Header erkennt die Sprache automatisch.

Sicherheit

  • nginx blockiert alle .php außer index.php
  • Session-Cookies: httponly, SameSite=Lax
  • Bot-Erkennung mit 30+ Mustern
  • Rate-Limiting bei 5 Anfragen/Sekunde
diff --git a/html/lng/de/home.phtml b/html/lng/de/home.phtml new file mode 100644 index 0000000..b2d569e --- /dev/null +++ b/html/lng/de/home.phtml @@ -0,0 +1 @@ +

Was ist bare-site?

bare-site ist ein PHP-Mikroframework für statische Inhaltswebsites. Keine Datenbank. Kein Composer. Kein ORM. Kein Build-Schritt. Nur Dateien auf der Festplatte.

Es wurde aus 8 Jahren Produktionseinsatz entwickelt — für Unternehmenswebsites, Enterprise-Identity-Demos und mehrsprachige Broschürenseiten. Der Router ist 80 Zeilen lang.

Wofür es geeignet ist

  • Unternehmens-Websites mit mehreren Sprachen
  • API-Integrationsdemos ohne Infrastruktur
  • Dokumentationsportale
  • Alles, was keine Datenbank benötigt

Philosophie: Man braucht keinen Presslufthammer, um ein Loch in Pappe zu stanzen. Die meisten Websites sind Pappe. Verwenden Sie das richtige Werkzeug.

diff --git a/html/lng/de/menu.php b/html/lng/de/menu.php new file mode 100644 index 0000000..a59ec21 --- /dev/null +++ b/html/lng/de/menu.php @@ -0,0 +1,13 @@ +400 Ungültige Anforderung'); +define( 'ERROR_404', '
404 Nicht gefunden
'); +define( 'ERROR_500', '
500 Interner Serverfehler
'); +define( 'ERROR_default', '
Ein Fehler ist aufgetreten
'); diff --git a/html/lng/de/start.phtml b/html/lng/de/start.phtml new file mode 100644 index 0000000..5ee0957 --- /dev/null +++ b/html/lng/de/start.phtml @@ -0,0 +1,5 @@ +

Loslegen

⚠️ Schritt 1: Webserver konfigurieren

Nicht optional. bare-site funktioniert nur mit diesen nginx-Regeln. Kopieren Sie nginx/default.conf — die drei kritischen Regeln:

# 1. NUR index.php darf PHP ausführen
+location = /index.php { fastcgi_pass ...; }
+location ~ \.php$ { return 404; }
+# 2. Alles über index.php routen
+location / { try_files $uri $uri/ /index.php?$query_string; }

2. Dateien auf Server ablegen

git clone https://your-repo/bare-site.git /var/www/html/

3. Config bearbeiten

class/config.phpSITE_NAME, Slugs, Sprachen setzen.

4. Seiten erstellen

.phtml-Dateien in lng/{lang}/ — reines HTML, keine Template-Engine.

5. Sprache hinzufügen

In config.php eintragen, Verzeichnis unter lng/ erstellen, übersetzen. Erscheint automatisch im Nav-Dropdown.

diff --git a/html/lng/en/about.phtml b/html/lng/en/about.phtml new file mode 100644 index 0000000..efa4553 --- /dev/null +++ b/html/lng/en/about.phtml @@ -0,0 +1,31 @@ +
+

How It Works

+
+ +

Request Flow

+

Every request hits index.php. That's the only PHP file nginx allows to execute — everything else returns 404. From there:

+
Browser → nginx → index.php
+  ├── POST/GET form?  → app/controler/request.php
+  └── Clean URL?      → app/controler/start.php → match slug → page
+ +

Routing

+

URLs are mapped to page templates via slug constants in class/config.php:

+
define('PAGE_00_SLUG','home');   // → /home
+define('PAGE_01_SLUG','about');  // → /about
+define('PAGE_02_SLUG','start');  // → /start
+

Add a new page: define the slug in config, add menu text in lng/{lang}/menu.php, create the template in lng/{lang}/{slug}.phtml. Done.

+ +

Languages

+

Multi-language support is file-based. Each language gets its own directory under lng/ with a menu.php (navigation text + error messages) and .phtml templates for each page. Browser Accept-Language header auto-detects the language. Users can switch via the nav dropdown.

+ +

Security (no config required)

+
    +
  • nginx blocks all .php except index.php
  • +
  • Session cookies: httponly, SameSite=Lax, strict_mode
  • +
  • Input sanitized via FILTER_SANITIZE_FULL_SPECIAL_CHARS
  • +
  • Bot detection: 30+ UA patterns + browser heuristic bypass
  • +
  • Rate limiting: blocks at 5 req/s per session
  • +
  • Path traversal protection via basename()
  • +
+
+
diff --git a/html/lng/en/home.phtml b/html/lng/en/home.phtml new file mode 100644 index 0000000..f0d915b --- /dev/null +++ b/html/lng/en/home.phtml @@ -0,0 +1,25 @@ +
+

A Zero-Dependency PHP Micro-Framework

+
+

bare-site is a PHP micro-framework for static content websites. No database. No Composer. No ORM. No build step. Just files on disk.

+ +

It was built from 8 years of production use across company websites, enterprise identity-verification demos, and multi-language brochure sites. The core router is 80 lines. The entire framework — routing, bot detection, rate limiting, geo-location, Telegram notifications, structured logging, and session management — fits in 3 class files.

+ +

What it's for

+
    +
  • Company brochure sites with multiple languages
  • +
  • API-integration demos that need zero infrastructure
  • +
  • Documentation portals or static content delivery
  • +
  • Anything that doesn't need a database
  • +
+ +

What it's NOT

+
    +
  • A WordPress alternative (no admin panel, no users, no plugins)
  • +
  • A headless CMS (no API, no content editing interface)
  • +
  • A Laravel competitor (no ORM, no migrations, no queues)
  • +
+ +

Philosophy: You don't need a hammer drill to punch a hole in cardboard. Most websites are cardboard. Use the right tool.

+
+
diff --git a/html/lng/en/menu.php b/html/lng/en/menu.php new file mode 100644 index 0000000..3b623c9 --- /dev/null +++ b/html/lng/en/menu.php @@ -0,0 +1,26 @@ +400 Bad Request'); +define( 'ERROR_401', '
401 Unauthorized
'); +define( 'ERROR_403', '
403 Forbidden
'); +define( 'ERROR_404', '
404 Not Found
'); +define( 'ERROR_405', '
405 Method Not Allowed
'); +define( 'ERROR_408', '
408 Request Timeout
'); +define( 'ERROR_409', '
409 Conflict
'); +define( 'ERROR_410', '
410 Gone
'); +define( 'ERROR_415', '
415 Unsupported Media Type
'); +define( 'ERROR_429', '
429 Too Many Requests
'); +define( 'ERROR_500', '
500 Internal Server Error
'); +define( 'ERROR_501', '
501 Not Implemented
'); +define( 'ERROR_502', '
502 Bad Gateway
'); +define( 'ERROR_503', '
503 Service Unavailable
'); +define( 'ERROR_504', '
504 Gateway Timeout
'); +define( 'ERROR_default', '
An Error Occurred
'); diff --git a/html/lng/en/start.phtml b/html/lng/en/start.phtml new file mode 100644 index 0000000..a6eb52e --- /dev/null +++ b/html/lng/en/start.phtml @@ -0,0 +1,85 @@ +
+

Get Started

+
+ +

⚠️ Step 1: Configure your web server

+

This is not optional. bare-site's entire security model depends on the web server blocking direct access to PHP files. The framework will not work without these rules.

+ +

nginx (recommended)

+

Copy nginx/default.conf to your server and update the paths. The three critical rules:

+
# 1. ONLY index.php can execute — all other PHP files are blocked
+location = /index.php {
+    include fastcgi_params;
+    fastcgi_pass unix:/run/php/php8.4-fpm.sock;
+    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+}
+location ~ \.php$ { return 404; }   # ← blocks config.php, functions.php, etc.
+
+# 2. Everything routes through index.php
+location / { try_files $uri $uri/ /index.php?$query_string; }
+ +

Apache (.htaccess)

+
RewriteEngine On
+# Route everything through index.php
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^(.*)$ index.php [QSA,L]
+
+# Block direct access to PHP files except index.php
+RewriteCond %{REQUEST_URI} !^/index\.php$
+RewriteRule \.php$ - [R=404,L]
+ +

Caddy

+
your-domain.com {
+    root * /var/www/html
+    php_fastcgi unix//run/php/php8.4-fpm.sock
+    file_server
+    # Block direct PHP access except index.php
+    @blockedPhp path_regexp /(?!index\.php$).*\.php$
+    respond @blockedPhp 404
+}
+ +

2. Drop the files on a server

+
git clone https://your-repo/bare-site.git /var/www/html/
+# or just copy the html/ directory contents
+ +

3. Edit your config

+

Open class/config.php and set:

+
    +
  • SITE_NAME — your site title
  • +
  • SITE_DESCRIPTION — meta description
  • +
  • Page slugs (PAGE_00_SLUG through PAGE_05_SLUG)
  • +
  • Supported languages in $languages array
  • +
  • Optional: GEO_KEY for visitor location, TELEGRAM_* for notifications
  • +
+ +

4. Create your pages

+
lng/en/
+├── menu.php       # navigation labels + error messages
+├── home.phtml     # your content — raw HTML, no template engine
+├── about.phtml    # another page
+└── start.phtml
+

Content files are plain HTML inside a <section>. No template syntax to learn. You can use PHP inline if needed — <?php echo date('Y'); ?>.

+ +

5. Add a language

+
# 1. Add to config.php:
+$languages = ['en' => 'English', 'de' => 'Deutsch'];
+
+# 2. Create the directory and copy files:
+mkdir lng/de/ && cp lng/en/* lng/de/
+
+# 3. Translate menu.php + .phtml files
+
+# Done — appears in the nav dropdown automatically.
+ +

Optional features

+ + + + + + +
Geo-locationSet GEO_KEY (free at ipgeolocation.io)
Telegram alertsSet TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID
Weekly logsAuto-rotated: logs/errors_2026-W26.log
Article sub-pagesCreate lng/en/start/{slug}.phtml → routed at /start/{slug}
Multi-languageAuto-detect from Accept-Language header. This demo ships with 7 languages.
+ +
+
diff --git a/html/lng/es/about.phtml b/html/lng/es/about.phtml new file mode 100644 index 0000000..f918e61 --- /dev/null +++ b/html/lng/es/about.phtml @@ -0,0 +1 @@ +

Cómo funciona

Flujo de solicitudes

Cada solicitud llega a index.php. Es el único archivo PHP que nginx permite ejecutar — todos los demás devuelven 404.

Enrutamiento

Las URLs se mapean a plantillas mediante constantes en class/config.php. Nueva página: define el slug, el texto del menú, crea la plantilla.

Idiomas

Cada idioma tiene su propio directorio bajo lng/. El encabezado Accept-Language del navegador detecta automáticamente el idioma.

Seguridad

  • nginx bloquea todos los .php excepto index.php
  • Cookies de sesión: httponly, SameSite=Lax
  • Detección de bots con más de 30 patrones
  • Limitación de tasa a 5 req/s
diff --git a/html/lng/es/home.phtml b/html/lng/es/home.phtml new file mode 100644 index 0000000..c61e988 --- /dev/null +++ b/html/lng/es/home.phtml @@ -0,0 +1 @@ +

¿Qué es bare-site?

bare-site es un micro-framework PHP para sitios web de contenido estático. Sin base de datos. Sin Composer. Sin ORM. Sin paso de compilación. Solo archivos en disco.

Desarrollado a partir de 8 años de uso en producción para sitios corporativos, demos de identidad y sitios multilingües. El router tiene 80 líneas.

Casos de uso

  • Sitios corporativos multilingües
  • Demos de integración API sin infraestructura
  • Portales de documentación
  • Todo lo que no necesita base de datos

Filosofía: No necesitas un martillo perforador para hacer un agujero en cartón. La mayoría de los sitios web son de cartón. Usa la herramienta adecuada.

diff --git a/html/lng/es/menu.php b/html/lng/es/menu.php new file mode 100644 index 0000000..e045f27 --- /dev/null +++ b/html/lng/es/menu.php @@ -0,0 +1,13 @@ +400 Solicitud incorrecta'); +define( 'ERROR_404', '
404 No encontrado
'); +define( 'ERROR_500', '
500 Error interno del servidor
'); +define( 'ERROR_default', '
Se ha producido un error
'); diff --git a/html/lng/es/start.phtml b/html/lng/es/start.phtml new file mode 100644 index 0000000..39a4d1e --- /dev/null +++ b/html/lng/es/start.phtml @@ -0,0 +1,5 @@ +

Empezar

⚠️ Paso 1: Configurar el servidor web

No es opcional. bare-site requiere estas reglas de nginx. Copie nginx/default.conf — las tres reglas críticas:

# 1. SOLO index.php puede ejecutar PHP
+location = /index.php { fastcgi_pass ...; }
+location ~ \.php$ { return 404; }
+# 2. Todo pasa por index.php
+location / { try_files $uri $uri/ /index.php?$query_string; }

2. Colocar los archivos

git clone https://your-repo/bare-site.git /var/www/html/

3. Config

class/config.phpSITE_NAME, slugs, idiomas.

4. Páginas

Archivos .phtml en lng/{lang}/ — HTML puro.

5. Idioma

Añadir en config.php, crear directorio, traducir. Aparece automáticamente.

diff --git a/html/lng/fr/about.phtml b/html/lng/fr/about.phtml new file mode 100644 index 0000000..e14c2cb --- /dev/null +++ b/html/lng/fr/about.phtml @@ -0,0 +1 @@ +

Comment ça marche

Flux de requêtes

Chaque requête arrive sur index.php. C’est le seul fichier PHP que nginx autorise — tous les autres renvoient 404.

Routage

Les URLs sont mappées aux templates via des constantes dans class/config.php. Ajouter une page : définir le slug, le texte du menu, créer le template.

Langues

Chaque langue a son propre répertoire sous lng/. L’en-tête Accept-Language du navigateur détecte automatiquement la langue.

Sécurité

  • nginx bloque tous les .php sauf index.php
  • Cookies de session : httponly, SameSite=Lax
  • Détection de bots avec 30+ motifs
  • Limitation de débit à 5 req/s
diff --git a/html/lng/fr/home.phtml b/html/lng/fr/home.phtml new file mode 100644 index 0000000..ec55782 --- /dev/null +++ b/html/lng/fr/home.phtml @@ -0,0 +1 @@ +

Qu’est-ce que bare-site ?

bare-site est un micro-framework PHP pour sites web à contenu statique. Pas de base de données. Pas de Composer. Pas d’ORM. Pas d’étape de build. Juste des fichiers sur disque.

Développé à partir de 8 ans d’utilisation en production pour des sites d’entreprise, des démos d’identité et des sites multilingues. Le routeur fait 80 lignes.

Cas d’usage

  • Sites vitrine multilingues
  • Démos d’intégration API sans infrastructure
  • Portails de documentation
  • Tout ce qui n’a pas besoin de base de données

Philosophie : On n’utilise pas un marteau-piqueur pour faire un trou dans du carton. La plupart des sites sont en carton. Utilisez le bon outil.

diff --git a/html/lng/fr/menu.php b/html/lng/fr/menu.php new file mode 100644 index 0000000..fd47886 --- /dev/null +++ b/html/lng/fr/menu.php @@ -0,0 +1,13 @@ +400 Requête incorrecte'); +define( 'ERROR_404', '
404 Page introuvable
'); +define( 'ERROR_500', '
500 Erreur interne du serveur
'); +define( 'ERROR_default', '
Une erreur s’est produite
'); diff --git a/html/lng/fr/start.phtml b/html/lng/fr/start.phtml new file mode 100644 index 0000000..d0dc7bf --- /dev/null +++ b/html/lng/fr/start.phtml @@ -0,0 +1,5 @@ +

Démarrer

⚠️ Étape 1 : Configurer le serveur web

Non optionnel. bare-site nécessite ces règles nginx. Copiez nginx/default.conf — les trois règles critiques :

# 1. SEUL index.php peut exécuter PHP
+location = /index.php { fastcgi_pass ...; }
+location ~ \.php$ { return 404; }
+# 2. Tout passe par index.php
+location / { try_files $uri $uri/ /index.php?$query_string; }

2. Déposer les fichiers

git clone https://your-repo/bare-site.git /var/www/html/

3. Config

class/config.phpSITE_NAME, slugs, langues.

4. Pages

Fichiers .phtml dans lng/{lang}/ — HTML pur.

5. Langue

Ajouter dans config.php, créer le répertoire, traduire. Apparaît automatiquement.

diff --git a/html/lng/it/about.phtml b/html/lng/it/about.phtml new file mode 100644 index 0000000..84f25fc --- /dev/null +++ b/html/lng/it/about.phtml @@ -0,0 +1 @@ +

Come funziona

Flusso delle richieste

Ogni richiesta arriva a index.php. È l’unico file PHP che nginx permette di eseguire — tutti gli altri restituiscono 404.

Routing

Gli URL sono mappati ai template tramite costanti in class/config.php. Nuova pagina: definisci lo slug, il testo del menu, crea il template.

Lingue

Ogni lingua ha la propria directory sotto lng/. L’header Accept-Language del browser rileva automaticamente la lingua.

Sicurezza

  • nginx blocca tutti i .php tranne index.php
  • Cookie di sessione: httponly, SameSite=Lax
  • Rilevamento bot con 30+ pattern
  • Rate limiting a 5 req/s
diff --git a/html/lng/it/home.phtml b/html/lng/it/home.phtml new file mode 100644 index 0000000..7da7eba --- /dev/null +++ b/html/lng/it/home.phtml @@ -0,0 +1 @@ +

Cos’è bare-site?

bare-site è un micro-framework PHP per siti web a contenuto statico. Nessun database. Nessun Composer. Nessun ORM. Nessun build step. Solo file su disco.

Sviluppato da 8 anni di utilizzo in produzione per siti aziendali, demo di identità e siti multilingue. Il router è di 80 righe.

Casi d’uso

  • Siti vetrina multilingue
  • Demo di integrazione API senza infrastruttura
  • Portali di documentazione
  • Tutto ciò che non necessita di un database
diff --git a/html/lng/it/menu.php b/html/lng/it/menu.php new file mode 100644 index 0000000..e8ff3c7 --- /dev/null +++ b/html/lng/it/menu.php @@ -0,0 +1,13 @@ +400 Richiesta non valida'); +define( 'ERROR_404', '
404 Non trovato
'); +define( 'ERROR_500', '
500 Errore interno del server
'); +define( 'ERROR_default', '
Si è verificato un errore
'); diff --git a/html/lng/it/start.phtml b/html/lng/it/start.phtml new file mode 100644 index 0000000..041d434 --- /dev/null +++ b/html/lng/it/start.phtml @@ -0,0 +1,5 @@ +

Iniziare

⚠️ Passo 1: Configurare il server web

Non opzionale. bare-site richiede queste regole nginx. Copia nginx/default.conf — le tre regole critiche:

# 1. SOLO index.php può eseguire PHP
+location = /index.php { fastcgi_pass ...; }
+location ~ \.php$ { return 404; }
+# 2. Tutto passa attraverso index.php
+location / { try_files $uri $uri/ /index.php?$query_string; }

2. Caricare i file

git clone https://your-repo/bare-site.git /var/www/html/

3. Config

class/config.phpSITE_NAME, slug, lingue.

4. Pagine

File .phtml in lng/{lang}/ — HTML puro.

5. Lingua

Aggiungi in config.php, crea la directory, traduci. Appare automaticamente.

diff --git a/html/lng/nl/about.phtml b/html/lng/nl/about.phtml new file mode 100644 index 0000000..60f3e2f --- /dev/null +++ b/html/lng/nl/about.phtml @@ -0,0 +1 @@ +

Hoe het werkt

Verzoekstroom

Elk verzoek komt binnen op index.php. Dat is het enige PHP-bestand dat nginx toestaat — alle andere retourneren 404.

Routering

URL’s worden via constanten in class/config.php aan templates gekoppeld. Nieuwe pagina: definieer de slug, menutekst, maak de template.

Talen

Elke taal heeft een eigen map onder lng/. De Accept-Language-header van de browser detecteert automatisch de taal.

Beveiliging

  • nginx blokkeert alle .php behalve index.php
  • Sessiecookies: httponly, SameSite=Lax
  • Botdetectie met 30+ patronen
  • Rate limiting op 5 req/s
diff --git a/html/lng/nl/home.phtml b/html/lng/nl/home.phtml new file mode 100644 index 0000000..9b619c5 --- /dev/null +++ b/html/lng/nl/home.phtml @@ -0,0 +1 @@ +

Wat is bare-site?

bare-site is een PHP-microframework voor statische contentwebsites. Geen database. Geen Composer. Geen ORM. Geen build-stap. Alleen bestanden op schijf.

Ontwikkeld vanuit 8 jaar productiegebruik voor bedrijfswebsites, identiteitsdemo’s en meertalige brochuresites. De router is 80 regels.

Gebruiksscenario’s

  • Bedrijfswebsites met meerdere talen
  • API-integratiedemo’s zonder infrastructuur
  • Documentatieportalen
  • Alles wat geen database nodig heeft
diff --git a/html/lng/nl/menu.php b/html/lng/nl/menu.php new file mode 100644 index 0000000..79a3d6e --- /dev/null +++ b/html/lng/nl/menu.php @@ -0,0 +1,13 @@ +400 Ongeldig verzoek'); +define( 'ERROR_404', '
404 Niet gevonden
'); +define( 'ERROR_500', '
500 Interne serverfout
'); +define( 'ERROR_default', '
Er is een fout opgetreden
'); diff --git a/html/lng/nl/start.phtml b/html/lng/nl/start.phtml new file mode 100644 index 0000000..a12539a --- /dev/null +++ b/html/lng/nl/start.phtml @@ -0,0 +1,5 @@ +

Starten

⚠️ Stap 1: Webserver configureren

Niet optioneel. bare-site vereist deze nginx-regels. Kopieer nginx/default.conf — de drie kritieke regels:

# 1. ALLEEN index.php mag PHP uitvoeren
+location = /index.php { fastcgi_pass ...; }
+location ~ \.php$ { return 404; }
+# 2. Alles loopt via index.php
+location / { try_files $uri $uri/ /index.php?$query_string; }

2. Bestanden plaatsen

git clone https://your-repo/bare-site.git /var/www/html/

3. Config

class/config.phpSITE_NAME, slugs, talen.

4. Pagina’s

.phtml-bestanden in lng/{lang}/ — pure HTML.

5. Taal

Toevoegen aan config.php, directory aanmaken, vertalen. Verschijnt automatisch.

diff --git a/html/lng/pt/about.phtml b/html/lng/pt/about.phtml new file mode 100644 index 0000000..db1b031 --- /dev/null +++ b/html/lng/pt/about.phtml @@ -0,0 +1 @@ +

Como funciona

Fluxo de solicitações

Cada solicitação chega ao index.php. É o único arquivo PHP que o nginx permite executar — todos os outros retornam 404.

Roteamento

URLs são mapeadas para templates através de constantes em class/config.php. Nova página: defina o slug, o texto do menu, crie o template.

Idiomas

Cada idioma tem seu próprio diretório sob lng/. O cabeçalho Accept-Language do navegador detecta automaticamente o idioma.

Segurança

  • nginx bloqueia todos os .php exceto index.php
  • Cookies de sessão: httponly, SameSite=Lax
  • Detecção de bots com mais de 30 padrões
  • Limitação de taxa a 5 req/s
diff --git a/html/lng/pt/home.phtml b/html/lng/pt/home.phtml new file mode 100644 index 0000000..c94dc0b --- /dev/null +++ b/html/lng/pt/home.phtml @@ -0,0 +1 @@ +

O que é bare-site?

bare-site é um micro-framework PHP para sites de conteúdo estático. Sem banco de dados. Sem Composer. Sem ORM. Sem etapa de build. Apenas arquivos em disco.

Desenvolvido a partir de 8 anos de uso em produção para sites corporativos, demonstrações de identidade e sites multilíngues. O roteador tem 80 linhas.

Casos de uso

  • Sites corporativos multilíngues
  • Demonstrações de integração API sem infraestrutura
  • Portais de documentação
  • Tudo que não precisa de banco de dados

Filosofia: Você não precisa de uma britadeira para furar papelão. A maioria dos sites é de papelão. Use a ferramenta certa.

diff --git a/html/lng/pt/menu.php b/html/lng/pt/menu.php new file mode 100644 index 0000000..dc8cf9c --- /dev/null +++ b/html/lng/pt/menu.php @@ -0,0 +1,13 @@ +400 Solicitação inválida'); +define( 'ERROR_404', '
404 Não encontrado
'); +define( 'ERROR_500', '
500 Erro interno do servidor
'); +define( 'ERROR_default', '
Ocorreu um erro
'); diff --git a/html/lng/pt/start.phtml b/html/lng/pt/start.phtml new file mode 100644 index 0000000..9755479 --- /dev/null +++ b/html/lng/pt/start.phtml @@ -0,0 +1,5 @@ +

Iniciar

⚠️ Passo 1: Configurar o servidor web

Não é opcional. O bare-site requer estas regras do nginx. Copie nginx/default.conf — as três regras críticas:

# 1. APENAS index.php pode executar PHP
+location = /index.php { fastcgi_pass ...; }
+location ~ \.php$ { return 404; }
+# 2. Tudo passa pelo index.php
+location / { try_files $uri $uri/ /index.php?$query_string; }

2. Colocar os arquivos

git clone https://your-repo/bare-site.git /var/www/html/

3. Config

class/config.phpSITE_NAME, slugs, idiomas.

4. Páginas

Arquivos .phtml em lng/{lang}/ — HTML puro.

5. Idioma

Adicionar em config.php, criar diretório, traduzir. Aparece automaticamente.

diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100755 index 0000000..ee84223 --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,62 @@ +# bare-site nginx configuration +# Place this in /etc/nginx/sites-available/ and symlink to sites-enabled/ +# Or include it in your main nginx.conf + +server { + listen 80; + listen [::]:80; + + server_name your-domain.com; + root /var/www/html; # ← set to your web root + index index.php index.html index.htm; + charset utf-8; + + access_log /var/log/nginx/bare-site-access.log; + error_log /var/log/nginx/bare-site-error.log; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # === THE CRITICAL RULES === + # These three location blocks are the security foundation of bare-site. + # Do NOT change the order — nginx processes locations in order. + + # 1. ONLY index.php can execute PHP + location = /index.php { + include fastcgi_params; + fastcgi_pass unix:/run/php/php8.4-fpm.sock; # ← adjust to your PHP-FPM socket + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_hide_header X-Powered-By; + } + + # 2. BLOCK all other .php files from direct execution + location ~ \.php$ { + return 404; + } + + # 3. Everything else routes through index.php + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # === Static file caching === + location ~ \.css$ { + access_log off; + expires max; + add_header Content-Type text/css; + add_header Cache-Control "public, immutable"; + } + + location ~* ^.+\.(jpg|jpeg|gif|png|webp|js|ico|svg|woff|woff2|ttf)$ { + access_log off; + expires max; + add_header Cache-Control "public, immutable"; + } + + # === Block sensitive files === + location ~ /\.ht { deny all; } + location ~* \.(env|log|sql|md|yml|yaml)$ { deny all; } +} diff --git a/nginx/security.conf b/nginx/security.conf new file mode 100755 index 0000000..f169f09 --- /dev/null +++ b/nginx/security.conf @@ -0,0 +1,12 @@ +# Additional security configurations +# This file is automatically included by nginx.conf + +# Rate limiting +limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; + +# Security headers for all responses +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; + +# Hide server version +server_tokens off;