beta
This commit is contained in:
parent
334ba91e69
commit
d5ac0af598
File diff suppressed because it is too large
Load Diff
@ -1,39 +1,99 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const chatForm = document.getElementById('chat-form');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
const header = document.querySelector('.site-header');
|
||||
const toastElement = document.getElementById('siteToast');
|
||||
const toastBody = toastElement ? toastElement.querySelector('.toast-body') : null;
|
||||
const pageToastMessage = document.body.dataset.toastMessage || '';
|
||||
|
||||
const appendMessage = (text, sender) => {
|
||||
const msgDiv = document.createElement('div');
|
||||
msgDiv.classList.add('message', sender);
|
||||
msgDiv.textContent = text;
|
||||
chatMessages.appendChild(msgDiv);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
const showToast = (message) => {
|
||||
if (!toastElement || !toastBody || !window.bootstrap) {
|
||||
return;
|
||||
}
|
||||
toastBody.textContent = message;
|
||||
window.bootstrap.Toast.getOrCreateInstance(toastElement, { delay: 3000 }).show();
|
||||
};
|
||||
|
||||
chatForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const message = chatInput.value.trim();
|
||||
if (!message) return;
|
||||
if (header) {
|
||||
const toggleHeaderState = () => {
|
||||
header.classList.toggle('is-scrolled', window.scrollY > 10);
|
||||
};
|
||||
toggleHeaderState();
|
||||
window.addEventListener('scroll', toggleHeaderState, { passive: true });
|
||||
}
|
||||
|
||||
appendMessage(message, 'visitor');
|
||||
chatInput.value = '';
|
||||
if (pageToastMessage) {
|
||||
showToast(pageToastMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('api/chat.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message })
|
||||
document.querySelectorAll('[data-copy-target]').forEach((button) => {
|
||||
button.addEventListener('click', async () => {
|
||||
const target = document.querySelector(button.dataset.copyTarget || '');
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = target.textContent.trim();
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
showToast('Reference copied to clipboard.');
|
||||
} catch (error) {
|
||||
showToast('Copy is unavailable in this browser.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-filter-group]').forEach((group) => {
|
||||
const targetSelector = group.dataset.filterTarget;
|
||||
const emptySelector = group.dataset.emptyState;
|
||||
const resetButton = document.querySelector('[data-reset-filter]');
|
||||
const target = targetSelector ? document.querySelector(targetSelector) : null;
|
||||
const emptyState = emptySelector ? document.querySelector(emptySelector) : null;
|
||||
const buttons = Array.from(group.querySelectorAll('[data-filter]'));
|
||||
const items = target ? Array.from(target.querySelectorAll('[data-filter-item]')) : [];
|
||||
|
||||
if (!target || buttons.length === 0 || items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const applyFilter = (filter) => {
|
||||
let visibleCount = 0;
|
||||
items.forEach((item) => {
|
||||
const matches = filter === 'all' || item.dataset.filterItem === filter;
|
||||
item.classList.toggle('is-hidden', !matches);
|
||||
if (matches) {
|
||||
visibleCount += 1;
|
||||
}
|
||||
});
|
||||
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');
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.classList.toggle('is-active', button.dataset.filter === filter);
|
||||
});
|
||||
|
||||
if (emptyState) {
|
||||
emptyState.classList.toggle('d-none', visibleCount > 0);
|
||||
}
|
||||
};
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener('click', () => applyFilter(button.dataset.filter || 'all'));
|
||||
});
|
||||
|
||||
if (resetButton) {
|
||||
resetButton.addEventListener('click', () => applyFilter('all'));
|
||||
}
|
||||
});
|
||||
|
||||
const navbarCollapse = document.getElementById('primaryNav');
|
||||
if (navbarCollapse && window.bootstrap) {
|
||||
document.querySelectorAll('#primaryNav .nav-link').forEach((link) => {
|
||||
link.addEventListener('click', () => {
|
||||
if (window.innerWidth < 992) {
|
||||
window.bootstrap.Collapse.getOrCreateInstance(navbarCollapse).hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
125
brief.php
Normal file
125
brief.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/site.php';
|
||||
require_once __DIR__ . '/includes/inquiries.php';
|
||||
|
||||
$reference = isset($_GET['ref']) ? trim((string) $_GET['ref']) : '';
|
||||
$toastMessage = isset($_GET['created']) && $_GET['created'] === '1' && $reference !== '' ? 'Brief received — reference ' . $reference : '';
|
||||
$activeNav = 'contact';
|
||||
$pageTitle = 'Project brief';
|
||||
$pageDescription = 'Confirmation and detail view for a submitted project brief.';
|
||||
$inquiry = null;
|
||||
$loadError = '';
|
||||
|
||||
if ($reference !== '') {
|
||||
try {
|
||||
$inquiry = find_project_inquiry($reference);
|
||||
} catch (Throwable $exception) {
|
||||
error_log('Project inquiry lookup failed: ' . $exception->getMessage());
|
||||
$loadError = 'We could not load that brief right now.';
|
||||
}
|
||||
}
|
||||
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<main>
|
||||
<section class="section-shell border-bottom page-hero">
|
||||
<div class="container">
|
||||
<span class="eyebrow">Project brief</span>
|
||||
<h1 class="section-title">Your request is in the queue.</h1>
|
||||
<p class="section-copy mb-0">Use the reference below if you want to discuss this brief internally or when we wire notifications next.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell">
|
||||
<div class="container">
|
||||
<?php if ($loadError !== ''): ?>
|
||||
<div class="alert alert-danger" role="alert"><?= htmlspecialchars($loadError) ?></div>
|
||||
<?php elseif (!$inquiry): ?>
|
||||
<div class="empty-state empty-state--page">
|
||||
<h2>We could not find that brief.</h2>
|
||||
<p>Start a new project brief and we will capture the details from there.</p>
|
||||
<a class="btn btn-primary" href="/contact.php">Start a project brief</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-7">
|
||||
<div class="surface-panel h-100">
|
||||
<div class="d-flex flex-wrap gap-3 justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<div class="panel-label">Reference</div>
|
||||
<div class="reference-code" id="briefReference"><?= htmlspecialchars($inquiry['reference_code']) ?></div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-dark btn-sm" data-copy-target="#briefReference">Copy reference</button>
|
||||
</div>
|
||||
<div class="detail-grid">
|
||||
<div>
|
||||
<span class="detail-label">Name</span>
|
||||
<strong><?= htmlspecialchars($inquiry['full_name']) ?></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Company</span>
|
||||
<strong><?= htmlspecialchars($inquiry['company_name']) ?></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Email</span>
|
||||
<strong><?= htmlspecialchars($inquiry['email']) ?></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Status</span>
|
||||
<strong><?= htmlspecialchars(ucfirst($inquiry['status'])) ?></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Project type</span>
|
||||
<strong><?= htmlspecialchars($inquiry['project_type']) ?></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Budget</span>
|
||||
<strong><?= htmlspecialchars($inquiry['budget_range']) ?></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Target launch</span>
|
||||
<strong><?= htmlspecialchars($inquiry['launch_window']) ?></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Submitted</span>
|
||||
<strong><?= htmlspecialchars(format_display_datetime($inquiry['created_at'])) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-panel mt-4">
|
||||
<span class="detail-label">Project brief</span>
|
||||
<p><?= nl2br(htmlspecialchars($inquiry['message'])) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="surface-panel compact-panel mb-4">
|
||||
<div class="panel-label">Next steps</div>
|
||||
<div class="content-row">
|
||||
<h3>Internal review</h3>
|
||||
<p>We review the brief against scope, urgency, and likely team shape.</p>
|
||||
</div>
|
||||
<div class="content-row">
|
||||
<h3>Response path</h3>
|
||||
<p>We recommend a discovery sprint, delivery pod, or a smaller technical diligence engagement.</p>
|
||||
</div>
|
||||
<div class="content-row mb-0">
|
||||
<h3>What you can do now</h3>
|
||||
<p>Share the reference with stakeholders or continue exploring our work and insights.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="surface-panel compact-panel">
|
||||
<div class="panel-label">Explore while you wait</div>
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<a class="btn btn-primary" href="/work.php">View work</a>
|
||||
<a class="btn btn-outline-dark" href="/insights.php">Read insights</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php require __DIR__ . '/partials/footer.php'; ?>
|
||||
139
case-study.php
Normal file
139
case-study.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/site.php';
|
||||
|
||||
$slug = isset($_GET['slug']) ? trim((string) $_GET['slug']) : '';
|
||||
$study = case_study_by_slug($slug);
|
||||
|
||||
if (!$study) {
|
||||
http_response_code(404);
|
||||
$pageTitle = 'Case study not found';
|
||||
$pageDescription = 'The requested case study could not be found.';
|
||||
$activeNav = 'work';
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<main>
|
||||
<section class="section-shell">
|
||||
<div class="container">
|
||||
<div class="empty-state empty-state--page">
|
||||
<h1>Case study not found</h1>
|
||||
<p>The selected case study is unavailable. You can return to the work overview and choose another representative engagement.</p>
|
||||
<a class="btn btn-primary" href="/work.php">Back to work</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php
|
||||
require __DIR__ . '/partials/footer.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$pageTitle = $study['title'];
|
||||
$pageDescription = $study['summary'];
|
||||
$activeNav = 'work';
|
||||
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<main>
|
||||
<section class="section-shell border-bottom page-hero">
|
||||
<div class="container">
|
||||
<a class="crumb-link" href="/work.php">← Back to case studies</a>
|
||||
<div class="row g-4 align-items-end mt-2">
|
||||
<div class="col-lg-8">
|
||||
<span class="eyebrow"><?= htmlspecialchars($study['sector']) ?></span>
|
||||
<h1 class="section-title"><?= htmlspecialchars($study['title']) ?></h1>
|
||||
<p class="section-copy mb-0"><?= htmlspecialchars($study['summary']) ?></p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="surface-panel compact-panel h-100">
|
||||
<div class="content-row mb-0">
|
||||
<h3>Client profile</h3>
|
||||
<p><?= htmlspecialchars($study['client']) ?> · <?= htmlspecialchars($study['engagement']) ?></p>
|
||||
</div>
|
||||
<div class="content-row mb-0">
|
||||
<h3>Timeline</h3>
|
||||
<p><?= htmlspecialchars($study['timeline']) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mt-3">
|
||||
<?php foreach ($study['results'] as $result): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="metric-card h-100">
|
||||
<strong><?= htmlspecialchars($result['value']) ?></strong>
|
||||
<span><?= htmlspecialchars($result['label']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell border-bottom">
|
||||
<div class="container">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="surface-panel h-100">
|
||||
<div class="panel-label">Challenge</div>
|
||||
<div class="detail-list">
|
||||
<?php foreach ($study['challenge'] as $item): ?>
|
||||
<div class="detail-list-item">
|
||||
<h2><?= htmlspecialchars($study['client']) ?> needed a lower-risk path forward</h2>
|
||||
<p><?= htmlspecialchars($item) ?></p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="surface-panel h-100">
|
||||
<div class="panel-label">Approach</div>
|
||||
<div class="detail-list">
|
||||
<?php foreach ($study['solution'] as $item): ?>
|
||||
<div class="detail-list-item">
|
||||
<h2>What we changed</h2>
|
||||
<p><?= htmlspecialchars($item) ?></p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell border-bottom">
|
||||
<div class="container">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="surface-panel h-100">
|
||||
<div class="panel-label">Capabilities used</div>
|
||||
<div class="result-pills result-pills--large">
|
||||
<?php foreach ($study['capabilities'] as $capability): ?>
|
||||
<span><?= htmlspecialchars($capability) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="quote-panel mt-4">
|
||||
<p>“<?= htmlspecialchars($study['quote']) ?>”</p>
|
||||
<span><?= htmlspecialchars($study['quote_by']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="surface-panel h-100">
|
||||
<div class="panel-label">Technology focus</div>
|
||||
<ul class="list-unstyled service-points mb-4">
|
||||
<?php foreach ($study['technology'] as $technology): ?>
|
||||
<li><?= htmlspecialchars($technology) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<a class="btn btn-primary w-100" href="/contact.php">Discuss a similar project</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php require __DIR__ . '/partials/footer.php'; ?>
|
||||
196
contact.php
Normal file
196
contact.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/site.php';
|
||||
require_once __DIR__ . '/includes/inquiries.php';
|
||||
|
||||
$pageTitle = 'Start a project';
|
||||
$pageDescription = 'Submit a short project brief to discuss a new software build, modernization initiative, or AI workflow.';
|
||||
$activeNav = 'contact';
|
||||
|
||||
$textLength = static function (string $value): int {
|
||||
return function_exists('mb_strlen') ? mb_strlen($value) : strlen($value);
|
||||
};
|
||||
|
||||
$form = [
|
||||
'full_name' => '',
|
||||
'company_name' => '',
|
||||
'email' => '',
|
||||
'project_type' => project_type_options()[0],
|
||||
'budget_range' => budget_options()[0],
|
||||
'launch_window' => launch_options()[0],
|
||||
'message' => '',
|
||||
];
|
||||
$errors = [];
|
||||
$submissionError = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$form['full_name'] = trim((string) ($_POST['full_name'] ?? ''));
|
||||
$form['company_name'] = trim((string) ($_POST['company_name'] ?? ''));
|
||||
$form['email'] = trim((string) ($_POST['email'] ?? ''));
|
||||
$form['project_type'] = trim((string) ($_POST['project_type'] ?? ''));
|
||||
$form['budget_range'] = trim((string) ($_POST['budget_range'] ?? ''));
|
||||
$form['launch_window'] = trim((string) ($_POST['launch_window'] ?? ''));
|
||||
$form['message'] = trim((string) ($_POST['message'] ?? ''));
|
||||
|
||||
if ($form['full_name'] === '' || $textLength($form['full_name']) < 2) {
|
||||
$errors['full_name'] = 'Please enter your name.';
|
||||
}
|
||||
if ($form['company_name'] === '' || $textLength($form['company_name']) < 2) {
|
||||
$errors['company_name'] = 'Please enter your company name.';
|
||||
}
|
||||
if (!filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
|
||||
$errors['email'] = 'Please enter a valid work email.';
|
||||
}
|
||||
if (!in_array($form['project_type'], project_type_options(), true)) {
|
||||
$errors['project_type'] = 'Please choose a valid project type.';
|
||||
}
|
||||
if (!in_array($form['budget_range'], budget_options(), true)) {
|
||||
$errors['budget_range'] = 'Please choose a valid budget range.';
|
||||
}
|
||||
if (!in_array($form['launch_window'], launch_options(), true)) {
|
||||
$errors['launch_window'] = 'Please choose a valid launch window.';
|
||||
}
|
||||
if ($form['message'] === '' || $textLength($form['message']) < 30) {
|
||||
$errors['message'] = 'Please share at least a short project summary (30+ characters).';
|
||||
}
|
||||
|
||||
if ($errors === []) {
|
||||
try {
|
||||
$reference = create_project_inquiry($form);
|
||||
header('Location: /brief.php?ref=' . urlencode($reference) . '&created=1');
|
||||
exit;
|
||||
} catch (Throwable $exception) {
|
||||
error_log('Project inquiry save failed: ' . $exception->getMessage());
|
||||
$submissionError = 'We could not save your brief right now. Please try again in a moment.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fieldValue = static function (string $key) use ($form): string {
|
||||
return htmlspecialchars($form[$key] ?? '');
|
||||
};
|
||||
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<main>
|
||||
<section class="section-shell border-bottom page-hero">
|
||||
<div class="container">
|
||||
<div class="row g-4 align-items-end">
|
||||
<div class="col-lg-8">
|
||||
<span class="eyebrow">Contact</span>
|
||||
<h1 class="section-title">Tell us what you are building and we will shape the next step.</h1>
|
||||
<p class="section-copy mb-0">This first form is intentionally concise. It gives us enough context to propose the right engagement model, timeline, and next conversation.</p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="surface-panel compact-panel h-100">
|
||||
<div class="panel-label">What happens next</div>
|
||||
<div class="content-row">
|
||||
<h3>1. Brief review</h3>
|
||||
<p>We assess scope, urgency, and likely delivery shape within one business day.</p>
|
||||
</div>
|
||||
<div class="content-row">
|
||||
<h3>2. Working session</h3>
|
||||
<p>If there is a fit, we use the first call to refine goals, constraints, and success metrics.</p>
|
||||
</div>
|
||||
<div class="content-row mb-0">
|
||||
<h3>3. Clear recommendation</h3>
|
||||
<p>You receive a pragmatic recommendation: discovery sprint, delivery pod, or a smaller advisory step.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell">
|
||||
<div class="container">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-7">
|
||||
<div class="surface-panel form-panel">
|
||||
<?php if ($submissionError !== ''): ?>
|
||||
<div class="alert alert-danger" role="alert"><?= htmlspecialchars($submissionError) ?></div>
|
||||
<?php elseif ($errors !== []): ?>
|
||||
<div class="alert alert-warning" role="alert">Please review the highlighted fields and try again.</div>
|
||||
<?php endif; ?>
|
||||
<form method="post" action="/contact.php" novalidate>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="full_name">Name</label>
|
||||
<input class="form-control<?= isset($errors['full_name']) ? ' is-invalid' : '' ?>" id="full_name" name="full_name" type="text" maxlength="120" required value="<?= $fieldValue('full_name') ?>">
|
||||
<?php if (isset($errors['full_name'])): ?><div class="invalid-feedback"><?= htmlspecialchars($errors['full_name']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="company_name">Company</label>
|
||||
<input class="form-control<?= isset($errors['company_name']) ? ' is-invalid' : '' ?>" id="company_name" name="company_name" type="text" maxlength="150" required value="<?= $fieldValue('company_name') ?>">
|
||||
<?php if (isset($errors['company_name'])): ?><div class="invalid-feedback"><?= htmlspecialchars($errors['company_name']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="email">Work email</label>
|
||||
<input class="form-control<?= isset($errors['email']) ? ' is-invalid' : '' ?>" id="email" name="email" type="email" maxlength="190" required value="<?= $fieldValue('email') ?>">
|
||||
<?php if (isset($errors['email'])): ?><div class="invalid-feedback"><?= htmlspecialchars($errors['email']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="project_type">Project type</label>
|
||||
<select class="form-select<?= isset($errors['project_type']) ? ' is-invalid' : '' ?>" id="project_type" name="project_type">
|
||||
<?php foreach (project_type_options() as $option): ?>
|
||||
<option value="<?= htmlspecialchars($option) ?>"<?= $form['project_type'] === $option ? ' selected' : '' ?>><?= htmlspecialchars($option) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if (isset($errors['project_type'])): ?><div class="invalid-feedback"><?= htmlspecialchars($errors['project_type']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="budget_range">Budget</label>
|
||||
<select class="form-select<?= isset($errors['budget_range']) ? ' is-invalid' : '' ?>" id="budget_range" name="budget_range">
|
||||
<?php foreach (budget_options() as $option): ?>
|
||||
<option value="<?= htmlspecialchars($option) ?>"<?= $form['budget_range'] === $option ? ' selected' : '' ?>><?= htmlspecialchars($option) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if (isset($errors['budget_range'])): ?><div class="invalid-feedback"><?= htmlspecialchars($errors['budget_range']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="launch_window">Target launch</label>
|
||||
<select class="form-select<?= isset($errors['launch_window']) ? ' is-invalid' : '' ?>" id="launch_window" name="launch_window">
|
||||
<?php foreach (launch_options() as $option): ?>
|
||||
<option value="<?= htmlspecialchars($option) ?>"<?= $form['launch_window'] === $option ? ' selected' : '' ?>><?= htmlspecialchars($option) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if (isset($errors['launch_window'])): ?><div class="invalid-feedback"><?= htmlspecialchars($errors['launch_window']) ?></div><?php endif; ?>
|
||||
</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="7" maxlength="3000" required placeholder="What are you building, why now, and what is the main constraint or outcome you care about most?"><?= $fieldValue('message') ?></textarea>
|
||||
<?php if (isset($errors['message'])): ?><div class="invalid-feedback"><?= htmlspecialchars($errors['message']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div class="col-12 d-flex flex-wrap gap-3 align-items-center pt-2">
|
||||
<button class="btn btn-primary btn-lg" type="submit">Submit brief</button>
|
||||
<span class="mini-label">Stored securely in your project database for follow-up and future admin tooling.</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="surface-panel compact-panel mb-4">
|
||||
<div class="panel-label">Good fit</div>
|
||||
<ul class="list-unstyled service-points mb-0">
|
||||
<li>New customer-facing products with real launch pressure</li>
|
||||
<li>Internal platforms that need cleaner workflows and reporting</li>
|
||||
<li>Modernization programs where downtime or regressions are expensive</li>
|
||||
<li>AI and automation work linked to measurable operational outcomes</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="surface-panel compact-panel">
|
||||
<div class="panel-label">Prefer to explore first?</div>
|
||||
<p class="mb-3">Review our representative work and editorial thinking before sending a brief.</p>
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<a class="btn btn-outline-dark" href="/work.php">View work</a>
|
||||
<a class="btn btn-outline-dark" href="/insights.php">Read insights</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php require __DIR__ . '/partials/footer.php'; ?>
|
||||
87
includes/inquiries.php
Normal file
87
includes/inquiries.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
function ensure_project_inquiries_table(PDO $pdo): void
|
||||
{
|
||||
$pdo->exec(
|
||||
'CREATE TABLE IF NOT EXISTS project_inquiries (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
reference_code VARCHAR(32) NOT NULL UNIQUE,
|
||||
full_name VARCHAR(120) NOT NULL,
|
||||
company_name VARCHAR(150) NOT NULL,
|
||||
email VARCHAR(190) NOT NULL,
|
||||
project_type VARCHAR(80) NOT NULL,
|
||||
budget_range VARCHAR(80) NOT NULL,
|
||||
launch_window VARCHAR(80) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
status VARCHAR(30) NOT NULL DEFAULT "new",
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
|
||||
);
|
||||
}
|
||||
|
||||
function generate_inquiry_reference(): string
|
||||
{
|
||||
return 'NSL-' . gmdate('ymd') . '-' . strtoupper(bin2hex(random_bytes(2)));
|
||||
}
|
||||
|
||||
function create_project_inquiry(array $payload): string
|
||||
{
|
||||
$pdo = db();
|
||||
ensure_project_inquiries_table($pdo);
|
||||
|
||||
$reference = generate_inquiry_reference();
|
||||
$statement = $pdo->prepare(
|
||||
'INSERT INTO project_inquiries (
|
||||
reference_code,
|
||||
full_name,
|
||||
company_name,
|
||||
email,
|
||||
project_type,
|
||||
budget_range,
|
||||
launch_window,
|
||||
message
|
||||
) VALUES (
|
||||
:reference_code,
|
||||
:full_name,
|
||||
:company_name,
|
||||
:email,
|
||||
:project_type,
|
||||
:budget_range,
|
||||
:launch_window,
|
||||
:message
|
||||
)'
|
||||
);
|
||||
|
||||
$statement->bindValue(':reference_code', $reference, PDO::PARAM_STR);
|
||||
$statement->bindValue(':full_name', $payload['full_name'], PDO::PARAM_STR);
|
||||
$statement->bindValue(':company_name', $payload['company_name'], PDO::PARAM_STR);
|
||||
$statement->bindValue(':email', $payload['email'], PDO::PARAM_STR);
|
||||
$statement->bindValue(':project_type', $payload['project_type'], PDO::PARAM_STR);
|
||||
$statement->bindValue(':budget_range', $payload['budget_range'], PDO::PARAM_STR);
|
||||
$statement->bindValue(':launch_window', $payload['launch_window'], PDO::PARAM_STR);
|
||||
$statement->bindValue(':message', $payload['message'], PDO::PARAM_STR);
|
||||
$statement->execute();
|
||||
|
||||
return $reference;
|
||||
}
|
||||
|
||||
function find_project_inquiry(string $reference): ?array
|
||||
{
|
||||
$pdo = db();
|
||||
ensure_project_inquiries_table($pdo);
|
||||
|
||||
$statement = $pdo->prepare(
|
||||
'SELECT reference_code, full_name, company_name, email, project_type, budget_range, launch_window, message, status, created_at
|
||||
FROM project_inquiries
|
||||
WHERE reference_code = :reference_code
|
||||
LIMIT 1'
|
||||
);
|
||||
$statement->bindValue(':reference_code', $reference, PDO::PARAM_STR);
|
||||
$statement->execute();
|
||||
|
||||
$result = $statement->fetch();
|
||||
return is_array($result) ? $result : null;
|
||||
}
|
||||
431
includes/site.php
Normal file
431
includes/site.php
Normal file
@ -0,0 +1,431 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function site_name(): string
|
||||
{
|
||||
$candidates = [
|
||||
$_SERVER['PROJECT_NAME'] ?? null,
|
||||
getenv('PROJECT_NAME') ?: null,
|
||||
$_SERVER['PROJECT_TITLE'] ?? null,
|
||||
getenv('PROJECT_TITLE') ?: null,
|
||||
];
|
||||
|
||||
foreach ($candidates as $value) {
|
||||
if (is_string($value) && trim($value) !== '') {
|
||||
return trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
return 'Northstar Labs';
|
||||
}
|
||||
|
||||
function site_initials(): string
|
||||
{
|
||||
$words = preg_split('/\s+/', site_name()) ?: [];
|
||||
$letters = '';
|
||||
|
||||
foreach ($words as $word) {
|
||||
$clean = trim((string) $word);
|
||||
if ($clean === '') {
|
||||
continue;
|
||||
}
|
||||
$letters .= strtoupper(substr($clean, 0, 1));
|
||||
if (strlen($letters) >= 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $letters !== '' ? $letters : 'NL';
|
||||
}
|
||||
|
||||
function site_description_default(): string
|
||||
{
|
||||
$description = $_SERVER['PROJECT_DESCRIPTION'] ?? getenv('PROJECT_DESCRIPTION') ?: '';
|
||||
if (is_string($description) && trim($description) !== '') {
|
||||
return trim($description);
|
||||
}
|
||||
|
||||
return 'Premium software engineering, modernization, and AI delivery for founders, product teams, and enterprise buyers.';
|
||||
}
|
||||
|
||||
function page_title(string $pageTitle = ''): string
|
||||
{
|
||||
return trim($pageTitle) !== '' ? trim($pageTitle) . ' | ' . site_name() : site_name();
|
||||
}
|
||||
|
||||
function asset_url(string $path): string
|
||||
{
|
||||
$relativePath = ltrim($path, '/');
|
||||
$absolutePath = dirname(__DIR__) . '/' . $relativePath;
|
||||
$version = is_file($absolutePath) ? (string) filemtime($absolutePath) : gmdate('U');
|
||||
|
||||
return '/' . $relativePath . '?v=' . rawurlencode($version);
|
||||
}
|
||||
|
||||
function slugify_label(string $value): string
|
||||
{
|
||||
$slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', trim($value)) ?? '');
|
||||
return trim($slug, '-');
|
||||
}
|
||||
|
||||
function nav_items(): array
|
||||
{
|
||||
return [
|
||||
['key' => 'home', 'label' => 'Home', 'href' => '/'],
|
||||
['key' => 'services', 'label' => 'Services', 'href' => '/#services'],
|
||||
['key' => 'work', 'label' => 'Work', 'href' => '/work.php'],
|
||||
['key' => 'insights', 'label' => 'Insights', 'href' => '/insights.php'],
|
||||
['key' => 'contact', 'label' => 'Contact', 'href' => '/contact.php'],
|
||||
];
|
||||
}
|
||||
|
||||
function home_stats(): array
|
||||
{
|
||||
return [
|
||||
['value' => '2 weeks', 'label' => 'Typical discovery sprint'],
|
||||
['value' => 'Senior-only', 'label' => 'Delivery team composition'],
|
||||
['value' => 'Weekly', 'label' => 'Executive decision cadence'],
|
||||
['value' => '0 fluff', 'label' => 'Practical communication style'],
|
||||
];
|
||||
}
|
||||
|
||||
function service_catalog(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'badge' => 'Strategy',
|
||||
'title' => 'Product strategy & discovery',
|
||||
'summary' => 'Clarify scope, technical risk, and launch priorities before you commit engineering capacity.',
|
||||
'points' => ['Discovery workshops', 'Technical due diligence', 'Execution roadmap'],
|
||||
],
|
||||
[
|
||||
'badge' => 'Build',
|
||||
'title' => 'Custom product development',
|
||||
'summary' => 'Design and engineering for web platforms, internal tools, and customer-facing software that must perform under pressure.',
|
||||
'points' => ['UX & UI systems', 'Front-end & back-end delivery', 'QA and release management'],
|
||||
],
|
||||
[
|
||||
'badge' => 'Modernize',
|
||||
'title' => 'Platform modernization',
|
||||
'summary' => 'Reduce release friction, untangle legacy dependencies, and upgrade critical paths without pausing the business.',
|
||||
'points' => ['Architecture refactoring', 'Incremental migrations', 'Performance and security hardening'],
|
||||
],
|
||||
[
|
||||
'badge' => 'Scale',
|
||||
'title' => 'AI & workflow automation',
|
||||
'summary' => 'Apply automation and AI where it improves margin, speed, or decision quality — not as a gimmick.',
|
||||
'points' => ['Operational copilots', 'Workflow orchestration', 'Internal knowledge systems'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function company_principles(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'title' => 'Senior operators, not a bloated bench',
|
||||
'body' => 'Lean teams of senior product, design, and engineering leaders who can make trade-offs quickly.',
|
||||
],
|
||||
[
|
||||
'title' => 'Commercially grounded decisions',
|
||||
'body' => 'Every scope, tooling, and roadmap choice is tied back to launch speed, operating cost, and customer value.',
|
||||
],
|
||||
[
|
||||
'title' => 'Structured communication',
|
||||
'body' => 'You get concise weekly updates, clear next actions, and visibility into blockers before they become problems.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function process_steps(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'title' => 'Align the commercial problem',
|
||||
'body' => 'We start with goals, constraints, and the decisions leadership actually needs to make.',
|
||||
],
|
||||
[
|
||||
'title' => 'Design the operating model',
|
||||
'body' => 'Delivery plans cover scope, architecture, staffing, milestones, and risk management from day one.',
|
||||
],
|
||||
[
|
||||
'title' => 'Ship with cadence',
|
||||
'body' => 'Weekly demos, tight QA loops, and direct access to the people doing the work.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function engagement_models(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'title' => 'Dedicated delivery pod',
|
||||
'body' => 'Best for teams that need continuous product and engineering execution over multiple releases.',
|
||||
],
|
||||
[
|
||||
'title' => 'Strike team',
|
||||
'body' => 'A focused squad to unblock a launch, migration, or high-stakes product initiative.',
|
||||
],
|
||||
[
|
||||
'title' => 'Advisory sprint',
|
||||
'body' => 'Discovery, technical diligence, and roadmap planning for teams defining what to build next.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function case_studies(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'slug' => 'ledgerflow-payments-platform',
|
||||
'title' => 'Payments platform modernization without service interruption',
|
||||
'client' => 'LedgerFlow',
|
||||
'sector' => 'Fintech',
|
||||
'engagement' => 'Representative modernization program',
|
||||
'timeline' => '16-week phased rollout',
|
||||
'summary' => 'A staged modernization of payout infrastructure that reduced release risk while the business continued to scale.',
|
||||
'challenge' => [
|
||||
'A legacy payout engine was slowing new feature delivery and introducing operational risk across finance workflows.',
|
||||
'Leadership needed a modernization plan that would not interrupt transaction volume or require a full rewrite.',
|
||||
],
|
||||
'solution' => [
|
||||
'Reframed the platform into bounded services and rebuilt the highest-risk release path first.',
|
||||
'Introduced shared observability, release runbooks, and a decision forum across product, engineering, and operations.',
|
||||
],
|
||||
'results' => [
|
||||
['value' => '10 days', 'label' => 'Release cycle, down from 6 weeks'],
|
||||
['value' => '38%', 'label' => 'Faster payout completion'],
|
||||
['value' => '0', 'label' => 'Customer-facing downtime during rollout'],
|
||||
],
|
||||
'capabilities' => ['Architecture modernization', 'Payments workflow design', 'Back-office tooling', 'QA automation'],
|
||||
'technology' => ['PHP', 'Vue', 'MySQL', 'Queue-based job orchestration'],
|
||||
'quote' => 'They brought clarity to a messy modernization program and made every decision feel lower risk.',
|
||||
'quote_by' => 'Representative VP Product',
|
||||
],
|
||||
[
|
||||
'slug' => 'atlasops-service-operations-suite',
|
||||
'title' => 'Unified operations software for a multi-region field service team',
|
||||
'client' => 'AtlasOps',
|
||||
'sector' => 'Enterprise',
|
||||
'engagement' => 'Representative platform build',
|
||||
'timeline' => '20-week delivery programme',
|
||||
'summary' => 'A custom operating layer for dispatch, customer visibility, and internal analytics across multiple service regions.',
|
||||
'challenge' => [
|
||||
'Operations teams were switching between spreadsheets, email, and legacy systems to manage critical field work.',
|
||||
'Executives wanted one system of record without forcing a long procurement cycle for off-the-shelf software.',
|
||||
],
|
||||
'solution' => [
|
||||
'Designed a modular operations suite with role-based dashboards, dispatch tooling, and client-facing status views.',
|
||||
'Mapped the rollout by region so training, process change, and adoption were handled incrementally.',
|
||||
],
|
||||
'results' => [
|
||||
['value' => '41%', 'label' => 'Faster dispatch coordination'],
|
||||
['value' => '29%', 'label' => 'Reduction in manual status updates'],
|
||||
['value' => '3 regions', 'label' => 'Rolled out in the first release wave'],
|
||||
],
|
||||
'capabilities' => ['Product design', 'Operations tooling', 'Reporting dashboards', 'Change management'],
|
||||
'technology' => ['PHP', 'Bootstrap', 'MySQL', 'REST integrations'],
|
||||
'quote' => 'The new platform gave our operators one place to work and our clients a far more credible experience.',
|
||||
'quote_by' => 'Representative COO',
|
||||
],
|
||||
[
|
||||
'slug' => 'carebridge-patient-access-portal',
|
||||
'title' => 'Patient access portal for a regulated care experience',
|
||||
'client' => 'CareBridge',
|
||||
'sector' => 'Health Tech',
|
||||
'engagement' => 'Representative regulated product launch',
|
||||
'timeline' => '14-week MVP launch',
|
||||
'summary' => 'A patient portal and intake experience built for clarity, compliance, and a lower support burden.',
|
||||
'challenge' => [
|
||||
'Patient onboarding was fragmented across forms, call centers, and third-party systems, creating friction and errors.',
|
||||
'The product needed to feel calm and trustworthy while supporting auditability and internal coordination.',
|
||||
],
|
||||
'solution' => [
|
||||
'Designed an accessibility-first portal with guided intake, milestone tracking, and internal routing workflows.',
|
||||
'Embedded compliance checkpoints into the delivery process so the product team could move quickly with confidence.',
|
||||
],
|
||||
'results' => [
|
||||
['value' => '33%', 'label' => 'Fewer incomplete intakes'],
|
||||
['value' => '21%', 'label' => 'Reduction in support tickets'],
|
||||
['value' => '4.8/5', 'label' => 'Pilot user satisfaction'],
|
||||
],
|
||||
'capabilities' => ['UX architecture', 'Accessible interfaces', 'Workflow automation', 'Compliance-aware delivery'],
|
||||
'technology' => ['PHP', 'Bootstrap', 'MySQL', 'API integrations'],
|
||||
'quote' => 'The experience felt clear, trustworthy, and operationally realistic from the first pilot cohort.',
|
||||
'quote_by' => 'Representative Product Lead',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function featured_case_studies(int $limit = 3): array
|
||||
{
|
||||
return array_slice(case_studies(), 0, $limit);
|
||||
}
|
||||
|
||||
function case_study_by_slug(string $slug): ?array
|
||||
{
|
||||
foreach (case_studies() as $study) {
|
||||
if ($study['slug'] === $slug) {
|
||||
return $study;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function insights(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'slug' => 'de-risking-software-roadmaps',
|
||||
'title' => 'How senior teams de-risk software roadmaps before engineering begins',
|
||||
'category' => 'Delivery strategy',
|
||||
'published' => '2026-04-18',
|
||||
'read_time' => '5 min read',
|
||||
'excerpt' => 'High-performing delivery teams reduce uncertainty long before the first sprint starts. Here is the operating model we recommend.',
|
||||
'takeaways' => [
|
||||
'Clarify decision rights before the kickoff.',
|
||||
'Define the minimum credible release, not the dream backlog.',
|
||||
'Treat risk management as a weekly operating rhythm.',
|
||||
],
|
||||
'sections' => [
|
||||
[
|
||||
'heading' => 'Start with the decisions that matter',
|
||||
'body' => 'Most software projects do not fail because teams cannot code. They fail because leadership, product, and delivery move with different assumptions. Senior teams align on commercial priorities, delivery constraints, and what success means in measurable terms before engineering capacity is committed.',
|
||||
],
|
||||
[
|
||||
'heading' => 'Name what you will not build',
|
||||
'body' => 'A credible roadmap has edges. Defining what stays out of scope protects launch velocity, reduces hidden dependencies, and forces cleaner product thinking. The discipline to leave nice-to-have work behind is often what protects momentum.',
|
||||
],
|
||||
[
|
||||
'heading' => 'Review risk every week',
|
||||
'body' => 'Risk should not live in a static planning document. It should be reviewed as part of an operating cadence: what changed, what assumptions are now weaker, and what decisions leadership needs to make to keep delivery moving.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'when-custom-software-beats-saas',
|
||||
'title' => 'When custom software is the strategic move — and when it is not',
|
||||
'category' => 'Product decisions',
|
||||
'published' => '2026-03-29',
|
||||
'read_time' => '4 min read',
|
||||
'excerpt' => 'Custom builds create leverage when they protect the operating model, customer experience, or economics of the business.',
|
||||
'takeaways' => [
|
||||
'Buy standard tools for standard processes.',
|
||||
'Build when software is tightly linked to margin, speed, or defensibility.',
|
||||
'The right answer may be a hybrid of both.',
|
||||
],
|
||||
'sections' => [
|
||||
[
|
||||
'heading' => 'Start from the business model',
|
||||
'body' => 'If the workflow is part of how you win, generic tooling can become expensive friction. The right custom product is not about novelty; it is about protecting the parts of the business that create real advantage.',
|
||||
],
|
||||
[
|
||||
'heading' => 'Cost of ownership matters more than sticker price',
|
||||
'body' => 'Software decisions are often framed as build versus buy. In practice, the better question is which path creates the lowest total cost of ownership once workarounds, support burden, training, and change management are included.',
|
||||
],
|
||||
[
|
||||
'heading' => 'Prototype strategically',
|
||||
'body' => 'Teams do not need to build everything at once. A well-defined MVP can validate the operating model, prove adoption, and create the confidence needed for a broader platform investment.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'calmer-enterprise-launches',
|
||||
'title' => 'The communication pattern behind calmer enterprise software launches',
|
||||
'category' => 'Operations',
|
||||
'published' => '2026-02-11',
|
||||
'read_time' => '6 min read',
|
||||
'excerpt' => 'Tense launches are usually communication problems in disguise. A tighter operating rhythm changes the quality of execution.',
|
||||
'takeaways' => [
|
||||
'Use one source of truth for blockers and release decisions.',
|
||||
'Keep executive updates concise and action-oriented.',
|
||||
'Design the rollout alongside the product, not after it.',
|
||||
],
|
||||
'sections' => [
|
||||
[
|
||||
'heading' => 'Executives need clarity, not volume',
|
||||
'body' => 'The most helpful project update is often the shortest one: what moved, what is at risk, and what decision is required. When updates become narrative-heavy, the signal leadership needs often gets buried.',
|
||||
],
|
||||
[
|
||||
'heading' => 'Release planning is an operating discipline',
|
||||
'body' => 'Strong launch programs think through enablement, support, ownership, and fallbacks early. Shipping code is only one part of the release. The surrounding operating plan determines whether the launch feels controlled.',
|
||||
],
|
||||
[
|
||||
'heading' => 'Create room for calm escalation',
|
||||
'body' => 'Healthy software teams know how issues escalate, who decides, and how quickly that decision gets made. This removes ambiguity when a launch gets noisy and protects both speed and trust.',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function featured_insights(int $limit = 3): array
|
||||
{
|
||||
return array_slice(insights(), 0, $limit);
|
||||
}
|
||||
|
||||
function insight_by_slug(string $slug): ?array
|
||||
{
|
||||
foreach (insights() as $article) {
|
||||
if ($article['slug'] === $slug) {
|
||||
return $article;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function project_type_options(): array
|
||||
{
|
||||
return [
|
||||
'New product build',
|
||||
'Platform modernization',
|
||||
'AI workflow automation',
|
||||
'Design system / front-end refresh',
|
||||
'Technical discovery',
|
||||
];
|
||||
}
|
||||
|
||||
function budget_options(): array
|
||||
{
|
||||
return [
|
||||
'$15k–$30k',
|
||||
'$30k–$60k',
|
||||
'$60k–$120k',
|
||||
'$120k+',
|
||||
];
|
||||
}
|
||||
|
||||
function launch_options(): array
|
||||
{
|
||||
return [
|
||||
'ASAP (0–4 weeks)',
|
||||
'1–2 months',
|
||||
'Quarterly plan (2–4 months)',
|
||||
'Exploring / timing not fixed',
|
||||
];
|
||||
}
|
||||
|
||||
function format_display_date(string $date): string
|
||||
{
|
||||
try {
|
||||
return (new DateTimeImmutable($date))->format('F j, Y');
|
||||
} catch (Throwable $exception) {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
function format_display_datetime(string $date): string
|
||||
{
|
||||
try {
|
||||
return (new DateTimeImmutable($date, new DateTimeZone('UTC')))->format('F j, Y \a\t H:i \U\T\C');
|
||||
} catch (Throwable $exception) {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
function current_year(): string
|
||||
{
|
||||
return gmdate('Y');
|
||||
}
|
||||
351
index.php
351
index.php
@ -1,150 +1,213 @@
|
||||
<?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');
|
||||
require_once __DIR__ . '/includes/site.php';
|
||||
|
||||
$pageTitle = 'Premium software engineering for ambitious product teams';
|
||||
$pageDescription = 'A high-end technology company website focused on custom software delivery, platform modernization, and AI workflows.';
|
||||
$activeNav = 'home';
|
||||
|
||||
$featuredStudies = featured_case_studies();
|
||||
$featuredInsights = featured_insights();
|
||||
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<!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>
|
||||
<main>
|
||||
<section class="hero-section section-shell border-bottom">
|
||||
<div class="container">
|
||||
<div class="row align-items-center g-5">
|
||||
<div class="col-lg-7">
|
||||
<span class="eyebrow">Software engineering partner for high-stakes digital products</span>
|
||||
<h1 class="hero-title">Build serious software with a team that thinks like owners.</h1>
|
||||
<p class="hero-copy">We help founders, product teams, and enterprise leaders design, build, and modernize software that needs to move with confidence. Strategy, delivery, and communication stay crisp from day one.</p>
|
||||
<div class="d-flex flex-wrap gap-3 pt-2">
|
||||
<a class="btn btn-primary btn-lg" href="/contact.php">Request a proposal</a>
|
||||
<a class="btn btn-outline-dark btn-lg" href="/work.php">Review case studies</a>
|
||||
</div>
|
||||
<div class="hero-proof d-flex flex-wrap gap-3 mt-4">
|
||||
<span>Senior-only delivery pods</span>
|
||||
<span>Launch-focused roadmaps</span>
|
||||
<span>Calm stakeholder communication</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="surface-panel hero-panel h-100">
|
||||
<div class="panel-label">Delivery snapshot</div>
|
||||
<div class="panel-heading">How we structure the first 30 days</div>
|
||||
<div class="panel-stack">
|
||||
<div class="panel-item">
|
||||
<span>Week 1</span>
|
||||
<strong>Commercial and technical discovery</strong>
|
||||
</div>
|
||||
<div class="panel-item">
|
||||
<span>Week 2</span>
|
||||
<strong>Decision-ready roadmap and solution direction</strong>
|
||||
</div>
|
||||
<div class="panel-item">
|
||||
<span>Week 3</span>
|
||||
<strong>Design, architecture, and delivery sprint setup</strong>
|
||||
</div>
|
||||
<div class="panel-item">
|
||||
<span>Week 4</span>
|
||||
<strong>Production-ready build cadence with reporting</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-note">Designed for teams that need a premium execution partner — not more noise.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 stats-row">
|
||||
<?php foreach (home_stats() as $stat): ?>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="metric-card h-100">
|
||||
<strong><?= htmlspecialchars($stat['value']) ?></strong>
|
||||
<span><?= htmlspecialchars($stat['label']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</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>
|
||||
</section>
|
||||
|
||||
<section class="section-shell border-bottom" id="services">
|
||||
<div class="container">
|
||||
<div class="section-heading-row row g-4 align-items-end">
|
||||
<div class="col-lg-7">
|
||||
<span class="eyebrow">Services</span>
|
||||
<h2 class="section-title">A compact service mix built for momentum, quality, and commercial clarity.</h2>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<p class="section-copy mb-0">Whether you need a new product, a cleaner architecture, or a more resilient operating system, we focus on the smallest move that creates durable leverage.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4 mt-1">
|
||||
<?php foreach (service_catalog() as $service): ?>
|
||||
<div class="col-md-6 col-xl-3 d-flex">
|
||||
<article class="service-card h-100 w-100">
|
||||
<span class="service-badge"><?= htmlspecialchars($service['badge']) ?></span>
|
||||
<h3><?= htmlspecialchars($service['title']) ?></h3>
|
||||
<p><?= htmlspecialchars($service['summary']) ?></p>
|
||||
<ul class="list-unstyled service-points mb-0">
|
||||
<?php foreach ($service['points'] as $point): ?>
|
||||
<li><?= htmlspecialchars($point) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell border-bottom">
|
||||
<div class="container">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-5">
|
||||
<span class="eyebrow">How we work</span>
|
||||
<h2 class="section-title">High-trust delivery for teams that care about both craft and decision speed.</h2>
|
||||
<p class="section-copy">We combine strategic clarity with hands-on execution so leadership gets confidence without needing to chase updates.</p>
|
||||
<div class="surface-panel compact-panel mt-4">
|
||||
<div class="panel-label">Why teams bring us in</div>
|
||||
<?php foreach (company_principles() as $principle): ?>
|
||||
<div class="content-row">
|
||||
<h3><?= htmlspecialchars($principle['title']) ?></h3>
|
||||
<p><?= htmlspecialchars($principle['body']) ?></p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="process-grid">
|
||||
<?php foreach (process_steps() as $index => $step): ?>
|
||||
<article class="process-card">
|
||||
<span class="process-index">0<?= $index + 1 ?></span>
|
||||
<h3><?= htmlspecialchars($step['title']) ?></h3>
|
||||
<p><?= htmlspecialchars($step['body']) ?></p>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell border-bottom">
|
||||
<div class="container">
|
||||
<div class="section-heading-row row g-4 align-items-end">
|
||||
<div class="col-lg-7">
|
||||
<span class="eyebrow">Representative work</span>
|
||||
<h2 class="section-title">Selected engagements that show how we approach product, platform, and operations problems.</h2>
|
||||
</div>
|
||||
<div class="col-lg-5 text-lg-end">
|
||||
<a class="btn btn-outline-dark" href="/work.php">View all case studies</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4 mt-1">
|
||||
<?php foreach ($featuredStudies as $study): ?>
|
||||
<div class="col-lg-4 d-flex">
|
||||
<article class="case-study-card h-100 w-100">
|
||||
<div class="card-topline">
|
||||
<span><?= htmlspecialchars($study['sector']) ?></span>
|
||||
<span><?= htmlspecialchars($study['timeline']) ?></span>
|
||||
</div>
|
||||
<h3><?= htmlspecialchars($study['title']) ?></h3>
|
||||
<p><?= htmlspecialchars($study['summary']) ?></p>
|
||||
<div class="result-pills">
|
||||
<?php foreach ($study['results'] as $result): ?>
|
||||
<span><?= htmlspecialchars($result['value']) ?> · <?= htmlspecialchars($result['label']) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<a class="text-link mt-auto" href="/case-study.php?slug=<?= urlencode($study['slug']) ?>">Read case study</a>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell border-bottom">
|
||||
<div class="container">
|
||||
<div class="section-heading-row row g-4 align-items-end">
|
||||
<div class="col-lg-7">
|
||||
<span class="eyebrow">Insights</span>
|
||||
<h2 class="section-title">Editorial thinking for teams planning their next software move.</h2>
|
||||
</div>
|
||||
<div class="col-lg-5 text-lg-end">
|
||||
<a class="btn btn-outline-dark" href="/insights.php">Browse insights</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4 mt-1">
|
||||
<?php foreach ($featuredInsights as $article): ?>
|
||||
<div class="col-lg-4 d-flex">
|
||||
<article class="insight-card h-100 w-100">
|
||||
<div class="card-topline">
|
||||
<span><?= htmlspecialchars($article['category']) ?></span>
|
||||
<span><?= htmlspecialchars($article['read_time']) ?></span>
|
||||
</div>
|
||||
<h3><?= htmlspecialchars($article['title']) ?></h3>
|
||||
<p><?= htmlspecialchars($article['excerpt']) ?></p>
|
||||
<a class="text-link mt-auto" href="/insight.php?slug=<?= urlencode($article['slug']) ?>">Read article</a>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell">
|
||||
<div class="container">
|
||||
<div class="cta-panel">
|
||||
<div>
|
||||
<span class="eyebrow">Start the conversation</span>
|
||||
<h2 class="section-title mb-2">Need a team that can turn ambiguity into a clear execution plan?</h2>
|
||||
<p class="section-copy mb-0">Send a short brief and we will shape the next steps around your business context, not a generic process.</p>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-3 justify-content-lg-end">
|
||||
<a class="btn btn-primary btn-lg" href="/contact.php">Start a project brief</a>
|
||||
<a class="btn btn-outline-dark btn-lg" href="/insights.php">Read our thinking</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php require __DIR__ . '/partials/footer.php'; ?>
|
||||
|
||||
91
insight.php
Normal file
91
insight.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/site.php';
|
||||
|
||||
$slug = isset($_GET['slug']) ? trim((string) $_GET['slug']) : '';
|
||||
$article = insight_by_slug($slug);
|
||||
|
||||
if (!$article) {
|
||||
http_response_code(404);
|
||||
$pageTitle = 'Article not found';
|
||||
$pageDescription = 'The requested insight article could not be found.';
|
||||
$activeNav = 'insights';
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<main>
|
||||
<section class="section-shell">
|
||||
<div class="container">
|
||||
<div class="empty-state empty-state--page">
|
||||
<h1>Insight not found</h1>
|
||||
<p>The article you requested is unavailable. You can return to the insights overview and pick another piece.</p>
|
||||
<a class="btn btn-primary" href="/insights.php">Back to insights</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php
|
||||
require __DIR__ . '/partials/footer.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$pageTitle = $article['title'];
|
||||
$pageDescription = $article['excerpt'];
|
||||
$activeNav = 'insights';
|
||||
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<main>
|
||||
<section class="section-shell border-bottom page-hero">
|
||||
<div class="container">
|
||||
<a class="crumb-link" href="/insights.php">← Back to insights</a>
|
||||
<div class="row g-4 align-items-end mt-2">
|
||||
<div class="col-lg-8">
|
||||
<span class="eyebrow"><?= htmlspecialchars($article['category']) ?></span>
|
||||
<h1 class="section-title"><?= htmlspecialchars($article['title']) ?></h1>
|
||||
<p class="section-copy mb-0"><?= htmlspecialchars($article['excerpt']) ?></p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="surface-panel compact-panel h-100">
|
||||
<div class="content-row mb-0">
|
||||
<h3>Published</h3>
|
||||
<p><?= htmlspecialchars(format_display_date($article['published'])) ?> · <?= htmlspecialchars($article['read_time']) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell border-bottom">
|
||||
<div class="container">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-4">
|
||||
<div class="surface-panel h-100">
|
||||
<div class="panel-label">Key takeaways</div>
|
||||
<ul class="list-unstyled service-points mb-0">
|
||||
<?php foreach ($article['takeaways'] as $takeaway): ?>
|
||||
<li><?= htmlspecialchars($takeaway) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<article class="surface-panel article-panel h-100">
|
||||
<?php foreach ($article['sections'] as $section): ?>
|
||||
<section class="article-section">
|
||||
<h2><?= htmlspecialchars($section['heading']) ?></h2>
|
||||
<p><?= htmlspecialchars($section['body']) ?></p>
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
<div class="quote-panel mt-4">
|
||||
<p>Want to apply this thinking to a live roadmap?</p>
|
||||
<a class="btn btn-primary" href="/contact.php">Start a project brief</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php require __DIR__ . '/partials/footer.php'; ?>
|
||||
71
insights.php
Normal file
71
insights.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/site.php';
|
||||
|
||||
$pageTitle = 'Insights';
|
||||
$pageDescription = 'Editorial content on software delivery, roadmap strategy, platform modernization, and calm launches.';
|
||||
$activeNav = 'insights';
|
||||
$articles = insights();
|
||||
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<main>
|
||||
<section class="section-shell border-bottom page-hero">
|
||||
<div class="container">
|
||||
<div class="row g-4 align-items-end">
|
||||
<div class="col-lg-8">
|
||||
<span class="eyebrow">Insights</span>
|
||||
<h1 class="section-title">Short, decision-ready thinking for software leaders.</h1>
|
||||
<p class="section-copy mb-0">A library of practical guidance on product strategy, delivery discipline, and how to move faster without creating unnecessary risk.</p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="surface-panel compact-panel h-100">
|
||||
<div class="panel-label">Editorial focus</div>
|
||||
<div class="content-row">
|
||||
<h3>Roadmaps</h3>
|
||||
<p>What to build, what to defer, and how to protect launch velocity.</p>
|
||||
</div>
|
||||
<div class="content-row">
|
||||
<h3>Delivery operations</h3>
|
||||
<p>Communication patterns, risk review, and release planning for complex programs.</p>
|
||||
</div>
|
||||
<div class="content-row mb-0">
|
||||
<h3>Build vs buy</h3>
|
||||
<p>Where custom software creates strategic leverage and where it does not.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell">
|
||||
<div class="container">
|
||||
<div class="row g-4">
|
||||
<?php foreach ($articles as $article): ?>
|
||||
<div class="col-lg-4 d-flex">
|
||||
<article class="insight-card h-100 w-100">
|
||||
<div class="card-topline">
|
||||
<span><?= htmlspecialchars($article['category']) ?></span>
|
||||
<span><?= htmlspecialchars($article['read_time']) ?></span>
|
||||
</div>
|
||||
<h2 class="h4"><?= htmlspecialchars($article['title']) ?></h2>
|
||||
<p><?= htmlspecialchars($article['excerpt']) ?></p>
|
||||
<div class="result-pills">
|
||||
<?php foreach ($article['takeaways'] as $takeaway): ?>
|
||||
<span><?= htmlspecialchars($takeaway) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="mt-auto pt-3 d-flex justify-content-between align-items-center gap-3">
|
||||
<span class="mini-label"><?= htmlspecialchars(format_display_date($article['published'])) ?></span>
|
||||
<a class="text-link" href="/insight.php?slug=<?= urlencode($article['slug']) ?>">Read article</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php require __DIR__ . '/partials/footer.php'; ?>
|
||||
43
partials/footer.php
Normal file
43
partials/footer.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
?>
|
||||
<footer class="site-footer border-top">
|
||||
<div class="container py-5">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-6">
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<span class="brand-mark brand-mark--footer"><?= htmlspecialchars(site_initials()) ?></span>
|
||||
<div>
|
||||
<div class="brand-name mb-1"><?= htmlspecialchars(site_name()) ?></div>
|
||||
<div class="footer-muted">High-end digital products for ambitious teams.</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="footer-copy mb-0">Strategy, design, engineering, and modernization delivered with a senior-only team and a calm operating rhythm.</p>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="footer-links">
|
||||
<?php foreach (nav_items() as $item): ?>
|
||||
<a href="<?= htmlspecialchars($item['href']) ?>"><?= htmlspecialchars($item['label']) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<p class="footer-small mb-0 mt-3">Remote delivery across North America and Europe · © <?= htmlspecialchars(current_year()) ?> <?= htmlspecialchars(site_name()) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||||
<div id="siteToast" class="toast site-toast" role="status" aria-live="polite" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<span class="toast-dot"></span>
|
||||
<strong class="me-auto"><?= htmlspecialchars(site_name()) ?></strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="<?= htmlspecialchars(asset_url('assets/js/main.js')) ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
65
partials/header.php
Normal file
65
partials/header.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$pageTitle = isset($pageTitle) ? (string) $pageTitle : '';
|
||||
$pageDescription = isset($pageDescription) ? (string) $pageDescription : '';
|
||||
$activeNav = isset($activeNav) ? (string) $activeNav : '';
|
||||
$toastMessage = isset($toastMessage) ? (string) $toastMessage : '';
|
||||
$bodyClass = isset($bodyClass) ? (string) $bodyClass : '';
|
||||
|
||||
$resolvedDescription = trim($pageDescription !== '' ? $pageDescription : site_description_default());
|
||||
$resolvedTitle = page_title($pageTitle);
|
||||
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title><?= htmlspecialchars($resolvedTitle) ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($resolvedDescription) ?>" />
|
||||
<meta name="author" content="<?= htmlspecialchars(site_name()) ?>" />
|
||||
<meta property="og:title" content="<?= htmlspecialchars($resolvedTitle) ?>" />
|
||||
<meta property="og:description" content="<?= htmlspecialchars($resolvedDescription) ?>" />
|
||||
<meta property="twitter:title" content="<?= htmlspecialchars($resolvedTitle) ?>" />
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($resolvedDescription) ?>" />
|
||||
<meta name="theme-color" content="#0f172a" />
|
||||
<?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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="<?= htmlspecialchars(asset_url('assets/css/custom.css')) ?>">
|
||||
</head>
|
||||
<body class="<?= htmlspecialchars($bodyClass) ?>"<?php if ($toastMessage !== ''): ?> data-toast-message="<?= htmlspecialchars($toastMessage) ?>"<?php endif; ?>>
|
||||
<header class="site-header sticky-top">
|
||||
<nav class="navbar navbar-expand-lg" aria-label="Primary navigation">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center gap-3" href="/">
|
||||
<span class="brand-mark"><?= htmlspecialchars(site_initials()) ?></span>
|
||||
<span>
|
||||
<span class="brand-name d-block"><?= htmlspecialchars(site_name()) ?></span>
|
||||
<span class="brand-subtitle d-block">Software engineering & product delivery</span>
|
||||
</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#primaryNav" aria-controls="primaryNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="primaryNav">
|
||||
<ul class="navbar-nav ms-auto mb-3 mb-lg-0 align-items-lg-center gap-lg-2">
|
||||
<?php foreach (nav_items() as $item): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link<?= $activeNav === $item['key'] ? ' active' : '' ?>" href="<?= htmlspecialchars($item['href']) ?>"><?= htmlspecialchars($item['label']) ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
<li class="nav-item ms-lg-2">
|
||||
<a class="btn btn-primary btn-sm site-cta" href="/contact.php">Start a project</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
80
work.php
Normal file
80
work.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/site.php';
|
||||
|
||||
$pageTitle = 'Case studies';
|
||||
$pageDescription = 'Representative technology engagements across fintech, enterprise operations, and health-tech delivery.';
|
||||
$activeNav = 'work';
|
||||
$caseStudies = case_studies();
|
||||
|
||||
$filters = ['all' => 'All sectors'];
|
||||
foreach ($caseStudies as $study) {
|
||||
$filters[slugify_label($study['sector'])] = $study['sector'];
|
||||
}
|
||||
|
||||
require __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<main>
|
||||
<section class="section-shell border-bottom page-hero">
|
||||
<div class="container">
|
||||
<div class="row g-4 align-items-end">
|
||||
<div class="col-lg-8">
|
||||
<span class="eyebrow">Work</span>
|
||||
<h1 class="section-title">Representative case studies for teams buying senior software delivery.</h1>
|
||||
<p class="section-copy mb-0">A look at the product, platform, and operations problems we solve when software has to feel credible, fast, and commercially sound.</p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="surface-panel compact-panel h-100">
|
||||
<div class="panel-label">Engagement models</div>
|
||||
<?php foreach (engagement_models() as $model): ?>
|
||||
<div class="content-row">
|
||||
<h3><?= htmlspecialchars($model['title']) ?></h3>
|
||||
<p><?= htmlspecialchars($model['body']) ?></p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-shell">
|
||||
<div class="container">
|
||||
<div class="filter-toolbar" data-filter-group data-filter-target="#workGrid" data-empty-state="#workEmptyState">
|
||||
<?php foreach ($filters as $key => $label): ?>
|
||||
<button type="button" class="filter-chip<?= $key === 'all' ? ' is-active' : '' ?>" data-filter="<?= htmlspecialchars($key) ?>"><?= htmlspecialchars($label) ?></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="row g-4 mt-1" id="workGrid">
|
||||
<?php foreach ($caseStudies as $study): ?>
|
||||
<div class="col-lg-4 d-flex" data-filter-item="<?= htmlspecialchars(slugify_label($study['sector'])) ?>">
|
||||
<article class="case-study-card h-100 w-100">
|
||||
<div class="card-topline">
|
||||
<span><?= htmlspecialchars($study['sector']) ?></span>
|
||||
<span><?= htmlspecialchars($study['timeline']) ?></span>
|
||||
</div>
|
||||
<h2 class="h4"><?= htmlspecialchars($study['title']) ?></h2>
|
||||
<p><?= htmlspecialchars($study['summary']) ?></p>
|
||||
<div class="result-pills">
|
||||
<?php foreach ($study['results'] as $result): ?>
|
||||
<span><?= htmlspecialchars($result['value']) ?> · <?= htmlspecialchars($result['label']) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="mt-auto pt-3 d-flex justify-content-between align-items-center gap-3">
|
||||
<span class="mini-label"><?= htmlspecialchars($study['engagement']) ?></span>
|
||||
<a class="text-link" href="/case-study.php?slug=<?= urlencode($study['slug']) ?>">View detail</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="empty-state d-none" id="workEmptyState">
|
||||
<h2>No case studies match that filter.</h2>
|
||||
<p>Try a different sector or view the full list again.</p>
|
||||
<button type="button" class="btn btn-outline-dark" data-reset-filter>Show all sectors</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php require __DIR__ . '/partials/footer.php'; ?>
|
||||
Loading…
x
Reference in New Issue
Block a user