# 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 ``` 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**: `
`, `
`, `