Added the Nav... Almost ready for the switcheroo
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
This commit is contained in:
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
|
Responsive Design
|
||||||
======================================== */
|
======================================== */
|
||||||
|
|||||||
@@ -35,48 +35,4 @@
|
|||||||
color: var(--color-text-tertiary);
|
color: var(--color-text-tertiary);
|
||||||
max-width: 100%;
|
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>
|
</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';
|
import { getCurrentYear } from './copyright-year';
|
||||||
|
|
||||||
describe('getCurrentYear', () => {
|
describe('getCurrentYear', () => {
|
||||||
it('returns the current calendar year', () => {
|
it('returns the current calendar year', () => {
|
||||||
expect(getCurrentYear()).toBe(new Date().getFullYear());
|
expect(getCurrentYear()).toBe(new Date().getFullYear());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
* The client-side footer year is updated by static/copyright-year.js.
|
* The client-side footer year is updated by static/copyright-year.js.
|
||||||
*/
|
*/
|
||||||
export function getCurrentYear(): number {
|
export function getCurrentYear(): number {
|
||||||
return new Date().getFullYear();
|
return new Date().getFullYear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
export const whatWeDoItems = [
|
export const whatWeDoItems = [
|
||||||
'Product-focused frontend and UI architecture for modern web applications, with an emphasis on clarity, scalability, and long-term maintainability.',
|
'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.',
|
'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.',
|
'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.',
|
'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.',
|
'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.',
|
'End-to-end feature delivery with clear ownership and documentation, spanning frontend and supporting backend work without unnecessary complexity.',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const impactItems = [
|
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.',
|
'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.',
|
'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.',
|
'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.',
|
'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.',
|
'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.',
|
'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 = [
|
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.',
|
'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.',
|
'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.',
|
'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.',
|
'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.",
|
"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.',
|
'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 = [
|
export const engagements = [
|
||||||
{
|
{
|
||||||
title: 'Atlassian — Senior UI Engineer (Enterprise SaaS)',
|
title: 'Atlassian — Senior UI Engineer (Enterprise SaaS)',
|
||||||
description:
|
description:
|
||||||
'Frontend architecture and feature delivery for Confluence integrations, including React 18 migration work and standardizing end-to-end testing practices.',
|
'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)',
|
title: 'CarGurus — Principal UI Engineer (Consumer Marketplace)',
|
||||||
description:
|
description:
|
||||||
'Built and maintained high-traffic frontend systems, improved Core Web Vitals and technical SEO, and developed shared UI platforms used across teams.',
|
'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)',
|
title: 'The TJX Companies (TJ Maxx) — UI Engineer (Enterprise Retail)',
|
||||||
description:
|
description:
|
||||||
'Delivered UX improvements for large-scale e-commerce experiences in close partnership with design, QA, and product teams.',
|
'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)',
|
title: 'Timberland — Senior Interactive Developer (Global Ecommerce)',
|
||||||
description:
|
description:
|
||||||
'Led global web initiatives across brand and e-commerce platforms, acting as a technical bridge between marketing, design, and engineering.',
|
'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)',
|
title: 'MFA Boston — Pro Bono Technical Lead (Nonprofit / Fundraising)',
|
||||||
description:
|
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.",
|
"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 = [
|
export const experienceLogos = [
|
||||||
{ src: '/assets/logos/atlassian.svg', alt: 'Atlassian', width: 2500, height: 2500 },
|
{ src: '/assets/logos/atlassian.svg', alt: 'Atlassian', width: 2500, height: 2500 },
|
||||||
{
|
{
|
||||||
src: '/assets/logos/tjx.svg',
|
src: '/assets/logos/tjx.svg',
|
||||||
alt: 'TJ Maxx (The TJX Companies)',
|
alt: 'TJ Maxx (The TJX Companies)',
|
||||||
width: 2500,
|
width: 2500,
|
||||||
height: 621,
|
height: 621,
|
||||||
},
|
},
|
||||||
{ src: '/assets/logos/cargurus.svg', alt: 'CarGurus', width: 2500, height: 398 },
|
{ 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/timberland.svg', alt: 'Timberland', width: 190, height: 35 },
|
||||||
{ src: '/assets/logos/vf.svg', alt: 'VF Corporation', width: 190, height: 155 },
|
{ src: '/assets/logos/vf.svg', alt: 'VF Corporation', width: 190, height: 155 },
|
||||||
{
|
{
|
||||||
src: '/assets/logos/bottomline.svg',
|
src: '/assets/logos/bottomline.svg',
|
||||||
alt: 'Bottomline Technologies',
|
alt: 'Bottomline Technologies',
|
||||||
width: 2702,
|
width: 2702,
|
||||||
height: 571,
|
height: 571,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: '/assets/logos/mfa-boston.svg',
|
src: '/assets/logos/mfa-boston.svg',
|
||||||
alt: 'Museum of Fine Arts Boston',
|
alt: 'Museum of Fine Arts Boston',
|
||||||
width: 572,
|
width: 572,
|
||||||
height: 88,
|
height: 88,
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const experienceTextList = experienceLogos.map((l) => l.alt);
|
export const experienceTextList = experienceLogos.map((l) => l.alt);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import type { PageMeta } from '$lib/seo';
|
|||||||
import { defaultJsonLdGraph } from './json-ld';
|
import { defaultJsonLdGraph } from './json-ld';
|
||||||
|
|
||||||
export const homeMeta: PageMeta = {
|
export const homeMeta: PageMeta = {
|
||||||
title: 'mifi Ventures — Software Engineering Consulting | Boston, MA',
|
title: 'mifi Ventures — Software Engineering Consulting | Boston, MA',
|
||||||
description:
|
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.',
|
'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,
|
jsonLd: defaultJsonLdGraph,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,145 +6,145 @@
|
|||||||
const BASE = 'https://mifi.ventures';
|
const BASE = 'https://mifi.ventures';
|
||||||
|
|
||||||
export const defaultJsonLdGraph: Record<string, unknown>[] = [
|
export const defaultJsonLdGraph: Record<string, unknown>[] = [
|
||||||
{
|
{
|
||||||
'@type': 'Organization',
|
'@type': 'Organization',
|
||||||
'@id': `${BASE}/#organization`,
|
'@id': `${BASE}/#organization`,
|
||||||
name: 'mifi Ventures, LLC',
|
name: 'mifi Ventures, LLC',
|
||||||
legalName: 'mifi Ventures, LLC',
|
legalName: 'mifi Ventures, LLC',
|
||||||
url: `${BASE}/`,
|
url: `${BASE}/`,
|
||||||
logo: { '@type': 'ImageObject', url: `${BASE}/favicon.svg` },
|
logo: { '@type': 'ImageObject', url: `${BASE}/favicon.svg` },
|
||||||
description:
|
description:
|
||||||
'Software engineering consulting specializing in product-focused frontend architecture, performance optimization, and accessibility-first engineering.',
|
'Software engineering consulting specializing in product-focused frontend architecture, performance optimization, and accessibility-first engineering.',
|
||||||
founder: { '@id': `${BASE}/#principal` },
|
founder: { '@id': `${BASE}/#principal` },
|
||||||
address: {
|
address: {
|
||||||
'@type': 'PostalAddress',
|
'@type': 'PostalAddress',
|
||||||
addressLocality: 'Boston',
|
addressLocality: 'Boston',
|
||||||
addressRegion: 'MA',
|
addressRegion: 'MA',
|
||||||
addressCountry: 'US',
|
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' },
|
'@type': 'Person',
|
||||||
hasOfferCatalog: { '@id': `${BASE}/#services` },
|
'@id': `${BASE}/#principal`,
|
||||||
sameAs: ['https://www.linkedin.com/in/the-mifi', 'https://github.com/the-mifi'],
|
name: 'Mike Fitzpatrick',
|
||||||
},
|
jobTitle: 'Principal Software Engineer and Architect',
|
||||||
{
|
description:
|
||||||
'@type': 'Person',
|
'Senior full-stack engineer and architect helping teams ship reliable, accessible, high-performance web products.',
|
||||||
'@id': `${BASE}/#principal`,
|
url: `${BASE}/`,
|
||||||
name: 'Mike Fitzpatrick',
|
worksFor: { '@id': `${BASE}/#organization` },
|
||||||
jobTitle: 'Principal Software Engineer and Architect',
|
knowsAbout: [
|
||||||
description:
|
'Frontend Architecture',
|
||||||
'Senior full-stack engineer and architect helping teams ship reliable, accessible, high-performance web products.',
|
'UI Architecture',
|
||||||
url: `${BASE}/`,
|
'React Development',
|
||||||
worksFor: { '@id': `${BASE}/#organization` },
|
'Web Performance Optimization',
|
||||||
knowsAbout: [
|
'Core Web Vitals',
|
||||||
'Frontend Architecture',
|
'Technical SEO',
|
||||||
'UI Architecture',
|
'Web Accessibility (WCAG)',
|
||||||
'React Development',
|
'Component Libraries',
|
||||||
'Web Performance Optimization',
|
'Design Systems',
|
||||||
'Core Web Vitals',
|
'JavaScript',
|
||||||
'Technical SEO',
|
'TypeScript',
|
||||||
'Web Accessibility (WCAG)',
|
'Modern Web Development',
|
||||||
'Component Libraries',
|
'Greenfield Product Development',
|
||||||
'Design Systems',
|
'Legacy System Modernization',
|
||||||
'JavaScript',
|
'Code Refactoring',
|
||||||
'TypeScript',
|
],
|
||||||
'Modern Web Development',
|
sameAs: ['https://www.linkedin.com/in/the-mifi', 'https://github.com/the-mifi'],
|
||||||
'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': 'WebSite',
|
||||||
'@type': 'WebPage',
|
'@id': `${BASE}/#website`,
|
||||||
'@id': `${BASE}/#webpage`,
|
url: `${BASE}/`,
|
||||||
url: `${BASE}/`,
|
name: 'mifi Ventures',
|
||||||
name: 'mifi Ventures — Software Engineering Consulting | Boston, MA',
|
description: 'Software Engineering Consulting — Boston, MA',
|
||||||
description:
|
publisher: { '@id': `${BASE}/#organization` },
|
||||||
'Boston-based software engineering consulting. Mike Fitzpatrick helps teams build reliable, accessible, high-performance web applications.',
|
potentialAction: {
|
||||||
isPartOf: { '@id': `${BASE}/#website` },
|
'@type': 'ReserveAction',
|
||||||
about: { '@id': `${BASE}/#organization` },
|
target: {
|
||||||
mainEntity: { '@id': `${BASE}/#organization` },
|
'@type': 'EntryPoint',
|
||||||
primaryImageOfPage: { '@type': 'ImageObject', url: `${BASE}/favicon.svg` },
|
urlTemplate: 'https://cal.mifi.ventures/the-mifi',
|
||||||
inLanguage: 'en-US',
|
},
|
||||||
},
|
name: 'Schedule a 30-minute intro call',
|
||||||
{
|
|
||||||
'@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',
|
'@type': 'WebPage',
|
||||||
itemOffered: {
|
'@id': `${BASE}/#webpage`,
|
||||||
'@type': 'Service',
|
url: `${BASE}/`,
|
||||||
name: 'Greenfield Product Development',
|
name: 'mifi Ventures — Software Engineering Consulting | Boston, MA',
|
||||||
description:
|
description:
|
||||||
'Greenfield product builds and early-stage foundations, getting new projects off the ground quickly with structures designed to grow, not be rewritten.',
|
'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` },
|
||||||
'@type': 'Offer',
|
primaryImageOfPage: { '@type': 'ImageObject', url: `${BASE}/favicon.svg` },
|
||||||
itemOffered: {
|
inLanguage: 'en-US',
|
||||||
'@type': 'Service',
|
},
|
||||||
name: 'Performance Optimization',
|
{
|
||||||
description:
|
'@type': 'OfferCatalog',
|
||||||
'Performance, Core Web Vitals, rendering strategy, and technical SEO optimization focused on real-world user journeys.',
|
'@id': `${BASE}/#services`,
|
||||||
},
|
name: 'Software Engineering Consulting Services',
|
||||||
},
|
description: 'Consulting services offered by mifi Ventures',
|
||||||
{
|
numberOfItems: 6,
|
||||||
'@type': 'Offer',
|
itemListElement: [
|
||||||
itemOffered: {
|
{
|
||||||
'@type': 'Service',
|
'@type': 'Offer',
|
||||||
name: 'Accessibility Engineering',
|
itemOffered: {
|
||||||
description:
|
'@type': 'Service',
|
||||||
'Accessibility-first engineering, ensuring WCAG-compliant interfaces with semantic markup, keyboard parity, and inclusive interaction patterns.',
|
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',
|
'@type': 'Offer',
|
||||||
name: 'System Modernization',
|
itemOffered: {
|
||||||
description:
|
'@type': 'Service',
|
||||||
'Modernization and stabilization of existing systems, including refactors, framework upgrades, and untangling overgrown frontend codebases.',
|
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',
|
'@type': 'Offer',
|
||||||
name: 'End-to-End Feature Delivery',
|
itemOffered: {
|
||||||
description:
|
'@type': 'Service',
|
||||||
'End-to-end feature delivery with clear ownership and documentation, spanning frontend and supporting backend work without unnecessary complexity.',
|
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 = {
|
export const SEO_DEFAULTS = {
|
||||||
siteName: 'mifi Ventures',
|
siteName: 'mifi Ventures',
|
||||||
baseUrl: 'https://mifi.ventures',
|
baseUrl: 'https://mifi.ventures',
|
||||||
defaultOgImage: '/assets/og-image.png',
|
defaultOgImage: '/assets/og-image.png',
|
||||||
ogImageWidth: 1200,
|
ogImageWidth: 1200,
|
||||||
ogImageHeight: 630,
|
ogImageHeight: 630,
|
||||||
locale: 'en_US',
|
locale: 'en_US',
|
||||||
twitterCard: 'summary_large_image' as const,
|
twitterCard: 'summary_large_image' as const,
|
||||||
themeColorLight: '#0052cc',
|
themeColorLight: '#0052cc',
|
||||||
themeColorDark: '#4da6ff',
|
themeColorDark: '#4da6ff',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export interface PageMeta {
|
export interface PageMeta {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
canonical?: string;
|
canonical?: string;
|
||||||
ogImage?: string;
|
ogImage?: string;
|
||||||
ogType?: string;
|
ogType?: string;
|
||||||
twitterTitle?: string;
|
twitterTitle?: string;
|
||||||
twitterDescription?: string;
|
twitterDescription?: string;
|
||||||
/** JSON-LD graph nodes (merged with defaults in layout) */
|
/** JSON-LD graph nodes (merged with defaults in layout) */
|
||||||
jsonLd?: Record<string, unknown>[];
|
jsonLd?: Record<string, unknown>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MergedMeta extends PageMeta {
|
export interface MergedMeta extends PageMeta {
|
||||||
canonical: string;
|
canonical: string;
|
||||||
ogImage: string;
|
ogImage: string;
|
||||||
ogImageAlt: string;
|
ogImageAlt: string;
|
||||||
jsonLdGraph: Record<string, unknown>[];
|
jsonLdGraph: Record<string, unknown>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Merge page meta with site defaults for rendering. */
|
/** Merge page meta with site defaults for rendering. */
|
||||||
export function mergeMeta(meta: PageMeta, path: string = '/'): MergedMeta {
|
export function mergeMeta(meta: PageMeta, path: string = '/'): MergedMeta {
|
||||||
const baseUrl = SEO_DEFAULTS.baseUrl;
|
const baseUrl = SEO_DEFAULTS.baseUrl;
|
||||||
const canonical = meta.canonical ?? `${baseUrl}${path === '/' ? '' : path}`;
|
const canonical = meta.canonical ?? `${baseUrl}${path === '/' ? '' : path}`;
|
||||||
const ogImage = meta.ogImage?.startsWith('http')
|
const ogImage = meta.ogImage?.startsWith('http')
|
||||||
? meta.ogImage
|
? meta.ogImage
|
||||||
: `${baseUrl}${meta.ogImage?.startsWith('/') ? meta.ogImage : SEO_DEFAULTS.defaultOgImage}`;
|
: `${baseUrl}${meta.ogImage?.startsWith('/') ? meta.ogImage : SEO_DEFAULTS.defaultOgImage}`;
|
||||||
const ogImageAlt = meta.title;
|
const ogImageAlt = meta.title;
|
||||||
const jsonLdGraph = meta.jsonLd ?? [];
|
const jsonLdGraph = meta.jsonLd ?? [];
|
||||||
return {
|
return {
|
||||||
...meta,
|
...meta,
|
||||||
canonical,
|
canonical,
|
||||||
ogImage,
|
ogImage,
|
||||||
ogImageAlt,
|
ogImageAlt,
|
||||||
jsonLdGraph,
|
jsonLdGraph,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Navigation from '$lib/components/Navigation.svelte';
|
||||||
import Hero from '$lib/components/Hero.svelte';
|
import Hero from '$lib/components/Hero.svelte';
|
||||||
import ExperienceSection from '$lib/components/ExperienceSection.svelte';
|
import ExperienceSection from '$lib/components/ExperienceSection.svelte';
|
||||||
import WhatWeDo from '$lib/components/WhatWeDo.svelte';
|
import WhatWeDo from '$lib/components/WhatWeDo.svelte';
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Navigation />
|
||||||
<Hero />
|
<Hero />
|
||||||
<main id="main">
|
<main id="main">
|
||||||
<ExperienceSection />
|
<ExperienceSection />
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ import type { PageLoad } from './$types';
|
|||||||
import { homeMeta } from '$lib/data/home-meta';
|
import { homeMeta } from '$lib/data/home-meta';
|
||||||
|
|
||||||
export const load: PageLoad = () => {
|
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