Prettier
Some checks failed
ci/woodpecker/pr/ci Pipeline failed

This commit is contained in:
2026-03-09 20:28:12 -03:00
parent 1e0afb103c
commit 9e692d072b
10 changed files with 450 additions and 15 deletions

View File

@@ -334,7 +334,7 @@ a {
margin: 0 auto;
padding: 0 var(--space-md);
&.container--narrow {
&.narrow {
max-width: var(--max-narrow-width);
}
}

View File

@@ -19,7 +19,7 @@
</script>
<section id={sectionId} class="section" aria-labelledby={headingId}>
<div class="container container--narrow">
<div class="container narrow">
<h2 id={headingId}>{heading}</h2>
{#if intro}
<p>{intro}</p>

View File

@@ -11,7 +11,7 @@
</script>
<section id="faq" class="section faq" aria-labelledby="faq-heading">
<div class="container container--narrow">
<div class="container narrow">
<h2 id="faq-heading">{title}</h2>
<dl class="faq-list">
{#each faqList as { question, answer }, index (index)}

View File

@@ -29,10 +29,9 @@
hidden
/>
<div class="mobile-nav-header">
<span class={[
'mobile nav-header-logo',
{ 'page-home': bodyClass === 'page-home' }
]}>
<span
class={['mobile nav-header-logo', { 'page-home': bodyClass === 'page-home' }]}
>
<Wordmark />
</span>
<button

View File

@@ -12,7 +12,7 @@
['section', section.sectionClass].filter(Boolean).join(' '),
);
const containerClass = $derived(
section.narrowContainer === false ? 'container' : 'container container--narrow',
section.narrowContainer === false ? 'container' : 'container narrow',
);
const listClass = $derived(
[section.bulletsListClass, 'content-list'].filter(Boolean).join(' '),

View File

@@ -35,7 +35,7 @@ export interface ServiceSectionContent {
headingSrOnly?: boolean;
/** Extra class(es) for the section element (e.g. service-credibility) */
sectionClass?: string;
/** Use container--narrow; set false for full-width strips (e.g. credibility) */
/** Use narrow; set false for full-width strips (e.g. credibility) */
narrowContainer?: boolean;
/** Optional class for the bullets ul (e.g. service-credibility__list) */
bulletsListClass?: string;

View File

@@ -0,0 +1,436 @@
<script lang="ts">
import Navigation from '$lib/components/Navigation.svelte';
import Footer from '$lib/components/Footer.svelte';
import ExternalLinkIcon from '$lib/components/Icon/ExternalLink.svelte';
import { faqItems } from '$lib/data/mvp-architecture-and-launch/faq';
const discoveryCallUrl =
'https://cal.mifi.ventures/the-mifi/30min?utm_source=website&utm_medium=cta&utm_campaign=schedule_call&utm_content=mvp_arch_launch_page';
const navItems = [
{ label: 'Home', href: '/', umamiEventLabel: 'home' },
{ label: 'My approach', href: '#approach', umamiEventLabel: 'approach' },
{ label: 'Engagement', href: '#engagement', umamiEventLabel: 'engagement' },
{ label: 'FAQ', href: '#faq', umamiEventLabel: 'faq' },
{
label: 'Book a call',
href: discoveryCallUrl,
umamiEventLabel: 'book-call',
},
];
</script>
<Navigation items={navItems} page="mvp-architecture-and-launch" />
<header id="header" class="service-hero">
<div class="container">
<h1 class="service-hero__title">
MVP Architecture & Launch for Early-Stage SaaS
</h1>
<p class="service-hero__subhead">
Shipping fast is good. Shipping chaos is expensive. I help early-stage SaaS
teams build MVPs that move quickly without creating frontend debt, fragile
CSS, or structural problems that slow iteration six months later.
</p>
<div class="cta-group">
<a
href={discoveryCallUrl}
class="btn btn-primary icon-button"
target="_blank"
rel="noopener noreferrer"
aria-label="Book a discovery call (opens in new tab)"
data-umami-event="book discovery call"
data-umami-event-location="hero"
>
Book a discovery call
<ExternalLinkIcon aria-label="Opens in new tab" size={17} />
</a>
<a
href="#approach"
class="btn btn-secondary"
data-umami-event="see how i work"
data-umami-event-location="hero"
>
See how I work
</a>
</div>
</div>
</header>
<main id="main">
<nav class="section service-toc" aria-label="Page contents">
<div class="container">
<h2 class="service-toc__title">On this page</h2>
<ul class="service-toc__list">
<li><a href="#common-pattern">The common MVP pattern</a></li>
<li>
<a href="#good-foundation">What a good MVP foundation looks like</a>
</li>
<li><a href="#approach">My approach</a></li>
<li><a href="#what-changes">What changes within 1&ndash;2 weeks</a></li>
<li><a href="#engagement">Engagement options</a></li>
<li><a href="#who-its-for">Who it's for</a></li>
<li><a href="#faq">FAQ</a></li>
<li><a href="#final-cta">Get in touch</a></li>
</ul>
</div>
</nav>
<section id="common-pattern" class="section" aria-labelledby="common-pattern-heading">
<div class="container narrow">
<h2 id="common-pattern-heading">
Most MVPs are built for speed—few are built for iteration
</h2>
<p>
Early MVPs often prioritize backend logic and feature delivery. The
frontend becomes an afterthought—functional, but brittle. Six months
later, every new feature feels heavier than the last.
</p>
<p>Common symptoms:</p>
<ul class="content-list">
<li>Poor separation of concerns</li>
<li>Backend-heavy architecture with fragile UI</li>
<li>Repeated components instead of reusable systems</li>
<li>Spaghetti CSS and specificity wars</li>
<li>Accessibility postponed</li>
<li>"We'll clean it up later" decisions compounding</li>
</ul>
<p>Speed isn't the problem. Structure is.</p>
</div>
</section>
<section
id="good-foundation"
class="section"
aria-labelledby="good-foundation-heading"
>
<div class="container narrow">
<h2 id="good-foundation-heading">MVP does not mean throwaway</h2>
<p>A well-built MVP is minimal—but intentional.</p>
<p>It includes:</p>
<ul class="content-list">
<li>Clear separation between layers</li>
<li>Reusable, composable frontend components</li>
<li>Tokenized design systems (color, spacing, typography)</li>
<li>Clean, maintainable CSS architecture</li>
<li>Accessibility baked in from day one</li>
<li>A simple, predictable deployment path</li>
</ul>
<p>You can move fast and build correctly at the same time.</p>
<p>
<a href="/hands-on-saas-architecture-consultant"
>Hands-on SaaS architecture</a
>
·
<a href="/fractional-cto-for-early-stage-saas"
>Fractional CTO for early-stage SaaS</a
>
·
<a href="/stage-aligned-infrastructure">Stage-aligned infrastructure</a>
</p>
</div>
</section>
<section id="approach" class="section" aria-labelledby="approach-heading">
<div class="container narrow">
<h2 id="approach-heading">Architecture through implementation</h2>
<p>I don't deliver diagrams and disappear. I work inside your codebase.</p>
<p>My approach:</p>
<ol class="content-list content-list--ordered">
<li>Fix the CSS foundation first.</li>
<li>Extract and standardize reusable components.</li>
<li>Introduce design tokens to prevent duplication.</li>
<li>Align frontend and backend boundaries.</li>
<li>Improve accessibility and semantics incrementally.</li>
<li>Keep shipping while refactoring.</li>
</ol>
<p>No rewrite mandates. No velocity freeze.</p>
</div>
</section>
<section id="what-changes" class="section" aria-labelledby="what-changes-heading">
<div class="container narrow">
<h2 id="what-changes-heading">What teams notice quickly</h2>
<p>
In most cases, teams feel the difference within 12 weeks once
foundational issues are corrected.
</p>
<p>You'll see:</p>
<ul class="content-list">
<li>Faster feature implementation</li>
<li>Lower bug rates</li>
<li>More consistent UI</li>
<li>Safer refactors</li>
<li>Increased release confidence</li>
<li>Better team morale</li>
</ul>
<p>
It's all one big ball of yarn—clean up the foundation and everything moves
more smoothly.
</p>
</div>
</section>
<section id="engagement" class="section" aria-labelledby="engagement-heading">
<div class="container narrow">
<h2 id="engagement-heading">How we can work together</h2>
<h3>MVP Architecture Engagement (fixed scope)</h3>
<ul class="content-list">
<li>Codebase review focused on frontend foundations</li>
<li>Structural audit and prioritized roadmap</li>
<li>Component system extraction plan</li>
<li>CSS cleanup and token strategy</li>
<li>Accessibility baseline</li>
</ul>
<h3>Hands-On Implementation (optional)</h3>
<ul class="content-list">
<li>Direct refactoring and component system creation</li>
<li>Tokenized design system rollout</li>
<li>Pairing with your engineers</li>
<li>Documentation and knowledge transfer</li>
</ul>
<h3>Ongoing Advisory (optional)</h3>
<ul class="content-list">
<li>Periodic architecture reviews</li>
<li>Guardrails as you scale</li>
<li>Guidance on feature/system tradeoffs</li>
</ul>
</div>
</section>
<section id="who-its-for" class="section" aria-labelledby="who-for-heading">
<div class="container">
<div class="who-grid">
<div class="who-block">
<h2 id="who-for-heading">Ideal fit</h2>
<ul class="content-list">
<li>Founder-led SaaS teams</li>
<li>110 engineers</li>
<li>Recently launched MVP</li>
<li>Feeling UI friction or code fragility</li>
<li>Want adult-level architecture without slowing down</li>
</ul>
</div>
<div class="who-block">
<h2 id="who-not-heading">Who this is not for</h2>
<ul class="content-list">
<li>
Teams who only want features shipped as fast as possible
without regard for structure
</li>
<li>Organizations looking purely for architecture slide decks</li>
<li>Large enterprises needing formal procurement processes</li>
</ul>
</div>
</div>
</div>
</section>
<section id="faq" class="section service-faq" aria-labelledby="faq-heading">
<div class="container narrow">
<h2 id="faq-heading">FAQ</h2>
<dl class="faq-list">
{#each faqItems as item (item.question)}
<dt>{item.question}</dt>
<dd>{item.answer}</dd>
{/each}
</dl>
</div>
</section>
<section
id="final-cta"
class="section schedule-section"
aria-labelledby="final-cta-heading"
>
<div class="container">
<h2 id="final-cta-heading">Ready to stabilize your MVP?</h2>
<p class="schedule-text">
If your MVP shipped fast but now feels fragile, let's reinforce the
foundation before iteration slows further.
</p>
<div class="cta-group">
<a
href={discoveryCallUrl}
class="btn btn-primary icon-button"
target="_blank"
rel="noopener noreferrer"
aria-label="Book a discovery call (opens in new tab)"
data-umami-event="book discovery call"
data-umami-event-location="final cta"
>
Book a discovery call
<ExternalLinkIcon aria-label="Opens in new tab" size={17} />
</a>
<a
href="mailto:hello@mifi.ventures"
class="btn btn-secondary"
data-umami-event="email"
data-umami-event-location="final cta"
>
Email me
</a>
</div>
</div>
</section>
</main>
<Footer />
<style>
.service-hero {
padding: var(--space-xxxl) 0 var(--space-xxl) 0;
text-align: center;
background-color: var(--color-bg);
border-bottom: 1px solid var(--color-border);
}
.service-hero__title {
margin-bottom: var(--space-lg);
font-family: var(--font-family-heading);
font-size: var(--font-size-xxl);
font-weight: var(--font-weight-bold);
letter-spacing: -0.03em;
max-width: var(--max-text-width);
margin-left: auto;
margin-right: auto;
}
.service-hero__subhead {
max-width: var(--max-narrow-width);
margin: 0 auto var(--space-xl) auto;
font-size: var(--font-size-large);
font-weight: var(--font-weight-normal);
color: var(--color-text-secondary);
line-height: var(--line-height-relaxed);
}
.cta-group {
display: flex;
flex-wrap: wrap;
gap: var(--space-md);
justify-content: center;
align-items: center;
margin-top: var(--space-lg);
}
@media (max-width: 768px) {
.cta-group {
flex-direction: column;
width: 100%;
}
}
.narrow {
max-width: var(--max-narrow-width);
}
.service-toc {
padding: var(--space-xl) 0;
border-bottom: 1px solid var(--color-border);
}
.service-toc__title {
font-size: var(--font-size-large);
font-weight: var(--font-weight-semibold);
margin-bottom: var(--space-md);
}
.service-toc__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: var(--space-sm) var(--space-xl);
}
.service-toc__list a {
color: var(--color-text-secondary);
font-size: var(--font-size-medium);
}
.service-toc__list a:hover {
color: var(--color-primary);
}
.content-list--ordered {
list-style: decimal;
padding-left: var(--space-xl);
}
.content-list--ordered li {
padding-left: var(--space-sm);
}
.content-list--ordered li::before {
content: none;
}
.who-grid {
display: grid;
gap: var(--space-xxl);
grid-template-columns: 1fr 1fr;
}
@media (max-width: 768px) {
.who-grid {
grid-template-columns: 1fr;
}
}
.who-block h2 {
font-size: var(--font-size-xl);
margin-bottom: var(--space-md);
}
.service-faq {
background-color: var(--color-bg-alt);
}
.faq-list {
margin: 0;
}
.faq-list dt {
font-weight: var(--font-weight-semibold);
color: var(--color-text);
margin-top: var(--space-xl);
margin-bottom: var(--space-sm);
}
.faq-list dt:first-child {
margin-top: 0;
}
.faq-list dd {
margin: 0 0 0 var(--space-md);
color: var(--color-text-secondary);
line-height: var(--line-height-relaxed);
max-width: var(--max-text-width);
}
.schedule-section {
text-align: center;
background-color: var(--color-bg-subtle);
}
.schedule-text {
margin-bottom: var(--space-lg);
font-size: var(--font-size-large);
font-weight: var(--font-weight-normal);
color: var(--color-text-secondary);
line-height: var(--line-height-relaxed);
max-width: var(--max-text-width);
margin-left: auto;
margin-right: auto;
}
/* Avoid anchor targets sitting under sticky nav */
[id] {
scroll-margin-top: 6rem;
}
</style>

View File

@@ -8,7 +8,7 @@
<Navigation items={navItems} page="privacy-policy" />
<main id="main" class="legal-page">
<div class="container container--narrow">
<div class="container narrow">
<header id="header" class="legal-header">
<h1>{privacyPolicy.title}</h1>
<p class="legal-last-updated">Last updated: {privacyPolicy.lastUpdated}</p>
@@ -81,7 +81,7 @@
background-color: var(--color-bg);
}
.container--narrow {
.narrow {
max-width: var(--max-narrow-width);
margin: 0 auto;
padding: 0 var(--space-md);

View File

@@ -13,7 +13,7 @@
<main id="main">
<section class="section services-intro" aria-labelledby="intro-heading">
<div class="container container--narrow">
<div class="container narrow">
<h2 id="intro-heading" class="sr-only">How we work together</h2>
{#each pageContent.introParagraphs as p}
<p>{p}</p>
@@ -30,7 +30,7 @@
/>
<section class="section services-ideal" aria-labelledby="ideal-heading">
<div class="container container--narrow">
<div class="container narrow">
<h2 id="ideal-heading">Ideal clients</h2>
<ul class="content-list">
{#each pageContent.idealClients as item}

View File

@@ -8,7 +8,7 @@
<Navigation items={navItems} page="terms-of-service" />
<main id="main" class="legal-page">
<div class="container container--narrow">
<div class="container narrow">
<header id="header" class="legal-header">
<h1>{termsOfService.title}</h1>
<p class="legal-last-updated">Last updated: {termsOfService.lastUpdated}</p>
@@ -64,7 +64,7 @@
background-color: var(--color-bg);
}
.container--narrow {
.narrow {
max-width: var(--max-narrow-width);
margin: 0 auto;
padding: 0 var(--space-md);