Initial commit... a site is born (again? finally?)

This commit is contained in:
2026-01-30 19:58:22 +00:00
commit 1519dfa460
62 changed files with 5674 additions and 0 deletions

18
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# Dev container for mifi Ventures static site
# Lightweight: Node for static server (npx serve), no app dependencies
FROM mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm
# Install system deps if needed (none required for static site)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# Ensure workspace dir exists (mount point)
RUN mkdir -p /workspaces/mifi-ventures-landing
# Default working directory
WORKDIR /workspaces/mifi-ventures-landing
# npx serve is used at runtime via postStartCommand
# No npm install needed — static site, no package.json

View File

@@ -0,0 +1,36 @@
{
"name": "mifi Ventures Landing",
"dockerFile": "Dockerfile",
"workspaceFolder": "/workspaces/mifi-ventures-landing",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/mifi-ventures-landing,type=bind",
"forwardPorts": [3000],
"portsAttributes": {
"3000": {
"label": "Site",
"onAutoForward": "notify"
}
},
"postStartCommand": "nohup npx -y serve site -l 3000 > /tmp/serve.log 2>&1 & sleep 1",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"settings": {
"files.associations": {
"*.html": "html",
"*.css": "css",
"*.svg": "svg"
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.watcherExclude": {
"**/node_modules/**": true,
"**/.git/objects/**": true
}
}
}
},
"remoteUser": "node"
}

84
.env.example Normal file
View File

@@ -0,0 +1,84 @@
# Woodpecker CI/CD Environment Variables Example
# Copy this file to your Woodpecker repository settings
# DO NOT commit actual secrets to git
# ============================================
# Docker Registry Configuration
# ============================================
# Registry base URL (no protocol, no trailing slash)
# Examples:
# - Docker Hub: docker.io
# - GitHub: ghcr.io
# - GitLab: registry.gitlab.com
# - Self-hosted: registry.example.com
REGISTRY_URL=registry.example.com
# Full image repository path
# Examples:
# - Docker Hub: docker.io/username/mifi-ventures-landing
# - GitHub: ghcr.io/username/mifi-ventures-landing
# - Self-hosted: registry.example.com/mifi-ventures-landing
REGISTRY_REPO=registry.example.com/mifi-ventures-landing
# Registry username
REGISTRY_USERNAME=myusername
# Registry password/token (SET AS SECRET, NOT ENVIRONMENT VARIABLE)
# This should be set in Woodpecker Secrets as: registry_password
# REGISTRY_PASSWORD=<set-as-secret>
# ============================================
# Deployment Configuration
# ============================================
# Container name on VPS
CONTAINER_NAME=mifi-ventures-landing
# Host port to expose (container always uses 80 internally)
# Examples:
# - Direct: 80 (if no reverse proxy)
# - Proxied: 8080 (if using Traefik/Nginx)
APP_PORT=8080
# ============================================
# SSH Deployment Secrets (SET IN WOODPECKER SECRETS)
# ============================================
# These should NEVER be set as environment variables
# Set them in Woodpecker Secrets UI instead
# deploy_host: Linode VPS IP or hostname
# Example: 123.45.67.89 or vps.example.com
# deploy_username: SSH username
# Example: deploy or root
# deploy_ssh_key: Private SSH key (multi-line)
# Example:
# -----BEGIN OPENSSH PRIVATE KEY-----
# b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
# ...
# -----END OPENSSH PRIVATE KEY-----
# deploy_port: SSH port
# Example: 22 (default)
# ============================================
# Notes
# ============================================
#
# Secrets vs Environment Variables:
# - SECRETS: Sensitive data (passwords, keys) - set in Woodpecker Secrets
# - ENV VARS: Non-sensitive config (URLs, usernames) - can be in Variables or here
#
# How to use:
# 1. Copy this file: cp .env.example .env
# 2. Fill in your values in .env (for local testing only)
# 3. Add secrets to Woodpecker UI (never commit)
# 4. Add env vars to Woodpecker Variables or repository settings
#
# Local testing:
# - Build: docker build -t test .
# - Run: docker run -d -p 8080:80 --name test test
# - Test: curl http://localhost:8080
# - Stop: docker stop test && docker rm test

36
.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# Operating System
.DS_Store
Thumbs.db
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# Dependencies
node_modules/
package-lock.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
.pnpm-store/
# Build artifacts
dist/
build/
# Environment variables (NEVER commit secrets)
.env
.env.local
.env*.local
!.env.example
# Docker
docker-compose.override.yml
# Logs
*.log
logs/

170
.woodpecker.yml Normal file
View File

@@ -0,0 +1,170 @@
# Woodpecker CI/CD Pipeline for mifi Ventures Landing Site
# Deploys static site to Linode VPS via Docker
# Documentation: https://woodpecker-ci.org/docs
# Trigger: Push to main branch or tag creation
when:
branch: main
event: [push, tag]
steps:
# ============================================
# Stage 1: Build Docker Image
# ============================================
- name: build
image: docker:latest
environment:
REGISTRY_REPO: ${REGISTRY_REPO}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- set -e # Exit on error
- echo "=== Building Docker image ==="
- echo "Commit SHA: ${CI_COMMIT_SHA:0:8}"
- echo "Registry repo: $REGISTRY_REPO"
- |
docker build \
--tag $REGISTRY_REPO:${CI_COMMIT_SHA} \
--tag $REGISTRY_REPO:latest \
--label "git.commit=${CI_COMMIT_SHA}" \
--label "git.branch=${CI_COMMIT_BRANCH}" \
.
- echo "✓ Docker image built successfully"
# ============================================
# Stage 2: Push to Registry
# ============================================
- name: push
image: docker:latest
environment:
REGISTRY_URL: ${REGISTRY_URL}
REGISTRY_REPO: ${REGISTRY_REPO}
REGISTRY_USERNAME: ${REGISTRY_USERNAME}
REGISTRY_PASSWORD:
from_secret: registry_password
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- set -e # Exit on error
- echo "=== Pushing to registry ==="
- echo "Registry: $REGISTRY_URL"
- echo "Repository: $REGISTRY_REPO"
- |
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" \
-u "$REGISTRY_USERNAME" \
--password-stdin
- docker push $REGISTRY_REPO:${CI_COMMIT_SHA}
- docker push $REGISTRY_REPO:latest
- echo "✓ Images pushed successfully"
depends_on:
- build
# ============================================
# Stage 3: Deploy to Linode VPS
# ============================================
- name: deploy
image: appleboy/drone-ssh
settings:
host:
from_secret: deploy_host
username:
from_secret: deploy_username
key:
from_secret: deploy_ssh_key
port:
from_secret: deploy_port
command_timeout: 5m
envs:
- REGISTRY_URL
- REGISTRY_REPO
- REGISTRY_USERNAME
- REGISTRY_PASSWORD
- CONTAINER_NAME
- APP_PORT
script:
- set -e
- set -o pipefail
- echo "=== Deploying to Linode VPS ==="
- echo "Container: $CONTAINER_NAME"
- echo "Port: $APP_PORT"
- |
# Login to registry
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" \
-u "$REGISTRY_USERNAME" \
--password-stdin
- |
# Pull latest image
echo "Pulling image: $REGISTRY_REPO:latest"
docker pull $REGISTRY_REPO:latest
- |
# Stop and remove existing container
echo "Stopping existing container..."
docker stop $CONTAINER_NAME 2>/dev/null || echo "Container not running"
docker rm $CONTAINER_NAME 2>/dev/null || echo "Container not found"
- |
# Start new container
echo "Starting new container..."
docker run -d \
--name $CONTAINER_NAME \
--restart unless-stopped \
-p $APP_PORT:80 \
--label "deployment.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--label "deployment.commit=${CI_COMMIT_SHA}" \
$REGISTRY_REPO:latest
- |
# Wait for container to be healthy
echo "Waiting for container health check..."
sleep 5
if ! docker ps | grep -q $CONTAINER_NAME; then
echo "❌ Container failed to start!"
docker logs $CONTAINER_NAME
exit 1
fi
- |
# Verify container is responding
echo "Verifying container health..."
if ! docker exec $CONTAINER_NAME wget -q --spider http://localhost/; then
echo "❌ Container health check failed!"
docker logs $CONTAINER_NAME
exit 1
fi
- |
# Cleanup old images
echo "Cleaning up old images..."
docker image prune -af --filter "until=72h" || true
- echo "✓ Deployment complete!"
environment:
REGISTRY_URL: ${REGISTRY_URL}
REGISTRY_REPO: ${REGISTRY_REPO}
REGISTRY_USERNAME: ${REGISTRY_USERNAME}
REGISTRY_PASSWORD:
from_secret: registry_password
CONTAINER_NAME: ${CONTAINER_NAME}
APP_PORT: ${APP_PORT}
depends_on:
- push
# ============================================
# Configuration Reference
# ============================================
#
# Required Secrets (set in Woodpecker UI):
# - registry_password: Docker registry password/token
# - deploy_host: Linode VPS hostname or IP
# - deploy_username: SSH username (e.g., root, deploy)
# - deploy_ssh_key: Private SSH key (multi-line)
# - deploy_port: SSH port (default: 22)
#
# Required Environment Variables:
# - REGISTRY_URL: Docker registry URL (e.g., registry.example.com)
# - REGISTRY_REPO: Full image path (e.g., registry.example.com/mifi-ventures-landing)
# - REGISTRY_USERNAME: Registry username
# - CONTAINER_NAME: Docker container name (e.g., mifi-ventures-landing)
# - APP_PORT: Host port to expose (e.g., 8080)
#
# Example .env for local testing:
# REGISTRY_URL=registry.example.com
# REGISTRY_REPO=registry.example.com/mifi-ventures-landing
# REGISTRY_USERNAME=myuser
# CONTAINER_NAME=mifi-ventures-landing
# APP_PORT=8080

