This commit is contained in:
Flatlogic Bot 2026-05-27 22:29:09 +00:00
parent 4e9f3637d4
commit ffe2db4413
4 changed files with 1147 additions and 556 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,56 @@
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 quoteForm = document.getElementById('quoteForm');
const toastEl = document.getElementById('quoteToast');
const nav = document.getElementById('mainNav');
const navToggle = document.querySelector('[data-bs-target="#mainNav"]');
const hasBootstrap = Boolean(window.bootstrap);
if (nav && navToggle && !hasBootstrap) {
navToggle.addEventListener('click', () => {
const isOpen = nav.classList.toggle('show');
navToggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
});
}
if (toastEl && hasBootstrap) {
window.bootstrap.Toast.getOrCreateInstance(toastEl, { delay: 5200 }).show();
}
document.querySelectorAll('a[href^="#"]').forEach((link) => {
link.addEventListener('click', () => {
if (nav && nav.classList.contains('show')) {
if (hasBootstrap) {
window.bootstrap.Collapse.getOrCreateInstance(nav).hide();
} else {
nav.classList.remove('show');
navToggle?.setAttribute('aria-expanded', 'false');
}
}
});
});
if (!quoteForm) return;
quoteForm.addEventListener('submit', (event) => {
const brief = quoteForm.querySelector('#message');
const submitButton = quoteForm.querySelector('button[type="submit"]');
if (brief && brief.value.trim().length < 20) {
brief.setCustomValidity('Share at least 20 characters about the project.');
} else if (brief) {
brief.setCustomValidity('');
}
if (!quoteForm.checkValidity()) {
event.preventDefault();
event.stopPropagation();
const firstInvalid = quoteForm.querySelector(':invalid');
if (firstInvalid) firstInvalid.focus({ preventScroll: false });
} else if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = 'Sending…';
}
quoteForm.classList.add('was-validated');
});
});

150
index.php
View File

@ -1,150 +1,2 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
</body>
</html>
require __DIR__ . '/landing.php';

281
landing.php Normal file
View File

