The Svelte 5 SSG migration—we brought sexy back... or to it? Or something...

This commit is contained in:
2026-01-30 23:29:39 -03:00
parent 40b770f8b5
commit 44f6743b45
93 changed files with 6409 additions and 3579 deletions

640
src/app.css Normal file
View File

@@ -0,0 +1,640 @@
/* ========================================
CSS Variables for Light/Dark Mode
======================================== */
:root {
/* Light mode colors */
--color-bg: #ffffff;
--color-bg-alt: #faf9ff; /* subtle violet-tinted off-white */
--color-bg-subtle: #f3f1ff; /* soft surface */
--color-text: #14121a;
--color-text-secondary: #3f3a4a;
--color-text-tertiary: #625b70;
--color-border: #e4e0f2;
--color-border-strong: #c9c1e3;
/* Brand accent (links, focus, highlights) */
--color-primary: #6d28d9; /* purple */
--color-primary-hover: #5b21b6;
--color-primary-bg: #efe7ff;
--color-secondary: #3f3a4a;
--color-secondary-hover: #14121a;
/* Focus */
--color-focus: #6d28d9;
--color-focus-outline: rgba(109, 40, 217, 0.45);
/* Typography */
--font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
--font-family-heading: 'Fraunces', ui-serif, Georgia, 'Times New Roman', serif;
--font-size-base: 18px;
--font-size-small: 15px;
--font-size-medium: 16px;
--font-size-large: 20px;
--font-size-xl: 32px;
--font-size-xxl: 52px;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-base: 1.75;
--line-height-relaxed: 1.85;
--line-height-tight: 1.65;
--line-height-heading: 1.25;
/* Spacing */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-xxl: 3rem;
--space-xxxl: 7rem;
/* Layout */
--max-width: 1100px;
--max-narrow-width: 680px;
--max-text-width: 70ch;
/* Border radius */
--border-radius: 6px;
--border-radius-small: 6px;
--border-radius-medium: 10px;
--border-radius-large: 16px;
/* Transition */
--transition-fast: 150ms ease;
--transition-base: 250ms ease;
/* CTA palette (orange primary CTA; AAA in light mode with white text) */
--accent-orange: #9a3412;
--accent-orange-hover: #7c2d12;
--accent-orange-soft: #fff1e7;
/* Button tokens */
--btn-primary-bg: var(--accent-orange);
--btn-primary-bg-hover: var(--accent-orange-hover);
--btn-primary-fg: #ffffff;
--btn-secondary-bg: transparent;
--btn-secondary-fg: var(--color-text);
--btn-secondary-border: var(--color-border-strong);
--btn-ghost-bg-hover: rgba(0, 0, 0, 0.06);
/* Focus ring (purple feels more “intentional” than orange) */
--btn-focus-ring: rgba(109, 40, 217, 0.45);
}
/* Dark mode - AAA contrast optimized */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0b0b12; /* cool slate (not pure black) */
--color-bg-alt: #121226;
--color-bg-subtle: #191934;
--color-text: #f3f2ff;
--color-text-secondary: #c9c6e4;
--color-text-tertiary: #a7a2c8;
--color-border: #2a2950;
--color-border-strong: #3a3870;
/* Brand accent (purple) */
--color-primary: #a78bfa;
--color-primary-hover: #c4b5fd;
--color-primary-bg: #1a1530;
--color-secondary: #c9c6e4;
--color-secondary-hover: #f3f2ff;
--color-focus: #a78bfa;
--color-focus-outline: rgba(167, 139, 250, 0.45);
/* CTA button: keep AAA in dark mode by using dark text on bright orange */
--btn-primary-bg: #fb923c;
--btn-primary-bg-hover: #fdba74; /* still AAA with dark text */
--btn-primary-fg: #0b0b12;
--btn-secondary-fg: var(--color-text);
--btn-secondary-border: var(--color-border-strong);
--btn-ghost-bg-hover: rgba(255, 255, 255, 0.08);
--btn-focus-ring: rgba(167, 139, 250, 0.45);
}
}
/* ========================================
Base Styles
======================================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-size: var(--font-size-base);
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.btn:hover {
transform: none;
}
}
body {
margin: 0;
padding: 0;
font-family: var(--font-family);
font-size: var(--font-size-base);
font-weight: var(--font-weight-normal);
line-height: var(--line-height-base);
color: var(--color-text);
background-color: var(--color-bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
/* ========================================
Skip Link (Accessibility)
======================================== */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
white-space: nowrap;
clip-path: inset(100%);
clip: rect(0 0 0 0);
overflow: hidden;
}
.skip-link {
position: absolute;
top: -100px;
left: 0;
padding: var(--space-sm) var(--space-lg);
background-color: var(--color-primary);
color: white;
text-decoration: none;
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-base);
z-index: 9999;
border-radius: 0 0 var(--border-radius-large) 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
border-bottom: none;
&:focus {
top: 0;
outline: 4px solid white;
outline-offset: 3px;
box-shadow: 0 0 0 8px rgba(255, 255, 255, 0.3);
}
&:focus-visible {
top: 0;
outline: 4px solid white;
outline-offset: 3px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
}
/* ========================================
Focus Styles (Strong, Accessible)
======================================== */
/* Strong focus indicators for keyboard navigation (WCAG 2.2 AAA) */
:focus {
outline: 3px solid var(--color-focus);
outline-offset: 3px;
transition: outline-offset var(--transition-fast);
&:not(:focus-visible) {
outline: none;
}
}
:focus-visible {
outline: 4px solid var(--color-focus);
outline-offset: 4px;
border-radius: 3px;
box-shadow: 0 0 0 8px var(--color-focus-outline);
*& {
outline-style: solid !important;
}
img& {
outline: 4px solid var(--color-focus);
outline-offset: 4px;
}
}
/* ========================================
Typography
======================================== */
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0 0 var(--space-lg) 0;
line-height: var(--line-height-heading);
font-weight: var(--font-weight-bold);
color: var(--color-text);
letter-spacing: -0.02em;
}
h1 {
font-size: var(--font-size-xxl);
font-weight: var(--font-weight-bold);
letter-spacing: -0.03em;
}
h2 {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
letter-spacing: -0.02em;
}
/* Heading font (keeps layout intact; just typography) */
h1,
h2,
h3,
h4,
h5,
h6,
.section-title {
font-family: var(--font-family-heading);
}
p {
margin: 0 0 var(--space-md) 0;
max-width: var(--max-text-width);
}
a {
color: var(--color-primary);
text-decoration: none;
text-decoration-skip-ink: auto;
transition: color var(--transition-fast);
border-bottom: 1px solid transparent;
&:hover {
color: var(--color-primary-hover);
border-bottom-color: currentColor;
}
&:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 3px;
border-bottom-color: transparent;
}
}
/* ========================================
Layout Containers
======================================== */
.container {
width: 100%;
max-width: var(--max-width);
margin: 0 auto;
padding: 0 var(--space-md);
}
.section {
padding: var(--space-xxxl) 0;
border-bottom: 1px solid var(--color-border);
&:last-child {
border-bottom: none;
}
&:nth-child(even) {
background-color: var(--color-bg-alt);
}
}
/* ========================================
Buttons
======================================== */
.btn {
display: inline-block;
padding: 1rem 2rem;
min-height: 48px;
min-width: 120px;
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
text-decoration: none;
border-radius: var(--border-radius-large);
transition: all var(--transition-base);
cursor: pointer;
border: 2px solid transparent;
letter-spacing: -0.01em;
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
}
}
/* PRIMARY CTA
Use the CTA/button tokens (defined in BOTH modes) to guarantee contrast.
This fixes the dark-mode purple/white contrast violation without changing your purple brand accents. */
.btn-primary {
background-color: var(--btn-primary-bg);
color: var(--btn-primary-fg);
border-color: var(--btn-primary-bg);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
&:hover {
background-color: var(--btn-primary-bg-hover);
border-color: var(--btn-primary-bg-hover);
color: var(--btn-primary-fg);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
}
&:focus-visible {
outline: 4px solid var(--color-focus);
outline-offset: 4px;
box-shadow:
0 0 0 8px var(--color-focus-outline),
0 2px 8px rgba(0, 0, 0, 0.12);
}
}
/* SECONDARY CTA
Keep it outlined and “lighter touch” (more professional than flipping to a heavy block).
Uses existing tokens only; works in both modes. */
.btn-secondary {
background-color: var(--btn-secondary-bg);
color: var(--btn-secondary-fg);
border-color: var(--btn-secondary-border);
box-shadow: 0 0 4px rgba(0, 0, 0, 0.05);
&:hover {
background-color: var(--accent-orange-soft);
color: var(--btn-secondary-fg);
border-color: var(--btn-primary-bg);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
@media (prefers-color-scheme: dark) {
background-color: rgba(251, 146, 60, 0.12);
border-color: var(--btn-primary-bg);
color: var(--btn-secondary-fg);
}
}
&:focus-visible {
outline: 4px solid var(--color-focus);
outline-offset: 4px;
box-shadow:
0 0 0 8px var(--color-focus-outline),
0 1px 4px rgba(0, 0, 0, 0.05);
}
}
/* ========================================
Section Titles
======================================== */
.section-title {
margin-bottom: var(--space-xl);
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
letter-spacing: -0.02em;
line-height: var(--line-height-heading);
text-align: center;
}
/* ========================================
Content Lists
======================================== */
.content-list {
max-width: var(--max-text-width);
margin: 0 auto;
padding: 0;
list-style: none;
& li {
position: relative;
padding-left: var(--space-lg);
margin-bottom: var(--space-lg);
font-size: var(--font-size-base);
line-height: var(--line-height-relaxed);
color: var(--color-text);
&::before {
content: '→';
position: absolute;
left: 0;
color: var(--color-primary);
font-weight: var(--font-weight-semibold);
font-size: 1.2em;
line-height: 1;
top: 0.1em;
}
}
}
/* ========================================
Responsive Design
======================================== */
@media (max-width: 768px) {
:root {
--font-size-base: 17px;
--font-size-small: 14px;
--font-size-medium: 15px;
--font-size-large: 19px;
--font-size-xl: 28px;
--font-size-xxl: 40px;
--space-lg: 2rem;
--space-xl: 3rem;
--space-xxl: 4.5rem;
--space-xxxl: 6rem;
}
.section {
padding: var(--space-xxl) 0;
}
.btn {
width: 100%;
max-width: 400px;
text-align: center;
min-height: 48px;
}
}
@media (max-width: 480px) {
:root {
--font-size-base: 16px;
--font-size-small: 13px;
--font-size-medium: 14px;
--font-size-large: 18px;
--font-size-xl: 24px;
--font-size-xxl: 34px;
--space-xl: 2.5rem;
--space-xxl: 3.5rem;
--space-xxxl: 5rem;
}
.container {
padding: 0 var(--space-md);
}
.section {
padding: var(--space-xl) 0;
}
.btn {
padding: 0.875rem 1.5rem;
min-height: 48px;
}
}
/* ========================================
High Contrast Mode Support
======================================== */
@media (prefers-contrast: high) {
:root {
--color-primary: #0047b3;
--color-border: #000000;
--color-text: #000000;
--color-text-secondary: #1a1a1a;
/* Maintain button contrast in high contrast mode (same tokens, same names) */
--btn-primary-bg: #000000;
--btn-primary-bg-hover: #000000;
--btn-primary-fg: #ffffff;
--btn-secondary-border: #000000;
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: #66b3ff;
--color-border: #ffffff;
--color-text: #ffffff;
--color-text-secondary: #e0e0e0;
--btn-primary-bg: #ffffff;
--btn-primary-bg-hover: #ffffff;
--btn-primary-fg: #000000;
--btn-secondary-border: #ffffff;
}
}
.btn {
border-width: 3px;
&:hover {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
}
:focus,
:focus-visible {
outline-width: 4px;
outline-offset: 4px;
}
a {
border-bottom-width: 2px;
text-decoration: underline;
}
}
/* ========================================
Print Styles (Accessibility)
======================================== */
@media print {
/* Show all content clearly for printing */
body {
font-size: 12pt;
line-height: 1.5;
color: #000;
background: #fff;
}
.skip-link {
display: none;
}
/* Expand all sections */
.section {
page-break-inside: avoid;
padding: 1rem 0;
border-bottom: 1pt solid #ccc;
}
/* Show URLs for external links */
a[href^='http']:after {
content: ' (' attr(href) ')';
font-size: 10pt;
color: #666;
}
/* Hide interactive elements that don't make sense in print */
.btn,
.cta-group {
display: none;
}
/* Ensure good contrast */
* {
color: #000 !important;
background: #fff !important;
}
h1,
h2,
h3,
dt {
color: #000 !important;
font-weight: bold;
}
}