355
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,355 @@
# Deployment Guide
Quick reference for setting up Woodpecker CI/CD deployment.
## Prerequisites
- Gitea instance with Woodpecker CI configured
- Private Docker registry (Docker Hub, GitHub Container Registry, Harbor, etc.)
- Linode VPS with Docker installed
- SSH access to VPS
## Step-by-Step Setup
### 1. Prepare VPS
SSH into your Linode VPS and install Docker:
```bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Create deploy user (optional, recommended)
sudo useradd -m -s /bin/bash deploy
sudo usermod -aG docker deploy
# Verify Docker works
docker --version
docker ps
```
### 2. Generate SSH Key for Deployment
On your local machine or CI server:
```bash
# Generate SSH key pair
ssh-keygen -t ed25519 -C "woodpecker-deploy" -f ~/.ssh/woodpecker_deploy
# Copy public key to VPS
ssh-copy-id -i ~/.ssh/woodpecker_deploy.pub deploy@your-vps-ip
# Test connection
ssh -i ~/.ssh/woodpecker_deploy deploy@your-vps-ip "echo 'Connection successful'"
# Get private key content (copy this to Woodpecker secret)
cat ~/.ssh/woodpecker_deploy
```
### 3. Configure Docker Registry
**Option A: Docker Hub**
```bash
REGISTRY_URL=docker.io
REGISTRY_REPO=docker.io/yourusername/mifi-ventures-landing
REGISTRY_USERNAME=yourusername
# Password: Use Docker Hub access token (not your account password)
# Generate at: https://hub.docker.com/settings/security
```
**Option B: GitHub Container Registry**
```bash
REGISTRY_URL=ghcr.io
REGISTRY_REPO=ghcr.io/yourusername/mifi-ventures-landing
REGISTRY_USERNAME=yourusername
# Password: GitHub Personal Access Token with write:packages scope
# Generate at: https://github.com/settings/tokens
```
**Option C: Self-hosted Registry**
```bash
REGISTRY_URL=registry.example.com
REGISTRY_REPO=registry.example.com/mifi-ventures-landing
REGISTRY_USERNAME=yourusername
# Password: Your registry password or token
```
### 4. Configure Woodpecker Secrets
Navigate to: `Gitea → Repository → Settings → Secrets`
Add these secrets (click "Add secret" for each):
| Name | Value | Notes |
|------|-------|-------|
| `registry_password` | Your registry password/token | From step 3 |
| `deploy_host` | `123.45.67.89` | Your Linode VPS IP |
| `deploy_username` | `deploy` | SSH user from step 1 |
| `deploy_ssh_key` | `-----BEGIN OPENSSH...` | Private key from step 2 |
| `deploy_port` | `22` | Default SSH port |
**Important**: For `deploy_ssh_key`, paste the entire private key including:
```
-----BEGIN OPENSSH PRIVATE KEY-----
...entire key content...
-----END OPENSSH PRIVATE KEY-----
```
### 5. Configure Woodpecker Variables
Navigate to: `Gitea → Repository → Settings → Variables`
Add these variables:
| Name | Value | Example |
|------|-------|---------|
| `REGISTRY_URL` | Registry base URL | `ghcr.io` |
| `REGISTRY_REPO` | Full image path | `ghcr.io/username/mifi-ventures-landing` |
| `REGISTRY_USERNAME` | Registry username | `yourusername` |
| `CONTAINER_NAME` | Container name | `mifi-ventures-landing` |
| `APP_PORT` | Host port | `8080` |
### 6. Test Deployment
#### Local Test Build
```bash
# Clone repo
git clone https://gitea.example.com/you/mifi-ventures-landing.git
cd mifi-ventures-landing
# Build locally
docker build -t test .
# Run locally
docker run -d -p 8080:80 --name test test
# Test
curl http://localhost:8080
# Cleanup
docker stop test && docker rm test
```
#### Trigger CI/CD
```bash
# Make a small change
echo "# Test deployment" >> README.md
# Commit and push to main
git add README.md
git commit -m "test: trigger deployment"
git push origin main
# Watch pipeline in Woodpecker UI
# Check: Gitea → Repository → Pipelines
```
### 7. Verify Deployment
After pipeline completes:
```bash
# Check container is running
ssh deploy@your-vps-ip "docker ps | grep mifi-ventures-landing"
# Check container logs
ssh deploy@your-vps-ip "docker logs mifi-ventures-landing"
# Test HTTP response
curl http://your-vps-ip:8080
# Check from browser
# Visit: http://your-vps-ip:8080
```
## Port Configuration
The pipeline uses `APP_PORT` to expose the container. Choose based on your setup:
### Option 1: Direct Port 80 (No Reverse Proxy)
```bash
APP_PORT=80
# Container listens on port 80 directly
# Access at: http://your-vps-ip
```
### Option 2: Custom Port (With Traefik/Nginx)
```bash
APP_PORT=8080
# Container listens on 8080
# Traefik/Nginx proxies from 80/443 → 8080
# Access via domain: https://mifi.ventures
```
**Note**: With Traefik, security headers are added at the proxy level.
## Traefik Integration Example
If using Traefik as reverse proxy, add labels to container:
```yaml
# In docker-compose.yml or docker run command
labels:
- "traefik.enable=true"
- "traefik.http.routers.mifi.rule=Host(`mifi.ventures`)"
- "traefik.http.routers.mifi.entrypoints=websecure"
- "traefik.http.routers.mifi.tls.certresolver=letsencrypt"
- "traefik.http.services.mifi.loadbalancer.server.port=80"
```
Update Woodpecker deploy script to include labels:
```bash
docker run -d \
--name $CONTAINER_NAME \
--restart unless-stopped \
--label "traefik.enable=true" \
--label "traefik.http.routers.mifi.rule=Host(\`mifi.ventures\`)" \
--label "traefik.http.routers.mifi.entrypoints=websecure" \
--label "traefik.http.routers.mifi.tls.certresolver=letsencrypt" \
--label "traefik.http.services.mifi.loadbalancer.server.port=80" \
--network traefik-public \
$REGISTRY_REPO:latest
```
## Common Issues
### SSH Permission Denied
```bash
# Check key permissions (must be 600)
chmod 600 ~/.ssh/woodpecker_deploy
# Verify public key is on server
ssh deploy@host "cat ~/.ssh/authorized_keys"
# Test with verbose output
ssh -vvv -i ~/.ssh/woodpecker_deploy deploy@host
```
### Registry Login Failed
```bash
# Test login locally
echo "PASSWORD" | docker login registry.example.com -u username --password-stdin
# For GitHub: Ensure token has write:packages scope
# For Docker Hub: Use access token, not password
```
### Container Won't Start
```bash
# Check logs
ssh deploy@host "docker logs mifi-ventures-landing"
# Check for port conflicts
ssh deploy@host "netstat -tulpn | grep :80"
# Verify image pulled successfully
ssh deploy@host "docker images | grep mifi-ventures"
```
### Health Check Fails
```bash
# Check nginx is running
ssh deploy@host "docker exec mifi-ventures-landing ps aux"
# Test internally
ssh deploy@host "docker exec mifi-ventures-landing wget -O- http://localhost/"
# Check if firewall blocking
ssh deploy@host "ufw status"
```
## Rollback Procedure
If deployment fails:
```bash
# SSH to VPS
ssh deploy@your-vps-ip
# List available images
docker images | grep mifi-ventures
# Find previous working SHA
# (from Gitea commit history or Docker labels)
# Stop current container
docker stop mifi-ventures-landing
docker rm mifi-ventures-landing
# Start previous version
docker run -d \
--name mifi-ventures-landing \
--restart unless-stopped \
-p 8080:80 \
registry.example.com/mifi-ventures-landing:PREVIOUS_SHA
# Verify
docker ps | grep mifi-ventures-landing
curl http://localhost:8080
```
## Monitoring
### Container Status
```bash
# Check if running
docker ps | grep mifi-ventures-landing
# View logs (last 100 lines)
docker logs --tail 100 mifi-ventures-landing
# Follow logs in real-time
docker logs -f mifi-ventures-landing
# Check resource usage
docker stats mifi-ventures-landing
```
### Nginx Access Logs
```bash
# View access logs
docker exec mifi-ventures-landing tail -f /var/log/nginx/access.log
# View error logs
docker exec mifi-ventures-landing tail -f /var/log/nginx/error.log
```
### Disk Space
```bash
# Check Docker disk usage
docker system df
# Cleanup old images (automatic in pipeline, or manual)
docker image prune -af --filter "until=72h"
# Full cleanup (careful!)
docker system prune -a --volumes
```
## Security Checklist
- [ ] SSH key is Ed25519 (not RSA)
- [ ] Private key is stored securely in Woodpecker (never in repo)
- [ ] Deploy user has minimal permissions (not root if possible)
- [ ] Registry uses authentication (not public)
- [ ] VPS firewall configured (ufw or iptables)
- [ ] Traefik handles TLS termination (if used)
- [ ] Container runs as non-root user (nginx user)
- [ ] Regular security updates (`apt update && apt upgrade`)
## Next Steps
1. Set up monitoring (Prometheus + Grafana)
2. Configure automated backups
3. Add Slack/Discord notifications to pipeline
4. Set up staging environment
5. Implement blue-green deployments for zero downtime
---
**Last Updated**: 2026-01-29
**Maintainer**: Mike Fitzpatrick

41
Dockerfile Normal file
View File

@@ -0,0 +1,41 @@
# Static site container for mifi Ventures
# Build stage: run critical CSS inlining; final stage: serve dist/ via nginx
# Stage 1: Build (critical CSS inlining + copy assets → dist/)
FROM node:20-alpine AS builder
WORKDIR /app
# Enable pnpm via Corepack
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
COPY package.json pnpm-lock.yaml* ./
RUN pnpm install --frozen-lockfile || pnpm install
COPY build.mjs ./
COPY site/ ./site/
RUN pnpm run build
# Stage 2: Serve
FROM nginx:alpine
# Copy custom nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Copy built site (dist/) from builder
COPY --from=builder /app/dist/ /usr/share/nginx/html/
# Set proper permissions
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chmod -R 755 /usr/share/nginx/html
# Expose port 80
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
# Run nginx in foreground
CMD ["nginx", "-g", "daemon off;"]

465
README.md Normal file
View File

