bare-site v1.0 — zero-dependency PHP micro-framework

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
This commit is contained in:
Claus Lohmar 2026-06-25 14:12:46 +00:00
commit b93de83519
44 changed files with 2142 additions and 0 deletions

22
.gitignore vendored Normal file
View file

@ -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/

View file

@ -0,0 +1,16 @@
<?php
// POST/GET form handler
// Process incoming form data — no database, no SQL injection surface
if(!empty($HTML_REQUEST['do'])){
switch ($HTML_REQUEST['do']) {
default:
$redirect = logging_errors("400",$HTML_REQUEST);
header('Location: '.__URL__ . $redirect);
exit;
}
} else {
$redirect = logging_errors("405",$HTML_REQUEST);
header('Location: '.__URL__ . $redirect);
exit;
}

View file

@ -0,0 +1,63 @@
<?php
loadLanguage();
$slugs = [PAGE_00_SLUG, PAGE_01_SLUG, PAGE_02_SLUG, PAGE_03_SLUG, PAGE_04_SLUG, PAGE_05_SLUG];
if (!empty($PARAMS['1'])) {
$slug = $PARAMS['1'];
if (in_array($slug, $slugs, true)) {
$_SESSION['SITE']['slug'] = $slug;
include(__ROOT__.'/app/view/_header.phtml');
if ($slug === PAGE_02_SLUG && !empty($PARAMS['2'])) {
$article = basename($PARAMS['2']);
include(__ROOT__.'/lng/'.$_SESSION['SITE']['language'].'/'.$_SESSION['SITE']['slug'].'/'.$article.'.phtml');
} else {
include(__ROOT__.'/lng/'.$_SESSION['SITE']['language'].'/'.$_SESSION['SITE']['slug'].'.phtml');
}
include(__ROOT__.'/app/view/_footer.phtml');
} elseif ($slug === 'error') {
if (!empty($PARAMS['2'])) {
$errorCode = $PARAMS['2'];
$_SESSION = array();
include(__ROOT__.'/app/view/_header.phtml');
include(__ROOT__.'/app/view/_error.phtml');
include(__ROOT__.'/app/view/_footer.phtml');
} else {
header('Location: '.__URL__.'error/404');
exit;
}
} elseif ($slug === 'dev') {
if (!DEBUG) { header('Location: '.__URL__); exit; }
echo '<center><h1>Development</h1></center>';
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');
}

19
html/app/model/menu.php Normal file
View file

@ -0,0 +1,19 @@
<?php
// Build the menu from language constants
$menue = '';
if (!empty(PAGE_00_TXT)) {$menue .= '<a href="'.__URL__.PAGE_00_SLUG.'">'.PAGE_00_TXT.'</a>';}
if (!empty(PAGE_01_TXT)) {$menue .= '<a href="'.__URL__.PAGE_01_SLUG.'">'.PAGE_01_TXT.'</a>';}
if (!empty(PAGE_02_TXT)) {$menue .= '<a href="'.__URL__.PAGE_02_SLUG.'">'.PAGE_02_TXT.'</a>';}
if (!empty(PAGE_03_TXT)) {$menue .= '<a href="'.__URL__.PAGE_03_SLUG.'">'.PAGE_03_TXT.'</a>';}
if (!empty(PAGE_04_TXT)) {$menue .= '<a href="'.__URL__.PAGE_04_SLUG.'">'.PAGE_04_TXT.'</a>';}
if (!empty(PAGE_05_TXT)) {$menue .= '<a href="'.__URL__.PAGE_05_SLUG.'">'.PAGE_05_TXT.'</a>';}
$menue .= '<div class="dropdown">';
$menue .= '<button class="dropbtn">' . LANGUAGE_TITLE . '</button>';
$menue .= '<div class="dropdown-content">';
foreach (LANGUAGES as $code => $name) {
$menue .= '<a href="'.__URL__.'set/'.$code.'">'.$name.'</a>';
}
$menue .= '</div>';
$menue .= '</div>';

11
html/app/view/_dev.php Normal file
View file

@ -0,0 +1,11 @@
<?php
echo "bare-site — development info";
echo '<hr>';
print_r($_SESSION['VISITOR']);
echo '<hr>';
echo "<h1>All Headers</h1>";
foreach ($_SERVER as $key => $value) {
echo "<strong>$key:</strong> " . htmlspecialchars($value) . "<br>";
}
echo '<hr>';
echo "workbench";

View file

@ -0,0 +1,20 @@
<?php
switch ($errorCode) {
case '400': $errorMessage = ERROR_400; break;
case '401': $errorMessage = ERROR_401; break;
case '403': $errorMessage = ERROR_403; break;
case '404': $errorMessage = ERROR_404; break;
case '405': $errorMessage = ERROR_405; break;
case '408': $errorMessage = ERROR_408; break;
case '409': $errorMessage = ERROR_409; break;
case '410': $errorMessage = ERROR_410; break;
case '415': $errorMessage = ERROR_415; break;
case '429': $errorMessage = ERROR_429; break;
case '500': $errorMessage = ERROR_500; break;
case '501': $errorMessage = ERROR_501; break;
case '502': $errorMessage = ERROR_502; break;
case '503': $errorMessage = ERROR_503; break;
case '504': $errorMessage = ERROR_504; break;
default: $errorMessage = ERROR_default; break;
}
echo $errorMessage;

View file

@ -0,0 +1,46 @@
<?php
# _footer.phtml
?>
</div>
<footer class="footer">
<div class="footer-content">
<div class="footer-info"><p>Powered by <strong>bare-site</strong></p></div>
<div class="footer-copyright">
<p>A zero-dependency PHP micro-framework. No database required.</p>
</div>
</div>
</footer>
<?php if (DEBUG): ?>
<div class="Debug"><!-- <?php echo json_encode($_SESSION, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); ?> --></div>
<?php endif; ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const hamburger = document.querySelector(".hamburger");
const navMenu = document.querySelector(".nav-menu");
if (hamburger && navMenu) {
hamburger.addEventListener("click", () => {
hamburger.classList.toggle("active");
navMenu.classList.toggle("active");
document.body.style.overflow = navMenu.classList.contains('active') ? 'hidden' : '';
});
document.querySelectorAll(".nav-menu a").forEach(n => n.addEventListener("click", () => {
hamburger.classList.remove("active");
navMenu.classList.remove("active");
document.body.style.overflow = '';
}));
}
document.querySelectorAll('.dropdown').forEach(function(dropdown) {
const button = dropdown.querySelector('.dropbtn');
const content = dropdown.querySelector('.dropdown-content');
content.style.display = 'none';
button.addEventListener('click', function() {
content.style.display = content.style.display === 'block' ? 'none' : 'block';
});
});
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,27 @@
<?php
include(__ROOT__.'/app/model/menu.php');
?>
<!DOCTYPE html>
<html lang="<?php echo $_SESSION['SITE']['language'] ?? 'en'; ?>">
<head>
<meta charset="utf-8">
<meta name="description" content="<?php echo SITE_DESCRIPTION; ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="index, nofollow" />
<title><?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="<?php echo __URL__; ?>cdn/css/default.css" >
<link rel="stylesheet" type="text/css" href="<?php echo __URL__; ?>cdn/css/mobile.css" >
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<div class="nav-logo">
<span class="nav-logo-text">&#9670;</span>
</div>
<div class="nav-title"><?php echo SITE_NAME ?></div>
<div class="nav-menu"><?php echo $menue ?></div>
<div class="hamburger"><span class="bar"></span><span class="bar"></span><span class="bar"></span></div>
</div>
</nav>
<div class="content-wrapper">

867
html/cdn/css/default.css Normal file
View file

@ -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; }
}

