From aeb101f333c9f98fafddb309b42583b5696e3462 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 29 May 2026 06:25:15 +0000 Subject: [PATCH] 5-29-26-Initial --- assets/css/custom.css | 525 +++++++++++------------------------------- assets/js/main.js | 63 ++--- contact.php | 78 +++++++ healthz/index.php | 9 + includes/app.php | 167 ++++++++++++++ index.php | 297 ++++++++++++------------ lead.php | 57 +++++ leads.php | 60 +++++ thank-you.php | 39 ++++ 9 files changed, 723 insertions(+), 572 deletions(-) create mode 100644 contact.php create mode 100644 healthz/index.php create mode 100644 includes/app.php create mode 100644 lead.php create mode 100644 leads.php create mode 100644 thank-you.php diff --git a/assets/css/custom.css b/assets/css/custom.css index 789132e..f74409e 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,403 +1,150 @@ +:root { + --color-bg: #f7f7f5; + --color-surface: #ffffff; + --color-surface-2: #eeeeeb; + --color-text: #181817; + --color-muted: #686865; + --color-border: #d9d9d3; + --color-accent: #111111; + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --shadow-sm: 0 1px 2px rgba(20, 20, 20, .05); + --shadow-md: 0 12px 30px rgba(20, 20, 20, .08); + --space-section: clamp(3rem, 7vw, 6rem); +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } body { - background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); - background-size: 400% 400%; - animation: gradient 15s ease infinite; - color: #212529; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - margin: 0; - min-height: 100vh; + margin: 0; + background: var(--color-bg); + color: var(--color-text); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 15px; + line-height: 1.55; } -.main-wrapper { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - width: 100%; - padding: 20px; - box-sizing: border-box; - position: relative; - z-index: 1; +a { color: inherit; text-underline-offset: 3px; } +a:hover { color: #000; } + +.site-header { + background: rgba(247, 247, 245, .92); + backdrop-filter: blur(14px); + border-bottom: 1px solid var(--color-border); +} +.navbar { padding-block: .75rem; } +.navbar-brand { font-weight: 700; letter-spacing: -.03em; } +.nav-link { color: var(--color-muted); font-weight: 600; font-size: .92rem; } +.nav-link.active, .nav-link:hover { color: var(--color-text); } +.navbar-toggler { border-radius: var(--radius-sm); border-color: var(--color-border); } + +.btn { + border-radius: var(--radius-sm); + font-weight: 700; + letter-spacing: -.01em; + padding: .72rem 1rem; +} +.btn-lg { padding: .86rem 1.15rem; font-size: .98rem; } +.btn-dark { background: var(--color-accent); border-color: var(--color-accent); } +.btn-outline-dark { border-color: #2c2c2a; } +.btn:focus-visible, .form-control:focus, .form-select:focus { + box-shadow: 0 0 0 .22rem rgba(24, 24, 23, .14); + border-color: var(--color-text); } -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } +.hero-section { padding: clamp(3rem, 8vw, 6.5rem) 0 var(--space-section); } +.eyebrow { + color: var(--color-muted); + font-size: .76rem; + font-weight: 800; + letter-spacing: .12em; + text-transform: uppercase; } - -.chat-container { - width: 100%; - max-width: 600px; - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 20px; - display: flex; - flex-direction: column; - height: 85vh; - box-shadow: 0 20px 40px rgba(0,0,0,0.2); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - overflow: hidden; +h1, h2, h3, .page-title { + color: var(--color-text); + letter-spacing: -.045em; + line-height: 1.05; } +h1 { font-size: clamp(2.35rem, 6vw, 4.75rem); font-weight: 800; max-width: 11ch; } +h2 { font-size: clamp(1.65rem, 3.5vw, 2.75rem); font-weight: 800; } +h3 { font-size: 1.05rem; font-weight: 800; } +.page-title { font-size: clamp(2rem, 4vw, 3.25rem); font-weight: 800; } +.lead-copy { color: var(--color-muted); font-size: clamp(1rem, 2vw, 1.14rem); max-width: 42rem; } -.chat-header { - padding: 1.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - background: rgba(255, 255, 255, 0.5); - font-weight: 700; - font-size: 1.1rem; - display: flex; - justify-content: space-between; - align-items: center; +.metric-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: .75rem; + max-width: 34rem; } - -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; +.metric-row div, .feature-card, .panel, .form-shell, .confirmation-card, .table-card, .detail-card, .empty-state { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); } +.metric-row div { padding: .9rem; } +.metric-row dt { font-size: 1.1rem; font-weight: 800; line-height: 1; } +.metric-row dd { margin: .25rem 0 0; color: var(--color-muted); font-size: .82rem; } -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; +.panel { padding: 1rem; } +.hero-panel { max-width: 34rem; margin-left: auto; } +.panel-header { + display: flex; align-items: center; gap: .5rem; + border-bottom: 1px solid var(--color-border); + padding-bottom: .85rem; margin-bottom: 1rem; + color: var(--color-muted); font-size: .86rem; font-weight: 700; } +.status-dot { width: .55rem; height: .55rem; border-radius: 50%; background: #198754; display: inline-block; } +.preview-card { background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-sm); padding: 1rem; margin-bottom: 1rem; } +.preview-line { height: .7rem; background: #cfcfca; border-radius: 999px; margin-bottom: .65rem; } +.preview-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: .6rem; margin-top: 1rem; } +.preview-grid span { min-height: 4.7rem; background: #fff; border: 1px solid var(--color-border); border-radius: var(--radius-sm); } +.check-list { list-style: none; padding: 0; display: grid; gap: .55rem; color: var(--color-muted); } +.check-list li { position: relative; padding-left: 1.35rem; } +.check-list li::before { content: "✓"; position: absolute; left: 0; color: var(--color-text); font-weight: 900; } -::-webkit-scrollbar-track { - background: transparent; +.section { padding: var(--space-section) 0; } +.muted-section { background: var(--color-surface-2); border-block: 1px solid var(--color-border); } +.section-heading { max-width: 44rem; margin-bottom: 1.5rem; } +.section-heading p:not(.eyebrow) { color: var(--color-muted); } +.feature-card { padding: 1.25rem; height: 100%; } +.feature-card p { color: var(--color-muted); margin-bottom: 0; } +.timeline-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .75rem; } +.timeline-list li { display: grid; grid-template-columns: 3rem 1fr; gap: 1rem; padding: 1rem; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); } +.timeline-list span { font-weight: 900; color: var(--color-muted); } +.timeline-list p { margin: .2rem 0 0; color: var(--color-muted); } + +.form-shell, .confirmation-card, .detail-card, .empty-state { padding: clamp(1.2rem, 3vw, 2rem); } +.form-label { font-weight: 700; font-size: .9rem; } +.form-control, .form-select { border-radius: var(--radius-sm); border-color: var(--color-border); padding: .78rem .86rem; } +.hp-field { position: absolute; left: -10000px; width: 1px; height: 1px; opacity: 0; } +.invalid-feedback { font-size: .8rem; } + +.alert { border-radius: var(--radius-sm); border: 1px solid var(--color-border); } +.confirmation-page { min-height: 64vh; display: flex; align-items: center; } +.submitted-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .75rem; } +.submitted-summary div { border: 1px solid var(--color-border); border-radius: var(--radius-sm); padding: .9rem; background: var(--color-bg); } +.submitted-summary span { display: block; color: var(--color-muted); font-size: .78rem; font-weight: 800; text-transform: uppercase; letter-spacing: .08em; } +.submitted-summary strong { display: block; margin-top: .18rem; overflow-wrap: anywhere; } + +.table-card { overflow: hidden; } +.table { --bs-table-bg: transparent; } +.table th { color: var(--color-muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .08em; } +.table td, .table th { padding: 1rem; border-color: var(--color-border); } +.empty-state { text-align: center; padding-block: 4rem; } +.empty-state p { color: var(--color-muted); } +.back-link { color: var(--color-muted); font-weight: 700; text-decoration: none; } +.back-link:hover { color: var(--color-text); } +.detail-layout { max-width: 940px; } +.message-box { background: var(--color-bg); border: 1px solid var(--color-border); border-radius: var(--radius-sm); padding: 1rem; white-space: normal; } + +.site-footer { padding: 1.5rem 0; border-top: 1px solid var(--color-border); color: var(--color-muted); font-size: .88rem; } + +@media (max-width: 767.98px) { + h1 { max-width: none; } + .metric-row, .submitted-summary { grid-template-columns: 1fr; } + .hero-panel { margin-left: 0; } + .table td, .table th { white-space: nowrap; } } - -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; -} - -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); -} - -.message { - max-width: 85%; - padding: 0.85rem 1.1rem; - border-radius: 16px; - line-height: 1.5; - font-size: 0.95rem; - box-shadow: 0 4px 15px rgba(0,0,0,0.05); - animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } -} - -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; -} - -.message.bot { - align-self: flex-start; - background: #ffffff; - color: #212529; - border-bottom-left-radius: 4px; -} - -.chat-input-area { - padding: 1.25rem; - background: rgba(255, 255, 255, 0.5); - border-top: 1px solid rgba(0, 0, 0, 0.05); -} - -.chat-input-area form { - display: flex; - gap: 0.75rem; -} - -.chat-input-area input { - flex: 1; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - padding: 0.75rem 1rem; - outline: none; - background: rgba(255, 255, 255, 0.9); - transition: all 0.3s ease; -} - -.chat-input-area input:focus { - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2); -} - -.chat-input-area button { - background: #212529; - color: #fff; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - transition: all 0.3s ease; -} - -.chat-input-area button:hover { - background: #000; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.2); -} - -/* Background Animations */ -.bg-animations { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; - pointer-events: none; -} - -.blob { - position: absolute; - width: 500px; - height: 500px; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - filter: blur(80px); - animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1); -} - -.blob-1 { - top: -10%; - left: -10%; - background: rgba(238, 119, 82, 0.4); -} - -.blob-2 { - bottom: -10%; - right: -10%; - background: rgba(35, 166, 213, 0.4); - animation-delay: -7s; - width: 600px; - height: 600px; -} - -.blob-3 { - top: 40%; - left: 30%; - background: rgba(231, 60, 126, 0.3); - animation-delay: -14s; - width: 450px; - height: 450px; -} - -@keyframes move { - 0% { transform: translate(0, 0) rotate(0deg) scale(1); } - 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); } - 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); } - 100% { transform: translate(0, 0) rotate(360deg) scale(1); } -} - -.header-link { - font-size: 14px; - color: #fff; - text-decoration: none; - background: rgba(0, 0, 0, 0.2); - padding: 0.5rem 1rem; - border-radius: 8px; - transition: all 0.3s ease; -} - -.header-link:hover { - background: rgba(0, 0, 0, 0.4); - text-decoration: none; -} - -/* Admin Styles */ -.admin-container { - max-width: 900px; - margin: 3rem auto; - padding: 2.5rem; - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-radius: 24px; - box-shadow: 0 20px 50px rgba(0,0,0,0.15); - border: 1px solid rgba(255, 255, 255, 0.4); - position: relative; - z-index: 1; -} - -.admin-container h1 { - margin-top: 0; - color: #212529; - font-weight: 800; -} - -.table { - width: 100%; - border-collapse: separate; - border-spacing: 0 8px; - margin-top: 1.5rem; -} - -.table th { - background: transparent; - border: none; - padding: 1rem; - color: #6c757d; - font-weight: 600; - text-transform: uppercase; - font-size: 0.75rem; - letter-spacing: 1px; -} - -.table td { - background: #fff; - padding: 1rem; - border: none; -} - -.table tr td:first-child { border-radius: 12px 0 0 12px; } -.table tr td:last-child { border-radius: 0 12px 12px 0; } - -.form-group { - margin-bottom: 1.25rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - font-size: 0.9rem; -} - -.form-control { - width: 100%; - padding: 0.75rem 1rem; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - background: #fff; - transition: all 0.3s ease; - box-sizing: border-box; -} - -.form-control:focus { - outline: none; - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1); -} - -.header-container { - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-links { - display: flex; - gap: 1rem; -} - -.admin-card { - background: rgba(255, 255, 255, 0.6); - padding: 2rem; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.5); - margin-bottom: 2.5rem; - box-shadow: 0 10px 30px rgba(0,0,0,0.05); -} - -.admin-card h3 { - margin-top: 0; - margin-bottom: 1.5rem; - font-weight: 700; -} - -.btn-delete { - background: #dc3545; - color: white; - border: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; - cursor: pointer; -} - -.btn-add { - background: #212529; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; - margin-top: 1rem; -} - -.btn-save { - background: #0088cc; - color: white; - border: none; - padding: 0.8rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - width: 100%; - transition: all 0.3s ease; -} - -.webhook-url { - font-size: 0.85em; - color: #555; - margin-top: 0.5rem; -} - -.history-table-container { - overflow-x: auto; - background: rgba(255, 255, 255, 0.4); - padding: 1rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.3); -} - -.history-table { - width: 100%; -} - -.history-table-time { - width: 15%; - white-space: nowrap; - font-size: 0.85em; - color: #555; -} - -.history-table-user { - width: 35%; - background: rgba(255, 255, 255, 0.3); - border-radius: 8px; - padding: 8px; -} - -.history-table-ai { - width: 50%; - background: rgba(255, 255, 255, 0.5); - border-radius: 8px; - padding: 8px; -} - -.no-messages { - text-align: center; - color: #777; -} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index d349598..da79629 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,26 @@ -document.addEventListener('DOMContentLoaded', () => { - const chatForm = document.getElementById('chat-form'); - const chatInput = document.getElementById('chat-input'); - const chatMessages = document.getElementById('chat-messages'); - - const appendMessage = (text, sender) => { - const msgDiv = document.createElement('div'); - msgDiv.classList.add('message', sender); - msgDiv.textContent = text; - chatMessages.appendChild(msgDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; - }; - - chatForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const message = chatInput.value.trim(); - if (!message) return; - - appendMessage(message, 'visitor'); - chatInput.value = ''; - - try { - const response = await fetch('api/chat.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }) - }); - const data = await response.json(); - - // Artificial delay for realism - setTimeout(() => { - appendMessage(data.reply, 'bot'); - }, 500); - } catch (error) { - console.error('Error:', error); - appendMessage("Sorry, something went wrong. Please try again.", 'bot'); - } +(() => { + const forms = document.querySelectorAll('.needs-validation'); + forms.forEach((form) => { + form.addEventListener('submit', (event) => { + const message = form.querySelector('textarea[name="message"]'); + if (message && message.value.trim().length < 10) { + message.setCustomValidity('Please enter at least 10 characters.'); + } else if (message) { + message.setCustomValidity(''); + } + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); }); -}); + }); + + const textarea = document.querySelector('textarea[name="message"]'); + const counter = document.querySelector('[data-char-count]'); + if (textarea && counter) { + const update = () => { counter.textContent = String(textarea.value.length); }; + textarea.addEventListener('input', update); + update(); + } +})(); diff --git a/contact.php b/contact.php new file mode 100644 index 0000000..6063a97 --- /dev/null +++ b/contact.php @@ -0,0 +1,78 @@ + 120) { + back_with_error('Name is required and must be under 120 characters.'); +} +if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > 190) { + back_with_error('A valid email address is required.'); +} +if (strlen($message) < 10 || strlen($message) > 2000) { + back_with_error('Message must be between 10 and 2000 characters.'); +} +if (strlen($company) > 160 || strlen($budget) > 80) { + back_with_error('One of the optional fields is too long.'); +} + +try { + ensure_leads_table(); + $token = bin2hex(random_bytes(16)); + $ip = $_SERVER['REMOTE_ADDR'] ?? null; + $agent = substr((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255); + + $stmt = db()->prepare('INSERT INTO leads (public_token, name, email, company, budget, message, source, ip_address, user_agent) VALUES (:token, :name, :email, :company, :budget, :message, :source, :ip, :agent)'); + $stmt->bindValue(':token', $token); + $stmt->bindValue(':name', $name); + $stmt->bindValue(':email', $email); + $stmt->bindValue(':company', $company !== '' ? $company : null); + $stmt->bindValue(':budget', $budget !== '' ? $budget : null); + $stmt->bindValue(':message', $message); + $stmt->bindValue(':source', 'Landing page'); + $stmt->bindValue(':ip', $ip); + $stmt->bindValue(':agent', $agent); + $stmt->execute(); + $leadId = (int)db()->lastInsertId(); + + $safeName = e($name); + $safeEmail = e($email); + $safeMessage = nl2br(e($message)); + $html = "

New landing page lead

Name: {$safeName}

Email: {$safeEmail}

Company: " . e($company ?: 'Not provided') . "

Budget: " . e($budget ?: 'Not sure') . "

Message:
{$safeMessage}

"; + $text = "New landing page lead\nName: {$name}\nEmail: {$email}\nCompany: " . ($company ?: 'Not provided') . "\nBudget: " . ($budget ?: 'Not sure') . "\n\n{$message}"; + $mailResult = MailService::sendMail(null, 'New landing page lead from ' . $name, $html, $text, ['reply_to' => $email]); + if (!empty($mailResult['success'])) { + $update = db()->prepare('UPDATE leads SET email_sent = 1 WHERE id = :id'); + $update->bindValue(':id', $leadId, PDO::PARAM_INT); + $update->execute(); + } else { + error_log('Lead notification email failed: ' . ($mailResult['error'] ?? 'unknown error')); + } + + header('Location: thank-you.php?token=' . urlencode($token)); + exit; +} catch (Throwable $exception) { + error_log('Lead submission failed: ' . $exception->getMessage()); + back_with_error('We could not save your request right now. Please try again in a moment.'); +} diff --git a/healthz/index.php b/healthz/index.php new file mode 100644 index 0000000..c65c4cb --- /dev/null +++ b/healthz/index.php @@ -0,0 +1,9 @@ + true, + 'service' => 'landing-leads', + 'time' => gmdate('c'), + 'php' => PHP_VERSION, +], JSON_UNESCAPED_SLASHES); diff --git a/includes/app.php b/includes/app.php new file mode 100644 index 0000000..986f0fd --- /dev/null +++ b/includes/app.php @@ -0,0 +1,167 @@ +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()); + ?> + + + + + + <?= e($title) ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + getMessage()); +} + +page_head(project_name() . ' — Landing Page & Lead Capture', project_description()); +page_nav('home'); ?> - - - - - - New Style - - - - - - - - - - - - - - - - - - - - - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… +
+
+
+ + + + + + +
+
+

Professional landing page hosting

+

Publish your offer and capture qualified leads in one focused page.

+

A restrained, conversion-ready landing shell with a secure contact workflow, confirmation page, and lead review area — ready to replace with your uploaded HTML whenever you provide it.

+ +
+
stored leads
+
24h
fast response target
+
PDO
secure storage
+
+
+
+ +
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- Page updated: (UTC) -
- - + + +
+
+
+

What is included

+

A complete first slice, not just a placeholder.

+
+
+

Landing message

Clear positioning, compact proof points, and focused calls-to-action for visitors.

+

Lead form

Name, email, company, budget, and message with accessible validation and smooth feedback.

+

Lead review

Submissions are saved to MariaDB and available in a concise list and detail page.

+
+
+
+ +
+
+
+
+

Visitor journey

+

From interest to follow-up in under a minute.

+
+
+
    +
  1. 01
    Understand the offer

    Visitors get a concise promise and proof points immediately.

  2. +
  3. 02
    Submit details

    The form validates required fields and blocks common bot noise.

  4. +
  5. 03
    Confirm and review

    The lead is stored, a notification is attempted, and the lead detail is available.

  6. +
+
+
+
+
+ +
+
+
+
+
+
+

Contact / lead form

+

Tell us what you want to launch.

+

Submissions are saved securely. Email delivery depends on your configured SMTP or MAIL_TO environment settings.

+
+
+ +
+
+ + +
Please enter your name.
+
+
+ + +
Please enter a valid email.
+
+
+ + +
+
+ + +
+
+ + +
Please enter at least 10 characters.
0/2000
+
+
+
+ +

Testing notice: Flatlogic does not guarantee mail server usage. Configure your own SMTP in environment MAIL_/SMTP_ variables before production.

+
+
+
+
+
+
+
+
+ diff --git a/lead.php b/lead.php new file mode 100644 index 0000000..f1eb794 --- /dev/null +++ b/lead.php @@ -0,0 +1,57 @@ + 0) { + $lead = fetch_lead_by_id($id); + } + if (!$lead) { + $error = 'Lead not found.'; + } +} catch (Throwable $exception) { + $error = 'Lead detail is temporarily unavailable.'; + error_log('Lead detail failed: ' . $exception->getMessage()); +} + +page_head('Lead Detail — ' . project_name(), 'Detailed view of a captured landing page lead.'); +page_nav('leads'); +?> +
+
+ ← Back to leads + + + +
+ +
+ +
+
+ diff --git a/leads.php b/leads.php new file mode 100644 index 0000000..8b69459 --- /dev/null +++ b/leads.php @@ -0,0 +1,60 @@ +getMessage()); +} + +page_head('Lead Inbox — ' . project_name(), 'Review captured landing page leads.'); +page_nav('leads'); +?> +
+
+
+
+

Lead inbox

+

Captured requests

+

Newest submissions from the landing page form.

+
+ Add test lead +
+ + + + +
+

No leads yet

+

Submit the landing page form to see requests appear here.

+ Open form +
+ +
+
+ + + + + + + + + + + + + + + + +
Captured landing page leads
NameCompanyBudgetStatusEmailCreated

Sent' : 'Not sent' ?>View
+
+
+ +
+
+ diff --git a/thank-you.php b/thank-you.php new file mode 100644 index 0000000..5564d4d --- /dev/null +++ b/thank-you.php @@ -0,0 +1,39 @@ +getMessage()); + } +} + +page_head('Thank You — ' . project_name(), 'Confirmation page for your landing page request.'); +page_nav('home'); +?> +
+
+
+
+
+

Request submitted

+

Thank you.

+

Your request has been saved. If email notifications are configured, the team also received a message with your details.

+ + + View saved lead + + Back to landing page +
+
+
+
+
+