The Svelte 5 SSG Migration #1
49
src/app.css
49
src/app.css
@@ -476,6 +476,55 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Nav Item and Footer Links Common Styles
|
||||
======================================== */
|
||||
|
||||
.footer-links,
|
||||
.nav-item {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Responsive Design
|
||||
======================================== */
|
||||
|
||||
@@ -35,48 +35,4 @@
|
||||
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>
|
||||
|
||||
319
src/lib/components/Navigation.svelte
Normal file
319
src/lib/components/Navigation.svelte
Normal file
@@ -0,0 +1,319 @@
|
||||
<script lang="ts">
|
||||
import Wordmark from './Wordmark.svelte';
|
||||
</script>
|
||||
|
||||
<nav id="nav" class="nav" aria-label="Main navigation">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="nav-toggle"
|
||||
class="nav-toggle-input"
|
||||
aria-hidden="true"
|
||||
hidden
|
||||
/>
|
||||
<div class="mobile-nav-header">
|
||||
<span class="mobile nav-header-logo">
|
||||
<Wordmark />
|
||||
</span>
|
||||
<label for="nav-toggle" class="nav-toggle" aria-label="Toggle navigation">
|
||||
<span class="nav-toggle-inner">
|
||||
<span class="nav-toggle-line"></span>
|
||||
<span class="nav-toggle-line"></span>
|
||||
<span class="nav-toggle-line"></span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="nav-menu container">
|
||||
<span class="nav-header-logo desktop">
|
||||
<Wordmark />
|
||||
</span>
|
||||
<ul class="nav-list">
|
||||
<li class="nav-item">
|
||||
<a href="#what-we-do" class="nav-link">Services</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#impact" class="nav-link">Impact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#how-we-work" class="nav-link">Process</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#schedule" class="nav-link">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="nav-item nav-back-to-top">
|
||||
<a href="#header" class="nav-link">Back to top</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.nav {
|
||||
background-color: var(--color-bg);
|
||||
background-color: var(--color-bg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
|
||||
padding: var(--space-md) 0;
|
||||
position: sticky;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.container,
|
||||
.nav-menu {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mobile-nav-header {
|
||||
anchor-name: --mobile-nav-header;
|
||||
display: none;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-header-logo {
|
||||
color: var(--color-text);
|
||||
display: inline-block;
|
||||
max-width: 250px;
|
||||
padding-left: var(--space-md);
|
||||
|
||||
&.mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
&.desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Hamburger toggle: mobile only, animates to X when open */
|
||||
.nav-toggle-input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
|
||||
&:checked {
|
||||
& ~ .mobile-nav-header .nav-toggle .nav-toggle-line {
|
||||
&:nth-child(1) {
|
||||
transform: translateY(8px) rotate(45deg);
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
transform: translateY(-8px) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
& ~ .nav-menu {
|
||||
max-height: 80vh;
|
||||
opacity: 1;
|
||||
padding-top: var(--space-md);
|
||||
padding-bottom: var(--space-md);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 0 0 auto;
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
width: calc(24px + var(--space-md) + var(--space-md));
|
||||
height: calc(31px + var(--space-sm) + var(--space-sm));
|
||||
cursor: pointer;
|
||||
color: var(--color-text);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--space-xs);
|
||||
transition:
|
||||
color 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 4px solid var(--color-focus);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-toggle-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.nav-toggle-line {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: currentColor;
|
||||
border-radius: 1px;
|
||||
transform-origin: center;
|
||||
transition:
|
||||
transform 0.25s ease,
|
||||
opacity 0.2s ease;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
transition-duration: 0.01ms;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin: 0 var(--space-md);
|
||||
}
|
||||
|
||||
/* Back to top + mobile nav logo: hidden until page is scrolled (CSS scroll-driven animation) */
|
||||
.nav-back-to-top,
|
||||
.nav-header-logo {
|
||||
/* Fallback when scroll-driven animations aren’t supported: always visible */
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Mobile: show toggle, collapse menu until opened; menu overlays content via anchor */
|
||||
@media (max-width: 768px) {
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
border-top: none;
|
||||
transition:
|
||||
max-height 0.3s ease,
|
||||
opacity 0.25s ease,
|
||||
padding 0.25s ease;
|
||||
|
||||
& .nav-list,
|
||||
& .nav-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
& .nav-list {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
& .nav-item {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
|
||||
& a:hover {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
& .nav-item a,
|
||||
& .nav-back-to-top a {
|
||||
display: block;
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
@supports (top: anchor(bottom)) {
|
||||
position: fixed;
|
||||
position-anchor: --mobile-nav-header;
|
||||
top: anchor(--mobile-nav-header bottom);
|
||||
left: anchor(--mobile-nav-header left);
|
||||
right: anchor(--mobile-nav-header right);
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
background-color: var(--color-bg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
transition-duration: 0.01ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@supports (animation-timeline: scroll()) {
|
||||
.nav-back-to-top,
|
||||
.nav-header-logo {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
animation: nav-reveal-on-scroll linear;
|
||||
animation-timeline: scroll(root block);
|
||||
animation-range: 300px 400px;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
@keyframes nav-reveal-on-scroll {
|
||||
from {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.nav-back-to-top,
|
||||
.nav-header-logo {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@ 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());
|
||||
});
|
||||
it('returns the current calendar year', () => {
|
||||
expect(getCurrentYear()).toBe(new Date().getFullYear());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
* The client-side footer year is updated by static/copyright-year.js.
|
||||
*/
|
||||
export function getCurrentYear(): number {
|
||||
return new Date().getFullYear();
|
||||
return new Date().getFullYear();
|
||||
}
|
||||
|
||||
@@ -1,26 +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.',
|
||||
'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.',
|
||||
'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.',
|
||||
'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.',
|
||||
];
|
||||
|
||||
@@ -1,27 +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.",
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,26 +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,
|
||||
},
|
||||
{ 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);
|
||||
|
||||
@@ -2,8 +2,8 @@ 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,
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -6,145 +6,145 @@
|
||||
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',
|
||||
{
|
||||
'@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'],
|
||||
},
|
||||
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': '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': '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': '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': '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.',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
'@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.',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -4,50 +4,50 @@
|
||||
*/
|
||||
|
||||
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',
|
||||
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>[];
|
||||
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>[];
|
||||
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,
|
||||
};
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Navigation from '$lib/components/Navigation.svelte';
|
||||
import Hero from '$lib/components/Hero.svelte';
|
||||
import ExperienceSection from '$lib/components/ExperienceSection.svelte';
|
||||
import WhatWeDo from '$lib/components/WhatWeDo.svelte';
|
||||
@@ -9,6 +10,7 @@
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
</script>
|
||||
|
||||
<Navigation />
|
||||
<Hero />
|
||||
<main id="main">
|
||||
<ExperienceSection />
|
||||
|
||||
@@ -2,5 +2,5 @@ import type { PageLoad } from './$types';
|
||||
import { homeMeta } from '$lib/data/home-meta';
|
||||
|
||||
export const load: PageLoad = () => {
|
||||
return { meta: homeMeta };
|
||||
return { meta: homeMeta };
|
||||
};
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 587 KiB After Width: | Height: | Size: 591 KiB |
Reference in New Issue
Block a user