157 lines
8.0 KiB
Markdown
157 lines
8.0 KiB
Markdown
# 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 (requires Chromium: `pnpm exec puppeteer browsers install chromium`) |
|
||
| `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 build:full` with Chromium installed)
|
||
|
||
## 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.
|