diff --git a/apply.php b/apply.php new file mode 100644 index 0000000..179ad4e --- /dev/null +++ b/apply.php @@ -0,0 +1,65 @@ +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 = " +

New Application Received

+

A new application has been submitted for FinMox founding access:

+ +"; +$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; + diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..58ae5b8 --- /dev/null +++ b/assets/css/custom.css @@ -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; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..bb04de6 --- /dev/null +++ b/assets/js/main.js @@ -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'); + } +} diff --git a/db/migration_ran.flag b/db/migration_ran.flag new file mode 100644 index 0000000..5c58490 --- /dev/null +++ b/db/migration_ran.flag @@ -0,0 +1 @@ +ran \ No newline at end of file diff --git a/db/migrations/01_create_applications_table.sql b/db/migrations/01_create_applications_table.sql new file mode 100644 index 0000000..dc8bceb --- /dev/null +++ b/db/migrations/01_create_applications_table.sql @@ -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 +); \ No newline at end of file diff --git a/index.php b/index.php index 7205f3d..0f9540b 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,185 @@ - - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + FinMox — HR Execution Operating System + + + + + + + + - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ + + + +
+
+
+
FM
+
FinMox
+
-
- + + + + +
+ + Apply for Access + + + +
+ + + + + + +
+ + +
+ + +
+
+ + +
+
+ + HR execution control — not another HR tool +
+ +

+ The HR Execution
+ Operating System +

+ +

+ FinMox sits on top of your existing stack and controls how hiring decisions get executed — + so teams move faster, stay consistent, and reduce risk without replacing your ATS or HR team. +

+ + + +
+ 1–10 roles/month + 50–300 candidates/role + Multiple stakeholders + Human-in-the-loop by design + Audit-ready recordkeeping +
+ +
+
FinMox controls the flow of labor decisions.
+
+ Humans define intent → systems enforce execution → agents remove friction → data creates defensibility. +
+
+
+
+
+
+ + + + + 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()); + } + } + ?> + +
+ + - + \ No newline at end of file