@ -0,0 +1,281 @@
<?php
declare(strict_types=1);
@date_default_timezone_set('UTC');
session_start();
require_once __DIR__ . '/mail/MailService.php';
$projectName = $_SERVER['PROJECT_NAME'] ?? 'Northstar Agency';
$now = date('Y-m-d H:i:s');
$flash = $_SESSION['quote_flash'] ?? null;
unset($_SESSION['quote_flash']);
$form = [
'name' => '',
'company' => '',
'email' => '',
'phone' => '',
'service' => '',
'budget' => '',
'timeline' => '',
'message' => '',
'consent' => '',
];
$errors = [];
if (empty($_SESSION['quote_token'])) {
$_SESSION['quote_token'] = bin2hex(random_bytes(24));
}
function h(?string $value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
foreach ($form as $key => $_) {
$form[$key] = trim((string)($_POST[$key] ?? ''));
}
$token = (string)($_POST['quote_token'] ?? '');
$honeypot = trim((string)($_POST['website'] ?? ''));
if (!hash_equals($_SESSION['quote_token'], $token)) {
$errors['form'] = 'Please refresh the page and try again.';
}
if ($honeypot !== '') {
$errors['form'] = 'We could not process this request.';
}
if ($form['name'] === '' || strlen($form['name']) < 2) {
$errors['name'] = 'Enter your name.';
}
if ($form['company'] === '') {
$errors['company'] = 'Enter your company name.';
}
if (!filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Enter a valid work email.';
}
if ($form['service'] === '') {
$errors['service'] = 'Choose a service area.';
}
if ($form['budget'] === '') {
$errors['budget'] = 'Choose an approximate budget.';
}
if (strlen($form['message']) < 20) {
$errors['message'] = 'Share at least 20 characters about the project.';
}
if ($form['consent'] !== '1') {
$errors['consent'] = 'Please confirm we can contact you about this request.';
}
if (!$errors) {
$emailBody = "Company: {$form['company']}\n"
. "Phone: {$form['phone']}\n"
. "Service: {$form['service']}\n"
. "Budget: {$form['budget']}\n"
. "Timeline: {$form['timeline']}\n\n"
. "Project brief:\n{$form['message']}";
$result = MailService::sendContactMessage(
$form['name'],
$form['email'],
$emailBody,
null,
'New agency quote request'
);
if (!empty($result['success'])) {
$_SESSION['quote_flash'] = [
'type' => 'success',
'message' => 'Thanks — your quote request was sent. We will reply with next steps shortly.',
];
$_SESSION['quote_token'] = bin2hex(random_bytes(24));
header('Location: /?quote=sent#quote');
exit;
}
error_log('Quote form mail error: ' . ($result['error'] ?? 'unknown'));
$errors['form'] = 'The request could not be sent right now. Please try again in a moment.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= h($projectName) ?> — Agency Growth & Lead Generation</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<meta name="robots" content="index, follow" />
<meta property="og:title" content="<?= h($projectName) ?> — Agency Growth & Lead Generation" />
<meta name="twitter:card" content="summary_large_image" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9Oer+R4ty2fYt4zpgp8+kCxO89H/MOOJ5iYvF0s3yD" crossorigin="anonymous">
<link rel="stylesheet" href="assets/css/custom.css?v=2025052702">
</head>
<body class="agency-page">
<a class="visually-hidden-focusable skip-link" href="#main">Skip to content</a>
<header class="site-header sticky-top">
<nav class="navbar navbar-expand-lg" aria-label="Primary navigation">
<div class="container">
<a class="navbar-brand" href="#top" aria-label="<?= h($projectName) ?> home">
<span class="brand-mark" aria-hidden="true">N</span><?= h($projectName) ?>
</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-2">
<li class="nav-item"><a class="nav-link" href="#services">Services</a></li>
<li class="nav-item"><a class="nav-link" href="#work">Case studies</a></li>
<li class="nav-item"><a class="nav-link" href="#proof">Proof</a></li>
<li class="nav-item"><a class="btn btn-dark btn-sm ms-lg-2" href="#quote">Request quote</a></li>
</ul>
</div>
</div>
</nav>
</header>
<main id="main" tabindex="-1">
<section id="top" class="hero-section">
<div class="container">
<div class="row align-items-center g-4 g-lg-5">
<div class="col-lg-7">
<p class="eyebrow">Performance-led agency for ambitious teams</p>
<h1>Win better leads with a sharper offer, proof, and conversion path.</h1>
<p class="hero-copy">We help B2B and service brands turn quiet websites into focused acquisition systems: clear positioning, fast pages, campaign-ready content, and quote forms that route straight to your inbox.</p>
<div class="d-flex flex-column flex-sm-row gap-2 mt-4">
<a class="btn btn-dark btn-lg" href="#quote">Request a quote</a>
<a class="btn btn-outline-dark btn-lg" href="#work">View outcomes</a>
</div>
<dl class="metric-strip" aria-label="Agency performance highlights">
<div><dt>42%</dt><dd>avg. lead lift</dd></div>
<div><dt>21 days</dt><dd>launch sprint</dd></div>
<div><dt>98</dt><dd>page speed score</dd></div>
</dl>
</div>
<div class="col-lg-5">
<aside class="offer-card" aria-label="Agency offer summary">
<div class="card-label">Core offer</div>
<h2>Lead generation audit + landing system</h2>
<p>Positioning, service pages, quote flow, analytics events, and post-launch optimization recommendations.</p>
<ul class="check-list">
<li>Messaging mapped to search intent</li>
<li>Conversion-focused page sections</li>
<li>Email-ready quote intake workflow</li>
</ul>
<a href="#quote" class="link-dark fw-semibold">Start with a project brief</a>
</aside>
</div>
</div>
</div>
</section>
<section class="logo-section" aria-labelledby="partner-heading">
<div class="container">
<h2 id="partner-heading" class="section-kicker">Trusted by teams building category authority</h2>
<div class="logo-grid" aria-label="Partner logos">
<span>Atlas</span><span>Mercury</span><span>Summit</span><span>Beacon</span><span>Vector</span>
</div>
</div>
</section>
<section id="services" class="section-pad">
<div class="container">
<div class="section-head">
<p class="eyebrow">Services</p>
<h2>Everything a lead-gen site needs, without excess.</h2>
<p>Strategy, copy, design, implementation, and measurement packaged into pragmatic delivery tracks.</p>
</div>
<div class="row g-3">
<div class="col-md-4"><article class="service-card h-100"><span class="icon-box" aria-hidden="true">01</span><h3>Offer & positioning</h3><p>Clarify the promise, objections, audiences, and proof points that make visitors take action.</p></article></div>
<div class="col-md-4"><article class="service-card h-100"><span class="icon-box" aria-hidden="true">02</span><h3>Landing pages</h3><p>Build fast, responsive pages with service detail, case studies, testimonials, and clear CTAs.</p></article></div>
<div class="col-md-4"><article class="service-card h-100"><span class="icon-box" aria-hidden="true">03</span><h3>Conversion systems</h3><p>Connect quote forms, email routing, analytics events, and follow-up recommendations.</p></article></div>
</div>
</div>
</section>
<section id="work" class="section-pad section-muted">
<div class="container">
<div class="section-head"><p class="eyebrow">Case studies</p><h2>Recent outcomes from focused acquisition work.</h2></div>
<div class="row g-3">
<div class="col-lg-4"><article class="case-card"><p class="case-meta">SaaS consulting</p><h3>Rebuilt service pages around buying intent.</h3><p>Reduced decision friction with clearer packages, proof blocks, and a shorter quote path.</p><strong>+58% qualified enquiries</strong></article></div>
<div class="col-lg-4"><article class="case-card"><p class="case-meta">Professional services</p><h3>Created a fast campaign landing system.</h3><p>Launched reusable pages for paid search and partner referrals in under three weeks.</p><strong>2.4× demo requests</strong></article></div>
<div class="col-lg-4"><article class="case-card"><p class="case-meta">Local agency</p><h3>Turned vague traffic into quote conversations.</h3><p>Restructured the homepage around services, objections, testimonials, and contact intent.</p><strong>-31% bounce rate</strong></article></div>
</div>
</div>
</section>
<section id="proof" class="section-pad">
<div class="container">
<div class="row g-3 align-items-stretch">
<div class="col-lg-5"><div class="section-head sticky-copy"><p class="eyebrow">Testimonials</p><h2>Clear strategy, clean execution, measurable progress.</h2><p>Proof is built into the page so prospects quickly understand what changed and why it matters.</p></div></div>
<div class="col-lg-7"><div class="testimonial-stack"><figure class="testimonial-card"><blockquote>“The new site made our value proposition obvious. We started getting better-fit enquiries within the first month.</blockquote><figcaption> Maya Chen, Founder at Beacon Advisory</figcaption></figure><figure class="testimonial-card"><blockquote>“They cut the clutter, tightened our proof, and shipped a page we were proud to send prospects to.</blockquote><figcaption> Jordan Mills, Growth Lead at Atlas Studio</figcaption></figure></div></div>
</div>
</div>
</section>
<section id="quote" class="section-pad section-muted">
<div class="container">
<div class="row g-4 g-lg-5 align-items-start">
<div class="col-lg-5">
<p class="eyebrow">Request a quote</p>
<h2>Tell us what you want to improve.</h2>
<p class="lead-small">Complete the brief and it will be emailed to the configured inbox. We will respond with recommended scope, timeline, and next steps.</p>
<div class="notice-box" role="note">This is for testing purposes only Flatlogic does not guarantee usage of the mail server. Please set up your own SMTP in <code>.env</code> (MAIL_/SMTP_ vars) with our AI Agent.</div>
</div>
<div class="col-lg-7">
<div class="form-panel">
<?php if ($flash): ?><div class="alert alert-success" role="status"><?= h($flash['message'] ?? '') ?></div><?php endif; ?>
<?php if (!empty($errors['form'])): ?><div class="alert alert-danger" role="alert"><?= h($errors['form']) ?></div><?php endif; ?>
<form method="post" action="#quote" id="quoteForm" novalidate>
<input type="hidden" name="quote_token" value="<?= h($_SESSION['quote_token']) ?>">
<div class="d-none" aria-hidden="true"><label for="website">Website</label><input id="website" name="website" tabindex="-1" autocomplete="off"></div>
<div class="row g-3">
<div class="col-md-6"><label class="form-label" for="name">Name</label><input class="form-control <?= isset($errors['name']) ? 'is-invalid' : '' ?>" id="name" name="name" value="<?= h($form['name']) ?>" autocomplete="name" required><div class="invalid-feedback"><?= h($errors['name'] ?? 'Enter your name.') ?></div></div>
<div class="col-md-6"><label class="form-label" for="company">Company</label><input class="form-control <?= isset($errors['company']) ? 'is-invalid' : '' ?>" id="company" name="company" value="<?= h($form['company']) ?>" autocomplete="organization" required><div class="invalid-feedback"><?= h($errors['company'] ?? 'Enter your company.') ?></div></div>
<div class="col-md-6"><label class="form-label" for="email">Work email</label><input class="form-control <?= isset($errors['email']) ? 'is-invalid' : '' ?>" id="email" type="email" name="email" value="<?= h($form['email']) ?>" autocomplete="email" required><div class="invalid-feedback"><?= h($errors['email'] ?? 'Enter a valid email.') ?></div></div>
<div class="col-md-6"><label class="form-label" for="phone">Phone <span class="text-secondary">optional</span></label><input class="form-control" id="phone" name="phone" value="<?= h($form['phone']) ?>" autocomplete="tel"></div>
<div class="col-md-6"><label class="form-label" for="service">Service interest</label><select class="form-select <?= isset($errors['service']) ? 'is-invalid' : '' ?>" id="service" name="service" required><option value="">Select one</option><?php foreach (['Offer & positioning', 'Landing page build', 'SEO content system', 'Conversion optimization', 'Full lead-gen sprint'] as $option): ?><option value="<?= h($option) ?>" <?= $form['service'] === $option ? 'selected' : '' ?>><?= h($option) ?></option><?php endforeach; ?></select><div class="invalid-feedback"><?= h($errors['service'] ?? 'Choose a service.') ?></div></div>
<div class="col-md-6"><label class="form-label" for="budget">Approx. budget</label><select class="form-select <?= isset($errors['budget']) ? 'is-invalid' : '' ?>" id="budget" name="budget" required><option value="">Select range</option><?php foreach (['Under $5k', '$5k$10k', '$10k$25k', '$25k+', 'Not sure yet'] as $option): ?><option value="<?= h($option) ?>" <?= $form['budget'] === $option ? 'selected' : '' ?>><?= h($option) ?></option><?php endforeach; ?></select><div class="invalid-feedback"><?= h($errors['budget'] ?? 'Choose a budget range.') ?></div></div>
<div class="col-12"><label class="form-label" for="timeline">Timeline</label><select class="form-select" id="timeline" name="timeline"><option value="">Select timeline</option><?php foreach (['ASAP', 'This month', 'This quarter', 'Planning ahead'] as $option): ?><option value="<?= h($option) ?>" <?= $form['timeline'] === $option ? 'selected' : '' ?>><?= h($option) ?></option><?php endforeach; ?></select></div>
<div class="col-12"><label class="form-label" for="message">Project brief</label><textarea class="form-control <?= isset($errors['message']) ? 'is-invalid' : '' ?>" id="message" name="message" rows="5" minlength="20" required placeholder="What should improve? Include goals, audience, current site, or campaign context."><?= h($form['message']) ?></textarea><div class="invalid-feedback"><?= h($errors['message'] ?? 'Share at least 20 characters.') ?></div></div>
<div class="col-12"><div class="form-check"><input class="form-check-input <?= isset($errors['consent']) ? 'is-invalid' : '' ?>" type="checkbox" value="1" id="consent" name="consent" <?= $form['consent'] === '1' ? 'checked' : '' ?> required><label class="form-check-label" for="consent">You may contact me about this quote request.</label><div class="invalid-feedback"><?= h($errors['consent'] ?? 'Please confirm consent.') ?></div></div></div>
<div class="col-12 d-grid d-sm-flex align-items-center gap-3"><button class="btn btn-dark btn-lg" type="submit">Send quote request</button><span class="form-assurance">Typical reply: 1 business day.</span></div>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="site-footer"><div class="container d-flex flex-column flex-md-row justify-content-between gap-2"><span>© <?= date('Y') ?> <?= h($projectName) ?>. Built for fast, search-friendly lead generation.</span><span>PHP runtime healthy — <?= h($now) ?> UTC</span></div></footer>
<?php if ($flash): ?>
<div class="toast-container position-fixed bottom-0 end-0 p-3"><div id="quoteToast" class="toast align-items-center text-bg-dark border-0" role="status" aria-live="polite" aria-atomic="true"><div class="d-flex"><div class="toast-body"><?= h($flash['message'] ?? '') ?></div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button></div></div></div>
<?php endif; ?>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous" defer></script>
<script src="assets/js/main.js?v=2025052702" defer></script>
</body>
</html>