diff --git a/api/pexels.php b/api/pexels.php new file mode 100644 index 0000000..9c1b178 --- /dev/null +++ b/api/pexels.php @@ -0,0 +1,31 @@ + 'assets/images/pexels/'.$filename, + 'photographer' => $p['photographer'] ?? 'Unknown', + 'photographer_url' => $p['photographer_url'] ?? '', + ]; + } else { + $out[] = [ + 'src' => 'https://picsum.photos/600/400?random='.rand(1,100), + 'photographer' => 'Random Picsum', + 'photographer_url' => 'https://picsum.photos/' + ]; + } +} +echo json_encode($out); diff --git a/assets/css/custom.css b/assets/css/custom.css index 789132e..e49103b 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,403 +1,118 @@ +:root { + --bg: #f6f7f9; + --surface: #ffffff; + --text: #111827; + --muted: #6b7280; + --border: #e5e7eb; + --primary: #2563eb; + --primary-dark: #1d4ed8; + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; +} + +* { + box-sizing: border-box; +} + 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; + font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: var(--bg); + color: var(--text); + margin: 0; } -.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; +main { + padding-top: 72px; } -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } +.section { + padding: 72px 0; } -.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; +.section-muted { + background: #f1f3f6; } -.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; +.eyebrow { + letter-spacing: 0.16em; + font-size: 0.75rem; } -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; +.navbar { + backdrop-filter: blur(6px); } -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; +.navbar-brand { + font-size: 1.05rem; } -::-webkit-scrollbar-track { - background: transparent; +.hero-section .lead { + font-size: 1.1rem; } -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; +.hero-card, +.about-panel, +.contact-box { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); } -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); +.badge-soft { + background: #f3f4f6; + color: var(--text); + border-radius: 999px; + padding: 6px 12px; + font-weight: 500; + font-size: 0.75rem; } -.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); +.btn-primary { + background: var(--primary); + border-color: var(--primary); + border-radius: var(--radius-sm); + padding: 0.65rem 1.4rem; } -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } +.btn-primary:hover, +.btn-primary:focus { + background: var(--primary-dark); + border-color: var(--primary-dark); } -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; +.btn-outline-dark { + border-radius: var(--radius-sm); + padding: 0.65rem 1.4rem; } -.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; +.card { + border-radius: var(--radius-md); } .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; + border-radius: var(--radius-sm); + border-color: var(--border); } .form-control:focus { - outline: none; - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1); + border-color: var(--primary); + box-shadow: 0 0 0 0.2rem rgba(37, 99, 235, 0.15); } -.header-container { - display: flex; - justify-content: space-between; - align-items: center; +.toast { + border-radius: var(--radius-sm); } -.header-links { - display: flex; - gap: 1rem; +footer a { + text-decoration: none; } -.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); +@media (max-width: 991px) { + .section { + padding: 56px 0; + } + .hero-section { + padding-top: 40px; + } } - -.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/images/pexels/2117283.jpg b/assets/images/pexels/2117283.jpg new file mode 100644 index 0000000..b134310 Binary files /dev/null and b/assets/images/pexels/2117283.jpg differ diff --git a/assets/images/pexels/4175028.jpg b/assets/images/pexels/4175028.jpg new file mode 100644 index 0000000..be371ee Binary files /dev/null and b/assets/images/pexels/4175028.jpg differ diff --git a/assets/images/pexels/4189611.jpg b/assets/images/pexels/4189611.jpg new file mode 100644 index 0000000..fa26db4 Binary files /dev/null and b/assets/images/pexels/4189611.jpg differ diff --git a/assets/images/pexels/6476782.jpg b/assets/images/pexels/6476782.jpg new file mode 100644 index 0000000..2fdb94a Binary files /dev/null and b/assets/images/pexels/6476782.jpg differ diff --git a/assets/images/pexels/7434184.jpg b/assets/images/pexels/7434184.jpg new file mode 100644 index 0000000..e750f40 Binary files /dev/null and b/assets/images/pexels/7434184.jpg differ diff --git a/assets/images/pexels/7816667.jpg b/assets/images/pexels/7816667.jpg new file mode 100644 index 0000000..d718698 Binary files /dev/null and b/assets/images/pexels/7816667.jpg differ diff --git a/assets/js/main.js b/assets/js/main.js index d349598..788bca6 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,19 @@ 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 navLinks = document.querySelectorAll('a.nav-link[href^="#"]'); + navLinks.forEach((link) => { + link.addEventListener('click', (event) => { + const targetId = link.getAttribute('href'); + const target = document.querySelector(targetId); + if (target) { + event.preventDefault(); + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } }); + }); + + const toastEl = document.querySelector('.toast'); + if (toastEl && window.bootstrap) { + const toast = new bootstrap.Toast(toastEl, { delay: 4000 }); + toast.show(); + } }); diff --git a/db/migrations/001_create_contact_requests.sql b/db/migrations/001_create_contact_requests.sql new file mode 100644 index 0000000..5ff462b --- /dev/null +++ b/db/migrations/001_create_contact_requests.sql @@ -0,0 +1,11 @@ +-- Create contact requests table +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; diff --git a/inbox.php b/inbox.php new file mode 100644 index 0000000..0d3421c --- /dev/null +++ b/inbox.php @@ -0,0 +1,118 @@ +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" + ); +} + +ensure_contact_table(); + +$detail = null; +if (isset($_GET['id']) && ctype_digit($_GET['id'])) { + $stmt = db()->prepare('SELECT * FROM contact_requests WHERE id = :id'); + $stmt->execute([':id' => (int) $_GET['id']]); + $detail = $stmt->fetch(); +} + +$stmt = db()->prepare('SELECT id, name, email, status, created_at FROM contact_requests ORDER BY created_at DESC LIMIT 50'); +$stmt->execute(); +$rows = $stmt->fetchAll(); +?> + + + + + + + Contact Requests + + + + + + +
+
+
+
+
+
+

Latest inquiries

+ +

No inquiries yet. New submissions will appear here.

+ +
+ + + + + + + + + + + + + + + + + + + + + +
IDNameEmailStatusReceived
#
+
+ +
+
+
+
+
+
+

Inquiry detail

+ +

Select a request to see the full message.

+ +
+

From

+

+

+
+
+

Message

+

+
+

Received · Status

+ +
+
+
+
+
+
+ + diff --git a/includes/pexels.php b/includes/pexels.php new file mode 100644 index 0000000..c96eaa2 --- /dev/null +++ b/includes/pexels.php @@ -0,0 +1,26 @@ + 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18'; +} +function pexels_get($url) { + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ], + CURLOPT_TIMEOUT => 15, + ]); + $resp = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true); + return null; +} +function download_to($srcUrl, $destPath) { + $data = file_get_contents($srcUrl); + if ($data === false) return false; + if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true); + return file_put_contents($destPath, $data) !== false; +} diff --git a/index.php b/index.php index 7205f3d..ea116bc 100644 --- a/index.php +++ b/index.php @@ -4,147 +4,412 @@ declare(strict_types=1); @error_reporting(E_ALL); @date_default_timezone_set('UTC'); -$phpVersion = PHP_VERSION; +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 +$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.'; + } + } +} ?> - New Style - + Personal Portfolio - - - + - - - + - + + -
-
-

