432 lines
18 KiB
PHP
432 lines
18 KiB
PHP
<?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');
|
||
}
|