2026-05-29 06:25:15 +00:00

168 lines
6.1 KiB
PHP

<?php
declare(strict_types=1);
function e(?string $value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}
function project_name(): string
{
$name = $_SERVER['PROJECT_NAME'] ?? $_SERVER['APP_NAME'] ?? getenv('PROJECT_NAME') ?: '';
return $name !== '' ? (string)$name : 'LaunchPage';
}
function short_text(?string $value, int $limit = 54): string
{
$text = trim((string)$value);
if (strlen($text) <= $limit) {
return $text;
}
return rtrim(substr($text, 0, max(0, $limit - 3))) . '…';
}
function project_description(): string
{
$description = $_SERVER['PROJECT_DESCRIPTION'] ?? getenv('PROJECT_DESCRIPTION') ?: '';
return $description !== '' ? $description : 'A focused landing page with a fast, secure lead capture workflow.';
}
function ensure_leads_table(): void
{
static $ready = false;
if ($ready) {
return;
}
require_once __DIR__ . '/../db/config.php';
$sql = "CREATE TABLE IF NOT EXISTS leads (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
public_token CHAR(32) NOT NULL UNIQUE,
name VARCHAR(120) NOT NULL,
email VARCHAR(190) NOT NULL,
company VARCHAR(160) NULL,
budget VARCHAR(80) NULL,
message TEXT NOT NULL,
source VARCHAR(120) NULL,
status ENUM('new','contacted','closed') NOT NULL DEFAULT 'new',
email_sent TINYINT(1) NOT NULL DEFAULT 0,
ip_address VARCHAR(45) NULL,
user_agent VARCHAR(255) NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_created_at (created_at),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
db()->exec($sql);
$ready = true;
}
function lead_count(): int
{
ensure_leads_table();
$stmt = db()->query('SELECT COUNT(*) AS total FROM leads');
return (int)($stmt->fetch()['total'] ?? 0);
}
function latest_leads(int $limit = 8): array
{
ensure_leads_table();
$stmt = db()->prepare('SELECT id, public_token, name, email, company, budget, message, status, email_sent, created_at FROM leads ORDER BY created_at DESC LIMIT :limit');
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
function fetch_lead_by_token(string $token): ?array
{
ensure_leads_table();
$stmt = db()->prepare('SELECT * FROM leads WHERE public_token = :token LIMIT 1');
$stmt->bindValue(':token', $token, PDO::PARAM_STR);
$stmt->execute();
$lead = $stmt->fetch();
return $lead ?: null;
}
function fetch_lead_by_id(int $id): ?array
{
ensure_leads_table();
$stmt = db()->prepare('SELECT * FROM leads WHERE id = :id LIMIT 1');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$lead = $stmt->fetch();
return $lead ?: null;
}
function page_head(string $title, string $description = ''): void
{
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? getenv('PROJECT_DESCRIPTION') ?: '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? getenv('PROJECT_IMAGE_URL') ?: '';
$metaDescription = $description !== '' ? $description : ($projectDescription !== '' ? $projectDescription : project_description());
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= e($title) ?></title>
<meta name="description" content="<?= e($metaDescription) ?>">
<?php if ($projectDescription): ?>
<!-- Meta description from project environment -->
<meta property="og:description" content="<?= e($projectDescription) ?>">
<meta property="twitter:description" content="<?= e($projectDescription) ?>">
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Platform-managed preview image -->
<meta property="og:image" content="<?= e($projectImageUrl) ?>">
<meta property="twitter:image" content="<?= e($projectImageUrl) ?>">
<?php endif; ?>
<meta property="og:title" content="<?= e($title) ?>">
<meta name="twitter:card" content="summary_large_image">
<link rel="preconnect" href="https://cdn.jsdelivr.net">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=2026052901">
</head>
<body>
<?php
}
function page_nav(string $active = 'home'): void
{
?>
<header class="site-header sticky-top">
<nav class="navbar navbar-expand-lg" aria-label="Primary navigation">
<div class="container">
<a class="navbar-brand" href="index.php" aria-label="<?= e(project_name()) ?> home"><?= e(project_name()) ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-1">
<li class="nav-item"><a class="nav-link <?= $active === 'home' ? 'active' : '' ?>" href="index.php#offer">Offer</a></li>
<li class="nav-item"><a class="nav-link <?= $active === 'process' ? 'active' : '' ?>" href="index.php#process">Process</a></li>
<li class="nav-item"><a class="nav-link <?= $active === 'leads' ? 'active' : '' ?>" href="leads.php">Leads</a></li>
<li class="nav-item"><a class="btn btn-dark btn-sm ms-lg-2" href="index.php#lead-form">Request info</a></li>
</ul>
</div>
</div>
</nav>
</header>
<?php
}
function page_footer(): void
{
$year = date('Y');
?>
<footer class="site-footer">
<div class="container d-flex flex-column flex-md-row justify-content-between gap-2">
<p class="mb-0">&copy; <?= e((string)$year) ?> <?= e(project_name()) ?>. Built for fast lead capture.</p>
<p class="mb-0"><a href="leads.php">View leads</a> <span aria-hidden="true">·</span> <a href="/healthz">Health</a></p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=2026052901" defer></script>
</body>
</html>
<?php
}