# mifi.dev landing

One-page static Linktree-style site for [mifi.dev](https://mifi.dev). Built with SvelteKit (static adapter), TypeScript, pnpm, PostCSS, and critical CSS.

## Prerequisites

- **Node.js** 22+
- **pnpm** (enable via `corepack enable && corepack prepare pnpm@latest --activate`)

## Quick start

### Option A: Dev container (recommended)

Use the same Linux environment as CI so Playwright snapshots match.

1. Open the repo in VS Code or Cursor.
2. When prompted, click **Reopen in Container** (or run **Dev Containers: Reopen in Container** from the command palette).
3. Wait for the container to build; **postCreateCommand** runs `pnpm install` and `pnpm exec playwright install chromium --with-deps` so dependencies and the Playwright browser are ready.
4. Run the scripts below inside the container.

### Option B: Local

```bash
pnpm install
pnpm dev          # dev server at http://localhost:5173
pnpm build        # output in build/
pnpm preview      # preview build at http://localhost:4173
```

## Scripts

| Script                      | Description                                                                                |
| --------------------------- | ------------------------------------------------------------------------------------------ |
| `pnpm dev:bio`              | Start Vite dev server for mifi.bio                                                         |
| `pnpm dev:dev`              | Start Vite dev server for mifi.dev                                                         |
| `pnpm build`                | Build static site to `build/`                                                              |
| `pnpm build:full`           | Build + inline critical CSS (run `pnpm run critical-css:install` once to install Chromium) |
| `pnpm critical-css:install` | Install Chromium for critical CSS (required once before first `build:full`)                |
| `pnpm preview`              | Serve `build/` locally                                                                     |
| `pnpm check`                | Run `svelte-kit sync` and `svelte-check`                                                   |
| `pnpm lint`                 | ESLint + Stylelint                                                                         |
| `pnpm format`               | Prettier (write)                                                                           |
| `pnpm format:check`         | Prettier (check only)                                                                      |
| `pnpm test`                 | Vitest (watch)                                                                             |
| `pnpm test:run`             | Vitest (single run)                                                                        |
| `pnpm test:coverage`        | Vitest with coverage                                                                       |
| `pnpm test:e2e`             | Playwright e2e (starts preview, then runs tests)                                           |
| `pnpm test:e2e:ui`          | Playwright e2e in UI mode                                                                  |

## Project layout

- `src/` – SvelteKit app (routes, layout, global CSS)
- `src/lib/data/` – JSON content (e.g. `links.json`) loaded at build time
- `static/` – Static assets
- `scripts/` – Post-build scripts (e.g. critical CSS)
- `e2e/` – Playwright e2e tests
- `build/` – Output after `pnpm build` (gitignored)

## Tooling

- **Package manager:** pnpm
- **Language:** TypeScript
- **Lint:** ESLint, Stylelint
- **Format:** Prettier
- **Unit tests:** Vitest
- **E2E / visual regression:** Playwright (use same Linux build in dev container and CI)
- **Critical CSS:** Post-build step via `critical` (run `pnpm run critical-css:install` once, then `pnpm build:full`)

## Build and run with Docker

One image contains both variants (mifi.dev and mifi.bio). The Dockerfile runs two SvelteKit builds (`CONTENT_VARIANT=dev` and `CONTENT_VARIANT=bio`) and nginx serves by host.

```bash
docker build -t mifi-links:local .
docker run --rm -p 8080:80 mifi-links:local
```

Then open `http://localhost:8080` with `Host: mifi.dev` or `Host: mifi.bio` (e.g. add `127.0.0.1 mifi.dev` to `/etc/hosts` and visit `http://mifi.dev:8080`).

For production, the Portainer stack uses the image from the package registry. To run the stack locally with the built image, use the same `docker-compose.yml` and either point it at your local tag or run `docker compose build` then `docker compose up`.

## Deploy

- Repo: `git.mifi.dev/mifi-holdings/mifi-links`
- One image (both mifi.dev and mifi.bio); deploy via webhook to Portainer stack.
- `docker-compose.yml` defines one service with nginx and Traefik labels for `mifi.dev`, `www.mifi.dev`, `mifi.bio`, and `www.mifi.bio` on network `marina-net`.

## Fonts

Fonts live in `static/assets/fonts/`. The wordmark uses **Plus Jakarta Sans** (700). The self-hosted “latin” woff2 subsets omit ligature (GSUB) tables, so the **fi** ligature in “mifi” does not appear with them.

**Current setup:** Plus Jakarta Sans 700 is loaded from **Google Fonts** in the layout so the wordmark fi ligature works. All other fonts (Fraunces, Inter, etc.) are self-hosted.

**To self-host the wordmark font with ligatures** instead of Google Fonts:

1. **Download the font**  
   [Google Fonts → Plus Jakarta Sans](https://fonts.google.com/specimen/Plus+Jakarta+Sans) → “Download family” (ZIP). Unzip; use the **Bold** static file (e.g. `PlusJakartaSans-Bold.ttf` or `PlusJakartaText-Bold.otf` from the `static` folder). Or clone [Tokotype/PlusJakartaSans](https://github.com/tokotype/PlusJakartaSans) and use `fonts/ttf/PlusJakartaSans-Bold.ttf` (or the OTF equivalent).

2. **Subset with ligatures** (from the repo root, with fonttools installed: `pip install fonttools brotli`):

```bash
# Replace PATH_TO_BOLD with the path to the Bold TTF/OTF (e.g. ~/Downloads/Plus_Jakarta_Sans/static/PlusJakartaSans-Bold.ttf)
# Use --layout-features='*' to keep all layout features (including liga); or 'liga','clig'
pyftsubset PATH_TO_BOLD \
  --output-file=static/assets/fonts/plus-jakarta-sans-700-liga.woff2 \
  --flavor=woff2 \
  --layout-features='*' \
  --unicodes='U+0020-007F,U+00A0-00FF,U+FB01,U+FB02'
```

Example if the ZIP is in your Downloads folder:

```bash
pyftsubset ~/Downloads/Plus_Jakarta_Sans/static/PlusJakartaSans-Bold.ttf \
  --output-file=static/assets/fonts/plus-jakarta-sans-700-liga.woff2 \
  --flavor=woff2 \
  --layout-features='*' \
  --unicodes='U+0020-007F,U+00A0-00FF,U+FB01,U+FB02'
```

3. **Switch back to self-hosted**  
   Put the new woff2 in `static/assets/fonts/`. In `src/lib/fonts.css`, uncomment the Plus Jakarta Sans `@font-face` and set its `url()` to `plus-jakarta-sans-700-liga.woff2`. In `src/routes/+layout.svelte`, remove the Google Fonts `<link>` for Plus Jakarta Sans.

### Fraunces variable (headings with opsz)

Headings use **Fraunces** with `font-variation-settings: "opsz" 36`. That only works with the **variable** font (opsz + wght axes). Static instances (e.g. `fraunces-v38-latin-500.woff2`) ignore opsz.

1. **Download the variable TTF**  
   [Google Fonts → Fraunces](https://fonts.google.com/specimen/Fraunces) → “Download family”. In the ZIP, use the file in the **variable** folder: `Fraunces-VariableFont_opsz,wght.ttf` (or similar name).

2. **Subset to woff2** (from repo root; `pip install fonttools brotli`):

```bash
# Replace PATH_TO_VARIANT with the path to Fraunces-VariableFont_opsz,wght.ttf
pyftsubset PATH_TO_VARIANT \
  --output-file=static/assets/fonts/fraunces-variable-opsz-wght.woff2 \
  --flavor=woff2 \
  --layout-features='*' \
  --unicodes='U+0020-007F,U+00A0-00FF'
```

Example if the ZIP is in Downloads:

```bash
pyftsubset ~/Downloads/Fraunces/fraunces-variable-opsz-wght.ttf \
  --output-file=static/assets/fonts/fraunces-variable-opsz-wght.woff2 \
  --flavor=woff2 \
  --layout-features='*' \
  --unicodes='U+0020-007F,U+00A0-00FF'
```

3. **Use it in the app**  
   In `src/lib/fonts.css`, replace the Fraunces `@font-face` with one that points at the variable woff2 and a `font-weight` range (e.g. 100 900) so the browser can use the wght axis. See the comment in `fonts.css` for the exact block.

## CSP

CSP is set via Traefik middleware, not in app code.
