2026-02-01 02:42:02 -03:00
2026-02-01 02:42:02 -03:00
2026-01-30 20:35:04 -03:00
2026-02-01 02:42:02 -03:00

mifi Ventures Landing Site

A minimal, production-ready static website for mifi Ventures, LLC — a software engineering consulting business.

🏗️ Technology Stack

  • Frontend: SvelteKit (Svelte 5) with adapter-static — prerendered HTML/CSS, zero app JS (no hydration)
  • Build: Vite, PostCSS (autoprefixer), Critters (critical CSS inlining)
  • 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
  • Minimal JS — only a tiny copyright-year script; no Svelte runtime or app bundle

🚀 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 SvelteKit dev server at http://localhost:5173 with live reload
pnpm run build SvelteKit build → dist/, then Critters inlines critical CSS
pnpm run preview Serve dist/ (Critters-processed) at http://localhost:4173 — same as deployed
pnpm test Run unit tests (Vitest)
pnpm run lint ESLint (JS/TS/Svelte)
pnpm run lint:css Stylelint (global CSS + Svelte styles)
pnpm run format Prettier (JS/TS/Svelte/CSS/JSON)

From the project root:

pnpm run dev

Opens http://localhost:5173 with live reload when you change files in src/ or static/.

Option 2: Preview production build

After building, serve the static output:

pnpm run build
pnpm run preview

Preview uses serve dist so you see the same HTML/CSS as in production (including critical CSS).

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 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:

pnpm install
pnpm run dev

The site is served at http://localhost:5173 (or the port shown) with live reload (port forwarded automatically).

Option 4: Docker (Production-like Test)

To test the production nginx image locally (same as deployed):

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

Content and links are driven by data and components:

  • Meta/SEO: src/lib/seo.ts, src/lib/data/home-meta.ts, and per-route +page.ts load functions
  • Section copy: src/lib/data/content.ts, src/lib/data/experience.ts, src/lib/data/engagements.ts
  • JSON-LD: src/lib/data/json-ld.ts
  • Layout and sections: src/routes/+layout.svelte, src/routes/+page.svelte, and components in src/lib/components/

To change company info, calendar link, social links, or resume path, edit the data modules and src/lib/data/home-meta.ts (or the relevant routes meta).

🗂️ Project Structure

mifi-ventures-landing/
├── .devcontainer/          # Dev container for local development
│   ├── devcontainer.json   # Dev container config (extensions)
│   └── Dockerfile          # Dev container image (Node)
├── .woodpecker/            # CI/CD pipelines (see below)
│   ├── lint-and-build.yaml # PR: lint, test, build (no deploy)
│   └── deploy.yaml          # main: lint, test, build, Docker, push, webhook
├── Dockerfile              # Production container (nginx:alpine)
├── nginx.conf              # nginx web server configuration
├── svelte.config.js        # SvelteKit config (adapter-static)
├── vite.config.ts          # Vite config
├── postcss.config.js       # PostCSS (autoprefixer)
├── scripts/critters.mjs    # Post-build critical CSS inlining
├── static/                 # Static assets (copied to dist as-is)
│   ├── favicon.svg, favicon.ico, robots.txt
│   ├── copyright-year.js   # Minimal client script (footer year)
│   └── assets/             # Fonts, images, logos, resume.pdf, og-image.png
├── src/
│   ├── app.css             # Global tokens + base styles
│   ├── app.html            # HTML shell for SvelteKit
│   ├── app.d.ts            # SvelteKit types
│   ├── routes/
│   │   ├── +layout.ts      # Prerender, csr: false
│   │   ├── +layout.svelte  # Shell, head, skip link, slot
│   │   ├── +page.ts        # Home page meta (load)
│   │   └── +page.svelte    # Home page content (components)
│   └── lib/
│       ├── seo.ts          # Meta defaults, mergeMeta, PageMeta type
│       ├── copyright-year.ts
│       ├── data/           # home-meta, json-ld, content, experience, engagements
│       └── components/     # Hero, sections, Footer, Logo, etc.
├── tests/                  # Playwright visual regression
└── README.md               # This file

🚢 CI/CD Deployment (Woodpecker + Gitea)

📖 Full deployment guide: See DEPLOYMENT.md for step-by-step setup instructions, troubleshooting, and examples.

Pipeline Overview

Woodpecker uses two workflows (.woodpecker/lint-and-build.yaml and .woodpecker/deploy.yaml):

  • Pull requests: Opening or updating a PR runs lint (ESLint + Stylelint), tests (Vitest), and a test build (SvelteKit + Critters) on the branch. No Docker image or deploy.
  • Push to main (or tag / manual run): Runs the same lint, test, and build, then:
    1. Build — Builds Docker image tagged with commit SHA + latest
  1. Push — Pushes images to private Docker registry
  2. 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:

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):

# 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:

# Build locally first (must succeed before Docker)
pnpm install
pnpm run build

# Check Dockerfile syntax
docker build -t test .

# Verify source is present
ls -la src/ static/

Push fails:

# Test registry login locally
echo "PASSWORD" | docker login registry.example.com -u username --password-stdin

# Verify registry URL and credentials

Deploy fails:

# 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:

# 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:

# 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 or schedule a call.

Description
The official homepage of mifi Ventures (just a static HTML/CSS website)—as Dad would've said, KISS... Keep It Simple, Stupid.
https://mifi.ventures
Readme 6.7 MiB
Languages
Svelte 40.9%
TypeScript 18%
HTML 14.9%
CSS 14.7%
JavaScript 9.1%
Other 2.4%