12/18/25 - V.1

This commit is contained in:
Flatlogic Bot 2025-12-19 04:59:06 +00:00
parent 4e47a77fe4
commit 5c4ffc3cf4
6 changed files with 375 additions and 145 deletions

65
apply.php Normal file
View File

@ -0,0 +1,65 @@
<?php
require_once 'db/config.php';
require_once 'mail/MailService.php';
// 1. Validation
$name = trim($_POST['name'] ?? '');
$company = trim($_POST['company'] ?? '');
$email = trim($_POST['email'] ?? '');
$role = trim($_POST['role'] ?? '');
$error = '';
if (empty($name) || empty($company) || empty($email) || empty($role)) {
$error = 'All fields are required.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = 'Please enter a valid email address.';
}
if ($error) {
header('Location: index.php?error=' . urlencode($error) . '#apply');
exit;
}
// 2. Database Insert
try {
$pdo = db();
$stmt = $pdo->prepare(
'INSERT INTO applications (name, company, email, role) VALUES (?, ?, ?, ?)'
);
$stmt->execute([$name, $company, $email, $role]);
} catch (PDOException $e) {
// In a real app, log this error. For now, redirect with a generic error.
error_log('DB Error: ' . $e->getMessage());
header('Location: index.php?error=' . urlencode('A database error occurred.') . '#apply');
exit;
}
// 3. Email Notification
$to = getenv('MAIL_TO') ?: null; // Use admin email from .env
$subject = 'New FinMox Founding Access Application';
$htmlBody = "
<h1>New Application Received</h1>
<p>A new application has been submitted for FinMox founding access:</p>
<ul>
<li><strong>Name:</strong> " . htmlspecialchars($name) . "</li>
<li><strong>Company:</strong> " . htmlspecialchars($company) . "</li>
<li><strong>Email:</strong> " . htmlspecialchars($email) . "</li>
<li><strong>Role:</strong> " . htmlspecialchars($role) . "</li>
</ul>
";
$textBody = "New Application:\nName: $name\nCompany: $company\nEmail: $email\nRole: $role";
// Use MailService, but don't block the user if it fails
try {
MailService::sendMail($to, $subject, $htmlBody, $textBody);
} catch (Exception $e) {
// Log email error but don't prevent user from seeing success
error_log('MailService Error: ' . $e->getMessage());
}
// 4. Redirect to Success
header('Location: index.php?status=applied#apply');
exit;

53
assets/css/custom.css Normal file
View File

@ -0,0 +1,53 @@
:root{
--ink:#0b0b0c;
--paper:#fbfaf7;
--warm:#f2efe7;
--warm2:#ebe6da;
--line: rgba(11,11,12,.08);
--shadow: 0 18px 48px rgba(11,11,12,.10);
--shadow2: 0 10px 26px rgba(11,11,12,.10);
--radius: 28px;
}
html, body { height: 100%; }
body { font-family: 'Inter', sans-serif; background: var(--paper); color: var(--ink); }
/* page routing */
.page.hidden { display: none; }
/* warm glass cards */
.panel { border: 1px solid var(--line); border-radius: var(--radius); background: rgba(255,255,255,.70); box-shadow: var(--shadow2); backdrop-filter: blur(10px); }
.panel-strong { border: 1px solid var(--line); border-radius: var(--radius); background: rgba(255,255,255,.92); box-shadow: var(--shadow); }
.chip { border: 1px solid var(--line); border-radius: 999px; background: rgba(255,255,255,.75); }
.softline { border-color: var(--line); }
/* hero background */
.bg-warm {
background:
radial-gradient(1200px 520px at 20% 15%, rgba(243,236,223,.95), rgba(251,250,247,0) 60%),
radial-gradient(900px 520px at 85% 20%, rgba(236,232,219,.9), rgba(251,250,247,0) 55%),
radial-gradient(900px 520px at 55% 90%, rgba(241,238,230,.9), rgba(251,250,247,0) 60%),
var(--paper);
}
/* subtle motion */
.fade { transition: opacity .18s ease, transform .18s ease; }
.hoverlift { transition: transform .18s ease, box-shadow .18s ease; }
.hoverlift:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
/* nav active state */
.navlink.active { font-weight: 700; }
/* mobile menu */
.drawer.hidden { display:none; }
/* details */
details[open] summary { font-weight: 700; }
summary { cursor: pointer; }
summary::-webkit-details-marker { display:none; }
/* input focus */
.chip:focus-within {
outline: 2px solid var(--ink);
outline-offset: 2px;
}