235
html/cdn/css/mobile.css Normal file
View file

@ -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;
}
}

89
html/class/config.php Normal file
View file

@ -0,0 +1,89 @@
<?php
## ./class/config.php — Application Configuration
## Copy this file to config.php and fill in your values
/// DEFAULT TIMEZONE
date_default_timezone_set('GMT');
//== ENVIRONMENT DETECTION
$host = $_SERVER["HTTP_HOST"] ?? 'localhost';
$subdomain = '';
if (strpos($host, '.') !== false) {
$parts = explode('.', $host);
if (count($parts) >= 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'
]);

332
html/class/functions.php Normal file
View file

@ -0,0 +1,332 @@
<?php
//====================================================
// LOGGING FUNCTIONS
//====================================================
function logging_errors($error_code, $data) {
switch ($error_code) {
case '400': $redirect = 'error/400'; break;
case '401': $redirect = 'error/401'; break;
case '403': $redirect = 'error/403'; break;
case '404': $redirect = 'error/404'; break;
case '405': $redirect = 'error/405'; break;
case '408': $redirect = 'error/408'; break;
case '409': $redirect = 'error/409'; break;
case '410': $redirect = 'error/410'; break;
case '415': $redirect = 'error/415'; break;
case '429': $redirect = 'error/429'; break;
case '500': $redirect = 'error/500'; break;
case '501': $redirect = 'error/501'; break;
case '502': $redirect = 'error/502'; break;
case '503': $redirect = 'error/503'; break;
case '504': $redirect = 'error/504'; break;
default: $redirect = 'error/default'; break;
}
$visitor = !empty($_SESSION['VISITOR']) && is_array($_SESSION['VISITOR']) ? $_SESSION['VISITOR'] : [];
$log_data = [
'correlation_id' => $_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;
}

15
html/class/site.php Normal file
View file

@ -0,0 +1,15 @@
<?php
# Set Site
if(empty($_SESSION)){
$_SESSION['ID']=generateCorrelationId();
$_SESSION['TIMESTAMP']=gmdate(DATE_ATOM);
$_SESSION['VISITOR'] = getVisitor();
$_SESSION['SITE']=array();
$_SESSION['SITE']['slug']='';
$_SESSION['SITE']['language']='';
$activeLanguage=substr($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en', 0, 2);
$supported = array_keys(LANGUAGES);
$_SESSION['SITE']['language'] = in_array($activeLanguage, $supported) ? $activeLanguage : 'en';
cleanupVelocityLog();
}
logging_requests();

19
html/index.php Normal file
View file

@ -0,0 +1,19 @@
<?php
session_start([
'cookie_lifetime' => 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');
}

1
html/lng/de/about.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Wie es funktioniert</h2><div class="section-content"><h3>Anfragefluss</h3><p>Jede Anfrage trifft <code>index.php</code>. Das ist die einzige PHP-Datei, die nginx ausführen lässt — alle anderen geben 404 zurück.</p><h3>Routing</h3><p>URLs werden über Slug-Konstanten in <code>class/config.php</code> auf Seitenvorlagen abgebildet. Neue Seite hinzufügen: Slug definieren, Menütext in <code>lng/{lang}/menu.php</code>, Vorlage in <code>lng/{lang}/{slug}.phtml</code>.</p><h3>Sprachen</h3><p>Jede Sprache hat ein eigenes Verzeichnis unter <code>lng/</code> mit <code>menu.php</code> und <code>.phtml</code>-Vorlagen. Der Browser <code>Accept-Language</code>-Header erkennt die Sprache automatisch.</p><h3>Sicherheit</h3><ul><li>nginx blockiert alle <code>.php</code> außer <code>index.php</code></li><li>Session-Cookies: httponly, SameSite=Lax</li><li>Bot-Erkennung mit 30+ Mustern</li><li>Rate-Limiting bei 5 Anfragen/Sekunde</li></ul></div></section>

1
html/lng/de/home.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Was ist bare-site?</h2><div class="section-content"><p><strong>bare-site</strong> ist ein PHP-Mikroframework für statische Inhaltswebsites. Keine Datenbank. Kein Composer. Kein ORM. Kein Build-Schritt. Nur Dateien auf der Festplatte.</p><p>Es wurde aus 8 Jahren Produktionseinsatz entwickelt — für Unternehmenswebsites, Enterprise-Identity-Demos und mehrsprachige Broschürenseiten. Der Router ist 80 Zeilen lang.</p><h3>Wofür es geeignet ist</h3><ul><li>Unternehmens-Websites mit mehreren Sprachen</li><li>API-Integrationsdemos ohne Infrastruktur</li><li>Dokumentationsportale</li><li>Alles, was keine Datenbank benötigt</li></ul><p><strong>Philosophie:</strong> Man braucht keinen Presslufthammer, um ein Loch in Pappe zu stanzen. Die meisten Websites sind Pappe. Verwenden Sie das richtige Werkzeug.</p></div></section>

13
html/lng/de/menu.php Normal file
View file

@ -0,0 +1,13 @@
<?php
define('LANGUAGE_TITLE','Sprache');
define('PAGE_00_TXT','Was');
define('PAGE_01_TXT','Wie');
define('PAGE_02_TXT','Start');
define('PAGE_03_TXT','');
define('PAGE_04_TXT','');
define('PAGE_05_TXT','');
define('DISCLAIMER','');
define( 'ERROR_400', '<center><strong>400 Ungültige Anforderung</strong></center>');
define( 'ERROR_404', '<center><strong>404 Nicht gefunden</strong></center>');
define( 'ERROR_500', '<center><strong>500 Interner Serverfehler</strong></center>');
define( 'ERROR_default', '<center><strong>Ein Fehler ist aufgetreten</strong></center>');

5
html/lng/de/start.phtml Normal file
View file

@ -0,0 +1,5 @@
<section class="content-section"><h2 class="section-title">Loslegen</h2><div class="section-content"><h3>⚠️ Schritt 1: Webserver konfigurieren</h3><p><strong>Nicht optional.</strong> bare-site funktioniert nur mit diesen nginx-Regeln. Kopieren Sie <code>nginx/default.conf</code> — die drei kritischen Regeln:</p><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;line-height:1.5;"># 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; }</pre><h3>2. Dateien auf Server ablegen</h3><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">git clone https://your-repo/bare-site.git /var/www/html/</pre><h3>3. Config bearbeiten</h3><p><code>class/config.php</code><code>SITE_NAME</code>, Slugs, Sprachen setzen.</p><h3>4. Seiten erstellen</h3><p><code>.phtml</code>-Dateien in <code>lng/{lang}/</code> — reines HTML, keine Template-Engine.</p><h3>5. Sprache hinzufügen</h3><p>In <code>config.php</code> eintragen, Verzeichnis unter <code>lng/</code> erstellen, übersetzen. Erscheint automatisch im Nav-Dropdown.</p></div></section>

31
html/lng/en/about.phtml Normal file
View file

@ -0,0 +1,31 @@
<section class="content-section">
<h2 class="section-title">How It Works</h2>
<div class="section-content">
<h3>Request Flow</h3>
<p>Every request hits <code>index.php</code>. That's the only PHP file nginx allows to execute — everything else returns 404. From there:</p>
<pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">Browser → nginx → index.php
├── POST/GET form? → app/controler/request.php
└── Clean URL? → app/controler/start.php → match slug → page</pre>
<h3>Routing</h3>
<p>URLs are mapped to page templates via slug constants in <code>class/config.php</code>:</p>
<pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">define('PAGE_00_SLUG','home'); // → /home
define('PAGE_01_SLUG','about'); // → /about
define('PAGE_02_SLUG','start'); // → /start</pre>
<p>Add a new page: define the slug in config, add menu text in <code>lng/{lang}/menu.php</code>, create the template in <code>lng/{lang}/{slug}.phtml</code>. Done.</p>
<h3>Languages</h3>
<p>Multi-language support is file-based. Each language gets its own directory under <code>lng/</code> with a <code>menu.php</code> (navigation text + error messages) and <code>.phtml</code> templates for each page. Browser <code>Accept-Language</code> header auto-detects the language. Users can switch via the nav dropdown.</p>
<h3>Security (no config required)</h3>
<ul>
<li>nginx blocks all <code>.php</code> except <code>index.php</code></li>
<li>Session cookies: httponly, SameSite=Lax, strict_mode</li>
<li>Input sanitized via <code>FILTER_SANITIZE_FULL_SPECIAL_CHARS</code></li>
<li>Bot detection: 30+ UA patterns + browser heuristic bypass</li>
<li>Rate limiting: blocks at 5 req/s per session</li>
<li>Path traversal protection via <code>basename()</code></li>
</ul>
</div>
</section>

25
html/lng/en/home.phtml Normal file
View file

@ -0,0 +1,25 @@
<section class="content-section">
<h2 class="section-title">A Zero-Dependency PHP Micro-Framework</h2>
<div class="section-content">
<p><strong>bare-site</strong> is a PHP micro-framework for static content websites. No database. No Composer. No ORM. No build step. Just files on disk.</p>
<p>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.</p>
<h3>What it's for</h3>
<ul>
<li>Company brochure sites with multiple languages</li>
<li>API-integration demos that need zero infrastructure</li>
<li>Documentation portals or static content delivery</li>
<li>Anything that doesn't need a database</li>
</ul>
<h3>What it's NOT</h3>
<ul>
<li>A WordPress alternative (no admin panel, no users, no plugins)</li>
<li>A headless CMS (no API, no content editing interface)</li>
<li>A Laravel competitor (no ORM, no migrations, no queues)</li>
</ul>
<p><strong>Philosophy:</strong> You don't need a hammer drill to punch a hole in cardboard. Most websites are cardboard. Use the right tool.</p>
</div>
</section>

26
html/lng/en/menu.php Normal file
View file

@ -0,0 +1,26 @@
<?php
// Language file — EN
define('LANGUAGE_TITLE','Language');
define('PAGE_00_TXT','What');
define('PAGE_01_TXT','How');
define('PAGE_02_TXT','Start');
define('PAGE_03_TXT','');
define('PAGE_04_TXT','');
define('PAGE_05_TXT','');
define('DISCLAIMER','');
define( 'ERROR_400', '<center><strong>400 Bad Request</strong></center>');
define( 'ERROR_401', '<center><strong>401 Unauthorized</strong></center>');
define( 'ERROR_403', '<center><strong>403 Forbidden</strong></center>');
define( 'ERROR_404', '<center><strong>404 Not Found</strong></center>');
define( 'ERROR_405', '<center><strong>405 Method Not Allowed</strong></center>');
define( 'ERROR_408', '<center><strong>408 Request Timeout</strong></center>');
define( 'ERROR_409', '<center><strong>409 Conflict</strong></center>');
define( 'ERROR_410', '<center><strong>410 Gone</strong></center>');
define( 'ERROR_415', '<center><strong>415 Unsupported Media Type</strong></center>');
define( 'ERROR_429', '<center><strong>429 Too Many Requests</strong></center>');
define( 'ERROR_500', '<center><strong>500 Internal Server Error</strong></center>');
define( 'ERROR_501', '<center><strong>501 Not Implemented</strong></center>');
define( 'ERROR_502', '<center><strong>502 Bad Gateway</strong></center>');
define( 'ERROR_503', '<center><strong>503 Service Unavailable</strong></center>');
define( 'ERROR_504', '<center><strong>504 Gateway Timeout</strong></center>');
define( 'ERROR_default', '<center><strong>An Error Occurred</strong></center>');

85
html/lng/en/start.phtml Normal file
View file

@ -0,0 +1,85 @@
<section class="content-section">
<h2 class="section-title">Get Started</h2>
<div class="section-content">
<h3>⚠️ Step 1: Configure your web server</h3>
<p><strong>This is not optional.</strong> bare-site's entire security model depends on the web server blocking direct access to PHP files. The framework <em>will not work</em> without these rules.</p>
<h4>nginx (recommended)</h4>
<p>Copy <code>nginx/default.conf</code> to your server and update the paths. The three critical rules:</p>
<pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;line-height:1.5;"># 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; }</pre>
<h4>Apache (.htaccess)</h4>
<pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;">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]</pre>
<h4>Caddy</h4>
<pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;">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
}</pre>
<h3>2. Drop the files on a server</h3>
<pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">git clone https://your-repo/bare-site.git /var/www/html/
# or just copy the html/ directory contents</pre>
<h3>3. Edit your config</h3>
<p>Open <code>class/config.php</code> and set:</p>
<ul>
<li><code>SITE_NAME</code> — your site title</li>
<li><code>SITE_DESCRIPTION</code> — meta description</li>
<li>Page slugs (<code>PAGE_00_SLUG</code> through <code>PAGE_05_SLUG</code>)</li>
<li>Supported languages in <code>$languages</code> array</li>
<li>Optional: <code>GEO_KEY</code> for visitor location, <code>TELEGRAM_*</code> for notifications</li>
</ul>
<h3>4. Create your pages</h3>
<pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">lng/en/
├── menu.php # navigation labels + error messages
├── home.phtml # your content — raw HTML, no template engine
├── about.phtml # another page
└── start.phtml</pre>
<p>Content files are plain HTML inside a <code>&lt;section&gt;</code>. No template syntax to learn. You can use PHP inline if needed — <code>&lt;?php echo date('Y'); ?&gt;</code>.</p>
<h3>5. Add a language</h3>
<pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;"># 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.</pre>
<h3>Optional features</h3>
<table style="width:100%;border-collapse:collapse;margin:1rem 0;">
<tr style="border-bottom:1px solid #e2e8f0;"><td style="padding:0.5rem;"><strong>Geo-location</strong></td><td style="padding:0.5rem;">Set <code>GEO_KEY</code> (free at ipgeolocation.io)</td></tr>
<tr style="border-bottom:1px solid #e2e8f0;"><td style="padding:0.5rem;"><strong>Telegram alerts</strong></td><td style="padding:0.5rem;">Set <code>TELEGRAM_BOT_TOKEN</code> + <code>TELEGRAM_CHAT_ID</code></td></tr>
<tr style="border-bottom:1px solid #e2e8f0;"><td style="padding:0.5rem;"><strong>Weekly logs</strong></td><td style="padding:0.5rem;">Auto-rotated: <code>logs/errors_2026-W26.log</code></td></tr>
<tr style="border-bottom:1px solid #e2e8f0;"><td style="padding:0.5rem;"><strong>Article sub-pages</strong></td><td style="padding:0.5rem;">Create <code>lng/en/start/{slug}.phtml</code> → routed at <code>/start/{slug}</code></td></tr>
<tr><td style="padding:0.5rem;"><strong>Multi-language</strong></td><td style="padding:0.5rem;">Auto-detect from <code>Accept-Language</code> header. This demo ships with 7 languages.</td></tr>
</table>
</div>
</section>

1
html/lng/es/about.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Cómo funciona</h2><div class="section-content"><h3>Flujo de solicitudes</h3><p>Cada solicitud llega a <code>index.php</code>. Es el único archivo PHP que nginx permite ejecutar — todos los demás devuelven 404.</p><h3>Enrutamiento</h3><p>Las URLs se mapean a plantillas mediante constantes en <code>class/config.php</code>. Nueva página: define el slug, el texto del menú, crea la plantilla.</p><h3>Idiomas</h3><p>Cada idioma tiene su propio directorio bajo <code>lng/</code>. El encabezado <code>Accept-Language</code> del navegador detecta automáticamente el idioma.</p><h3>Seguridad</h3><ul><li>nginx bloquea todos los <code>.php</code> excepto <code>index.php</code></li><li>Cookies de sesión: httponly, SameSite=Lax</li><li>Detección de bots con más de 30 patrones</li><li>Limitación de tasa a 5 req/s</li></ul></div></section>

1
html/lng/es/home.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">¿Qué es bare-site?</h2><div class="section-content"><p><strong>bare-site</strong> 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.</p><p>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.</p><h3>Casos de uso</h3><ul><li>Sitios corporativos multilingües</li><li>Demos de integración API sin infraestructura</li><li>Portales de documentación</li><li>Todo lo que no necesita base de datos</li></ul><p><strong>Filosofía:</strong> 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.</p></div></section>

13
html/lng/es/menu.php Normal file
View file

@ -0,0 +1,13 @@
<?php
define('LANGUAGE_TITLE','Idioma');
define('PAGE_00_TXT','Qué');
define('PAGE_01_TXT','Cómo');
define('PAGE_02_TXT','Empezar');
define('PAGE_03_TXT','');
define('PAGE_04_TXT','');
define('PAGE_05_TXT','');
define('DISCLAIMER','');
define( 'ERROR_400', '<center><strong>400 Solicitud incorrecta</strong></center>');
define( 'ERROR_404', '<center><strong>404 No encontrado</strong></center>');
define( 'ERROR_500', '<center><strong>500 Error interno del servidor</strong></center>');
define( 'ERROR_default', '<center><strong>Se ha producido un error</strong></center>');

5
html/lng/es/start.phtml Normal file
View file

@ -0,0 +1,5 @@
<section class="content-section"><h2 class="section-title">Empezar</h2><div class="section-content"><h3>⚠️ Paso 1: Configurar el servidor web</h3><p><strong>No es opcional.</strong> bare-site requiere estas reglas de nginx. Copie <code>nginx/default.conf</code> — las tres reglas críticas:</p><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;line-height:1.5;"># 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; }</pre><h3>2. Colocar los archivos</h3><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">git clone https://your-repo/bare-site.git /var/www/html/</pre><h3>3. Config</h3><p><code>class/config.php</code><code>SITE_NAME</code>, slugs, idiomas.</p><h3>4. Páginas</h3><p>Archivos <code>.phtml</code> en <code>lng/{lang}/</code> — HTML puro.</p><h3>5. Idioma</h3><p>Añadir en <code>config.php</code>, crear directorio, traducir. Aparece automáticamente.</p></div></section>

1
html/lng/fr/about.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Comment ça marche</h2><div class="section-content"><h3>Flux de requêtes</h3><p>Chaque requête arrive sur <code>index.php</code>. C&rsquo;est le seul fichier PHP que nginx autorise — tous les autres renvoient 404.</p><h3>Routage</h3><p>Les URLs sont mappées aux templates via des constantes dans <code>class/config.php</code>. Ajouter une page : définir le slug, le texte du menu, créer le template.</p><h3>Langues</h3><p>Chaque langue a son propre répertoire sous <code>lng/</code>. L&rsquo;en-tête <code>Accept-Language</code> du navigateur détecte automatiquement la langue.</p><h3>Sécurité</h3><ul><li>nginx bloque tous les <code>.php</code> sauf <code>index.php</code></li><li>Cookies de session : httponly, SameSite=Lax</li><li>Détection de bots avec 30+ motifs</li><li>Limitation de débit à 5 req/s</li></ul></div></section>

1
html/lng/fr/home.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Qu&rsquo;est-ce que bare-site ?</h2><div class="section-content"><p><strong>bare-site</strong> est un micro-framework PHP pour sites web à contenu statique. Pas de base de données. Pas de Composer. Pas d&rsquo;ORM. Pas d&rsquo;étape de build. Juste des fichiers sur disque.</p><p>Développé à partir de 8 ans d&rsquo;utilisation en production pour des sites d&rsquo;entreprise, des démos d&rsquo;identité et des sites multilingues. Le routeur fait 80 lignes.</p><h3>Cas d&rsquo;usage</h3><ul><li>Sites vitrine multilingues</li><li>Démos d&rsquo;intégration API sans infrastructure</li><li>Portails de documentation</li><li>Tout ce qui n&rsquo;a pas besoin de base de données</li></ul><p><strong>Philosophie :</strong> On n&rsquo;utilise pas un marteau-piqueur pour faire un trou dans du carton. La plupart des sites sont en carton. Utilisez le bon outil.</p></div></section>

13
html/lng/fr/menu.php Normal file
View file

@ -0,0 +1,13 @@
<?php
define('LANGUAGE_TITLE','Langue');
define('PAGE_00_TXT','Quoi');
define('PAGE_01_TXT','Comment');
define('PAGE_02_TXT','Démarrer');
define('PAGE_03_TXT','');
define('PAGE_04_TXT','');
define('PAGE_05_TXT','');
define('DISCLAIMER','');
define( 'ERROR_400', '<center><strong>400 Requête incorrecte</strong></center>');
define( 'ERROR_404', '<center><strong>404 Page introuvable</strong></center>');
define( 'ERROR_500', '<center><strong>500 Erreur interne du serveur</strong></center>');
define( 'ERROR_default', '<center><strong>Une erreur s&rsquo;est produite</strong></center>');

5
html/lng/fr/start.phtml Normal file
View file

@ -0,0 +1,5 @@
<section class="content-section"><h2 class="section-title">Démarrer</h2><div class="section-content"><h3>⚠️ Étape 1 : Configurer le serveur web</h3><p><strong>Non optionnel.</strong> bare-site nécessite ces règles nginx. Copiez <code>nginx/default.conf</code> — les trois règles critiques :</p><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;line-height:1.5;"># 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; }</pre><h3>2. Déposer les fichiers</h3><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">git clone https://your-repo/bare-site.git /var/www/html/</pre><h3>3. Config</h3><p><code>class/config.php</code><code>SITE_NAME</code>, slugs, langues.</p><h3>4. Pages</h3><p>Fichiers <code>.phtml</code> dans <code>lng/{lang}/</code> — HTML pur.</p><h3>5. Langue</h3><p>Ajouter dans <code>config.php</code>, créer le répertoire, traduire. Apparaît automatiquement.</p></div></section>

1
html/lng/it/about.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Come funziona</h2><div class="section-content"><h3>Flusso delle richieste</h3><p>Ogni richiesta arriva a <code>index.php</code>. È l&rsquo;unico file PHP che nginx permette di eseguire — tutti gli altri restituiscono 404.</p><h3>Routing</h3><p>Gli URL sono mappati ai template tramite costanti in <code>class/config.php</code>. Nuova pagina: definisci lo slug, il testo del menu, crea il template.</p><h3>Lingue</h3><p>Ogni lingua ha la propria directory sotto <code>lng/</code>. L&rsquo;header <code>Accept-Language</code> del browser rileva automaticamente la lingua.</p><h3>Sicurezza</h3><ul><li>nginx blocca tutti i <code>.php</code> tranne <code>index.php</code></li><li>Cookie di sessione: httponly, SameSite=Lax</li><li>Rilevamento bot con 30+ pattern</li><li>Rate limiting a 5 req/s</li></ul></div></section>

1
html/lng/it/home.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Cos&rsquo;è bare-site?</h2><div class="section-content"><p><strong>bare-site</strong> è un micro-framework PHP per siti web a contenuto statico. Nessun database. Nessun Composer. Nessun ORM. Nessun build step. Solo file su disco.</p><p>Sviluppato da 8 anni di utilizzo in produzione per siti aziendali, demo di identità e siti multilingue. Il router è di 80 righe.</p><h3>Casi d&rsquo;uso</h3><ul><li>Siti vetrina multilingue</li><li>Demo di integrazione API senza infrastruttura</li><li>Portali di documentazione</li><li>Tutto ciò che non necessita di un database</li></ul></div></section>

13
html/lng/it/menu.php Normal file
View file

@ -0,0 +1,13 @@
<?php
define('LANGUAGE_TITLE','Lingua');
define('PAGE_00_TXT','Cosa');
define('PAGE_01_TXT','Come');
define('PAGE_02_TXT','Inizia');
define('PAGE_03_TXT','');
define('PAGE_04_TXT','');
define('PAGE_05_TXT','');
define('DISCLAIMER','');
define( 'ERROR_400', '<center><strong>400 Richiesta non valida</strong></center>');
define( 'ERROR_404', '<center><strong>404 Non trovato</strong></center>');
define( 'ERROR_500', '<center><strong>500 Errore interno del server</strong></center>');
define( 'ERROR_default', '<center><strong>Si è verificato un errore</strong></center>');

5
html/lng/it/start.phtml Normal file
View file

@ -0,0 +1,5 @@
<section class="content-section"><h2 class="section-title">Iniziare</h2><div class="section-content"><h3>⚠️ Passo 1: Configurare il server web</h3><p><strong>Non opzionale.</strong> bare-site richiede queste regole nginx. Copia <code>nginx/default.conf</code> — le tre regole critiche:</p><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;line-height:1.5;"># 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; }</pre><h3>2. Caricare i file</h3><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">git clone https://your-repo/bare-site.git /var/www/html/</pre><h3>3. Config</h3><p><code>class/config.php</code><code>SITE_NAME</code>, slug, lingue.</p><h3>4. Pagine</h3><p>File <code>.phtml</code> in <code>lng/{lang}/</code> — HTML puro.</p><h3>5. Lingua</h3><p>Aggiungi in <code>config.php</code>, crea la directory, traduci. Appare automaticamente.</p></div></section>

1
html/lng/nl/about.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Hoe het werkt</h2><div class="section-content"><h3>Verzoekstroom</h3><p>Elk verzoek komt binnen op <code>index.php</code>. Dat is het enige PHP-bestand dat nginx toestaat — alle andere retourneren 404.</p><h3>Routering</h3><p>URL&rsquo;s worden via constanten in <code>class/config.php</code> aan templates gekoppeld. Nieuwe pagina: definieer de slug, menutekst, maak de template.</p><h3>Talen</h3><p>Elke taal heeft een eigen map onder <code>lng/</code>. De <code>Accept-Language</code>-header van de browser detecteert automatisch de taal.</p><h3>Beveiliging</h3><ul><li>nginx blokkeert alle <code>.php</code> behalve <code>index.php</code></li><li>Sessiecookies: httponly, SameSite=Lax</li><li>Botdetectie met 30+ patronen</li><li>Rate limiting op 5 req/s</li></ul></div></section>

1
html/lng/nl/home.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Wat is bare-site?</h2><div class="section-content"><p><strong>bare-site</strong> is een PHP-microframework voor statische contentwebsites. Geen database. Geen Composer. Geen ORM. Geen build-stap. Alleen bestanden op schijf.</p><p>Ontwikkeld vanuit 8 jaar productiegebruik voor bedrijfswebsites, identiteitsdemo&rsquo;s en meertalige brochuresites. De router is 80 regels.</p><h3>Gebruiksscenario&rsquo;s</h3><ul><li>Bedrijfswebsites met meerdere talen</li><li>API-integratiedemo&rsquo;s zonder infrastructuur</li><li>Documentatieportalen</li><li>Alles wat geen database nodig heeft</li></ul></div></section>

13
html/lng/nl/menu.php Normal file
View file

@ -0,0 +1,13 @@
<?php
define('LANGUAGE_TITLE','Taal');
define('PAGE_00_TXT','Wat');
define('PAGE_01_TXT','Hoe');
define('PAGE_02_TXT','Start');
define('PAGE_03_TXT','');
define('PAGE_04_TXT','');
define('PAGE_05_TXT','');
define('DISCLAIMER','');
define( 'ERROR_400', '<center><strong>400 Ongeldig verzoek</strong></center>');
define( 'ERROR_404', '<center><strong>404 Niet gevonden</strong></center>');
define( 'ERROR_500', '<center><strong>500 Interne serverfout</strong></center>');
define( 'ERROR_default', '<center><strong>Er is een fout opgetreden</strong></center>');

5
html/lng/nl/start.phtml Normal file
View file

@ -0,0 +1,5 @@
<section class="content-section"><h2 class="section-title">Starten</h2><div class="section-content"><h3>⚠️ Stap 1: Webserver configureren</h3><p><strong>Niet optioneel.</strong> bare-site vereist deze nginx-regels. Kopieer <code>nginx/default.conf</code> — de drie kritieke regels:</p><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;line-height:1.5;"># 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; }</pre><h3>2. Bestanden plaatsen</h3><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">git clone https://your-repo/bare-site.git /var/www/html/</pre><h3>3. Config</h3><p><code>class/config.php</code><code>SITE_NAME</code>, slugs, talen.</p><h3>4. Pagina&rsquo;s</h3><p><code>.phtml</code>-bestanden in <code>lng/{lang}/</code> — pure HTML.</p><h3>5. Taal</h3><p>Toevoegen aan <code>config.php</code>, directory aanmaken, vertalen. Verschijnt automatisch.</p></div></section>

1
html/lng/pt/about.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">Como funciona</h2><div class="section-content"><h3>Fluxo de solicitações</h3><p>Cada solicitação chega ao <code>index.php</code>. É o único arquivo PHP que o nginx permite executar — todos os outros retornam 404.</p><h3>Roteamento</h3><p>URLs são mapeadas para templates através de constantes em <code>class/config.php</code>. Nova página: defina o slug, o texto do menu, crie o template.</p><h3>Idiomas</h3><p>Cada idioma tem seu próprio diretório sob <code>lng/</code>. O cabeçalho <code>Accept-Language</code> do navegador detecta automaticamente o idioma.</p><h3>Segurança</h3><ul><li>nginx bloqueia todos os <code>.php</code> exceto <code>index.php</code></li><li>Cookies de sessão: httponly, SameSite=Lax</li><li>Detecção de bots com mais de 30 padrões</li><li>Limitação de taxa a 5 req/s</li></ul></div></section>

1
html/lng/pt/home.phtml Normal file
View file

@ -0,0 +1 @@
<section class="content-section"><h2 class="section-title">O que é bare-site?</h2><div class="section-content"><p><strong>bare-site</strong> é 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.</p><p>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.</p><h3>Casos de uso</h3><ul><li>Sites corporativos multilíngues</li><li>Demonstrações de integração API sem infraestrutura</li><li>Portais de documentação</li><li>Tudo que não precisa de banco de dados</li></ul><p><strong>Filosofia:</strong> Você não precisa de uma britadeira para furar papelão. A maioria dos sites é de papelão. Use a ferramenta certa.</p></div></section>

13
html/lng/pt/menu.php Normal file
View file

@ -0,0 +1,13 @@
<?php
define('LANGUAGE_TITLE','Idioma');
define('PAGE_00_TXT','O quê');
define('PAGE_01_TXT','Como');
define('PAGE_02_TXT','Iniciar');
define('PAGE_03_TXT','');
define('PAGE_04_TXT','');
define('PAGE_05_TXT','');
define('DISCLAIMER','');
define( 'ERROR_400', '<center><strong>400 Solicitação inválida</strong></center>');
define( 'ERROR_404', '<center><strong>404 Não encontrado</strong></center>');
define( 'ERROR_500', '<center><strong>500 Erro interno do servidor</strong></center>');
define( 'ERROR_default', '<center><strong>Ocorreu um erro</strong></center>');

5
html/lng/pt/start.phtml Normal file
View file

@ -0,0 +1,5 @@
<section class="content-section"><h2 class="section-title">Iniciar</h2><div class="section-content"><h3>⚠️ Passo 1: Configurar o servidor web</h3><p><strong>Não é opcional.</strong> O bare-site requer estas regras do nginx. Copie <code>nginx/default.conf</code> — as três regras críticas:</p><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.85rem;line-height:1.5;"># 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; }</pre><h3>2. Colocar os arquivos</h3><pre style="background:#f5f5f5;padding:1rem;border-radius:6px;font-size:0.9rem;">git clone https://your-repo/bare-site.git /var/www/html/</pre><h3>3. Config</h3><p><code>class/config.php</code><code>SITE_NAME</code>, slugs, idiomas.</p><h3>4. Páginas</h3><p>Arquivos <code>.phtml</code> em <code>lng/{lang}/</code> — HTML puro.</p><h3>5. Idioma</h3><p>Adicionar em <code>config.php</code>, criar diretório, traduzir. Aparece automaticamente.</p></div></section>

62
nginx/default.conf Executable file
View file

@ -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; }
}

12
nginx/security.conf Executable file
View file

@ -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;