Initial commit... a site is born (again? finally?)
18
.devcontainer/Dockerfile
Normal 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
|
||||||
36
.devcontainer/devcontainer.json
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
BIN
logos/favicon cutout block.af
Normal file
BIN
logos/favicon.af
Normal file
BIN
logos/mifi-ventures-wordmark.af
Normal file
BIN
logos/wordmark with tagline.af
Normal file
589
logos/wordmark.html
Normal 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 won’t 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
@@ -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
@@ -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
BIN
site/assets/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
site/assets/avatar.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
1
site/assets/favicon-cutout.svg
Normal 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 |
BIN
site/assets/fonts/fraunces-v38-latin-500.woff2
Normal file
BIN
site/assets/fonts/fraunces-v38-latin-500italic.woff2
Normal file
BIN
site/assets/fonts/fraunces-v38-latin-600.woff2
Normal file
BIN
site/assets/fonts/fraunces-v38-latin-600italic.woff2
Normal file
BIN
site/assets/fonts/fraunces-v38-latin-700.woff2
Normal file
BIN
site/assets/fonts/fraunces-v38-latin-italic.woff2
Normal file
BIN
site/assets/fonts/fraunces-v38-latin-regular.woff2
Normal file
BIN
site/assets/fonts/inter-v20-latin-500.woff2
Normal file
BIN
site/assets/fonts/inter-v20-latin-500italic.woff2
Normal file
BIN
site/assets/fonts/inter-v20-latin-600.woff2
Normal file
BIN
site/assets/fonts/inter-v20-latin-600italic.woff2
Normal file
BIN
site/assets/fonts/inter-v20-latin-700.woff2
Normal file
BIN
site/assets/fonts/inter-v20-latin-700italic.woff2
Normal file
BIN
site/assets/fonts/inter-v20-latin-italic.woff2
Normal file
BIN
site/assets/fonts/inter-v20-latin-regular.woff2
Normal file
BIN
site/assets/fonts/plus-jakarta-sans-v12-latin-500.woff2
Normal file
BIN
site/assets/fonts/plus-jakarta-sans-v12-latin-500italic.woff2
Normal file
BIN
site/assets/fonts/plus-jakarta-sans-v12-latin-600.woff2
Normal file
BIN
site/assets/fonts/plus-jakarta-sans-v12-latin-600italic.woff2
Normal file
BIN
site/assets/fonts/plus-jakarta-sans-v12-latin-700.woff2
Normal file
BIN
site/assets/fonts/plus-jakarta-sans-v12-latin-700italic.woff2
Normal file
BIN
site/assets/fonts/plus-jakarta-sans-v12-latin-italic.woff2
Normal file
BIN
site/assets/fonts/plus-jakarta-sans-v12-latin-regular.woff2
Normal file
11
site/assets/logos/atlassian.svg
Normal 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 |
19
site/assets/logos/bottomline.svg
Normal 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 |
1
site/assets/logos/cargurus.svg
Normal 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 |
12
site/assets/logos/mfa-boston.svg
Normal 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 |
10
site/assets/logos/timberland.svg
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
1
site/assets/logos/tjx.svg
Normal 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
@@ -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
|
After Width: | Height: | Size: 50 KiB |
BIN
site/assets/resume.pdf
Normal file
BIN
site/assets/wordmark.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
1
site/assets/wordmark.svg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
site/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
15
site/favicon.svg
Normal 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
@@ -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
@@ -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
@@ -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();
|
||||||
|
}
|
||||||
|
})();
|
||||||