13
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

11
src/app.html Normal file
View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import { engagements } from '$lib/data/engagements';
</script>
<section id="engagements" class="section" aria-labelledby="engagements-heading">
<div class="container">
<h2 id="engagements-heading" class="section-title">Recent Engagements</h2>
<dl class="engagements-list">
{#each engagements as engagement (engagement.title)}
<div class="engagement">
<dt>{engagement.title}</dt>
<dd>{engagement.description}</dd>
</div>
{/each}
</dl>
</div>
</section>
<style>
.engagements-list {
max-width: var(--max-text-width);
margin: 0 auto;
}
.engagement {
margin-bottom: var(--space-xl);
padding-bottom: var(--space-lg);
padding-left: var(--space-md);
border-left: 3px solid var(--color-border);
transition: border-color var(--transition-base);
&:hover,
&:focus-within {
border-left-color: var(--color-primary);
}
&:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
@media (max-width: 768px) {
padding-left: var(--space-sm);
}
@media (prefers-contrast: high) {
border-left-width: 4px;
}
& dt {
margin-bottom: var(--space-sm);
font-size: var(--font-size-large);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
line-height: var(--line-height-tight);
}
& dd {
margin: 0;
font-size: var(--font-size-base);
line-height: var(--line-height-relaxed);
color: var(--color-text-secondary);
}
}
</style>

View File

@@ -0,0 +1,276 @@
<script lang="ts">
import { experienceLogos, experienceTextList } from '$lib/data/experience';
</script>
<section
id="experience"
class="section experience-section"
aria-labelledby="experience-heading"
>
<div class="container">
<h2 id="experience-heading" class="section-title">
Experience includes teams at:
</h2>
<div class="logo-strip" role="list" aria-label="Company logos">
{#each experienceLogos as logo (logo.alt)}
<div class="logo-item" role="listitem">
<img
src={logo.src}
alt={logo.alt}
loading="lazy"
width={logo.width}
height={logo.height}
/>
<span class="logo-fallback-text">{logo.alt}</span>
</div>
{/each}
</div>
<ul class="logo-text-list" aria-hidden="true">
{#each experienceTextList as name (name)}
<li>{name}</li>
{/each}
</ul>
<p class="footnote">Logos are trademarks of their respective owners.</p>
</div>
</section>
<style>
.experience-section {
text-align: center;
background-color: var(--color-bg);
}
.logo-strip {
display: flex;
flex-wrap: wrap;
gap: var(--space-xl);
justify-content: center;
align-items: center;
margin: 0;
padding: var(--space-lg) 0;
@media (max-width: 480px) {
display: none;
}
@media (max-width: 768px) and (min-width: 481px) {
gap: var(--space-lg);
padding: var(--space-md) 0;
}
@media (max-width: 768px) {
gap: var(--space-md);
}
@media print {
display: none;
}
}
.logo-item {
position: relative;
flex: 0 1 auto;
min-width: 120px;
max-width: 160px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-xs);
/* Make logo containers keyboard focusable for screen reader users */
&:focus-within {
outline: 3px solid var(--color-focus);
outline-offset: 2px;
border-radius: var(--border-radius);
}
@media (max-width: 768px) and (min-width: 481px) {
min-width: 110px;
max-width: 140px;
}
@media (max-width: 768px) {
max-width: 120px;
}
@media (max-width: 480px) {
max-width: 100px;
}
& img {
width: 100%;
height: auto;
max-height: 50px;
object-fit: contain;
opacity: 0.75;
transition: all var(--transition-base);
filter: grayscale(100%) contrast(1.15);
&:hover,
&:focus,
&:focus-visible {
opacity: 1;
filter: grayscale(25%) contrast(1.05);
transform: scale(1.05);
}
&:focus-visible {
outline: 4px solid var(--color-focus);
outline-offset: 4px;
border-radius: var(--border-radius);
}
&[alt]:not([src]),
&[alt][src=''],
&[alt]:not([src*='.svg']):not([src*='.png']):not([src*='.jpg']) {
display: none;
}
@media (max-width: 768px) and (min-width: 481px) {
max-height: 45px;
}
@media (prefers-reduced-motion: reduce) {
&:hover,
&:focus {
transform: none;
}
}
/* Dark mode logo adaptations */
@media (prefers-color-scheme: dark) {
filter: grayscale(100%) brightness(0) invert(1) contrast(1.25);
opacity: 0.65;
&:hover,
&:focus,
&:focus-visible {
filter: grayscale(50%) brightness(1) invert(1) contrast(1.1);
opacity: 0.9;
}
}
@media (prefers-contrast: high) {
opacity: 1;
filter: contrast(1.6);
@media (prefers-color-scheme: dark) {
filter: brightness(0) invert(1) contrast(1.9);
opacity: 1;
}
}
@media print {
opacity: 1;
filter: none;
max-height: 40px;
}
}
}
/* Fallback text (shown when image fails to load or on very small screens) */
.logo-fallback-text {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.logo-item:has(img[alt]:not([src])) .logo-fallback-text,
.logo-item:has(img[alt][src='']) .logo-fallback-text {
position: static;
width: auto;
height: auto;
padding: var(--space-sm);
margin: 0;
overflow: visible;
clip: auto;
white-space: normal;
font-size: var(--font-size-base);
font-weight: 600;
color: var(--color-text);
background-color: var(--color-bg-alt);
border: 2px solid var(--color-border);
border-radius: var(--border-radius);
display: inline-block;
}
/* Text-only list (hidden by default, shown on very small screens) */
.logo-text-list {
display: none;
list-style: none;
padding: 0;
margin: 0 auto;
max-width: 400px;
text-align: left;
& li {
padding: var(--space-sm) var(--space-md);
margin-bottom: var(--space-sm);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
color: var(--color-text);
background-color: var(--color-bg-subtle);
border-left: 3px solid var(--color-primary);
border-radius: var(--border-radius);
line-height: var(--line-height-base);
@media (prefers-contrast: high) {
border-left-width: 4px;
}
}
&:last-child {
margin-bottom: 0;
}
@media (max-width: 480px) {
display: block;
}
@media print {
display: block !important;
}
}
.footnote {
margin-top: var(--space-lg);
font-size: var(--font-size-small);
font-weight: var(--font-weight-normal);
color: var(--color-text-tertiary);
font-style: italic;
line-height: var(--line-height-base);
max-width: 100%;
@media (max-width: 480px) {
display: none;
}
}
@media (prefers-contrast: high) {
.logo-item img {
opacity: 1;
filter: contrast(1.6);
}
@media (prefers-color-scheme: dark) {
.logo-item img {
filter: brightness(0) invert(1) contrast(1.9);
opacity: 1;
}
}
.logo-text-list li {
border-left-width: 4px;
}
}
</style>

View File

@@ -0,0 +1,82 @@
<footer class="footer">
<div class="container">
<p class="copyright">
© <span id="copyright-year">2026</span> mifi Ventures, LLC · Boston, MA
</p>
<nav class="footer-links" aria-label="Social media links">
<a
href="https://linkedin.com/in/the-mifi"
target="_blank"
rel="noopener noreferrer"
aria-label="LinkedIn profile (opens in new tab)">LinkedIn</a
>
<a
href="https://github.com/the-mifi"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub profile (opens in new tab)">GitHub</a
>
</nav>
</div>
</footer>
<style>
.footer {
padding: var(--space-xxl) 0 var(--space-xl) 0;
text-align: center;
background-color: var(--color-bg);
border-top: 1px solid var(--color-border);
}
.copyright {
margin-bottom: var(--space-md);
font-size: var(--font-size-medium);
font-weight: var(--font-weight-normal);
color: var(--color-text-tertiary);
max-width: 100%;
}
.footer-links {
display: flex;
gap: var(--space-lg);
justify-content: center;
align-items: center;
@media (max-width: 480px) {
gap: var(--space-md);
}
& a {
font-size: var(--font-size-medium);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
text-decoration: none;
transition: color var(--transition-fast);
border-bottom: 1px solid transparent;
padding: var(--space-xs) var(--space-sm);
margin: calc(-1 * var(--space-xs)) calc(-1 * var(--space-sm));
min-height: 44px;
display: inline-flex;
align-items: center;
&:hover {
color: var(--color-primary);
border-bottom-color: var(--color-primary);
}
&:focus-visible {
outline: 4px solid var(--color-focus);
outline-offset: 4px;
border-bottom-color: transparent;
box-shadow: 0 0 0 8px var(--color-focus-outline);
border-radius: 3px;
}
@media print {
&:after {
content: none;
}
}
}
}
</style>

View File

@@ -0,0 +1,82 @@
<script lang="ts">
import Logo from './Logo.svelte';
</script>
<header id="header" class="hero">
<div class="container">
<Logo />
<p class="headline">Software Engineering Consulting</p>
<p class="subhead">
Principal: Mike Fitzpatrick — senior full-stack engineer and architect helping
teams ship reliable, accessible, high-performance web products.
</p>
<div class="cta-group">
<a
href="https://cal.mifi.ventures/the-mifi"
class="btn btn-primary"
target="_blank"
rel="noopener noreferrer"
aria-label="Schedule a 30-minute intro call (opens in new tab)"
>
Schedule a 30-minute intro call
</a>
<a
href="/downloads/resume.pdf"
class="btn btn-secondary"
download
aria-label="Download Mike Fitzpatrick's resume as PDF"
>
Download resume
</a>
</div>
</div>
</header>
<style>
.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);
@media (max-width: 768px) {
padding: var(--space-xxl) 0 var(--space-xl) 0;
}
@media (max-width: 480px) {
padding: var(--space-xl) 0 var(--space-lg) 0;
}
}
.headline {
margin-bottom: var(--space-lg);
font-family: var(--font-family-heading);
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
letter-spacing: -0.02em;
}
.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) {
flex-direction: column;
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { howWeWorkItems } from '$lib/data/content';
</script>
<section id="how-we-work" class="section" aria-labelledby="how-we-work-heading">
<div class="container">
<h2 id="how-we-work-heading" class="section-title">How We Work</h2>
<ul class="content-list">
{#each howWeWorkItems as item (item)}
<li>{item}</li>
{/each}
</ul>
</div>
</section>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { impactItems } from '$lib/data/content';
</script>
<section id="impact" class="section" aria-labelledby="impact-heading">
<div class="container">
<h2 id="impact-heading" class="section-title">Selected Impact</h2>
<ul class="content-list">
{#each impactItems as item (item)}
<li>{item}</li>
{/each}
</ul>
</div>
</section>

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import Wordmark from './Wordmark.svelte';
</script>
<h1 class="logo">
<Wordmark />
<span class="sr-only">mifi Ventures</span>
</h1>
<style>
.logo {
color: var(--color-text);
font-family: var(--font-family-heading);
margin: 0 auto;
max-width: 350px;
width: 100%;
}
</style>

View File

@@ -0,0 +1,35 @@
<section
id="schedule"
class="section schedule-section"
aria-labelledby="schedule-heading"
>
<div class="container">
<h2 id="schedule-heading" class="section-title">Let's Talk</h2>
<p class="schedule-text">Ready to discuss your project?</p>
<a
href="https://cal.mifi.ventures/the-mifi"
class="btn btn-primary"
target="_blank"
rel="noopener noreferrer"
aria-label="Schedule a 30-minute intro call (opens in new tab)"
>
Schedule a 30-minute intro call
</a>
</div>
</section>
<style>
.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: 100%;
}
</style>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { whatWeDoItems } from '$lib/data/content';
</script>
<section id="what-we-do" class="section" aria-labelledby="what-we-do-heading">
<div class="container">
<h2 id="what-we-do-heading" class="section-title">What We Do</h2>
<ul class="content-list">
{#each whatWeDoItems as item (item)}
<li>{item}</li>
{/each}
</ul>
</div>
</section>

View File

@@ -0,0 +1,61 @@
<script lang="ts">
let { color = 'currentColor' }: { color?: string } = $props();
</script>
<svg
width="100%"
height="100%"
viewBox="0 0 3934 513"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
><g
><path
fill={color}
d="M0,504.667l0,-362.333l82.5,0l0,83.5l-9.5,-13.5c6.444,-26.222 19.75,-45.778 39.917,-58.667c20.167,-12.889 43.806,-19.333 70.917,-19.333c29.556,0 55.694,7.694 78.417,23.083c22.722,15.389 37.417,35.861 44.083,61.417l-25,2.167c11.222,-29.222 27.917,-50.972 50.083,-65.25c22.167,-14.278 47.75,-21.417 76.75,-21.417c25.667,0 48.611,5.778 68.833,17.333c20.222,11.556 36.194,27.611 47.917,48.167c11.722,20.556 17.583,44.333 17.583,71.333l0,233.5l-87.5,0l0,-212.667c0,-16.111 -2.889,-29.889 -8.667,-41.333c-5.778,-11.444 -13.833,-20.361 -24.167,-26.75c-10.333,-6.389 -22.667,-9.583 -37,-9.583c-13.889,0 -26.139,3.194 -36.75,9.583c-10.611,6.389 -18.833,15.333 -24.667,26.833c-5.833,11.5 -8.75,25.25 -8.75,41.25l0,212.667l-87.5,0l0,-212.667c0,-16.111 -2.889,-29.889 -8.667,-41.333c-5.778,-11.444 -13.833,-20.361 -24.167,-26.75c-10.333,-6.389 -22.667,-9.583 -37,-9.583c-14,0 -26.278,3.194 -36.833,9.583c-10.556,6.389 -18.75,15.333 -24.583,26.833c-5.833,11.5 -8.75,25.25 -8.75,41.25l0,212.667l-87.5,0Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M614.5,504.667l0,-362.333l87.5,0l0,362.333l-87.5,0Zm0,-403.333l0,-93.333l87.5,0l0,93.333l-87.5,0Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M823.5,504.667l0,-284.833l-63.833,0l0,-77.5l63.833,0l0,-12c0,-27.889 5.639,-51.472 16.917,-70.75c11.278,-19.278 27.194,-34.028 47.75,-44.25c20.556,-10.222 44.778,-15.333 72.667,-15.333c5.444,0 11.389,0.333 17.833,1c6.444,0.667 11.778,1.444 16,2.333l0,75.333c-4.111,-0.889 -8.083,-1.444 -11.917,-1.667c-3.833,-0.222 -7.361,-0.333 -10.583,-0.333c-19.333,0 -34.361,4.361 -45.083,13.083c-10.722,8.722 -16.083,22.25 -16.083,40.583l0,12l158.667,0l0,77.5l-158.667,0l0,284.833l-87.5,0Zm213.667,0l0,-362.333l87.5,0l0,362.333l-87.5,0Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M1474.74,50.297c0,-3.892 1.365,-6.915 4.096,-9.068c2.731,-2.153 6.86,-3.229 12.388,-3.229l106.333,0c5.83,0 10.083,1.052 12.76,3.156c2.677,2.104 4.016,5.056 4.016,8.854c0,2.844 -0.913,5.219 -2.74,7.125c-1.826,1.906 -5.043,3.74 -9.651,5.5l-13.286,4.479c-6.694,2.681 -12.347,6.961 -16.958,12.841c-4.611,5.88 -8.964,15.216 -13.057,28.008l-114.453,337.807c-2.128,6.608 -3.694,12.082 -4.695,16.424c-1.002,4.342 -1.503,8.846 -1.503,13.513l0,15.141c0,4.351 -1.242,7.741 -3.727,10.172c-2.484,2.431 -5.817,3.646 -9.997,3.646l-71.854,0c-4.354,0 -7.762,-1.215 -10.224,-3.646c-2.462,-2.431 -3.693,-5.965 -3.693,-10.604l0,-14.969c0,-3.542 -0.508,-7.257 -1.523,-11.146c-1.016,-3.889 -2.428,-8.453 -4.237,-13.693l-125.854,-363.062c-2.097,-6.191 -4.4,-10.66 -6.909,-13.406c-2.509,-2.747 -5.987,-4.977 -10.435,-6.693l-14.141,-4.193c-7.497,-2.809 -11.245,-7.128 -11.245,-12.958c0,-3.892 1.394,-6.915 4.182,-9.068c2.788,-2.153 7.002,-3.229 12.641,-3.229l151.687,0c5.75,0 9.968,1.076 12.654,3.229c2.686,2.153 4.029,5.175 4.029,9.068c0,3.128 -1.044,5.646 -3.133,7.552c-2.089,1.906 -5.221,3.55 -9.398,4.932l-25.328,4.427c-5.542,1.573 -8.939,4.237 -10.193,7.992c-1.253,3.755 -0.444,9.841 2.427,18.258l124.146,361.656l-26.328,20.599l125.198,-369.797c3.542,-10.618 4.107,-18.966 1.695,-25.044c-2.411,-6.078 -9.02,-10.76 -19.826,-14.044l-21.573,-4.193c-3.972,-1.382 -7.014,-2.978 -9.125,-4.789c-2.111,-1.811 -3.167,-4.327 -3.167,-7.549Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M1905.721,311.365c0,9.878 -2.872,17.47 -8.615,22.776c-5.743,5.306 -14.118,7.958 -25.125,7.958l-210,0l0,-21.203l147.052,0c10.229,0 15.344,-4.622 15.344,-13.865c0,-30.597 -5.845,-54.022 -17.536,-70.273c-11.691,-16.252 -27.196,-24.378 -46.516,-24.378c-15.257,0 -28.655,4.508 -40.195,13.523c-11.54,9.016 -20.561,21.96 -27.062,38.833c-6.502,16.873 -9.753,37.037 -9.753,60.492c0,43.872 10.266,76.974 30.799,99.307c20.533,22.333 47.546,33.5 81.039,33.5c21.031,0 39.325,-4.677 54.88,-14.031c15.556,-9.354 26.748,-22.125 33.578,-38.313c2.969,-3.653 5.411,-6.146 7.326,-7.479c1.915,-1.333 3.937,-2 6.065,-2c2.938,0 5.082,1.291 6.432,3.872c1.351,2.582 1.97,5.721 1.859,9.419c-1.174,18.809 -7.769,36.038 -19.786,51.688c-12.017,15.649 -28.122,28.109 -48.315,37.38c-20.193,9.271 -43.218,13.906 -69.076,13.906c-31.069,0 -58.526,-6.501 -82.37,-19.503c-23.844,-13.002 -42.481,-31.285 -55.911,-54.849c-13.431,-23.564 -20.146,-51.166 -20.146,-82.805c0,-32.92 6.497,-62.108 19.49,-87.562c12.993,-25.455 31.546,-45.48 55.659,-60.076c24.113,-14.595 52.768,-21.893 85.966,-21.893c27.938,-0 51.99,5.361 72.159,16.083c20.168,10.722 35.67,25.512 46.505,44.37c10.835,18.858 16.253,40.564 16.253,65.12Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M2076.711,205.234l0,253.599c0,6.017 0.997,10.479 2.99,13.385c1.993,2.906 4.998,4.97 9.016,6.193l14.234,3.453c6.413,2.337 9.62,6.03 9.62,11.078c0,7.816 -5.082,11.724 -15.245,11.724l-124.286,0c-5.035,0 -8.755,-1.004 -11.161,-3.013c-2.406,-2.009 -3.609,-4.721 -3.609,-8.138c0,-2.733 0.865,-5.056 2.596,-6.969c1.731,-1.913 4.438,-3.458 8.122,-4.635l15.234,-3.5c4.031,-1.222 7.04,-3.262 9.026,-6.12c1.986,-2.858 2.979,-7.281 2.979,-13.271l0,-204.677c0,-4.844 -0.782,-8.342 -2.346,-10.495c-1.564,-2.153 -4.126,-3.467 -7.685,-3.943l-20.542,-1c-3.559,-0.667 -6.109,-1.819 -7.651,-3.456c-1.542,-1.637 -2.312,-3.734 -2.312,-6.289c0,-2.972 0.918,-5.387 2.753,-7.245c1.835,-1.858 5.192,-3.66 10.07,-5.406l62.24,-21.5c6.941,-2.556 12.56,-4.39 16.857,-5.503c4.297,-1.113 8.263,-1.669 11.898,-1.669c5.688,0 9.977,1.576 12.867,4.729c2.891,3.153 4.336,7.375 4.336,12.667Zm-9.385,71.365l-12.724,-13.036l13.526,-11.927c26.417,-23.639 49.119,-40.515 68.107,-50.628c18.988,-10.113 37.15,-15.169 54.487,-15.169c26.26,0 46.657,8.724 61.19,26.172c14.533,17.448 23.444,41.141 26.732,71.078l20.208,174.552c0.729,6.417 2.038,11.217 3.927,14.401c1.889,3.184 4.993,5.387 9.313,6.609l13.427,3.26c3.684,1.16 6.391,2.697 8.122,4.612c1.731,1.915 2.596,4.246 2.596,6.992c0,3.417 -1.175,6.129 -3.526,8.138c-2.351,2.009 -6.115,3.013 -11.292,3.013l-125.594,0c-10.198,0 -15.297,-3.908 -15.297,-11.724c-0,-5.017 3.177,-8.71 9.531,-11.078l14.896,-3.453c4.448,-1.222 7.823,-3.425 10.125,-6.609c2.302,-3.184 3.087,-7.905 2.354,-14.161l-18.995,-162.974c-2.476,-20.635 -7.75,-36.081 -15.823,-46.336c-8.073,-10.255 -19.927,-15.383 -35.562,-15.383c-9.844,0 -20.199,2.655 -31.065,7.966c-10.866,5.311 -22.546,13.293 -35.039,23.945l-13.625,11.74Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M2387.845,220.714l-18.271,-4.667c-4.927,-1.479 -8.352,-3.2 -10.273,-5.161c-1.922,-1.962 -2.883,-4.276 -2.883,-6.943c0,-3.51 1.223,-6.199 3.669,-8.065c2.446,-1.866 5.704,-2.799 9.773,-2.799l21.99,0c5.101,-0 9.294,-0.87 12.581,-2.609c3.286,-1.74 6.416,-4.936 9.388,-9.589l34.552,-51.276c3.59,-4.91 7.104,-8.504 10.542,-10.784c3.438,-2.28 6.943,-3.419 10.516,-3.419c3.878,0 6.89,1.22 9.034,3.659c2.144,2.439 3.216,5.905 3.216,10.398l0,288.479c0,15.747 3.147,27.707 9.44,35.88c6.293,8.174 15.034,12.26 26.221,12.26c7.67,0 13.736,-1.373 18.198,-4.12c4.462,-2.747 8.049,-6.002 10.763,-9.766c2.714,-3.764 5.304,-7.236 7.771,-10.417c2.467,-3.181 5.447,-5.205 8.94,-6.073c2.733,-0.177 4.901,0.624 6.505,2.404c1.604,1.78 2.375,4.773 2.312,8.982c-0.507,11.427 -4.431,21.975 -11.773,31.643c-7.342,9.668 -17.384,17.448 -30.125,23.339c-12.741,5.891 -27.367,8.836 -43.878,8.836c-26.191,0 -46.827,-6.628 -61.909,-19.883c-15.082,-13.255 -22.622,-33.345 -22.622,-60.268l0,-192.13c0,-5.128 -1.021,-9.007 -3.063,-11.635c-2.042,-2.628 -5.58,-4.72 -10.615,-6.276Zm61.109,-0.854l0.281,-26.781l106.25,0c4.444,-0 7.866,0.858 10.266,2.573c2.399,1.715 3.599,4.257 3.599,7.625c0,4.733 -2.383,8.68 -7.148,11.841c-4.766,3.161 -12.326,4.742 -22.68,4.742l-90.568,0Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M2855.8,465.307l0,-21.885l-2.146,-1.526l0,-187.313c0,-4.847 -0.782,-8.346 -2.346,-10.497c-1.564,-2.151 -4.126,-3.464 -7.685,-3.94l-20.542,-1.005c-3.559,-0.667 -6.109,-1.818 -7.648,-3.453c-1.54,-1.635 -2.31,-3.733 -2.31,-6.292c0,-2.969 0.917,-5.382 2.75,-7.24c1.833,-1.858 5.189,-3.661 10.068,-5.411l62.24,-21.495c6.924,-2.559 12.538,-4.394 16.844,-5.505c4.306,-1.111 8.276,-1.667 11.911,-1.667c5.687,0 9.977,1.576 12.867,4.729c2.891,3.153 4.336,7.373 4.336,12.661l0,253.365c0,6.017 0.997,10.503 2.99,13.456c1.993,2.953 4.998,4.994 9.016,6.122l14.516,3.312c3.812,1.16 6.609,2.701 8.388,4.622c1.78,1.922 2.669,4.312 2.669,7.169c0,3.417 -1.223,6.129 -3.669,8.138c-2.446,2.009 -6.258,3.013 -11.435,3.013l-65.932,0c-10.229,0 -18.6,-3.578 -25.112,-10.734c-6.512,-7.156 -9.768,-16.698 -9.768,-28.625Zm-208.76,-50.839l0,-159.885c0,-4.847 -0.786,-8.346 -2.357,-10.497c-1.571,-2.151 -4.143,-3.464 -7.716,-3.94l-20.547,-1.005c-3.559,-0.667 -6.109,-1.818 -7.648,-3.453c-1.54,-1.635 -2.31,-3.733 -2.31,-6.292c0,-2.969 0.918,-5.382 2.753,-7.24c1.835,-1.858 5.19,-3.661 10.065,-5.411l62.286,-21.495c7.226,-2.653 12.964,-4.511 17.214,-5.576c4.25,-1.064 7.908,-1.596 10.974,-1.596c5.972,0 10.428,1.576 13.367,4.729c2.939,3.153 4.409,7.373 4.409,12.661l0,197.422c0,20.92 5.023,36.531 15.07,46.833c10.047,10.302 23.452,15.453 40.216,15.453c10.382,0 21.431,-2.572 33.146,-7.716c11.715,-5.144 23.986,-13.207 36.813,-24.19l13.62,-11.74l12.724,13.031l-13.526,11.927c-26.653,24.323 -49.98,41.37 -69.982,51.141c-20.002,9.771 -38.973,14.656 -56.914,14.656c-27.354,0 -49.469,-8.744 -66.344,-26.232c-16.875,-17.488 -25.313,-41.35 -25.313,-71.586Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M3130.82,330.943c0,-31.67 4.576,-58.287 13.729,-79.852c9.153,-21.564 21.071,-37.831 35.755,-48.799c14.684,-10.969 30.319,-16.453 46.906,-16.453c20.118,0 35.681,5.683 46.69,17.049c11.009,11.366 16.513,27.369 16.513,48.008c0,17.24 -3.655,30.185 -10.964,38.836c-7.309,8.651 -16.743,12.977 -28.302,12.977c-11.556,0 -20.418,-3.171 -26.586,-9.513c-6.168,-6.342 -9.284,-15.235 -9.346,-26.68l-0.047,-11.573c-0.111,-7.236 -1.802,-12.64 -5.073,-16.211c-3.271,-3.571 -8.644,-5.357 -16.12,-5.357c-8.635,0 -16.97,3.531 -25.003,10.594c-8.033,7.062 -14.597,17.733 -19.693,32.01c-5.095,14.278 -7.643,32.392 -7.643,54.344l-10.818,0.62Zm6.812,-125.427l4.005,81.208l0,171.87c0,5.497 1.199,9.661 3.596,12.495c2.398,2.833 6.598,4.719 12.602,5.656l29.714,4.427c4.462,0.701 7.769,2.029 9.922,3.982c2.153,1.953 3.229,4.694 3.229,8.221c0,3.51 -1.299,6.27 -3.896,8.279c-2.597,2.009 -6.398,3.013 -11.401,3.013l-147.318,0c-5.083,0 -8.836,-1.013 -11.258,-3.039c-2.422,-2.026 -3.633,-4.739 -3.633,-8.138c0,-2.747 0.885,-5.085 2.656,-7.016c1.771,-1.931 4.514,-3.483 8.229,-4.656l15.068,-3.406c4.035,-1.128 7.044,-3.137 9.029,-6.026c1.984,-2.889 2.977,-7.295 2.977,-13.219l0,-204.391c0,-4.861 -0.779,-8.379 -2.336,-10.555c-1.557,-2.175 -4.114,-3.503 -7.669,-3.982l-20.667,-1c-3.51,-0.667 -6.04,-1.818 -7.589,-3.453c-1.549,-1.635 -2.323,-3.724 -2.323,-6.266c0,-2.955 0.942,-5.393 2.826,-7.315c1.884,-1.922 5.232,-3.709 10.044,-5.362l61.26,-20.76c8.799,-3.306 15.307,-5.466 19.526,-6.482c4.219,-1.016 7.575,-1.523 10.068,-1.523c4.08,0 7.156,1.345 9.229,4.036c2.073,2.691 3.443,7.158 4.109,13.401Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M3622.771,311.365c0,9.878 -2.872,17.47 -8.615,22.776c-5.743,5.306 -14.118,7.958 -25.125,7.958l-210,0l0,-21.203l147.052,0c10.229,0 15.344,-4.622 15.344,-13.865c0,-30.597 -5.845,-54.022 -17.536,-70.273c-11.691,-16.252 -27.196,-24.378 -46.516,-24.378c-15.257,0 -28.655,4.508 -40.195,13.523c-11.54,9.016 -20.561,21.96 -27.063,38.833c-6.502,16.873 -9.753,37.037 -9.753,60.492c0,43.872 10.266,76.974 30.799,99.307c20.533,22.333 47.546,33.5 81.039,33.5c21.031,0 39.325,-4.677 54.88,-14.031c15.556,-9.354 26.748,-22.125 33.578,-38.313c2.969,-3.653 5.411,-6.146 7.326,-7.479c1.915,-1.333 3.937,-2 6.065,-2c2.938,0 5.082,1.291 6.432,3.872c1.351,2.582 1.97,5.721 1.859,9.419c-1.174,18.809 -7.769,36.038 -19.786,51.688c-12.017,15.649 -28.122,28.109 -48.315,37.38c-20.193,9.271 -43.218,13.906 -69.076,13.906c-31.069,0 -58.526,-6.501 -82.37,-19.503c-23.844,-13.002 -42.481,-31.285 -55.911,-54.849c-13.431,-23.564 -20.146,-51.166 -20.146,-82.805c0,-32.92 6.497,-62.108 19.49,-87.562c12.993,-25.455 31.546,-45.48 55.659,-60.076c24.113,-14.595 52.768,-21.893 85.966,-21.893c27.938,-0 51.99,5.361 72.159,16.083c20.168,10.722 35.67,25.512 46.505,44.37c10.835,18.858 16.253,40.564 16.253,65.12Z"
style="fill-rule:nonzero;"
/><path
fill={color}
d="M3813.044,487.698c15.937,0 28.422,-4.111 37.453,-12.333c9.031,-8.222 13.547,-18.745 13.547,-31.568c0,-8.125 -1.861,-15.469 -5.583,-22.031c-3.722,-6.562 -10.543,-12.562 -20.464,-17.997c-9.92,-5.436 -24.125,-10.471 -42.615,-15.107c-31.625,-7.188 -56.268,-15.988 -73.93,-26.401c-17.661,-10.413 -30.004,-22.363 -37.029,-35.849c-7.024,-13.486 -10.536,-28.356 -10.536,-44.609c0,-29.288 10.358,-52.604 31.073,-69.948c20.715,-17.344 50.299,-26.016 88.75,-26.016c14.302,0 26.049,1.234 35.24,3.703c9.191,2.469 16.712,4.961 22.562,7.477c5.851,2.516 10.873,3.773 15.068,3.773c4.382,0 7.961,-1.258 10.737,-3.773c2.776,-2.516 5.492,-5.031 8.148,-7.547c2.656,-2.516 5.993,-3.773 10.01,-3.773c2.781,0 5.272,0.905 7.471,2.716c2.2,1.811 4.03,5.162 5.492,10.055l22.667,71.646c2.066,6.226 2.702,11.364 1.909,15.414c-0.793,4.05 -3.287,6.918 -7.482,8.602c-4.097,1.556 -7.667,1.551 -10.708,-0.013c-3.042,-1.564 -5.937,-4.451 -8.687,-8.659c-10.062,-18.646 -20.847,-33.42 -32.354,-44.323c-11.507,-10.903 -23.586,-18.714 -36.237,-23.435c-12.651,-4.72 -25.85,-7.081 -39.596,-7.081c-19.764,0 -34.641,4.182 -44.633,12.547c-9.991,8.365 -14.987,19.563 -14.987,33.594c0,8.41 2.122,16.051 6.367,22.924c4.245,6.873 12.054,13.202 23.427,18.987c11.373,5.785 27.584,11.288 48.633,16.51c27.465,6.378 49.29,14.37 65.474,23.974c16.184,9.604 27.82,21.081 34.909,34.43c7.089,13.349 10.633,28.883 10.633,46.602c0,18.205 -4.623,34.268 -13.87,48.19c-9.247,13.922 -22.141,24.768 -38.682,32.539c-16.542,7.771 -35.79,11.656 -57.745,11.656c-13.83,0 -25.069,-1.468 -33.719,-4.404c-8.649,-2.936 -15.788,-5.848 -21.417,-8.737c-5.628,-2.889 -10.825,-4.333 -15.589,-4.333c-4.257,0 -7.92,1.424 -10.99,4.273c-3.069,2.849 -6.016,5.722 -8.841,8.62c-2.825,2.898 -6.07,4.346 -9.737,4.346c-2.701,0 -5.029,-0.989 -6.982,-2.966c-1.953,-1.977 -3.39,-5.31 -4.31,-9.997l-13.906,-67.13c-1.462,-7.559 -1.775,-13.197 -0.94,-16.914c0.835,-3.717 3.119,-6.322 6.852,-7.815c3.986,-1.587 7.492,-1.376 10.518,0.633c3.026,2.009 6.143,5.641 9.352,10.898c13.649,24.583 28.663,42.171 45.042,52.763c16.378,10.592 33.123,15.888 50.234,15.888Z"
style="fill-rule:nonzero;"
/></g
></svg
>

View File

@@ -0,0 +1,8 @@
import { describe, it, expect } from 'vitest';
import { getCurrentYear } from './copyright-year';
describe('getCurrentYear', () => {
it('returns the current calendar year', () => {
expect(getCurrentYear()).toBe(new Date().getFullYear());
});
});

View File

@@ -0,0 +1,7 @@
/**
* Returns the current calendar year (for testing and any server use).
* The client-side footer year is updated by static/copyright-year.js.
*/
export function getCurrentYear(): number {
return new Date().getFullYear();
}

26
src/lib/data/content.ts Normal file
View File

@@ -0,0 +1,26 @@
export const whatWeDoItems = [
'Product-focused frontend and UI architecture for modern web applications, with an emphasis on clarity, scalability, and long-term maintainability.',
'Greenfield product builds and early-stage foundations, getting new projects off the ground quickly with structures designed to grow, not be rewritten.',
'Performance, Core Web Vitals, rendering strategy, and technical SEO optimization focused on real-world user journeys—not just lab scores.',
'Accessibility-first engineering, ensuring WCAG-compliant interfaces with semantic markup, keyboard parity, and inclusive interaction patterns.',
'Modernization and stabilization of existing systems, including refactors, framework upgrades, and untangling overgrown frontend codebases.',
'End-to-end feature delivery with clear ownership and documentation, spanning frontend and supporting backend work without unnecessary complexity.',
];
export const impactItems = [
'Get new products off the ground quickly by establishing durable frontend and platform foundations—clean architecture, clear patterns, and pragmatic defaults designed to scale with teams and traffic.',
'Improve performance, Core Web Vitals, and technical SEO on high-traffic user journeys through rendering strategy, bundle discipline, and careful attention to real-world loading behavior.',
'Build accessibility into core UI systems, not as a retrofit—semantic markup, keyboard parity, and screen reader support baked into reusable components and design patterns.',
'Bring order to complex or aging codebases by simplifying structure, reducing duplication, and clarifying ownership, enabling teams to ship confidently without over-engineering.',
'Design and evolve shared component libraries and UI systems that improve consistency, velocity, and long-term maintainability across multiple teams.',
'Partner closely with product, design, and engineering leadership (including marketing teams and non-technical organizations) to translate goals into shippable systems, balancing speed, quality, and technical risk.',
];
export const howWeWorkItems = [
'Engagements are consulting-led and senior-driven. I work directly with founders, product leaders, marketing teams, and engineering teams—including organizations without in-house technical staff—to establish direction and deliver solutions with a high degree of autonomy.',
'Focused, pragmatic scope. Work is scoped to deliver real progress quickly, with an emphasis on building the right foundation rather than over-engineering for hypothetical futures.',
'Async-friendly, low-friction communication. Clear written updates, documented decisions, and scheduled calls when they add value—not meetings for their own sake.',
'Quality as a default. Accessibility, performance, and maintainability are built into the work from the start, not added later as cleanup.',
"Flexible engagement models. Hourly or fixed-scope work depending on clarity and needs; longer-term engagements welcome when there's ongoing product momentum.",
'Clean handoff. Code, documentation, and context are left in a state where internal teams—or future vendors—can confidently extend the work without dependency.',
];

View File

@@ -0,0 +1,27 @@
export const engagements = [
{
title: 'Atlassian — Senior UI Engineer (Enterprise SaaS)',
description:
'Frontend architecture and feature delivery for Confluence integrations, including React 18 migration work and standardizing end-to-end testing practices.',
},
{
title: 'CarGurus — Principal UI Engineer (Consumer Marketplace)',
description:
'Built and maintained high-traffic frontend systems, improved Core Web Vitals and technical SEO, and developed shared UI platforms used across teams.',
},
{
title: 'The TJX Companies (TJ Maxx) — UI Engineer (Enterprise Retail)',
description:
'Delivered UX improvements for large-scale e-commerce experiences in close partnership with design, QA, and product teams.',
},
{
title: 'Timberland — Senior Interactive Developer (Global Ecommerce)',
description:
'Led global web initiatives across brand and e-commerce platforms, acting as a technical bridge between marketing, design, and engineering.',
},
{
title: 'MFA Boston — Pro Bono Technical Lead (Nonprofit / Fundraising)',
description:
"Designed and built a custom auction application for the MFA's annual Young Patrons fundraiser; subsequently iterated on and supported the platform over multiple years as the event grew, until it concluded during the pandemic.",
},
];

View File

@@ -0,0 +1,26 @@
export const experienceLogos = [
{ src: '/assets/logos/atlassian.svg', alt: 'Atlassian', width: 2500, height: 2500 },
{
src: '/assets/logos/tjx.svg',
alt: 'TJ Maxx (The TJX Companies)',
width: 2500,
height: 621,
},
{ src: '/assets/logos/cargurus.svg', alt: 'CarGurus', width: 2500, height: 398 },
{ src: '/assets/logos/timberland.svg', alt: 'Timberland', width: 190, height: 35 },
{ src: '/assets/logos/vf.svg', alt: 'VF Corporation', width: 190, height: 155 },
{
src: '/assets/logos/bottomline.svg',
alt: 'Bottomline Technologies',
width: 2702,
height: 571,
},
{
src: '/assets/logos/mfa-boston.svg',
alt: 'Museum of Fine Arts Boston',
width: 572,
height: 88,
},
] as const;
export const experienceTextList = experienceLogos.map((l) => l.alt);

View File

@@ -0,0 +1,9 @@
import type { PageMeta } from '$lib/seo';
import { defaultJsonLdGraph } from './json-ld';
export const homeMeta: PageMeta = {
title: 'mifi Ventures — Software Engineering Consulting | Boston, MA',
description:
'Boston-based software engineering consulting. Mike Fitzpatrick helps teams build reliable, accessible, high-performance web applications. Specializing in frontend architecture, performance optimization, and modern web development.',
jsonLd: defaultJsonLdGraph,
};

150
src/lib/data/json-ld.ts Normal file
View File

@@ -0,0 +1,150 @@
/**
* Default JSON-LD graph nodes (Organization, Person, WebSite, WebPage, OfferCatalog).
* Used for the home page; other pages can add or override via meta.jsonLd.
*/
const BASE = 'https://mifi.ventures';
export const defaultJsonLdGraph: Record<string, unknown>[] = [
{
'@type': 'Organization',
'@id': `${BASE}/#organization`,
name: 'mifi Ventures, LLC',
legalName: 'mifi Ventures, LLC',
url: `${BASE}/`,
logo: { '@type': 'ImageObject', url: `${BASE}/favicon.svg` },
description:
'Software engineering consulting specializing in product-focused frontend architecture, performance optimization, and accessibility-first engineering.',
founder: { '@id': `${BASE}/#principal` },
address: {
'@type': 'PostalAddress',
addressLocality: 'Boston',
addressRegion: 'MA',
addressCountry: 'US',
},
geo: { '@type': 'GeoCoordinates', latitude: 42.360082, longitude: -71.05888 },
areaServed: { '@type': 'Country', name: 'United States' },
hasOfferCatalog: { '@id': `${BASE}/#services` },
sameAs: ['https://www.linkedin.com/in/the-mifi', 'https://github.com/the-mifi'],
},
{
'@type': 'Person',
'@id': `${BASE}/#principal`,
name: 'Mike Fitzpatrick',
jobTitle: 'Principal Software Engineer and Architect',
description:
'Senior full-stack engineer and architect helping teams ship reliable, accessible, high-performance web products.',
url: `${BASE}/`,
worksFor: { '@id': `${BASE}/#organization` },
knowsAbout: [
'Frontend Architecture',
'UI Architecture',
'React Development',
'Web Performance Optimization',
'Core Web Vitals',
'Technical SEO',
'Web Accessibility (WCAG)',
'Component Libraries',
'Design Systems',
'JavaScript',
'TypeScript',
'Modern Web Development',
'Greenfield Product Development',
'Legacy System Modernization',
'Code Refactoring',
],
sameAs: ['https://www.linkedin.com/in/the-mifi', 'https://github.com/the-mifi'],
},
{
'@type': 'WebSite',
'@id': `${BASE}/#website`,
url: `${BASE}/`,
name: 'mifi Ventures',
description: 'Software Engineering Consulting — Boston, MA',
publisher: { '@id': `${BASE}/#organization` },
potentialAction: {
'@type': 'ReserveAction',
target: {
'@type': 'EntryPoint',
urlTemplate: 'https://cal.mifi.ventures/the-mifi',
},
name: 'Schedule a 30-minute intro call',
},
},
{
'@type': 'WebPage',
'@id': `${BASE}/#webpage`,
url: `${BASE}/`,
name: 'mifi Ventures — Software Engineering Consulting | Boston, MA',
description:
'Boston-based software engineering consulting. Mike Fitzpatrick helps teams build reliable, accessible, high-performance web applications.',
isPartOf: { '@id': `${BASE}/#website` },
about: { '@id': `${BASE}/#organization` },
mainEntity: { '@id': `${BASE}/#organization` },
primaryImageOfPage: { '@type': 'ImageObject', url: `${BASE}/favicon.svg` },
inLanguage: 'en-US',
},
{
'@type': 'OfferCatalog',
'@id': `${BASE}/#services`,
name: 'Software Engineering Consulting Services',
description: 'Consulting services offered by mifi Ventures',
numberOfItems: 6,
itemListElement: [
{
'@type': 'Offer',
itemOffered: {
'@type': 'Service',
name: 'Frontend and UI Architecture',
description:
'Product-focused frontend and UI architecture for modern web applications, with an emphasis on clarity, scalability, and long-term maintainability.',
},
},
{
'@type': 'Offer',
itemOffered: {
'@type': 'Service',
name: 'Greenfield Product Development',
description:
'Greenfield product builds and early-stage foundations, getting new projects off the ground quickly with structures designed to grow, not be rewritten.',
},
},
{
'@type': 'Offer',
itemOffered: {
'@type': 'Service',
name: 'Performance Optimization',
description:
'Performance, Core Web Vitals, rendering strategy, and technical SEO optimization focused on real-world user journeys.',
},
},
{
'@type': 'Offer',
itemOffered: {
'@type': 'Service',
name: 'Accessibility Engineering',
description:
'Accessibility-first engineering, ensuring WCAG-compliant interfaces with semantic markup, keyboard parity, and inclusive interaction patterns.',
},
},
{
'@type': 'Offer',
itemOffered: {
'@type': 'Service',
name: 'System Modernization',
description:
'Modernization and stabilization of existing systems, including refactors, framework upgrades, and untangling overgrown frontend codebases.',
},
},
{
'@type': 'Offer',
itemOffered: {
'@type': 'Service',
name: 'End-to-End Feature Delivery',
description:
'End-to-end feature delivery with clear ownership and documentation, spanning frontend and supporting backend work without unnecessary complexity.',
},
},
],
},
];

53
src/lib/seo.ts Normal file
View File

@@ -0,0 +1,53 @@
/**
* SEO / meta: site-wide defaults, page-meta type, and merge helper.
* Layout renders <head> from merged meta; each route can export meta from +page.ts.
*/
export const SEO_DEFAULTS = {
siteName: 'mifi Ventures',
baseUrl: 'https://mifi.ventures',
defaultOgImage: '/assets/og-image.png',
ogImageWidth: 1200,
ogImageHeight: 630,
locale: 'en_US',
twitterCard: 'summary_large_image' as const,
themeColorLight: '#0052cc',
themeColorDark: '#4da6ff',
} as const;
export interface PageMeta {
title: string;
description?: string;
canonical?: string;
ogImage?: string;
ogType?: string;
twitterTitle?: string;
twitterDescription?: string;
/** JSON-LD graph nodes (merged with defaults in layout) */
jsonLd?: Record<string, unknown>[];
}
export interface MergedMeta extends PageMeta {
canonical: string;
ogImage: string;
ogImageAlt: string;
jsonLdGraph: Record<string, unknown>[];
}
/** Merge page meta with site defaults for rendering. */
export function mergeMeta(meta: PageMeta, path: string = '/'): MergedMeta {
const baseUrl = SEO_DEFAULTS.baseUrl;
const canonical = meta.canonical ?? `${baseUrl}${path === '/' ? '' : path}`;
const ogImage = meta.ogImage?.startsWith('http')
? meta.ogImage
: `${baseUrl}${meta.ogImage?.startsWith('/') ? meta.ogImage : SEO_DEFAULTS.defaultOgImage}`;
const ogImageAlt = meta.title;
const jsonLdGraph = meta.jsonLd ?? [];
return {
...meta,
canonical,
ogImage,
ogImageAlt,
jsonLdGraph,
};
}

141
src/routes/+layout.svelte Normal file
View File

@@ -0,0 +1,141 @@
<script lang="ts">
import { page } from '$app/state';
import { mergeMeta, SEO_DEFAULTS } from '$lib/seo';
import { homeMeta } from '$lib/data/home-meta';
import '../app.css';
let { children } = $props();
const meta = $derived(page.data?.meta ?? homeMeta);
const path = $derived(page.url?.pathname ?? '/');
const merged = $derived(mergeMeta(meta, path));
const jsonLdScript = $derived(
merged.jsonLdGraph.length > 0
? JSON.stringify({
'@context': 'https://schema.org',
'@graph': merged.jsonLdGraph,
})
: '',
);
const jsonLdHtml = $derived(
jsonLdScript
? '<script type="application/ld+json">' + jsonLdScript + '</scr' + 'ipt>'
: '',
);
</script>
<svelte:head>
<title>{merged.title}</title>
<meta name="description" content={merged.description ?? ''} />
<link rel="canonical" href={merged.canonical} />
<link
rel="preload"
href="/assets/fonts/fraunces-v38-latin-600.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="/assets/fonts/fraunces-v38-latin-700.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-regular.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-italic.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-500.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-600.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-700.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<meta
name="robots"
content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
/>
<meta name="author" content="Mike Fitzpatrick" />
<meta name="geo.region" content="US-MA" />
<meta name="geo.placename" content="Boston" />
<meta name="geo.position" content="42.360082;-71.058880" />
<meta name="ICBM" content="42.360082, -71.058880" />
<meta
name="theme-color"
content={SEO_DEFAULTS.themeColorLight}
media="(prefers-color-scheme: light)"
/>
<meta
name="theme-color"
content={SEO_DEFAULTS.themeColorDark}
media="(prefers-color-scheme: dark)"
/>
<meta property="og:type" content={merged.ogType ?? 'website'} />
<meta property="og:url" content={merged.canonical} />
<meta property="og:site_name" content={SEO_DEFAULTS.siteName} />
<meta property="og:title" content={merged.twitterTitle ?? merged.title} />
<meta
property="og:description"
content={merged.twitterDescription ?? merged.description ?? ''}
/>
<meta property="og:image" content={merged.ogImage} />
<meta property="og:image:width" content={String(SEO_DEFAULTS.ogImageWidth)} />
<meta property="og:image:height" content={String(SEO_DEFAULTS.ogImageHeight)} />
<meta property="og:image:alt" content={merged.ogImageAlt} />
<meta property="og:locale" content={SEO_DEFAULTS.locale} />
<meta name="twitter:card" content={SEO_DEFAULTS.twitterCard} />
<meta name="twitter:url" content={merged.canonical} />
<meta name="twitter:title" content={merged.twitterTitle ?? merged.title} />
<meta
name="twitter:description"
content={merged.twitterDescription ?? merged.description ?? ''}
/>
<meta name="twitter:image" content={merged.ogImage} />
<meta name="twitter:image:alt" content={merged.ogImageAlt} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/favicon.svg" />
{#if jsonLdHtml}
{@html jsonLdHtml}
{/if}
<script src="/assets/scripts/copyright-year.js" defer></script>
</svelte:head>
<a href="#main" class="skip-link">Skip to main content</a>
{@render children()}

3
src/routes/+layout.ts Normal file
View File

@@ -0,0 +1,3 @@
export const prerender = true;
export const ssr = true;
export const csr = false;

21
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,21 @@
<script lang="ts">
import Hero from '$lib/components/Hero.svelte';
import ExperienceSection from '$lib/components/ExperienceSection.svelte';
import WhatWeDo from '$lib/components/WhatWeDo.svelte';
import ImpactSection from '$lib/components/ImpactSection.svelte';
import HowWeWork from '$lib/components/HowWeWork.svelte';
import EngagementsSection from '$lib/components/EngagementsSection.svelte';
import ScheduleSection from '$lib/components/ScheduleSection.svelte';
import Footer from '$lib/components/Footer.svelte';
</script>
<Hero />
<main id="main">
<ExperienceSection />
<WhatWeDo />
<ImpactSection />
<HowWeWork />
<EngagementsSection />
<ScheduleSection />
</main>
<Footer />

6
src/routes/+page.ts Normal file
View File

@@ -0,0 +1,6 @@
import type { PageLoad } from './$types';
import { homeMeta } from '$lib/data/home-meta';
export const load: PageLoad = () => {
return { meta: homeMeta };
};