@@ -0,0 +1,465 @@
# mifi Ventures Landing Site
A minimal, production-ready static website for mifi Ventures, LLC — a software engineering consulting business.
## 🏗️ Technology Stack
- **Frontend**: Pure semantic HTML5, modern CSS with CSS variables, minimal JavaScript
- **Server**: nginx (Alpine Linux)
- **Containerization**: Docker
- **CI/CD**: Woodpecker CI
- **Deployment**: Linode VPS
## 🎨 Features
-**Single-page design** with anchored sections
-**Responsive** and mobile-friendly
-**Light/Dark mode** via `prefers-color-scheme`
-**WCAG 2.2 AAA oriented** with strong focus states, keyboard navigation, semantic markup
-**SEO optimized** with Open Graph, Twitter Cards, JSON-LD structured data
-**Performance optimized** with nginx gzip compression and cache headers
-**Zero frameworks** — pure HTML/CSS/JS for maximum speed and simplicity
## 🚀 Local Development
This project uses **pnpm** as the package manager. After cloning, run `pnpm install` (or ensure Corepack is enabled so `pnpm` is available).
| Command | Description |
|---------|-------------|
| `pnpm install` | Install dependencies |
| `pnpm run dev` | Serve `site/` at http://localhost:3000 with **live reload** (watcher) |
| `pnpm run build` | Copy `site/``dist/` and inline critical CSS in `index.html` |
| `pnpm run preview` | Serve built `dist/` to test production output |
### Option 1: pnpm dev (recommended for editing)
From the project root:
```bash
pnpm run dev
```
Opens http://localhost:3000 with live reload when you change files in `site/`.
### Option 2: Other local servers (quick start)
Open `site/index.html` directly in a browser, or use a simple HTTP server:
```bash
# Python 3
cd site
python3 -m http.server 8000
# Node (if you prefer not to use pnpm dev)
cd site
pnpm exec serve .
# PHP
cd site
php -S localhost:8000
```
Then visit the URL shown (e.g. `http://localhost:8000`).
### Option 3: Dev Container
Open the project in a dev container for a consistent local environment:
1. **Open in Cursor or VS Code** with the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension installed.
2. **Reopen in Container**: Command Palette (`Cmd/Ctrl+Shift+P`) → **Dev Containers: Reopen in Container**.
3. Wait for the container to build and start.
**Inside the container**, run:
```bash
pnpm install
pnpm run dev
```
The site is served at **http://localhost:3000** with live reload (port forwarded automatically).
### Option 4: Docker (Production-like Test)
To test the production nginx image locally (same as deployed):
```bash
docker build -t mifi-ventures-landing .
docker run -d -p 8080:80 --name mifi-ventures-landing mifi-ventures-landing
```
Then visit: `http://localhost:8080`. Stop with `docker stop mifi-ventures-landing && docker rm mifi-ventures-landing`.
## 📝 Content Updates
The HTML file includes an editable constants block at the top for easy updates:
```html
<!--
EDITABLE CONSTANTS:
- DOMAIN
- ORG_NAME
- PRINCIPAL_NAME
- CAL_LINK
- RESUME_PATH
- LINKEDIN_URL
- GITHUB_URL
-->
```
Update these values directly in `site/index.html` to modify:
- Company information
- Calendar booking link
- Social media links
- Resume file path
## 🗂️ Project Structure
```
mifi-ventures-landing/
├── .devcontainer/ # Dev container for local development
│ ├── devcontainer.json # Dev container config (port 3000, extensions)
│ └── Dockerfile # Dev container image (Node + serve)
├── .woodpecker.yml # CI/CD pipeline configuration
├── Dockerfile # Production container (nginx:alpine)
├── nginx.conf # nginx web server configuration
├── README.md # This file
├── .gitignore # Git ignore rules
└── site/ # Static website files
├── index.html # Main HTML file
├── styles.css # CSS styles (light/dark mode)
├── script.js # Minimal JavaScript (dynamic year)
├── robots.txt # Search engine directives
├── favicon.svg # Site favicon
└── assets/
├── resume.pdf # Resume download (placeholder)
└── logos/ # Company logo SVGs
├── atlassian.svg
├── tjx.svg
├── cargurus.svg
├── timberland.svg
└── mfa-boston.svg
```
## 🚢 CI/CD Deployment (Woodpecker + Gitea)
> 📖 **Full deployment guide**: See [DEPLOYMENT.md](DEPLOYMENT.md) for step-by-step setup instructions, troubleshooting, and examples.
### Pipeline Overview
The `.woodpecker.yml` pipeline automates deployment on push to `main`:
1. **Build** — Builds Docker image tagged with commit SHA + `latest`
2. **Push** — Pushes images to private Docker registry
3. **Deploy** — SSH to Linode VPS, pulls latest image, restarts container with health checks
### Required Configuration
#### Secrets (Configure in Woodpecker UI)
Navigate to your repository → Settings → Secrets and add:
| Secret Name | Description | Example |
|-------------|-------------|---------|
| `registry_password` | Docker registry password or token | `dckr_pat_xxxxx` |
| `deploy_host` | Linode VPS hostname or IP | `vps.example.com` or `192.0.2.1` |
| `deploy_username` | SSH username | `deploy` or `root` |
| `deploy_ssh_key` | Private SSH key (multi-line) | `-----BEGIN OPENSSH PRIVATE KEY-----...` |
| `deploy_port` | SSH port | `22` (default) |
**Generate SSH key for deployment:**
```bash
ssh-keygen -t ed25519 -C "woodpecker-deploy" -f ~/.ssh/woodpecker_deploy
# Add public key to server: ssh-copy-id -i ~/.ssh/woodpecker_deploy.pub user@host
# Copy private key content to Woodpecker secret
```
#### Environment Variables (Configure in Woodpecker)
Set these as repository or organization-level variables:
| Variable | Description | Example |
|----------|-------------|---------|
| `REGISTRY_URL` | Docker registry base URL | `registry.example.com` |
| `REGISTRY_REPO` | Full image repository path | `registry.example.com/mifi-ventures-landing` |
| `REGISTRY_USERNAME` | Registry username | `myusername` |
| `CONTAINER_NAME` | Container name on server | `mifi-ventures-landing` |
| `APP_PORT` | Host port to expose | `8080` (or `80` if direct) |
#### Example Configuration
**In Woodpecker UI (Repository Settings):**
```yaml
# Secrets (Values tab)
registry_password: "your-registry-token"
deploy_host: "123.45.67.89"
deploy_username: "deploy"
deploy_ssh_key: |
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
...
-----END OPENSSH PRIVATE KEY-----
deploy_port: "22"
# Environment Variables (Variables tab)
REGISTRY_URL: "registry.example.com"
REGISTRY_REPO: "registry.example.com/mifi-ventures-landing"
REGISTRY_USERNAME: "myuser"
CONTAINER_NAME: "mifi-ventures-landing"
APP_PORT: "8080"
```
### Pipeline Features
-**Deterministic builds** — Commit SHA tagging ensures reproducibility
-**Fail-fast** — Exits immediately on any error (`set -e`)
-**Health checks** — Verifies container starts and responds before completing
-**Automatic cleanup** — Prunes old images older than 72 hours
-**Zero-downtime** — Old container runs until new one is healthy
-**Detailed logging** — Clear output at each stage
### Troubleshooting
**Build fails:**
```bash
# Check Dockerfile syntax
docker build -t test .
# Verify files are present
ls -la site/
```
**Push fails:**
```bash
# Test registry login locally
echo "PASSWORD" | docker login registry.example.com -u username --password-stdin
# Verify registry URL and credentials
```
**Deploy fails:**
```bash
# Test SSH connection
ssh -i ~/.ssh/key user@host "docker ps"
# Check if Docker is installed on server
ssh user@host "docker --version"
# Verify environment variables are passed
# Check Woodpecker build logs for "REGISTRY_URL" values
```
**Container fails health check:**
```bash
# SSH to server and check logs
ssh user@host "docker logs mifi-ventures-landing"
# Check if port is already in use
ssh user@host "netstat -tulpn | grep :80"
```
### Manual Deployment
For emergency deployments or testing:
```bash
# Build and push manually
docker build -t registry.example.com/mifi-ventures-landing:latest .
docker push registry.example.com/mifi-ventures-landing:latest
# Deploy manually via SSH
ssh user@host << 'EOF'
docker pull registry.example.com/mifi-ventures-landing:latest
docker stop mifi-ventures-landing || true
docker rm mifi-ventures-landing || true
docker run -d \
--name mifi-ventures-landing \
--restart unless-stopped \
-p 8080:80 \
registry.example.com/mifi-ventures-landing:latest
EOF
```
## 🔧 nginx Configuration
The custom `nginx.conf` provides optimized static file delivery:
### Caching Strategy
- **HTML files**: `no-cache, must-revalidate` (always fresh from server)
- **CSS/JS**: `max-age=31536000, immutable` (1 year, content-addressed)
- **Images** (JPG, PNG, WebP, AVIF): `max-age=2592000` (30 days)
- **SVG images**: `max-age=2592000` (30 days)
- **Fonts**: `max-age=31536000, immutable` (1 year)
- **Documents** (PDF): `max-age=2592000` (30 days)
- **robots.txt**: `max-age=86400` (1 day)
- **favicon.svg**: `max-age=2592000` (30 days)
### Gzip Compression
Enabled for all text-based content with compression level 6:
- HTML, CSS, JavaScript
- JSON, XML
- SVG images
Minimum size: 256 bytes (avoids compressing tiny files)
### Other Features
- **Server tokens**: Disabled for security
- **Access logs**: Disabled for static assets (performance)
- **Hidden files**: Denied (.git, .env, etc.)
- **404 handling**: Falls back to index.html
- **Health check**: Available on port 80 for container orchestration
### Security Headers
**Note**: Security headers (CSP, HSTS, X-Frame-Options, etc.) are handled upstream by Traefik and are NOT included in this nginx configuration to avoid duplication.
## 🎯 SEO & Performance
### Current Optimizations
#### On-Page SEO
- **Title tag**: Includes business name, service, and location
- **Meta description**: Natural, compelling copy (155 characters) emphasizing Boston location and services
- **Canonical URL**: Set to `https://mifi.ventures/` to prevent duplicate content issues
- **Robots meta**: `index, follow` with enhanced directives for snippet and image preview control
- **Semantic HTML5**: Proper heading hierarchy (single H1, logical H2 structure)
- **Geographic metadata**: Boston, MA coordinates for local SEO
- **Author attribution**: Mike Fitzpatrick properly credited
- **Language declaration**: `lang="en-US"` for US English
#### Social Media Share Previews
- **Open Graph tags**: Complete OG implementation for Facebook, LinkedIn
- Site name, title, description, URL, image
- Image dimensions (1200x630px) and alt text
- Locale set to `en_US`
- **Twitter Cards**: `summary_large_image` card with full metadata
- Creator and site handles (update with actual Twitter)
- Image with alt text for accessibility
- **Theme colors**: Dynamic based on light/dark mode preference
#### Structured Data (JSON-LD)
Comprehensive @graph structure with interconnected entities:
- **Organization** (`#organization`): mifi Ventures, LLC with Boston address, geo coordinates, and service catalog
- **Person** (`#principal`): Mike Fitzpatrick as "Principal Software Engineer and Architect" with worksFor relationship and knowsAbout expertise areas
- **WebSite** (`#website`): Site-level metadata with ReserveAction pointing to Cal.com scheduling
- **WebPage** (`#webpage`): Page-level metadata with inLanguage and primaryImageOfPage
- **OfferCatalog** (`#services`): Six service offerings aligned with "What We Do" section
- **LinkedIn profile**: https://linkedin.com/in/the-mifi
- **No email or phone**: Complies with privacy requirements
#### Technical SEO
- **robots.txt**: Properly configured for full site crawling
- **Lazy loading**: Images load on-demand for performance
- **Minimal JavaScript**: Only essential scripts (copyright year)
- **System font stack**: No web font loading delays
- **Clean URLs**: No parameters or session IDs
- **Mobile-friendly**: Responsive design, passes mobile-usability tests
- **Fast loading**: Optimized assets, gzip compression, cache headers
### Action Items
Before launch, update these placeholders:
1. Create OG image: 1200x630px PNG at `/assets/og-image.png`
2. Update Twitter handles in meta tags (lines 57-58) if you have a Twitter presence
3. Update GitHub URL in footer and constants if you want to include it (currently optional)
### SEO Testing & Validation
Before going live, validate with these tools:
- **Google Search Console**: Submit site, monitor indexing
- **Rich Results Test**: Verify JSON-LD structured data
- **Facebook Sharing Debugger**: Test OG tags preview
- **Twitter Card Validator**: Test Twitter card appearance
- **Lighthouse SEO Audit**: Aim for 100/100 score
- **Mobile-Friendly Test**: Ensure mobile usability
- **PageSpeed Insights**: Check Core Web Vitals
Key metrics to monitor post-launch:
- Indexing status in Google Search Console
- Click-through rates (CTR) from search results
- Share engagement on social platforms
- Core Web Vitals (LCP, FID, CLS)
- Page load times and performance scores
### Future Enhancements
- Add sitemap.xml for better crawl efficiency
- Implement Content Security Policy (CSP) headers
- Add preconnect hints for external resources (Cal.com)
- Consider blog or case studies for content marketing
- Add FAQ schema markup if adding FAQ section
- Consider breadcrumb schema for better SERP display
- Local business listings (Google Business Profile)
- Schema markup for reviews/testimonials if applicable
## ♿ Accessibility
This site is built to meet **WCAG 2.2 Level AAA** standards wherever applicable to a static informational website.
### Implemented Features
#### Keyboard Navigation
- **Skip link**: Visible on keyboard focus, jumps directly to main content (`#main`)
- **Logical tab order**: All interactive elements follow natural reading order
- **No keyboard traps**: Users can navigate through and exit all interactive regions
- **Focus indicators**: 4px high-contrast outlines with 4px offset and subtle glow on all focusable elements
- **Focus never removed**: Outline styles are enforced with `!important` to prevent accidental removal
#### Semantic Structure
- **Proper landmarks**: `<header>`, `<main>`, `<footer>`, and `<nav>` for clear page regions
- **Single H1**: One `<h1>` element ("mifi Ventures") with logical H2 nesting for all sections
- **ARIA labelledby**: All sections connected to their headings via `aria-labelledby` attributes
- **Language declaration**: `lang="en-US"` attribute on `<html>` element
#### Visual & Color
- **AAA contrast ratios**: All text meets AAA standards (7:1 for normal text, 4.5:1 for large text)
- Light mode: `#1a1a1a` text on `#ffffff` background (16.1:1 ratio)
- Dark mode: `#f5f5f5` text on `#0a0a0a` background (18.4:1 ratio)
- **Color independence**: No information conveyed by color alone
- **High contrast mode**: Enhanced borders, outlines, and contrast for users with `prefers-contrast: high`
#### Interactive Elements
- **Adequate touch targets**: All buttons and links meet minimum 44x44px size (AAA requirement)
- **Descriptive link text**: All links have meaningful text or enhanced ARIA labels
- **External link warnings**: Links opening in new tabs clearly labeled "(opens in new tab)"
- **Button spacing**: Generous gaps between CTAs prevent accidental activation
#### Motion & Animation
- **Respects `prefers-reduced-motion`**: All animations and transforms disabled when users prefer reduced motion
- **Safe default animations**: Subtle hover effects that don't cause vestibular issues
- **No auto-playing content**: No carousels, videos, or content that moves automatically
#### Images & Media
- **Descriptive alt text**: All images have clear, concise alternative text
- **Text fallbacks**: Logo strip includes visually-hidden text that appears if images fail
- **Mobile text list**: On small screens, logo images replaced with accessible text list
- **Decorative images marked**: Images that don't convey content use appropriate ARIA attributes
#### Screen Reader Support
- **Clear labels**: All form controls, buttons, and navigation have proper labels
- **ARIA landmarks**: Supplementary ARIA roles for enhanced screen reader navigation
- **Visually-hidden content**: Important text available to screen readers but hidden visually where appropriate
- **Logical reading order**: Content structure follows visual hierarchy
### Testing Recommendations
For best results, test with:
- **Keyboard only**: Tab through entire page without mouse
- **Screen readers**: NVDA (Windows), JAWS (Windows), VoiceOver (macOS/iOS), TalkBack (Android)
- **Browser extensions**: axe DevTools, WAVE, Lighthouse accessibility audit
- **High contrast mode**: Windows High Contrast, macOS Increase Contrast
- **Zoom**: Test at 200% and 400% zoom levels
- **Reduced motion**: Enable in OS settings and verify animations stop
### Known Limitations
- Logo images use CSS filters for dark mode adaptation (works well but may not be perfect for all logos)
- External link icons not implemented (relies on ARIA labels and "opens in new tab" text)
- No live regions or dynamic content requiring ARIA live announcements
## 📄 License
© 2026 mifi Ventures, LLC. All rights reserved.
## 📞 Contact
For inquiries, visit [mifi.ventures](https://mifi.ventures/) or [schedule a call](https://cal.mifi.ventures/the-mifi).

164
SEO-CHECKLIST.md Normal file
View File

@@ -0,0 +1,164 @@
# SEO Pre-Launch Checklist
Use this checklist before deploying to production.
## ✅ Meta Tags
- [ ] Title tag is descriptive and includes location (under 60 characters)
- [ ] Meta description is compelling and natural (150-160 characters)
- [ ] Canonical URL is set to `https://mifi.ventures/`
- [ ] Robots meta allows indexing (`index, follow`)
- [ ] Language is declared (`lang="en-US"`)
- [ ] Author meta tag is present
- [ ] Geographic meta tags include Boston coordinates
## ✅ Open Graph Tags
- [ ] `og:type` is set to "website"
- [ ] `og:url` matches canonical URL
- [ ] `og:site_name` is "mifi Ventures"
- [ ] `og:title` is descriptive and compelling
- [ ] `og:description` matches meta description
- [ ] `og:image` points to actual 1200x630px image
- [ ] `og:image:width` and `og:image:height` are specified
- [ ] `og:image:alt` provides context
- [ ] `og:locale` is set to "en_US"
## ✅ Twitter Cards
- [ ] Card type is `summary_large_image`
- [ ] `twitter:title` matches OG title
- [ ] `twitter:description` matches OG description
- [ ] `twitter:image` matches OG image
- [ ] `twitter:image:alt` is descriptive
- [ ] `twitter:creator` handle is updated (if applicable)
- [ ] `twitter:site` handle is updated (if applicable)
## ✅ Structured Data (JSON-LD)
- [ ] Uses @graph structure with stable @id anchors
- [ ] **Organization** entity (`#organization`) is complete
- [ ] Legal name matches LLC
- [ ] Boston address is accurate
- [ ] Geographic coordinates are correct
- [ ] hasOfferCatalog links to services
- [ ] **Person** entity (`#principal`) is complete
- [ ] Name and title are accurate
- [ ] worksFor links to organization
- [ ] knowsAbout lists relevant expertise
- [ ] LinkedIn URL is correct (https://linkedin.com/in/the-mifi)
- [ ] **WebSite** entity (`#website`) is complete
- [ ] potentialAction/ReserveAction points to Cal.com
- [ ] Action name is descriptive
- [ ] **WebPage** entity (`#webpage`) is complete
- [ ] isPartOf links to website
- [ ] primaryImageOfPage is set
- [ ] inLanguage is "en-US"
- [ ] **OfferCatalog** entity (`#services`) is complete
- [ ] All 6 services from "What We Do" are listed
- [ ] Descriptions match page copy
- [ ] No email or phone anywhere in JSON-LD
- [ ] All @id values use mifi.ventures domain
## ✅ Content & Copy
- [ ] No keyword stuffing in any content
- [ ] Copy sounds natural and professional
- [ ] Boston location is mentioned naturally
- [ ] Services are clearly described
- [ ] No email or phone numbers in meta tags
- [ ] All text is grammatically correct
- [ ] Tone matches brand (professional, technical, credible)
## ✅ Technical SEO
- [ ] `robots.txt` exists and allows crawling
- [ ] All images have descriptive alt text
- [ ] Heading hierarchy is correct (one H1, logical H2s)
- [ ] Links have descriptive anchor text
- [ ] No broken links (404s)
- [ ] HTTPS is enforced (if applicable)
- [ ] Mobile-responsive design
- [ ] Fast page load (< 3 seconds)
- [ ] favicon.svg is present and loads
## ✅ Assets
- [ ] OG image created (1200x630px, under 1MB)
- [ ] OG image uploaded to `/assets/og-image.png`
- [ ] OG image looks good when scaled down
- [ ] OG image includes readable text
- [ ] Resume PDF is uploaded (if publishing)
- [ ] Company logos are actual logos (not placeholders)
## ✅ External Links
- [ ] LinkedIn URL updated in constants and JSON-LD
- [ ] GitHub URL updated in constants and JSON-LD
- [ ] Cal.com link is correct and working
- [ ] All external links open in new tab with `rel="noopener noreferrer"`
## ✅ Pre-Launch Testing
Run these tests and fix any issues:
### Google Tools
- [ ] Google Rich Results Test: [https://search.google.com/test/rich-results](https://search.google.com/test/rich-results)
- Should show valid ProfessionalService schema
- [ ] Google Mobile-Friendly Test: [https://search.google.com/test/mobile-friendly](https://search.google.com/test/mobile-friendly)
- Should pass without issues
- [ ] Google Lighthouse (in Chrome DevTools)
- SEO score: 100/100
- Performance: 90+/100
- Accessibility: 100/100
- Best Practices: 100/100
### Social Media Preview Tools
- [ ] Facebook Sharing Debugger: [https://developers.facebook.com/tools/debug/](https://developers.facebook.com/tools/debug/)
- Preview looks correct
- Image loads properly
- [ ] Twitter Card Validator: [https://cards-dev.twitter.com/validator](https://cards-dev.twitter.com/validator)
- Card preview looks good
- Image displays correctly
- [ ] LinkedIn Post Inspector: [https://www.linkedin.com/post-inspector/](https://www.linkedin.com/post-inspector/)
- Preview is accurate
### SEO Validators
- [ ] Schema.org Validator: [https://validator.schema.org/](https://validator.schema.org/)
- No errors in JSON-LD
- [ ] W3C HTML Validator: [https://validator.w3.org/](https://validator.w3.org/)
- No critical HTML errors
### Manual Checks
- [ ] View source and verify all meta tags are present
- [ ] Test share preview by sharing URL on social media
- [ ] Check that `robots.txt` is accessible at `https://mifi.ventures/robots.txt`
- [ ] Verify canonical URL in browser DevTools
- [ ] Test page on mobile device
- [ ] Verify skip link appears on Tab key press
## ✅ Post-Launch
After going live:
- [ ] Submit site to Google Search Console
- [ ] Submit site to Bing Webmaster Tools
- [ ] Create and submit sitemap.xml (future enhancement)
- [ ] Share on social media to test previews
- [ ] Monitor Google Search Console for indexing issues
- [ ] Set up Google Analytics (if desired)
- [ ] Monitor Core Web Vitals
- [ ] Check search appearance after 1-2 weeks
## 📝 Notes
Record any issues or observations during testing:
```
[Add your notes here]
```
---
**Last Updated**: [Date]
**Reviewed By**: [Name]

48
build.mjs Normal file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env node
/**
* Build script: copies entire site/ to dist/, then inlines critical CSS in dist/index.html.
* Uses Critters (no headless browser) so the build runs in any environment.
* Dockerfile copies only dist/ — single source of truth for the built site.
*/
import Critters from "critters";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname);
const SITE = path.join(ROOT, "site");
const DIST = path.join(ROOT, "dist");
async function main() {
// Copy entire site structure to dist
if (fs.existsSync(DIST)) {
fs.rmSync(DIST, { recursive: true });
}
fs.cpSync(SITE, DIST, { recursive: true });
console.log("✓ Copied site/ → dist/");
// Inline critical CSS in dist/index.html (Critters reads/writes relative to path)
const indexPath = path.join(DIST, "index.html");
let html = fs.readFileSync(indexPath, "utf8");
const critters = new Critters({
path: DIST,
preload: "swap",
noscriptFallback: true,
pruneSource: false,
logLevel: "warn",
});
html = await critters.process(html);
fs.writeFileSync(indexPath, html, "utf8");
console.log("✓ Critical CSS inlined → dist/index.html");
console.log("Build complete. Output: dist/");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

BIN
logos/avatar.af Normal file

Binary file not shown.

Binary file not shown.

BIN
logos/favicon.af Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

589
logos/wordmark.html Normal file
View File

@@ -0,0 +1,589 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>mifi Ventures — Option 1 vs Plus Jakarta Sans (600/700)</title>
<!-- Preconnect -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Fonts: Inter, Fraunces, Plus Jakarta Sans -->
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<style>
:root {
--bg: #0b0b0f;
--card: rgba(255, 255, 255, 0.06);
--border: rgba(255, 255, 255, 0.1);
--text: rgba(255, 255, 255, 0.92);
--muted: rgba(255, 255, 255, 0.68);
--muted2: rgba(255, 255, 255, 0.52);
--shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: radial-gradient(
1200px 700px at 20% 0%,
rgba(120, 90, 255, 0.15),
transparent 55%
),
radial-gradient(
900px 600px at 80% 20%,
rgba(0, 210, 255, 0.1),
transparent 55%
),
var(--bg);
color: var(--text);
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto,
"Helvetica Neue", Arial, sans-serif;
padding: 28px;
}
header {
max-width: 1120px;
margin: 0 auto 18px;
display: grid;
gap: 10px;
}
h1 {
margin: 0;
font-size: 18px;
font-weight: 600;
letter-spacing: -0.01em;
}
p.lede {
margin: 0;
color: var(--muted);
font-size: 14px;
line-height: 1.5;
max-width: 90ch;
}
.grid {
max-width: 1120px;
margin: 18px auto 0;
display: grid;
grid-template-columns: 1fr;
gap: 14px;
}
@media (min-width: 980px) {
.grid {
grid-template-columns: 1fr 1fr 1fr;
}
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 16px 16px 18px;
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
min-height: 520px;
display: grid;
grid-template-rows: auto auto 1fr auto;
gap: 12px;
}
.card h2 {
margin: 0;
font-size: 14px;
font-weight: 600;
letter-spacing: -0.01em;
}
.tag {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: var(--muted);
font-size: 12px;
}
.pill {
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 999px;
color: var(--muted2);
font-size: 11px;
}
.mock {
border: 1px dashed rgba(255, 255, 255, 0.18);
border-radius: 14px;
padding: 16px;
display: grid;
gap: 16px;
align-content: start;
}
.row {
display: grid;
gap: 10px;
}
.label {
font-size: 11px;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted2);
}
.surface {
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 14px;
background: rgba(0, 0, 0, 0.18);
display: grid;
gap: 10px;
}
.surface.light {
background: rgba(255, 255, 255, 0.92);
color: #0b0b0f;
border-color: rgba(0, 0, 0, 0.08);
}
.hint {
font-size: 12px;
opacity: 0.65;
}
/* Wordmark base */
.wordmark {
display: inline-flex;
align-items: baseline;
gap: 0.22em; /* slightly tighter than default word space */
line-height: 1;
white-space: nowrap;
font-variant-ligatures: common-ligatures; /* keep ligatures on */
font-feature-settings: "liga" 1, "clig" 1;
}
.wordmark .mifi {
text-transform: lowercase;
}
/* Size scale */
.size-hero {
font-size: 52px;
}
.size-header {
font-size: 30px;
}
.size-signature {
font-size: 18px;
}
.size-micro {
font-size: 16px;
}
/* Tracking helpers */
.track-tight {
letter-spacing: -0.015em;
} /* ~ -1.5% */
.track-tight2 {
letter-spacing: -0.02em;
} /* ~ -2% */
.track-loose {
letter-spacing: 0.03em;
} /* ~ +3% */
.track-loose2 {
letter-spacing: 0.045em;
} /* ~ +4.5% */
/* ---------------------------------
Option 1 (old): Inter + Fraunces
--------------------------------- */
.opt1 .mifi {
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial,
sans-serif;
font-weight: 600;
}
.opt1 .ventures {
font-family: Fraunces, Georgia, "Times New Roman", Times, serif;
font-weight: 500;
font-style: normal;
font-variation-settings: "opsz" 36;
}
/* ---------------------------------
Jakarta 600: Plus Jakarta + Fraunces
--------------------------------- */
.jak600 .mifi {
font-family: "Plus Jakarta Sans", Inter, system-ui, -apple-system,
Segoe UI, Roboto, Arial, sans-serif;
font-weight: 600;
}
.jak600 .ventures {
font-family: Fraunces, Georgia, "Times New Roman", Times, serif;
font-weight: 500;
font-style: normal;
font-variation-settings: "opsz" 36;
}
/* ---------------------------------
Jakarta 700: Plus Jakarta + Fraunces
--------------------------------- */
.jak700 .mifi {
font-family: "Plus Jakarta Sans", Inter, system-ui, -apple-system,
Segoe UI, Roboto, Arial, sans-serif;
font-weight: 700;
}
.jak700 .ventures {
font-family: Fraunces, Georgia, "Times New Roman", Times, serif;
font-weight: 500;
font-style: normal;
font-variation-settings: "opsz" 36;
}
/* Footer notes */
.spec {
margin: 0;
padding-left: 16px;
color: var(--muted);
font-size: 12px;
line-height: 1.45;
}
.spec li {
margin: 4px 0;
}
code.k {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
font-size: 11px;
color: rgba(255, 255, 255, 0.75);
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 2px 6px;
border-radius: 8px;
}
.surface.light code.k {
color: rgba(0, 0, 0, 0.65);
background: rgba(0, 0, 0, 0.04);
border-color: rgba(0, 0, 0, 0.08);
}
.divider {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 8px 0 0;
}
</style>
</head>
<body>
<header>
<h1>mifi Ventures — Option 1 vs Plus Jakarta Sans (600/700)</h1>
<p class="lede">
Three side-by-side mockups. Each card shows:
<strong>mifi alone</strong> and <strong>mifi Ventures</strong> together,
at hero/header/signature/micro sizes, plus a light-surface invoice
preview. Ligatures are enabled to let Plus Jakarta do its thing with
<code class="k">fi</code>.
</p>
</header>
<main class="grid">
<!-- OLD OPTION 1 -->
<section class="card">
<div>
<h2>Old Option 1 — Inter 600 + Fraunces 500</h2>
<div class="tag">
<span class="pill">Baseline</span>
<span class="pill">Clean</span>
<span class="pill">Familiar</span>
</div>
</div>
<div class="mock opt1">
<div class="row">
<div class="label">Hero — mifi alone</div>
<div class="surface">
<div class="wordmark size-hero">
<span class="mifi track-tight2">mifi</span>
</div>
<div class="hint">Solo mark test</div>
</div>
</div>
<div class="row">
<div class="label">Hero — full lockup</div>
<div class="surface">
<div class="wordmark size-hero">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Landing / hero</div>
</div>
</div>
<div class="row">
<div class="label">Header</div>
<div class="surface">
<div class="wordmark size-header">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Nav / sticky header</div>
</div>
</div>
<div class="row">
<div class="label">Invoice (light)</div>
<div class="surface light">
<div class="wordmark size-header">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">PDF invoice header</div>
</div>
</div>
<div class="row">
<div class="label">Email signature</div>
<div class="surface">
<div class="wordmark size-signature">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Signature legibility</div>
</div>
</div>
<div class="row">
<div class="label">Micro — mifi alone</div>
<div class="surface">
<div class="wordmark size-micro">
<span class="mifi track-tight2">mifi</span>
</div>
<div class="hint">Favicon-ish / tiny footer</div>
</div>
</div>
</div>
<ul class="spec">
<li><code class="k">mifi</code> Inter 600, tracking 2%</li>
<li>
<code class="k">Ventures</code> Fraunces 500, tracking +4.5%, opsz
~36
</li>
<li>Ligatures ON (Inter wont change; Jakarta will)</li>
</ul>
</section>
<!-- JAKARTA 600 -->
<section class="card">
<div>
<h2>Jakarta Variant — Plus Jakarta 600 + Fraunces 500</h2>
<div class="tag">
<span class="pill">More character</span>
<span class="pill">Technical-warm</span>
<span class="pill">Ligature magic</span>
</div>
</div>
<div class="mock jak600">
<div class="row">
<div class="label">Hero — mifi alone</div>
<div class="surface">
<div class="wordmark size-hero">
<span class="mifi track-tight2">mifi</span>
</div>
<div class="hint">
Solo mark test (watch the <code class="k">fi</code>)
</div>
</div>
</div>
<div class="row">
<div class="label">Hero — full lockup</div>
<div class="surface">
<div class="wordmark size-hero">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Landing / hero</div>
</div>
</div>
<div class="row">
<div class="label">Header</div>
<div class="surface">
<div class="wordmark size-header">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Nav / sticky header</div>
</div>
</div>
<div class="row">
<div class="label">Invoice (light)</div>
<div class="surface light">
<div class="wordmark size-header">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">PDF invoice header</div>
</div>
</div>
<div class="row">
<div class="label">Email signature</div>
<div class="surface">
<div class="wordmark size-signature">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Signature legibility</div>
</div>
</div>
<div class="row">
<div class="label">Micro — mifi alone</div>
<div class="surface">
<div class="wordmark size-micro">
<span class="mifi track-tight2">mifi</span>
</div>
<div class="hint">Favicon-ish / tiny footer</div>
</div>
</div>
</div>
<ul class="spec">
<li>
<code class="k">mifi</code> Plus Jakarta Sans 600, tracking 2%,
ligatures ON
</li>
<li>
<code class="k">Ventures</code> Fraunces 500, tracking +4.5%, opsz
~36
</li>
<li>Same lockup mechanics as Option 1 for apples-to-apples</li>
</ul>
</section>
<!-- JAKARTA 700 -->
<section class="card">
<div>
<h2>Jakarta Variant — Plus Jakarta 700 + Fraunces 500</h2>
<div class="tag">
<span class="pill">More authority</span>
<span class="pill">Sharper silhouette</span>
<span class="pill">Still friendly</span>
</div>
</div>
<div class="mock jak700">
<div class="row">
<div class="label">Hero — mifi alone</div>
<div class="surface">
<div class="wordmark size-hero">
<span class="mifi track-tight2">mifi</span>
</div>
<div class="hint">Solo mark test (700 weight)</div>
</div>
</div>
<div class="row">
<div class="label">Hero — full lockup</div>
<div class="surface">
<div class="wordmark size-hero">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Landing / hero</div>
</div>
</div>
<div class="row">
<div class="label">Header</div>
<div class="surface">
<div class="wordmark size-header">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Nav / sticky header</div>
</div>
</div>
<div class="row">
<div class="label">Invoice (light)</div>
<div class="surface light">
<div class="wordmark size-header">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">PDF invoice header</div>
</div>
</div>
<div class="row">
<div class="label">Email signature</div>
<div class="surface">
<div class="wordmark size-signature">
<span class="mifi track-tight2">mifi</span>
<span class="ventures track-loose2">Ventures</span>
</div>
<div class="hint">Signature legibility</div>
</div>
</div>
<div class="row">
<div class="label">Micro — mifi alone</div>
<div class="surface">
<div class="wordmark size-micro">
<span class="mifi track-tight2">mifi</span>
</div>
<div class="hint">Favicon-ish / tiny footer</div>
</div>
</div>
</div>
<ul class="spec">
<li>
<code class="k">mifi</code> Plus Jakarta Sans 700, tracking 2%,
ligatures ON
</li>
<li>
<code class="k">Ventures</code> Fraunces 500, tracking +4.5%, opsz
~36
</li>
<li>700 may feel more “mark-like” vs “UI-like”</li>
</ul>
</section>
</main>
<div
style="
max-width: 1120px;
margin: 18px auto 0;
color: rgba(255, 255, 255, 0.62);
font-size: 12px;
line-height: 1.55;
"
>
<div class="divider"></div>
<p style="margin: 12px 0 0">
Tip: If you want to exaggerate the Plus Jakarta ligature effect for
testing, temporarily set the tracking on <code class="k">mifi</code> to
<code class="k">-0.01em</code> (less tight) and bump the hero size to
<code class="k">60px</code>. Then revert once you pick the winner.
</p>
</div>
</body>
</html>

121
nginx.conf Normal file
View File

@@ -0,0 +1,121 @@
# Minimal nginx configuration for static site delivery
# Security headers are handled upstream by Traefik
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# Gzip compression for text-based assets
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types
text/html
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/rss+xml
application/atom+xml
image/svg+xml;
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# HTML files: no-cache (always revalidate)
location ~ \.html$ {
add_header Cache-Control "no-cache, must-revalidate";
expires 0;
}
# CSS and JavaScript: long cache with immutable
location ~* \.(css|js)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# Images: long cache (30 days)
location ~* \.(jpg|jpeg|png|gif|webp|avif)$ {
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# SVG images: long cache (30 days)
location ~* \.svg$ {
add_header Cache-Control "public, max-age=2592000";
add_header Content-Type image/svg+xml;
access_log off;
}
# Fonts: long cache with immutable
location ~* \.(woff|woff2|ttf|otf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# Documents: medium cache (30 days)
location ~* \.(pdf|doc|docx)$ {
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# robots.txt: short cache (1 day)
location = /robots.txt {
add_header Cache-Control "public, max-age=86400";
access_log off;
}
# favicon: long cache (30 days)
location = /favicon.svg {
add_header Cache-Control "public, max-age=2592000";
add_header Content-Type image/svg+xml;
access_log off;
}
# Default location
location / {
try_files $uri $uri/ /index.html;
}
# Deny access to hidden files (.git, .env, etc.)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 404 falls back to index.html for SPA-style routing
error_page 404 /index.html;
}
}

16
package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "mifi-ventures-landing",
"version": "1.0.0",
"private": true,
"packageManager": "pnpm@9.15.0",
"description": "mifi Ventures landing site — static build with critical CSS inlining",
"scripts": {
"build": "node build.mjs",
"preview": "npx serve dist",
"dev": "live-server site --port=3000 --open=/"
},
"devDependencies": {
"critters": "^0.0.24",
"live-server": "^1.2.2"
}
}

1590
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
site/assets/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 2134 2134" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M2133.333,400l0,1333.333c0,220.766 -179.234,400 -400,400l-1333.333,0c-220.766,0 -400,-179.234 -400,-400l0,-1333.333c0,-220.766 179.234,-400 400,-400l1333.333,0c220.766,0 400,179.234 400,400Zm-592.367,1113.667l157.5,0l0,-652.2l-157.5,0l0,652.2Zm0,-726l157.5,0l0,-168l-157.5,0l0,168Zm-574.116,163.919c-14.286,-24.594 -34.264,-45.584 -59.934,-62.969c-40.9,-27.7 -87.95,-41.55 -141.15,-41.55c-48.8,0 -91.35,11.6 -127.65,34.8c-23.368,14.935 -41.618,34.843 -54.75,59.723l0,-80.123l-148.5,0l0,652.2l157.5,0l0,-382.8c0,-28.8 5.25,-53.55 15.75,-74.25c10.5,-20.7 25.25,-36.8 44.25,-48.3c19,-11.5 41.1,-17.25 66.3,-17.25c25.8,0 48,5.75 66.6,17.25c18.6,11.5 33.1,27.55 43.5,48.15c10.4,20.6 15.6,45.4 15.6,74.4l0,382.8l157.5,0l0,-382.8c0,-28.8 5.25,-53.55 15.75,-74.25c10.5,-20.7 25.3,-36.8 44.4,-48.3c19.1,-11.5 41.15,-17.25 66.15,-17.25c25.8,0 48,5.75 66.6,17.25c18.6,11.5 33.1,27.55 43.5,48.15c10.4,20.6 15.6,45.4 15.6,74.4l0,382.8l157.5,0l0,-420.3c0,-48.6 -10.55,-91.4 -31.65,-128.4c-21.1,-37 -49.85,-65.9 -86.25,-86.7c-36.4,-20.8 -77.7,-31.2 -123.9,-31.2c-52.2,0 -98.25,12.85 -138.15,38.55c-25.566,16.467 -47.088,38.457 -64.566,65.969Z" style="fill:#0b0b0f;"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 145.22149045698177 145.22149045698177" xmlns="http://www.w3.org/2000/svg" width="2500" height="2500">
<linearGradient id="a" gradientTransform="matrix(1 0 0 -1 0 228)" gradientUnits="userSpaceOnUse" x1="62.57" x2="25.03" y1="150.13" y2="85.11">
<stop offset="0" stop-color="#0052cc"/>
<stop offset="0.92" stop-color="#2684ff"/>
</linearGradient>
<g transform="matrix(1, 0, 0, 1, 0.059736, 0.123965)">
<path d="M43 67a4.14 4.14 0 0 0-5.79-.78A4.29 4.29 0 0 0 36 67.73L.45 138.85a4.25 4.25 0 0 0 1.9 5.7 4.18 4.18 0 0 0 1.9.45h49.53a4.08 4.08 0 0 0 3.8-2.35C68.27 120.57 61.79 87 43 67z" fill="url(#a)"/>
<path d="M69.13 2.28a93.82 93.82 0 0 0-5.48 92.61l23.88 47.76a4.25 4.25 0 0 0 3.8 2.35h49.52a4.24 4.24 0 0 0 4.25-4.25 4.31 4.31 0 0 0-.44-1.9L76.36 2.26a4 4 0 0 0-7.23 0z" fill="#2684ff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 885 B

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" version="1.1" viewBox="0 0 2702 571">
<defs>
<style>
.st0 {
fill: #0070b9;
}
.st1 {
fill: currentColor;
}
</style>
</defs>
<g/>
<g transform="matrix(1, 0, 0, 1, 5.624947, -0.29002)">
<path class="st0" d="M 547.55 285.79 C 545.43 393.07 550.88 477.71 510.71 515.19 C 473.23 555.36 388.6 549.9 281.31 552.03 C 174.03 549.89 89.4 555.36 51.91 515.19 C 11.75 477.71 17.19 393.08 15.07 285.79 C 17.21 178.51 11.73 93.88 51.91 56.39 C 89.39 16.22 174.02 21.68 281.31 19.55 C 388.6 21.69 473.22 16.22 510.7 56.39 C 550.88 93.87 545.41 178.51 547.54 285.79 Z M 418.08 290.18 C 418.08 261.96 403.02 237.26 380.49 223.68 C 390.11 212.81 395.95 198.52 395.95 182.86 C 395.95 148.81 368.35 121.21 334.3 121.21 L 167.32 121.21 L 167.32 367.8 L 340.47 367.8 C 383.33 367.8 418.08 333.05 418.08 290.19 Z M 212.01 157.49 L 322.87 157.49 C 338.82 157.49 351.76 170.42 351.76 186.38 C 351.76 202.34 338.83 215.27 322.87 215.27 L 212.01 215.27 Z M 212.01 256.58 L 339.36 256.58 C 357.8 256.58 372.76 271.53 372.76 289.98 C 372.76 308.43 357.81 323.37 339.36 323.37 L 212.01 323.37 Z M 184.2 409.02 C 172.78 409.02 163.52 418.28 163.52 429.7 C 163.52 441.12 172.78 450.38 184.2 450.38 L 394.19 450.38 C 406.28 450.38 415.95 440.01 414.77 427.67 C 413.75 416.92 404.1 409.02 393.3 409.02 Z"/>
<path class="st1" d="M1396.06,235.85h-38.17v-73.72h-61.94v73.72h-50.76v-73.72h-61.95v73.72h-21.3v52.28h21.3v165.62h61.95v-165.62h50.76v165.62h61.94v-165.62h38.17v-52.28ZM1040.69,228.91c-67.92,0-114.41,48.4-114.41,116.11s46.05,115.67,114.41,115.67,114.41-48.39,114.41-115.67-46.49-116.11-114.41-116.11ZM1040.69,405.63c-31.99,0-51.54-31.41-51.54-60.61s19.54-61.05,51.54-61.05,51.54,30.98,51.54,61.05-19.55,60.61-51.54,60.61ZM1500.43,228.91c-67.93,0-114.41,48.4-114.41,116.11s46.04,115.67,114.41,115.67,114.41-48.39,114.41-115.67-46.49-116.11-114.41-116.11ZM1500.43,405.63c-31.99,0-51.55-31.41-51.55-60.61s19.56-61.05,51.55-61.05,51.54,30.98,51.54,61.05-19.55,60.61-51.54,60.61ZM858.6,274.58c20.96-13.36,29.41-40.9,29.41-64.88,0-66.53-38.25-90.91-100.46-90.91h-81.16v334.94h102.48c60.8,0,106.93-29.24,106.93-95.01,0-36.94-17.75-77.03-57.19-84.15ZM771.57,171.08h8.44c27.54,0,47.08,8.88,47.08,43.06s-15.54,43.96-47.52,43.96h-8v-87.02ZM785.34,401.47h-13.77v-93.5h10.65c32.87,0,68.4,1.79,68.4,45.41s-30.2,48.09-65.28,48.09ZM2113.3,107.24c-20.37,0-36.74,16.84-36.74,36.78s16.37,36.78,36.74,36.78,36.76-16.39,36.76-36.78-16.39-36.78-36.76-36.78ZM2302.53,228.96c-22.26,0-44.09,7.57-56.96,27.16h-.91v-20.27h-61.94v217.9h61.94v-107.86c0-25.87.45-67.05,37.38-67.05s33.82,33.65,33.82,59.78v115.13h61.93v-132.85c0-50.53-16.79-91.94-75.26-91.94ZM1877.6,228.96c-25.41,0-50.39,12.91-63.68,34.28-14.16-23.16-35.41-34.28-62.45-34.28-20.07,0-41.46,9.35-53.51,26.72h-.89v-19.83h-61.95v217.9h61.95v-106.98c0-22.77-1.78-67.94,32.99-67.94,32.52,0,29.89,43.4,29.89,64.65v110.26h61.94v-106.72c0-22.58-.45-68.19,33.43-68.19,30.75,0,29.43,39.41,29.43,60.67v114.25h61.93v-135.5c0-47.43-13.71-89.29-69.09-89.29ZM2503.36,228.91c-70.55,0-105.58,51.84-105.58,117.77s41.24,113.96,108.68,113.96c45.72,0,83.88-24.45,98.96-67.91l-57.24-9.29c-8.45,17.26-21.31,29.64-41.73,29.64-32.4,0-43.05-30.96-43.05-58.38h143.36v-7.58c0-65.49-31.97-118.21-103.42-118.21ZM2465.18,315.93c3.12-22.42,16.87-43.53,41.73-43.53s38.15,21.54,41.27,43.53h-83ZM1982.14,453.75h61.95V77.17h-61.95v376.57ZM2082.57,453.75h61.94v-217.9h-61.94v217.9ZM2648.85,404.25c-14.99,0-26.84,12-26.84,27.06s11.85,27.06,26.84,27.06,26.83-12,26.83-27.06-11.85-27.06-26.83-27.06ZM2648.85,452.51c-11.58,0-20.98-9.13-20.98-21.2s9.4-21.2,20.98-21.2,20.98,9.07,20.98,21.2-9.4,21.2-20.98,21.2ZM2662.13,425.72c0-7.43-4.43-8.59-10.9-8.59h-13.62v28.08h5.86v-11.18h4.9l6.54,11.18h6.4l-6.81-11.18c4.97-.34,7.63-3.14,7.63-8.32ZM2651.37,428.99h-7.9v-6.82h6.47c2.72,0,6.47,0,6.47,3.07s-1.91,3.75-5.04,3.75Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1 @@
<svg height="398" viewBox=".454 .782 290.092 46.89160498" width="2500" xmlns="http://www.w3.org/2000/svg"><g fill="#ef3742"><path d="m34.68 30.646c-2.463 5.439-7.44 8.672-13.239 8.672-8.313 0-15.035-7.286-15.035-16.266s6.773-16.267 15.035-16.267c6.004 0 10.827 3.181 13.239 8.672l.103.257h6.26l-.205-.616c-2.772-8.724-10.212-14.111-19.397-14.111-11.597 0-20.987 9.852-20.987 22.013s9.39 22.065 20.987 22.065c9.082 0 16.523-5.439 19.396-14.162l.205-.616h-6.209zm24.784 8.826c-5.439 0-9.852-4.823-9.852-10.776 0-6.055 4.413-10.93 9.852-10.93s10.006 5.029 10.006 10.93c-.051 5.85-4.618 10.776-10.006 10.776m10.16-22.578a13.35 13.35 0 0 0 -10.16-4.875c-8.877 0-15.856 7.286-15.856 16.626 0 9.288 6.979 16.574 15.856 16.574a13.297 13.297 0 0 0 10.16-4.926v4.156h5.798v-31.66h-5.798zm25.451-4.875a11.691 11.691 0 0 0 -8.467 3.489v-2.72h-5.798v31.66h6.004v-19.242c0-4.413 3.181-7.594 7.594-7.594 3.746 0 6.312 2.206 6.927 5.901v.103l5.952-.051v-.154c-.82-6.979-5.438-11.392-12.212-11.392"/><path d="m20.107 18.228h3.9v11.443h-3.9z"/><circle cx="29.292" cy="19.973" r="2.258"/><circle cx="14.77" cy="19.973" r="2.258"/></g><g fill="#00a0dd"><path d="m231.262 44.928c-7.532 0-12.999-5.467-12.999-12.999v-18.798h5.267v18.797c0 4.686 3.108 7.835 7.732 7.835 4.594 0 7.68-3.149 7.68-7.835v-18.797h5.319v18.797c-.001 7.534-5.468 13-12.999 13zm-61.269 0c-7.532 0-12.999-5.467-12.999-12.999v-18.798h5.267v18.797c0 4.686 3.108 7.835 7.732 7.835 4.594 0 7.68-3.149 7.68-7.835v-18.797h5.319v18.797c0 7.534-5.467 13-12.999 13zm92.98-20.543c-6.26-.975-7.389-1.796-7.389-3.489 0-2.155 2.874-3.848 6.517-3.848 3.797 0 6.312 1.693 6.927 4.567l.103.359h6.055l-.051-.513c-.667-6.158-5.644-10.006-12.982-10.006-8.621 0-12.52 4.977-12.52 9.596 0 6.106 4.721 8.005 12.367 9.082 7.389.975 7.954 2.463 7.954 4.31 0 2.258-2.463 4.567-7.235 4.567-4.362 0-7.543-2.104-8.159-5.439l-.051-.359h-6.004l.051.513c.872 6.619 6.414 10.93 14.162 10.93 7.8 0 13.034-4.208 13.034-10.468.049-7.185-5.492-8.673-12.779-9.802m-132.953-.718v5.285h13.188l.154-.257c.38-.817.656-1.679.821-2.566.168-.812.271-1.635.308-2.463h-14.471z"/><path d="m146.851 23.667c-.308 8.826-6.671 15.497-14.727 15.497-8.415 0-15.291-7.286-15.291-16.164 0-8.929 6.876-16.215 15.291-16.215 5.696 0 10.571 3.233 13.085 8.672l.103.257h6.414l-.205-.616c-3.027-8.723-10.622-14.316-19.396-14.316-11.751 0-21.295 9.955-21.295 22.167s9.544 22.167 21.295 22.167a19.909 19.909 0 0 0 14.265-5.952 21.58 21.58 0 0 0 6.158-15.497zm55.828-11.648a11.691 11.691 0 0 0 -8.467 3.489v-2.72h-5.798v31.66h6.004v-19.242c0-4.413 3.181-7.594 7.594-7.594 3.746 0 6.312 2.206 6.927 5.901v.103l5.952-.051v-.154c-.82-6.979-5.438-11.392-12.212-11.392m83.026.257a1.89 1.89 0 0 0 -.192-.287 1.047 1.047 0 0 0 -.242-.213 2.24 2.24 0 0 0 1.303-.737c.294-.373.447-.837.434-1.312a1.781 1.781 0 0 0 -.676-1.513 3.47 3.47 0 0 0 -2.095-.513h-2.353v7.312h1.279v-2.9h.648a.673.673 0 0 1 .316.057c.07.041.13.097.176.164l1.656 2.475a.422.422 0 0 0 .393.205h1.221l-1.869-2.736v-.002zm-1.598-1.099h-.943v-2.492h1.074c.242-.005.483.02.718.073.17.038.33.113.467.221.119.099.208.23.257.377.055.172.081.352.078.533a1.22 1.22 0 0 1 -.369.955 1.904 1.904 0 0 1 -1.282.333z"/><circle cx="131.867" cy="17.663" r="2.36"/><circle cx="140.847" cy="17.663" r="2.36"/><path d="m284.457 17.445a6.088 6.088 0 1 1 6.089-6.089 6.094 6.094 0 0 1 -6.089 6.089zm0-11.19a5.102 5.102 0 1 0 5.104 5.101 5.106 5.106 0 0 0 -5.104-5.101z"/></g></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,12 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 572 88">
<g transform="matrix(1, 0, 0, 1, 0.244995, 0.184998)">
<polygon fill="currentColor" points="96.5 0.07 96.5 86.07 115.62 86.07 115.62 49.8 143.68 49.8 151.31 34.27 115.62 34.27 115.62 15.63 160.4 15.63 168 0.07 96.5 0.07"/>
<path fill="currentColor" d="M 243.281 59.78 C 243.281 67 238.611 70.45 228.731 70.45 L 210.501 70.45 L 210.501 49.78 L 227.841 49.78 C 238.611 49.78 243.281 52.78 243.281 59.78 M 240.391 24.78 C 240.391 31.56 235.951 34.89 226.951 34.89 L 210.501 34.89 L 210.501 15.56 L 227.061 15.56 C 235.951 15.56 240.391 18.56 240.391 24.78 M 191.391 15.62 L 191.391 49.77 L 170.011 49.77 L 186.731 15.62 L 191.391 15.62 Z M 244.731 41.11 L 244.731 40.89 C 253.731 37.67 259.281 30.22 259.281 21.45 C 259.281 7.11 248.841 0 227.841 0 L 174.061 0.07 L 131.991 86.07 L 152.211 86.07 L 162.881 64.31 L 191.391 64.31 L 191.391 86.07 L 210.691 86.07 L 230.141 86 C 251.921 86 262.591 77.88 262.591 61.11 C 262.591 50.99 256.141 43.66 244.701 41.11"/>
<polygon fill="currentColor" points="67.34 0.07 46 62.63 24.78 0.07 0 0.07 0 86.07 17.22 86.07 17.22 27.96 36.23 86.07 53.12 86.07 72 27.51 72 86.07 89.78 86.07 89.78 0.07 67.34 0.07"/>
<path fill="currentColor" d="M299.16,72.34c-8.56,0-13.67-6.67-13.67-18.23,0-11.88,4.89-18.33,13.67-18.33,8.55,0,13.55,6.67,13.55,18.33,0,11.89-4.66,18.23-13.55,18.23m.22-51.78c-19.45,0-32.56,13.22-32.67,33.67-.11,20.89,12.22,33.33,32.34,33.33S331.49,75,331.49,53.78c0-20.44-12.67-33.22-32.11-33.22"/>
<path fill="currentColor" d="M374.88,42.85l15.89-2.67a22.07,22.07,0,0,0-7.89-13,21.44,21.44,0,0,0-7.78-4.33,37.42,37.42,0,0,0-12.77-2c-8,0-14.45,2-19.34,5.89a19,19,0,0,0-7.33,15.66c0,4.78,1.55,9.56,4.66,12.67,4,3.78,9.56,5.11,19.12,6.56,1.22.22,8.33,1.22,9,1.33,4.33.56,6.55,1.78,6.55,4.67,0,4.55-4.44,6.55-10.22,6.55-7.89,0-13.22-2.77-14.89-9.55l-17,3.33a23,23,0,0,0,10.33,14.56c5.34,3.33,12.45,5,21.23,5,8.55,0,15.33-2,20.44-5.89s7.56-9,7.56-15.56c0-4.89-1-9-4.56-12.33-3.66-3.67-8.77-4.67-19.66-6.22-.56-.12-7.89-1.12-9-1.34-4-.78-6.12-2-6.12-5.33,0-4.56,3.78-6.67,9.23-6.67,6.44,0,11.55,2.67,12.55,8.67"/>
<path fill="currentColor" d="M432.66,87a45,45,0,0,0,6.45-1.44v-13a32.32,32.32,0,0,1-3.22.44,30.53,30.53,0,0,1-3.23.11c-3.77,0-6.44-.66-7.77-2s-2.12-3.89-2.12-7.55V35.74H439V22.18H422.77l.12-18L404.66,10.4V22.18H391.88V35.74h12.45V66.52c0,7.33,1.67,12.66,5.11,16s8.67,5,16,5a46.75,46.75,0,0,0,7.22-.56"/>
<path fill="currentColor" d="M473.16,72.41c-8.56,0-13.67-6.67-13.67-18.23,0-11.89,4.89-18.33,13.67-18.33,8.55,0,13.55,6.67,13.55,18.33,0,11.89-4.66,18.23-13.55,18.23m.22-51.79c-19.45,0-32.56,13.23-32.67,33.67C440.6,75.18,452.93,87.63,473,87.63s32.45-12.56,32.45-33.78c0-20.45-12.67-33.23-32.11-33.23"/>
<path fill="currentColor" d="M571.51,86.07v-39c0-17-7.56-26.22-21.45-26.22-9,0-16.55,4.66-21.11,12.78h-.33V22.18H510.5V86.07H529V52.63c0-10.78,4.78-17.12,13.11-17.12,7.67,0,11,5,11,16.67V86.07Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1 @@
<svg height="621" width="2500" xmlns="http://www.w3.org/2000/svg" viewBox="5.762 4.683 317.67 73.293"><g clip-rule="evenodd" fill="#c2121c" fill-rule="evenodd"><path d="M283.395 5.941h-20.031l-5.005 6.47h20.019M313.215 14.459c-1.867 0-3.377 1.466-3.377 3.248 0 1.821 1.51 3.287 3.377 3.287 1.854 0 3.358-1.466 3.358-3.287 0-1.782-1.504-3.248-3.358-3.248m0 5.899c-1.523 0-2.735-1.16-2.735-2.651 0-1.445 1.212-2.619 2.735-2.619 1.504 0 2.716 1.174 2.716 2.619 0 1.491-1.212 2.651-2.716 2.651"/><path d="M314.945 16.857c0-.304-.148-.628-.421-.79-.285-.162-.597-.176-.921-.176h-1.633v3.656h.544v-1.691h.668l1.082 1.691h.629l-1.134-1.691c.662-.012 1.186-.285 1.186-.999m-1.808.558h-.622v-1.114h.979c.473 0 .888.064.888.551 0 .648-.727.563-1.245.563M55.409 37.498a8.326 8.326 0 0 0-8.33-8.33 8.329 8.329 0 0 0-8.327 8.33c0 4.59 3.73 8.33 8.327 8.33 4.606 0 8.33-3.74 8.33-8.33M83.78 29.168a8.33 8.33 0 0 0-8.33 8.33c0 4.597 3.73 8.324 8.33 8.324 4.593 0 8.33-3.728 8.33-8.324 0-4.602-3.737-8.33-8.33-8.33"/><path d="M51.309 5.954H5.762v13.932H20.87v35.439h15.321V19.886h15.118M57.943 6.104V53.42c0 5.886-2.939 8.232-11.191 8.232V76.99c14.65 0 26.52-8.933 26.52-23.57l-.006-47.316M147.433 5.261a21.216 21.216 0 0 0-15.063 6.236c-3.863-3.857-9.286-6.236-15.176-6.236-11.769 0-22.676 9.536-22.676 21.302v30.332h15.225l.032-30.332c0-4.104 3.325-7.43 7.419-7.43a7.427 7.427 0 0 1 7.436 7.43v30.332h15.341l.026-30.332c0-4.104 3.339-7.43 7.436-7.43s7.436 3.326 7.436 7.43l-.026 30.332h15.276V26.563c-.001-11.766-10.917-21.302-22.686-21.302M291.628 36.649L308.6 14.473h-19.228l-7.371 9.515-7.357-9.515h-20.42l17.464 22.863-19.617 25.717h19.914l10.016-13.646 21.996 28.569h19.435"/><path d="M249.86 12.541h2.548l-4.9-6.49h-19.532l17.38 22.669-17.38 22.58V6.051h-15.318v2.172a26.351 26.351 0 0 0-13.251-3.54c-14.793 0-26.785 11.986-26.785 26.793 0 14.8 11.992 26.799 26.785 26.799 4.83 0 9.361-1.479 13.264-3.708l-.013 2.333h31.116l11.487-15.221 5.153 6.625 8.583-10.949m-69.591 6.334c-6.748 0-12.213-5.465-12.213-12.213 0-6.755 5.465-12.214 12.213-12.214 6.755 0 12.22 5.459 12.22 12.214 0 6.749-5.465 12.213-12.22 12.213"/></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

7
site/assets/logos/vf.svg Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="190" height="155" viewBox="0 0 190 155">
<g fill-rule="evenodd" clip-rule="evenodd" transform="matrix(1, 0, 0, 1, -1.378006, -16.044001)">
<path fill="#272727" d="M183.758 145.377h-1.1v-.439h2.641v.439h-1.1v4.182h-.441v-4.182zM185.74 149.559v-4.621h.66l1.541 4.181 1.321-4.181h.66v4.621h-.442v-4.182l-1.32 4.182h-.439l-1.541-4.182v4.182h-.44zM2.834 149.119h4.403l2.641 14.527 3.301-14.527h5.063l3.081 14.527 2.641-14.527h4.182l-4.622 20.69h-4.622l-3.301-15.188-3.522 15.188H7.237l-4.403-20.69zM31.888 161.006c0-1.322 0-3.523 1.761-3.523 1.981 0 1.981 1.982 1.981 3.523h-3.742zm7.704 2.199c.22-4.621-.44-8.143-5.943-8.143-5.282 0-5.723 3.301-5.723 7.264 0 4.621.66 7.703 5.723 7.703 1.981 0 3.302-.441 4.402-1.32.88-.881 1.321-2.201 1.541-3.742H35.41c0 1.1-.22 2.422-1.761 2.422-1.761 0-1.761-2.422-1.761-4.184h7.704zM48.836 169.809v-20.69h10.565v3.301h-6.163v5.283h5.943v3.082h-5.943v9.024h-4.402zM60.941 155.281h3.962v14.527h-3.962v-14.527zm0-6.162h3.962v3.301h-3.962v-3.301zM66.884 155.281h1.541v-2.199l4.182-1.762v3.961h1.981v2.643h-1.981v7.703c0 .881 0 1.762 1.101 1.762.44 0 .66 0 .88-.221v2.641h-2.201c-3.302 0-3.962-2.201-3.962-3.08v-8.805h-1.541v-2.643zM86.693 162.545l-5.282-13.426h4.622l3.082 8.805 3.302-8.805h4.401l-5.722 13.426v7.264h-4.403v-7.264zM100.561 167.389c-1.32 0-1.762-.881-1.762-5.283 0-3.082.221-4.402 1.762-4.402 1.76 0 1.98 1.32 1.98 4.402 0 4.402-.439 5.283-1.98 5.283zm0 2.64c5.281 0 5.941-3.082 5.941-7.703 0-3.963-.66-7.264-5.941-7.264-5.283 0-5.723 3.301-5.723 7.264-.001 4.621.66 7.703 5.723 7.703zM115.527 168.268c-.66 1.32-1.762 1.762-3.303 1.762-1.98 0-3.521-1.102-3.521-3.082v-11.666h3.963v10.125c0 1.102.219 2.201 1.541 2.201 1.1 0 1.32-1.1 1.32-2.201v-10.125h4.182V169.809h-4.182v-1.541zM126.752 155.281v1.982c.66-1.762 1.98-2.201 3.742-2.201v3.521c-3.521-.221-3.742 1.98-3.742 3.301v7.924h-3.961v-14.527h3.961zM138.857 169.809v-20.69h4.182v17.168h6.604v3.522h-10.786zM150.963 155.281h3.963v14.527h-3.963v-14.527zm0-6.162h3.963v3.301h-3.963v-3.301zM164.609 151.76c-1.541-.219-1.98.441-1.98 1.982v1.539h1.98v2.643h-1.98v11.885h-3.963v-11.885h-1.76v-2.643h1.76c0-3.961-.219-6.162 4.623-6.162h1.32v2.641zM169.451 161.006c0-1.322 0-3.523 1.762-3.523 1.98 0 1.98 1.982 1.98 3.523h-3.742zm7.705 2.199c0-4.621-.441-8.143-5.943-8.143-5.283 0-5.943 3.301-5.943 7.264 0 4.621.881 7.703 5.943 7.703 1.98 0 3.301-.441 4.182-1.32 1.1-.881 1.541-2.201 1.541-3.742h-3.963c0 1.1-.219 2.422-1.76 2.422-1.762 0-1.762-2.422-1.762-4.184h7.705zM179.797 169.809h3.301v-3.743h-3.301v3.743z"/>
<path d="M 36.07 74.945 C 36.07 69.863 36.728 64.932 37.965 60.233 L 37.611 62.178 L 45.535 62.178 C 45.535 62.178 65.344 118.085 68.426 126.887 L 71.066 128.209 L 88.454 73.404 C 90.655 66.801 94.176 62.179 100.339 62.179 L 110.023 62.179 L 110.023 110.822 L 99.24 110.822 C 99.24 110.822 98.359 111.041 98.359 111.922 C 98.359 113.024 99.24 113.242 99.24 113.242 L 129.613 113.242 C 129.613 113.242 130.715 113.023 130.715 111.922 C 130.715 111.041 129.613 110.822 129.613 110.822 L 118.828 110.822 L 118.828 62.179 L 129.613 62.179 C 129.613 62.179 130.715 61.959 130.715 61.078 C 130.715 59.977 129.613 59.757 129.613 59.757 L 118.828 59.757 L 118.828 37.307 C 119.049 33.125 122.57 30.484 125.652 30.484 C 129.613 30.484 132.254 33.125 132.035 37.307 C 132.035 37.307 132.035 36.646 130.273 36.646 C 128.072 36.646 126.091 38.407 126.091 41.269 C 126.091 43.69 127.853 45.671 130.493 45.671 C 133.133 45.671 135.335 43.47 135.335 40.389 C 135.335 36.867 134.454 34.446 132.694 32.025 C 132.504 31.797 132.32 31.589 132.141 31.397 C 144.241 42.016 151.844 57.603 151.844 74.945 C 151.844 107.08 126.092 133.053 93.957 133.053 C 62.042 133.053 36.07 107.08 36.07 74.945 Z M 93.957 17.058 C 107.669 17.058 120.219 21.787 130.102 29.689 C 129.943 29.586 129.78 29.485 129.612 29.384 C 128.952 28.944 126.751 28.504 124.77 28.504 C 117.506 28.504 110.022 34.887 110.022 44.351 L 110.022 59.758 L 78.11 59.758 C 78.11 59.758 77.229 59.978 77.229 61.079 C 77.229 61.959 78.11 62.18 78.11 62.18 C 87.134 62.18 88.455 65.701 85.153 74.946 L 72.387 114.564 L 54.999 62.179 L 63.363 62.179 C 63.363 62.179 64.464 61.959 64.464 61.078 C 64.464 59.977 63.584 59.757 63.584 59.757 L 38.092 59.757 C 44.782 35.183 67.296 17.058 93.957 17.058 Z" fill="#5f6785"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
site/assets/og-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
site/assets/resume.pdf Normal file

Binary file not shown.

BIN
site/assets/wordmark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
site/assets/wordmark.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

BIN
site/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

15
site/favicon.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg width="100%" height="100%" viewBox="0 0 2134 2134" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<style>
.bg { fill: #0b0b0f; }
.fg { fill: #fff; }
@media (prefers-color-scheme: dark) {
.bg { fill: #f2f2f2; }
.fg { fill: #0b0b0f; }
}
</style>
<path class="bg" d="M2133.333,400l0,1333.333c0,220.766 -179.234,400 -400,400l-1333.333,0c-220.766,0 -400,-179.234 -400,-400l0,-1333.333c0,-220.766 179.234,-400 400,-400l1333.333,0c220.766,0 400,179.234 400,400Z"/>
<g>
<path class="fg" d="M434.867,1513.667l0,-652.2l148.5,0l0,150.3l-17.1,-24.3c11.6,-47.2 35.55,-82.4 71.85,-105.6c36.3,-23.2 78.85,-34.8 127.65,-34.8c53.2,0 100.25,13.85 141.15,41.55c40.9,27.7 67.35,64.55 79.35,110.55l-45,3.9c20.2,-52.6 50.25,-91.75 90.15,-117.45c39.9,-25.7 85.95,-38.55 138.15,-38.55c46.2,0 87.5,10.4 123.9,31.2c36.4,20.8 65.15,49.7 86.25,86.7c21.1,37 31.65,79.8 31.65,128.4l0,420.3l-157.5,0l0,-382.8c0,-29 -5.2,-53.8 -15.6,-74.4c-10.4,-20.6 -24.9,-36.65 -43.5,-48.15c-18.6,-11.5 -40.8,-17.25 -66.6,-17.25c-25,0 -47.05,5.75 -66.15,17.25c-19.1,11.5 -33.9,27.6 -44.4,48.3c-10.5,20.7 -15.75,45.45 -15.75,74.25l0,382.8l-157.5,0l0,-382.8c0,-29 -5.2,-53.8 -15.6,-74.4c-10.4,-20.6 -24.9,-36.65 -43.5,-48.15c-18.6,-11.5 -40.8,-17.25 -66.6,-17.25c-25.2,0 -47.3,5.75 -66.3,17.25c-19,11.5 -33.75,27.6 -44.25,48.3c-10.5,20.7 -15.75,45.45 -15.75,74.25l0,382.8l-157.5,0Z" style="fill-rule:nonzero;"/>
<path class="fg" d="M1540.967,1513.667l0,-652.2l157.5,0l0,652.2l-157.5,0Zm0,-726l0,-168l157.5,0l0,168l-157.5,0Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

768
site/index.html Normal file
View File

@@ -0,0 +1,768 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>mifi Ventures — Software Engineering Consulting | Boston, MA</title>
<meta
name="description"
content="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."
/>
<link rel="canonical" href="https://mifi.ventures/" />
<link
rel="preload"
href="/assets/fonts/fraunces-v38-latin-600.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/assets/fonts/fraunces-v38-latin-700.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-italic.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-500.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-600.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/assets/fonts/inter-v20-latin-700.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<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" />
<!-- Theme Color -->
<meta
name="theme-color"
content="#0052cc"
media="(prefers-color-scheme: light)"
/>
<meta
name="theme-color"
content="#4da6ff"
media="(prefers-color-scheme: dark)"
/>
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://mifi.ventures/" />
<meta property="og:site_name" content="mifi Ventures" />
<meta
property="og:title"
content="mifi Ventures — Software Engineering Consulting | Boston, MA"
/>
<meta
property="og:description"
content="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."
/>
<meta
property="og:image"
content="https://mifi.ventures/assets/og-image.png"
/>
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="mifi Ventures — Software Engineering Consulting"
/>
<meta property="og:locale" content="en_US" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://mifi.ventures/" />
<meta
name="twitter:title"
content="mifi Ventures — Software Engineering Consulting | Boston, MA"
/>
<meta
name="twitter:description"
content="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."
/>
<meta
name="twitter:image"
content="https://mifi.ventures/assets/og-image.png"
/>
<meta
name="twitter:image:alt"
content="mifi Ventures — Software Engineering Consulting"
/>
<!-- Favicon -->
<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" />
<!-- Styles -->
<link rel="stylesheet" href="/styles.css" />
<!-- Structured Data (JSON-LD) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://mifi.ventures/#organization",
"name": "mifi Ventures, LLC",
"legalName": "mifi Ventures, LLC",
"url": "https://mifi.ventures/",
"logo": {
"@type": "ImageObject",
"url": "https://mifi.ventures/favicon.svg"
},
"description": "Software engineering consulting specializing in product-focused frontend architecture, performance optimization, and accessibility-first engineering.",
"founder": {
"@id": "https://mifi.ventures/#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": "https://mifi.ventures/#services"
},
"sameAs": [
"https://www.linkedin.com/in/the-mifi",
"https://github.com/the-mifi"
]
},
{
"@type": "Person",
"@id": "https://mifi.ventures/#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": "https://mifi.ventures/",
"worksFor": {
"@id": "https://mifi.ventures/#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": "https://mifi.ventures/#website",
"url": "https://mifi.ventures/",
"name": "mifi Ventures",
"description": "Software Engineering Consulting — Boston, MA",
"publisher": {
"@id": "https://mifi.ventures/#organization"
},
"potentialAction": {
"@type": "ReserveAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://cal.mifi.ventures/the-mifi"
},
"name": "Schedule a 30-minute intro call"
}
},
{
"@type": "WebPage",
"@id": "https://mifi.ventures/#webpage",
"url": "https://mifi.ventures/",
"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": "https://mifi.ventures/#website"
},
"about": {
"@id": "https://mifi.ventures/#organization"
},
"mainEntity": {
"@id": "https://mifi.ventures/#organization"
},
"primaryImageOfPage": {
"@type": "ImageObject",
"url": "https://mifi.ventures/favicon.svg"
},
"inLanguage": "en-US"
},
{
"@type": "OfferCatalog",
"@id": "https://mifi.ventures/#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."
}
}
]
}
]
}
</script>
</head>
<body>
<!-- Skip to main content link for keyboard users -->
<a href="#main" class="skip-link">Skip to main content</a>
<!-- Header + Hero Section -->
<header id="header" class="hero">
<div class="container">
<h1 class="logo">
<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"
xmlns:serif="http://www.serif.com/"
style="
fill-rule: evenodd;
clip-rule: evenodd;
stroke-linejoin: round;
stroke-miterlimit: 2;
"
>
<g>
<path
fill="currentColor"
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="currentColor"
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="currentColor"
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="currentColor"
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="currentColor"
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="currentColor"
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="currentColor"
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="currentColor"
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="currentColor"
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="currentColor"
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="currentColor"
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>
<span class="sr-only">mifi Ventures</span>
</h1>
<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="/assets/resume.pdf"
class="btn btn-secondary"
download
aria-label="Download Mike Fitzpatrick's resume as PDF"
>
Download resume
</a>
</div>
</div>
</header>
<!-- Main Content -->
<main id="main">
<!-- Experience Includes Section -->
<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>
<!-- Logo strip with accessible images -->
<div class="logo-strip" role="list" aria-label="Company logos">
<div class="logo-item" role="listitem">
<img
src="/assets/logos/atlassian.svg"
alt="Atlassian"
loading="lazy"
width="2500"
height="2500"
/>
<span class="logo-fallback-text">Atlassian</span>
</div>
<div class="logo-item" role="listitem">
<img
src="/assets/logos/tjx.svg"
alt="TJ Maxx (The TJX Companies)"
loading="lazy"
width="2500"
height="621"
/>
<span class="logo-fallback-text">TJ Maxx</span>
</div>
<div class="logo-item" role="listitem">
<img
src="/assets/logos/cargurus.svg"
alt="CarGurus"
loading="lazy"
width="2500"
height="398"
/>
<span class="logo-fallback-text">CarGurus</span>
</div>
<div class="logo-item" role="listitem">
<img
src="/assets/logos/timberland.svg"
alt="Timberland"
loading="lazy"
width="190"
height="35"
/>
<span class="logo-fallback-text">Timberland</span>
</div>
<div class="logo-item" role="listitem">
<img
src="/assets/logos/vf.svg"
alt="VF Corporation"
loading="lazy"
width="190"
height="155"
/>
<span class="logo-fallback-text">VF Corporation</span>
</div>
<div class="logo-item" role="listitem">
<img
src="/assets/logos/bottomline.svg"
alt="Bottomline Technologies"
loading="lazy"
width="2702"
height="571"
/>
<span class="logo-fallback-text">Bottomline Technologies</span>
</div>
<div class="logo-item" role="listitem">
<img
src="/assets/logos/mfa-boston.svg"
alt="Museum of Fine Arts Boston"
loading="lazy"
width="572"
height="88"
/>
<span class="logo-fallback-text">MFA Boston</span>
</div>
</div>
<!-- Text-only fallback list (visible on very small screens or when images fail) -->
<ul class="logo-text-list" aria-hidden="true">
<li>Atlassian</li>
<li>TJ Maxx (The TJX Companies)</li>
<li>CarGurus</li>
<li>Timberland</li>
<li>VF Corporation</li>
<li>Bottomline Technologies</li>
<li>Museum of Fine Arts Boston</li>
</ul>
<p class="footnote">
Logos are trademarks of their respective owners.
</p>
</div>
</section>
<!-- What We Do Section -->
<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">
<li>
Product-focused frontend and UI architecture for modern web
applications, with an emphasis on clarity, scalability, and
long-term maintainability.
</li>
<li>
Greenfield product builds and early-stage foundations, getting new
projects off the ground quickly with structures designed to grow,
not be rewritten.
</li>
<li>
Performance, Core Web Vitals, rendering strategy, and technical
SEO optimization focused on real-world user journeys—not just lab
scores.
</li>
<li>
Accessibility-first engineering, ensuring WCAG-compliant
interfaces with semantic markup, keyboard parity, and inclusive
interaction patterns.
</li>
<li>
Modernization and stabilization of existing systems, including
refactors, framework upgrades, and untangling overgrown frontend
codebases.
</li>
<li>
End-to-end feature delivery with clear ownership and
documentation, spanning frontend and supporting backend work
without unnecessary complexity.
</li>
</ul>
</div>
</section>
<!-- Selected Impact Section -->
<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">
<li>
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.
</li>
<li>
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.
</li>
<li>
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.
</li>
<li>
Bring order to complex or aging codebases by simplifying
structure, reducing duplication, and clarifying ownership,
enabling teams to ship confidently without over-engineering.
</li>
<li>
Design and evolve shared component libraries and UI systems that
improve consistency, velocity, and long-term maintainability
across multiple teams.
</li>
<li>
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.
</li>
</ul>
</div>
</section>
<!-- How We Work Section -->
<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">
<li>
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.
</li>
<li>
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.
</li>
<li>
Async-friendly, low-friction communication. Clear written updates,
documented decisions, and scheduled calls when they add value—not
meetings for their own sake.
</li>
<li>
Quality as a default. Accessibility, performance, and
maintainability are built into the work from the start, not added
later as cleanup.
</li>
<li>
Flexible engagement models. Hourly or fixed-scope work depending
on clarity and needs; longer-term engagements welcome when there's
ongoing product momentum.
</li>
<li>
Clean handoff. Code, documentation, and context are left in a
state where internal teams—or future vendors—can confidently
extend the work without dependency.
</li>
</ul>
</div>
</section>
<!-- Recent Engagements Section -->
<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">
<div class="engagement">
<dt>Atlassian — Senior UI Engineer (Enterprise SaaS)</dt>
<dd>
Frontend architecture and feature delivery for Confluence
integrations, including React 18 migration work and
standardizing end-to-end testing practices.
</dd>
</div>
<div class="engagement">
<dt>CarGurus — Principal UI Engineer (Consumer Marketplace)</dt>
<dd>
Built and maintained high-traffic frontend systems, improved
Core Web Vitals and technical SEO, and developed shared UI
platforms used across teams.
</dd>
</div>
<div class="engagement">
<dt>
The TJX Companies (TJ Maxx) — UI Engineer (Enterprise Retail)
</dt>
<dd>
Delivered UX improvements for large-scale e-commerce experiences
in close partnership with design, QA, and product teams.
</dd>
</div>
<div class="engagement">
<dt>
Timberland — Senior Interactive Developer (Global Ecommerce)
</dt>
<dd>
Led global web initiatives across brand and e-commerce
platforms, acting as a technical bridge between marketing,
design, and engineering.
</dd>
</div>
<div class="engagement">
<dt>
MFA Boston — Pro Bono Technical Lead (Nonprofit / Fundraising)
</dt>
<dd>
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.
</dd>
</div>
</dl>
</div>
</section>
<!-- Schedule Section -->
<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>
</main>
<!-- Footer -->
<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>
<script src="/script.js" defer></script>
</body>
</html>

16
site/robots.txt Normal file
View File

@@ -0,0 +1,16 @@
# robots.txt for mifi.ventures
User-agent: *
Allow: /
# Crawl delay (optional, uncomment if needed)
# Crawl-delay: 1
# Disallow specific paths if needed
# Disallow: /private/
# Sitemap (add when available)
# Sitemap: https://mifi.ventures/sitemap.xml
# Host preference (helps search engines understand the canonical domain)
# Host: https://mifi.ventures

15
site/script.js Normal file
View File

@@ -0,0 +1,15 @@
/**
* Minimal JavaScript for mifi Ventures website
* Primary purpose: Dynamic copyright year
*/
(function() {
'use strict';
// Update copyright year to current year
// Script runs with defer, so DOM is always ready
const yearElement = document.getElementById('copyright-year');
if (yearElement) {
yearElement.textContent = new Date().getFullYear();
}
})();

1064
site/styles.css Normal file

File diff suppressed because it is too large Load Diff