Analyzing your requirements and generating your website…

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

Product & Brand Designer

+

Designing calm, precise digital experiences that ship.

+

I help modern teams turn complex ideas into elegant product experiences. Focused on UX strategy, UI systems, and launch-ready web design.

+ +
+
+

Availability

+

New projects from April

+
+
+

Location

+

New York · Remote

+
+
+
+
+
+

Recent highlights

+
    +
  • + Fintech onboarding revamp + +42% activation +
  • +
  • + Healthcare mobile redesign + 4.8★ rating +
  • +
  • + SaaS dashboard system + 12 screens +
  • +
+
+ UX Strategy + Design Systems + Web & Mobile + Prototyping +
+
+
+
+
+
+ +
+
+
+
+

Selected work

+

A handful of case studies covering product strategy, UX, and UI design.

+
+ 2023 — 2026 +
+
+ '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']; + ?> +
+
+ <?= htmlspecialchars($project['title']) ?> +
+

+

+

+
+ View case study → +
+
+
+
+ +
+
+
+ +
+
+
+
+

About

+

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.

+
+ Figma + Webflow + Framer + Design Systems +
+
+
+
+
+
+

Services

+
    +
  • Product discovery workshops
  • +
  • UX & UI design
  • +
  • Design system build
  • +
  • Launch planning
  • +
+
+
+

Focus areas

+
    +
  • Fintech & SaaS platforms
  • +
  • Mobile-first experiences
  • +
  • B2B onboarding
  • +
  • UX copy + tone
  • +
+
+
+
+
+

Clients

+

Stripe, Calm, Notion

+
+
+

Engagements

+

Strategy · Design · Launch

+
+
+
+
+
+
+
+ +
+
+
+
+

Testimonials

+

Short notes from founders and product leads.

+
+ Updated March +
+
+ '“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']; + ?> +
+
+
+ Testimonial photo +
+

+

+
+
+
+
+ +
+
+
+ +
+
+
+
+

Let’s work together

+

Send a short note about your project or role. I’ll reply with availability, timing, and next steps.

+
+

Typical engagements

+
    +
  • UX audit + roadmap (2 weeks)
  • +
  • New product experience (6–8 weeks)
  • +
  • Design system sprint (3 weeks)
  • +
+
+
+
+
+
+

Contact form

+ + + + + + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + I respond within 48 hours. +
+
+ + +

This is for testing purposes only — Flatlogic does not guarantee usage of the mail server. Please set up your own SMTP in .env (MAIL_/SMTP_ vars) with out AI Agent.

+ +
+
+

Admin view: View contact requests

+
+
+
+
-