Files
landing/README.md
mifi 0266d472d9
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/deploy unknown status
Fingers crossed for working CI e2e tests
2026-02-01 14:36:17 -03:00

508 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 test:e2e` | Run Playwright visual regression (e2e) |
| `pnpm run test:e2e:update-snapshots` | Regenerate e2e snapshots in CI Docker image (see below) |
| `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) |
### Option 1: pnpm dev (recommended for editing)
From the project root:
```bash
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:
```bash
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](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:5173** (or the port shown) with live reload (port forwarded automatically).
### Visual regression (e2e)
E2e tests use Playwright and compare full-page screenshots to committed snapshots. CI runs in `mcr.microsoft.com/playwright:v1.58.0-noble`; the **Linux** snapshot (`home-chromium-linux.png`) must be generated in that same environment or CI will fail (e.g. different height due to font/layout rendering).
If CI fails with a snapshot mismatch (e.g. "Expected 1280×5985px, received 1280×5782px"):
1. Regenerate the Linux snapshot in the CI Docker image:
```bash
pnpm run test:e2e:update-snapshots
```
2. Commit the updated file(s) under `tests/visual.spec.ts-snapshots/`.
Local `pnpm run test:e2e` uses the **Darwin** snapshot on macOS; the Linux snapshot is only used in CI.
### 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
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)
│ ├── ci.yaml # one clone/workspace: install → lint → build → test
│ └── deploy.yaml # Docker → push → webhook (main only, after ci)
├── 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](DEPLOYMENT.md) for step-by-step setup instructions, troubleshooting, and examples.
### Pipeline Overview
Woodpecker uses two workflows (`.woodpecker/ci.yaml`, `deploy.yaml`):
- **Pull requests** (and **push/tag/manual on main**): **ci** runs install → lint → build → test in one workspace (one clone, one install). No Docker or deploy on PRs.
- **Push to main** (or tag / manual on main): After ci succeeds, **deploy** runs:
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
# 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:**
```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).