68
assets/js/main.js Normal file
View File

@ -0,0 +1,68 @@
document.addEventListener('DOMContentLoaded', () => {
// Set initial page based on URL hash or default to 'home'
const initialPage = window.location.hash.substring(1) || 'home';
openPage(initialPage, true);
// Handle back/forward navigation
window.addEventListener('popstate', (event) => {
const pageId = event.state ? event.state.page : 'home';
openPage(pageId, true);
});
// Check for query params like ?status=applied and switch to the right page
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('status') || urlParams.has('error')) {
openPage('apply', true);
}
});
function openPage(pageId, isHistoryNavigation = false) {
if (!pageId) pageId = 'home';
// Hide all pages
document.querySelectorAll('.page').forEach(page => {
page.classList.add('hidden');
});
// Show the target page
const targetPage = document.getElementById(`page-${pageId}`);
if (targetPage) {
targetPage.classList.remove('hidden');
} else {
// Fallback to home if page not found
document.getElementById('page-home').classList.remove('hidden');
pageId = 'home';
}
// Update nav links
document.querySelectorAll('.navlink').forEach(link => {
// Use endsWith to correctly match hrefs like '/#home' and '#home'
if (link.getAttribute('href').endsWith(`#${pageId}`)) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
// Construct the new URL, preserving the base path
const newUrl = window.location.pathname + `#${pageId}`;
if (!isHistoryNavigation) {
// Create a new history state
history.pushState({ page: pageId }, null, newUrl);
} else {
// If it IS history navigation, we might need to clean up query params from the URL
if (window.location.search) {
history.replaceState({ page: pageId }, null, newUrl);
}
}
}
function toggleMenu(forceClose = false) {
const drawer = document.getElementById('drawer');
if (forceClose || !drawer.classList.contains('hidden')) {
drawer.classList.add('hidden');
} else {
drawer.classList.remove('hidden');
}
}

1
db/migration_ran.flag Normal file
View File

@ -0,0 +1 @@
ran

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS `applications` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`company` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`role` VARCHAR(255) NOT NULL,
`applied_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

325
index.php
View File

@ -1,150 +1,185 @@
<?php <!DOCTYPE html>
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"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>New Style</title> <title>FinMox HR Execution Operating System</title>
<?php
// Read project preview data from environment <!-- Tailwind CDN (AppWizzy-safe) -->
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; <script src="https://cdn.tailwindcss.com"></script>
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?> <!-- Font -->
<?php if ($projectDescription): ?> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' /> <link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<!-- 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> </head>
<body>
<main> <body class="bg-warm">
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1> <!-- TOP NAV -->
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <header class="max-w-7xl mx-auto px-6 py-5">
<span class="sr-only">Loading…</span> <div class="panel-strong px-5 py-4 flex items-center justify-between">
</div> <div class="flex items-center gap-3">
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p> <div class="h-9 w-9 rounded-2xl bg-black text-white flex items-center justify-center text-sm font-semibold">FM</div>
<p class="hint">This page will update automatically as the plan is implemented.</p> <div class="font-semibold tracking-tight">FinMox</div>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p> <span class="hidden sm:inline chip px-3 py-1 text-xs text-gray-700">HR Execution OS</span>
</div> </div>
</main>
<footer> <!-- Desktop nav -->
Page updated: <?= htmlspecialchars($now) ?> (UTC) <nav class="hidden lg:flex items-center gap-7 text-sm">
</footer> <a class="navlink" href="#home" onclick="openPage('home'); return false;">Home</a>
<a class="navlink" href="#problem" onclick="openPage('problem'); return false;">Problem</a>
<a class="navlink" href="#why" onclick="openPage('why'); return false;">Why FinMox</a>
<a class="navlink" href="#how" onclick="openPage('how'); return false;">How It Works</a>
<a class="navlink" href="#roi" onclick="openPage('roi'); return false;">ROI</a>
<a class="navlink" href="#pricing" onclick="openPage('pricing'); return false;">Pricing</a>
<a class="navlink" href="#who" onclick="openPage('who'); return false;">Who Its For</a>
<a class="navlink" href="#trust" onclick="openPage('trust'); return false;">Security & Trust</a>
<a class="navlink" href="#faq" onclick="openPage('faq'); return false;">FAQ</a>
</nav>
<div class="flex items-center gap-2">
<a class="navlink hidden sm:inline text-sm" href="#signin" onclick="openPage('signin'); return false;">Sign In</a>
<a href="#apply" onclick="openPage('apply'); return false;" class="bg-black text-white text-sm px-4 py-2 rounded-2xl hoverlift">Apply for Access</a>
<!-- Mobile menu -->
<button class="lg:hidden chip px-3 py-2 text-sm" onclick="toggleMenu()" aria-label="Open menu">Menu</button>
</div>
</div>
<!-- Mobile drawer -->
<div id="drawer" class="drawer hidden mt-3 panel-strong p-4">
<div class="grid gap-2 text-sm">
<a class="navlink" href="#home" onclick="openPage('home'); toggleMenu(true); return false;">Home</a>
<a class="navlink" href="#problem" onclick="openPage('problem'); toggleMenu(true); return false;">Problem</a>
<a class="navlink" href="#why" onclick="openPage('why'); toggleMenu(true); return false;">Why FinMox</a>
<a class="navlink" href="#how" onclick="openPage('how'); toggleMenu(true); return false;">How It Works</a>
<a class="navlink" href="#roi" onclick="openPage('roi'); toggleMenu(true); return false;">ROI</a>
<a class="navlink" href="#pricing" onclick="openPage('pricing'); toggleMenu(true); return false;">Pricing</a>
<a class="navlink" href="#who" onclick="openPage('who'); toggleMenu(true); return false;">Who Its For</a>
<a class="navlink" href="#trust" onclick="openPage('trust'); toggleMenu(true); return false;">Security & Trust</a>
<a class="navlink" href="#roadmap" onclick="openPage('roadmap'); toggleMenu(true); return false;">Roadmap</a>
<a class="navlink" href="#faq" onclick="openPage('faq'); toggleMenu(true); return false;">FAQ</a>
<div class="h-px softline border-t my-2"></div>
<a class="navlink" href="#signin" onclick="openPage('signin'); toggleMenu(true); return false;">Sign In</a>
<a href="#apply" onclick="openPage('apply'); toggleMenu(true); return false;" class="bg-black text-white text-sm px-4 py-2 rounded-2xl">Apply for Access</a>
</div>
</div>
</header>
<main class="max-w-7xl mx-auto px-6 pb-16">
<!-- HOME PAGE -->
<section id="page-home" class="page fade">
<!-- HERO -->
<section class="panel-strong overflow-hidden">
<div class="grid lg:grid-cols-12 gap-10 p-8 lg:p-10 items-center">
<!-- Copy -->
<div class="lg:col-span-6">
<div class="inline-flex items-center gap-2 chip px-3 py-1 text-xs">
<span class="h-2 w-2 rounded-full bg-black"></span>
HR execution control not another HR tool
</div>
<h1 class="mt-6 text-4xl md:text-6xl font-extrabold tracking-tight leading-[1.02]">
The HR Execution<br/>
Operating System
</h1>
<p class="mt-6 text-base md:text-lg text-gray-700">
FinMox sits on top of your existing stack and controls how hiring decisions get executed
so teams move faster, stay consistent, and reduce risk <strong>without replacing your ATS or HR team</strong>.
</p>
<div class="mt-7 flex flex-wrap gap-3">
<a href="#apply" onclick="openPage('apply'); return false;" class="bg-black text-white px-5 py-3 rounded-2xl text-sm hoverlift">Apply for Founding Access</a>
<a href="#apply" onclick="openPage('apply'); return false;" class="chip px-5 py-3 rounded-2xl text-sm hoverlift">Book a Demo</a>
</div>
<div class="mt-7 flex flex-wrap gap-2 text-xs text-gray-600">
<span class="chip px-3 py-1">110 roles/month</span>
<span class="chip px-3 py-1">50300 candidates/role</span>
<span class="chip px-3 py-1">Multiple stakeholders</span>
<span class="chip px-3 py-1">Human-in-the-loop by design</span>
<span class="chip px-3 py-1">Audit-ready recordkeeping</span>
</div>
<div class="mt-7 panel p-5">
<div class="text-sm font-semibold">FinMox controls the flow of labor decisions.</div>
<div class="mt-2 text-sm text-gray-700">
Humans define intent systems enforce execution agents remove friction data creates defensibility.
</div>
</div>
</div>
</div>
</section>
</section>
<!-- APPLY PAGE (New) -->
<section id="page-apply" class="page hidden fade">
<div class="panel-strong p-8 lg:p-12 max-w-2xl mx-auto">
<?php if (isset($_GET['status']) && $_GET['status'] == 'applied'): ?>
<div class="text-center">
<h2 class="text-2xl font-bold tracking-tight">Thank you!</h2>
<p class="mt-4 text-gray-700">Your application for founding access has been received. We're excited to review it and will get back to you shortly.</p>
<a href="#home" onclick="openPage('home'); return false;" class="mt-6 inline-block bg-black text-white px-5 py-3 rounded-2xl text-sm hoverlift">Back to Home</a>
</div>
<?php else: ?>
<h2 class="text-2xl font-bold tracking-tight">Apply for Founding Access</h2>
<p class="mt-3 text-gray-600">Join a select group of companies to shape the future of HR execution. Fill out the form below to get started.</p>
<?php if (isset($_GET['error'])): ?>
<div class="mt-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-xl relative" role="alert">
<strong class="font-bold">Oops!</strong>
<span class="block sm:inline"><?php echo htmlspecialchars($_GET['error']); ?></span>
</div>
<?php endif; ?>
<form action="apply.php" method="POST" class="mt-6 grid gap-y-6">
<div>
<label for="name" class="block text-sm font-medium text-gray-800">Full Name</label>
<input type="text" name="name" id="name" required class="mt-2 block w-full chip px-4 py-3 text-base" placeholder="Jane Doe">
</div>
<div>
<label for="company" class="block text-sm font-medium text-gray-800">Company Name</label>
<input type="text" name="company" id="company" required class="mt-2 block w-full chip px-4 py-3 text-base" placeholder="Acme Inc.">
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-800">Work Email</label>
<input type="email" name="email" id="email" required class="mt-2 block w-full chip px-4 py-3 text-base" placeholder="you@company.com">
</div>
<div>
<label for="role" class="block text-sm font-medium text-gray-800">Your Role / Title</label>
<input type="text" name="role" id="role" required class="mt-2 block w-full chip px-4 py-3 text-base" placeholder="Head of Talent">
</div>
<div>
<button type="submit" class="w-full bg-black text-white px-5 py-3 rounded-2xl text-sm font-semibold hoverlift">Submit Application</button>
</div>
</form>
<?php endif; ?>
</div>
</section>
<?php
// This will run the migration on the first visit
if (!file_exists('db/migration_ran.flag')) {
require_once 'db/config.php';
try {
$pdo = db();
$sql = file_get_contents('db/migrations/01_create_applications_table.sql');
$pdo->exec($sql);
file_put_contents('db/migration_ran.flag', 'ran');
} catch (PDOException $e) {
// Do not block UI for db errors, just log it.
error_log("Migration failed: " . $e->getMessage());
}
}
?>
</main>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body> </body>
</html> </html>