# 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) | ### 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). ### 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 route’s 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**: `
`, `
`, `