454 lines
21 KiB
PHP
454 lines
21 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
@ini_set('display_errors', '1');
|
||
@error_reporting(E_ALL);
|
||
@date_default_timezone_set('UTC');
|
||
|
||
require_once __DIR__ . '/db/config.php';
|
||
require_once __DIR__ . '/mail/MailService.php';
|
||
|
||
// Helper to fetch images
|
||
function get_images(string $queries): array {
|
||
$apiUrl = 'http://127.0.0.1/api/pexels.php?queries=' . urlencode($queries);
|
||
$ch = curl_init();
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_URL => $apiUrl,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 5,
|
||
]);
|
||
$resp = curl_exec($ch);
|
||
curl_close($ch);
|
||
return json_decode($resp ?: '[]', true) ?: [];
|
||
}
|
||
|
||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||
$now = date('Y-m-d H:i:s');
|
||
|
||
// Fetch images for sections
|
||
$heroImages = get_images('studio,workspace,creative,editorial');
|
||
$workImages = get_images('design,code,analytics');
|
||
$testimonialImages = get_images('person,professional,business');
|
||
|
||
$formData = [
|
||
'name' => '',
|
||
'email' => '',
|
||
'message' => '',
|
||
];
|
||
$errors = [];
|
||
$toast = null;
|
||
$toastType = 'success';
|
||
$mailWarning = null;
|
||
|
||
function ensure_contact_table(): void {
|
||
db()->exec(
|
||
"CREATE TABLE IF NOT EXISTS contact_requests (
|
||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||
name VARCHAR(120) NOT NULL,
|
||
email VARCHAR(160) NOT NULL,
|
||
message TEXT NOT NULL,
|
||
status VARCHAR(30) NOT NULL DEFAULT 'new',
|
||
ip_address VARCHAR(45) DEFAULT NULL,
|
||
user_agent VARCHAR(255) DEFAULT NULL,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
||
);
|
||
}
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$formData['name'] = trim($_POST['name'] ?? '');
|
||
$formData['email'] = trim($_POST['email'] ?? '');
|
||
$formData['message'] = trim($_POST['message'] ?? '');
|
||
|
||
if (mb_strlen($formData['name']) < 2) {
|
||
$errors[] = 'Please enter your name.';
|
||
}
|
||
if (!filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) {
|
||
$errors[] = 'Please provide a valid email address.';
|
||
}
|
||
if (mb_strlen($formData['message']) < 10) {
|
||
$errors[] = 'Please add a brief message (at least 10 characters).';
|
||
}
|
||
|
||
if (!$errors) {
|
||
try {
|
||
ensure_contact_table();
|
||
$stmt = db()->prepare(
|
||
'INSERT INTO contact_requests (name, email, message, ip_address, user_agent) VALUES (:name, :email, :message, :ip, :agent)'
|
||
);
|
||
$stmt->execute([
|
||
':name' => $formData['name'],
|
||
':email' => $formData['email'],
|
||
':message' => $formData['message'],
|
||
':ip' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||
':agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
|
||
]);
|
||
|
||
$to = getenv('MAIL_TO') ?: null;
|
||
$mailRes = MailService::sendContactMessage(
|
||
$formData['name'],
|
||
$formData['email'],
|
||
$formData['message'],
|
||
$to,
|
||
'New portfolio inquiry'
|
||
);
|
||
|
||
if (empty($mailRes['success'])) {
|
||
$mailWarning = 'Saved your message, but email delivery failed. Please confirm SMTP settings.';
|
||
}
|
||
|
||
$toast = 'Thanks for reaching out! I will reply within 1–2 business days.';
|
||
$toastType = $mailWarning ? 'warning' : 'success';
|
||
$formData = ['name' => '', 'email' => '', 'message' => ''];
|
||
} catch (Throwable $e) {
|
||
$errors[] = 'Something went wrong while saving your message. Please try again.';
|
||
}
|
||
}
|
||
}
|
||
?>
|
||
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Personal Portfolio</title>
|
||
<?php if ($projectDescription): ?>
|
||
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||
<?php endif; ?>
|
||
<?php if ($projectImageUrl): ?>
|
||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||
<?php endif; ?>
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<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;500;600&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time(); ?>">
|
||
<style>
|
||
.card-img-top { height: 200px; object-fit: cover; }
|
||
.testi-img { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="site-background" aria-hidden="true">
|
||
<span class="bg-grid"></span>
|
||
<span class="bg-orb bg-orb-one" data-depth="18"></span>
|
||
<span class="bg-orb bg-orb-two" data-depth="28"></span>
|
||
<span class="bg-orb bg-orb-three" data-depth="38"></span>
|
||
<span class="bg-cursor-glow"></span>
|
||
</div>
|
||
|
||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom fixed-top">
|
||
<div class="container">
|
||
<a class="navbar-brand fw-semibold" href="#top">Alex Carter</a>
|
||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#siteNav" aria-controls="siteNav" aria-expanded="false" aria-label="Toggle navigation">
|
||
<span class="navbar-toggler-icon"></span>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="siteNav">
|
||
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||
<li class="nav-item"><a class="nav-link" href="#work">Work</a></li>
|
||
<li class="nav-item"><a class="nav-link" href="#about">About</a></li>
|
||
<li class="nav-item"><a class="nav-link" href="#testimonials">Testimonials</a></li>
|
||
<li class="nav-item"><a class="nav-link" href="#contact">Contact</a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<main id="top" class="pt-5">
|
||
<section class="section hero-section">
|
||
<div class="container">
|
||
<div class="row align-items-center g-4">
|
||
<div class="col-lg-6">
|
||
<p class="eyebrow text-uppercase text-muted mb-3">Product & Brand Designer</p>
|
||
<h1 class="display-5 fw-semibold mb-3">Designing calm, precise digital experiences that ship.</h1>
|
||
<p class="lead text-muted mb-4">I help modern teams turn complex ideas into elegant product experiences. Focused on UX strategy, UI systems, and launch-ready web design.</p>
|
||
<div class="d-flex flex-wrap gap-3">
|
||
<a class="btn btn-primary btn-lg" href="#contact">Book a project</a>
|
||
<a class="btn btn-outline-dark btn-lg" href="#work">See portfolio</a>
|
||
</div>
|
||
<div class="hero-meta d-flex flex-wrap gap-4 mt-4">
|
||
<div>
|
||
<p class="text-muted small mb-1">Availability</p>
|
||
<p class="fw-semibold mb-0">New projects from April</p>
|
||
</div>
|
||
<div>
|
||
<p class="text-muted small mb-1">Location</p>
|
||
<p class="fw-semibold mb-0">New York · Remote</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-6">
|
||
<div class="hero-card p-4 p-lg-5">
|
||
<div class="hero-collage mb-4">
|
||
<?php
|
||
$heroDefaults = [
|
||
['src' => 'https://picsum.photos/seed/hero-main/900/1080', 'alt' => 'Creative desk setup with sketches and display screens'],
|
||
['src' => 'https://picsum.photos/seed/hero-side-a/720/720', 'alt' => 'Moodboard with typography and color samples'],
|
||
['src' => 'https://picsum.photos/seed/hero-side-b/720/720', 'alt' => 'Product interface on a laptop screen'],
|
||
['src' => 'https://picsum.photos/seed/hero-side-c/720/720', 'alt' => 'Team workshop notes and sticky planning cards'],
|
||
];
|
||
$heroSources = [];
|
||
for ($i = 0; $i < 4; $i++) {
|
||
$heroSources[$i] = $heroImages[$i] ?? $heroDefaults[$i];
|
||
}
|
||
?>
|
||
<div class="hero-collage-main">
|
||
<img src="<?= htmlspecialchars($heroSources[0]['src']) ?>" class="hero-photo parallax-media" data-parallax="18" alt="<?= htmlspecialchars($heroSources[0]['alt']) ?>">
|
||
<span class="hero-collage-badge">Live creative direction</span>
|
||
</div>
|
||
<div class="hero-collage-stack">
|
||
<img src="<?= htmlspecialchars($heroSources[1]['src']) ?>" class="hero-photo parallax-media" data-parallax="12" data-parallax-direction="-1" alt="<?= htmlspecialchars($heroSources[1]['alt']) ?>">
|
||
<img src="<?= htmlspecialchars($heroSources[2]['src']) ?>" class="hero-photo parallax-media" data-parallax="14" alt="<?= htmlspecialchars($heroSources[2]['alt']) ?>">
|
||
</div>
|
||
</div>
|
||
<div class="hero-collage-strip mb-4">
|
||
<img src="<?= htmlspecialchars($heroSources[3]['src']) ?>" class="hero-strip-photo parallax-media" data-parallax="9" data-parallax-direction="-1" alt="<?= htmlspecialchars($heroSources[3]['alt']) ?>">
|
||
<div class="hero-strip-copy">
|
||
<p class="text-muted small text-uppercase mb-1">Current focus</p>
|
||
<p class="fw-semibold mb-1">Visual systems, motion, and product storytelling</p>
|
||
<p class="text-muted small mb-0">Designed to keep the page lively without overpowering the content.</p>
|
||
</div>
|
||
</div>
|
||
<h2 class="h5 fw-semibold mb-3">Recent highlights</h2>
|
||
<ul class="list-unstyled mb-4">
|
||
<li class="d-flex justify-content-between border-bottom py-3">
|
||
<span class="fw-medium">Fintech onboarding revamp</span>
|
||
<span class="text-muted small">+42% activation</span>
|
||
</li>
|
||
<li class="d-flex justify-content-between border-bottom py-3">
|
||
<span class="fw-medium">Healthcare mobile redesign</span>
|
||
<span class="text-muted small">4.8★ rating</span>
|
||
</li>
|
||
<li class="d-flex justify-content-between py-3">
|
||
<span class="fw-medium">SaaS dashboard system</span>
|
||
<span class="text-muted small">12 screens</span>
|
||
</li>
|
||
</ul>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
<span class="badge badge-soft">UX Strategy</span>
|
||
<span class="badge badge-soft">Design Systems</span>
|
||
<span class="badge badge-soft">Web & Mobile</span>
|
||
<span class="badge badge-soft">Prototyping</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="work" class="section section-muted">
|
||
<div class="container">
|
||
<div class="d-flex justify-content-between align-items-end mb-4">
|
||
<div>
|
||
<h2 class="h3 fw-semibold mb-2">Selected work</h2>
|
||
<p class="text-muted mb-0">A handful of case studies covering product strategy, UX, and UI design.</p>
|
||
</div>
|
||
<span class="text-muted small">2023 — 2026</span>
|
||
</div>
|
||
<div class="row g-4">
|
||
<?php
|
||
$projects = [
|
||
['title' => 'Signal Labs', 'type' => 'Fintech onboarding', 'summary' => 'Simplified KYC flow, redesigned primary journey, and mapped conversion milestones.'],
|
||
['title' => 'Northline Health', 'type' => 'Patient app', 'summary' => 'Built a modular design system and streamlined appointment booking.'],
|
||
['title' => 'Sprout Commerce', 'type' => 'SaaS analytics', 'summary' => 'Designed a cross-functional dashboard with usage insights for teams.'],
|
||
];
|
||
foreach ($projects as $i => $project):
|
||
$img = $workImages[$i] ?? ['src' => 'https://picsum.photos/600/400'];
|
||
?>
|
||
<div class="col-md-4">
|
||
<article class="card work-card shadow-sm h-100 border-0">
|
||
<img src="<?= htmlspecialchars($img['src']) ?>" class="card-img-top parallax-media" data-parallax="10" data-parallax-direction="<?= $i % 2 === 0 ? 1 : -1 ?>" alt="<?= htmlspecialchars($project['title']) ?>">
|
||
<div class="card-body d-flex flex-column">
|
||
<p class="text-muted small text-uppercase mb-2"><?= htmlspecialchars($project['type']) ?></p>
|
||
<h3 class="h5 fw-semibold"><?= htmlspecialchars($project['title']) ?></h3>
|
||
<p class="text-muted mt-3"><?= htmlspecialchars($project['summary']) ?></p>
|
||
<div class="mt-auto">
|
||
<span class="text-primary small fw-semibold">View case study →</span>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="about" class="section">
|
||
<div class="container">
|
||
<div class="row g-4 align-items-center">
|
||
<div class="col-lg-5">
|
||
<h2 class="h3 fw-semibold mb-3">About</h2>
|
||
<p class="text-muted mb-4">I’m a multidisciplinary designer with 9+ years building digital products for startups and enterprise teams. I specialize in UX direction, visual systems, and stakeholder alignment that turns ideas into shipped work.</p>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
<span class="badge badge-soft">Figma</span>
|
||
<span class="badge badge-soft">Webflow</span>
|
||
<span class="badge badge-soft">Framer</span>
|
||
<span class="badge badge-soft">Design Systems</span>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-7">
|
||
<div class="about-panel p-4 p-lg-5">
|
||
<div class="row g-4">
|
||
<div class="col-md-6">
|
||
<h3 class="h6 text-uppercase text-muted">Services</h3>
|
||
<ul class="list-unstyled text-muted mb-0">
|
||
<li class="py-1">Product discovery workshops</li>
|
||
<li class="py-1">UX & UI design</li>
|
||
<li class="py-1">Design system build</li>
|
||
<li class="py-1">Launch planning</li>
|
||
</ul>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<h3 class="h6 text-uppercase text-muted">Focus areas</h3>
|
||
<ul class="list-unstyled text-muted mb-0">
|
||
<li class="py-1">Fintech & SaaS platforms</li>
|
||
<li class="py-1">Mobile-first experiences</li>
|
||
<li class="py-1">B2B onboarding</li>
|
||
<li class="py-1">UX copy + tone</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="border-top mt-4 pt-4 d-flex justify-content-between">
|
||
<div>
|
||
<p class="text-muted small mb-1">Clients</p>
|
||
<p class="fw-semibold mb-0">Stripe, Calm, Notion</p>
|
||
</div>
|
||
<div>
|
||
<p class="text-muted small mb-1">Engagements</p>
|
||
<p class="fw-semibold mb-0">Strategy · Design · Launch</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="testimonials" class="section section-muted">
|
||
<div class="container">
|
||
<div class="d-flex justify-content-between align-items-end mb-4">
|
||
<div>
|
||
<h2 class="h3 fw-semibold mb-2">Testimonials</h2>
|
||
<p class="text-muted mb-0">Short notes from founders and product leads.</p>
|
||
</div>
|
||
<span class="text-muted small">Updated March <?= date('Y'); ?></span>
|
||
</div>
|
||
<div class="row g-4">
|
||
<?php
|
||
$testi = [
|
||
['quote' => '“Alex quickly aligned our team and shipped a full design system. The project paid for itself within weeks.”', 'name' => 'Jordan Lee · Product Lead'],
|
||
['quote' => '“Clear, structured, and incredibly fast. We felt in control while launching a brand new onboarding.”', 'name' => 'Priya Patel · Founder']
|
||
];
|
||
foreach ($testi as $i => $item):
|
||
$img = $testimonialImages[$i] ?? ['src' => 'https://picsum.photos/100'];
|
||
?>
|
||
<div class="col-md-6">
|
||
<div class="card testimonial-card border-0 shadow-sm h-100">
|
||
<div class="card-body d-flex gap-3">
|
||
<img src="<?= htmlspecialchars($img['src']) ?>" class="testi-img parallax-media" data-parallax="6" data-parallax-direction="<?= $i % 2 === 0 ? 1 : -1 ?>" alt="Testimonial photo">
|
||
<div>
|
||
<p class="text-muted mb-2"><?= htmlspecialchars($item['quote']) ?></p>
|
||
<p class="fw-semibold mb-0"><?= htmlspecialchars($item['name']) ?></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="contact" class="section">
|
||
<div class="container">
|
||
<div class="row g-4 align-items-start">
|
||
<div class="col-lg-5">
|
||
<h2 class="h3 fw-semibold mb-3">Let’s work together</h2>
|
||
<p class="text-muted mb-4">Send a short note about your project or role. I’ll reply with availability, timing, and next steps.</p>
|
||
<div class="contact-box p-4">
|
||
<h3 class="h6 text-uppercase text-muted">Typical engagements</h3>
|
||
<ul class="list-unstyled text-muted mb-0">
|
||
<li class="py-1">UX audit + roadmap (2 weeks)</li>
|
||
<li class="py-1">New product experience (6–8 weeks)</li>
|
||
<li class="py-1">Design system sprint (3 weeks)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-7">
|
||
<div class="card border-0 shadow-sm">
|
||
<div class="card-body p-4 p-lg-5">
|
||
<h3 class="h5 fw-semibold mb-3">Contact form</h3>
|
||
|
||
<?php if ($errors): ?>
|
||
<div class="alert alert-danger" role="alert">
|
||
<?= htmlspecialchars(implode(' ', $errors)) ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($mailWarning): ?>
|
||
<div class="alert alert-warning" role="alert">
|
||
<?= htmlspecialchars($mailWarning) ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<form method="post" action="#contact" class="row g-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label">Name</label>
|
||
<input class="form-control" type="text" name="name" value="<?= htmlspecialchars($formData['name']) ?>" required>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">Email</label>
|
||
<input class="form-control" type="email" name="email" value="<?= htmlspecialchars($formData['email']) ?>" required>
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label">Message</label>
|
||
<textarea class="form-control" name="message" rows="4" required><?= htmlspecialchars($formData['message']) ?></textarea>
|
||
</div>
|
||
<div class="col-12 d-flex flex-wrap gap-3 align-items-center">
|
||
<button class="btn btn-primary btn-lg" type="submit">Send inquiry</button>
|
||
<span class="text-muted small">I respond within 48 hours.</span>
|
||
</div>
|
||
</form>
|
||
|
||
<?php if (!getenv('MAIL_TO')): ?>
|
||
<p class="text-muted small mt-3 mb-0">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 out AI Agent.</p>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<p class="text-muted small mt-3">Admin view: <a class="link-dark" href="inbox.php">View contact requests</a></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer class="border-top py-4">
|
||
<div class="container d-flex flex-wrap justify-content-between align-items-center gap-3">
|
||
<p class="text-muted small mb-0">© <?= date('Y'); ?> Alex Carter. All rights reserved.</p>
|
||
<div class="d-flex gap-3">
|
||
<a class="text-muted small" href="#work">Portfolio</a>
|
||
<a class="text-muted small" href="#contact">Contact</a>
|
||
<span class="text-muted small">Updated <?= htmlspecialchars($now) ?> UTC</span>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||
<?php if ($toast): ?>
|
||
<div class="toast align-items-center text-bg-<?= $toastType ?> border-0 show" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="true">
|
||
<div class="d-flex">
|
||
<div class="toast-body">
|
||
<?= htmlspecialchars($toast) ?>
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" defer></script>
|
||
<script src="assets/js/main.js?v=<?= time(); ?>" defer></script>
|
||
</body>
|
||
</html>
|