# n8 Landing Page — Agent Context

**n8.lu** is a Blazor WASM static SPA portfolio/landing page — no backend, no database, no authentication. Hosted on Azure Static Web Apps. One solution (`n8.sln`), one project (`Landing/Landing.csproj`).

## Commands

```bash
# Dev server (http://localhost:5050 via launchSettings; pass --urls to change)
dotnet run --project Landing/Landing.csproj

# E2E tests (Playwright; set N8_PORT when 5050 is taken by another app)
cd Landing && N8_PORT=5057 npx playwright test

# Production publish (AOT is off by default; enable with -p:RunAOTCompilation=true)
dotnet publish Landing/Landing.csproj -c Release

# Regenerate sitemap after adding/modifying apps
node scripts/generate-sitemap.js

# Regenerate per-app OG images
python3 scripts/generate-og-images.py

# Brotli-compress publish output (CI runs this automatically)
./scripts/compress-publish.sh Landing/bin/Release/net10.0/publish
```

CSS is compiled via the Tailwind CLI (NOT Parcel). The csproj runs `npm run build-css` in a BeforeBuild target. If Tailwind output is missing/stale, run `dotnet build` or `npm run build-css` from `Landing/`.

E2E coverage lives in `Landing/tests/n8.spec.js` (Playwright, ~46 tests; `npx playwright test` boots the dev server itself). `TESTING_GUIDE.md` defines additional manual checks.

## Build Pipeline (csproj target order)

1. **TailwindBuild** (BeforeTargets=Build) → `npm run build-css`
2. **GenerateSitemap** (BeforeTargets=Publish) → `node ../scripts/generate-sitemap.js`
3. **PrerenderRoutes** (AfterTargets=Publish, Release only) → Playwright renders every route to static HTML snapshots
4. **BrotliCompressPublish** (AfterTargets=Publish, Release only) → `bash ../scripts/compress-publish.sh`

CI deploys on push to `master` via `.github/workflows/azure-static-web-apps-*.yml`.

## Architecture

- **Target**: `net10.0` (SDK 10.0.0 in `global.json`, `rollForward: latestMajor`)
- **CSS**: Tailwind CSS 3.4 via PostCSS + autoprefixer (dev deps in `Landing/package.json`)
- **JS**: Three vanilla files under `Landing/wwwroot/js/` — `n8-interop.js`, `n8-hero.js`, `n8-page.js`
- **Fonts**: Orbitron (brand), Outfit (headings), Inter (body) — async non-blocking load via `index.html`
- **AOT**: Disabled by default (`RunAOTCompilation=false`). Enable locally with `-p:RunAOTCompilation=true` (requires wasm-tools)
- **Prerender**: `scripts/prerender.mjs` uses Playwright to snapshot every route to static HTML for crawlers

### No Backend

All data is static JSON in `Landing/wwwroot/`:
- `apps.json` — 11 app entries (page slugs determine routes at `/apps/{page}`)
- `ai.json` — AI Lab artifacts (slugs determine routes at `/ai/{slug}`); each artifact's raw markdown lives at `ai/{slug}.md` so it stays directly curl-able
- `services.json` — 4 service offerings (no page renders these)
- `locales/{en,fr,de}.json` — translations

### Service Registration (Program.cs)

Both services are **Scoped** (NOT singleton), so each page/component gets fresh instances:
- `AppDataService` — fetches + caches JSON data via `HttpClient`
- `LocalizationService` — loads locale JSON, exposes `CurrentCulture`/`UICulture`, fires `OnLanguageChanged`

Components consume services via `@inject`. No cascading values or state containers.

## Routes & Pages

| Route | Component | Notes |
|-------|-----------|-------|
| `/` | Home.razor | Renders `<HeroSection /><WorkSection /><AiLabSection />` |
| `/apps/{Page}` | AppPage.razor | Per-app detail page |
| `/ai` | AiPage.razor | AI Lab index (reuses AiLabSection) |
| `/ai/{Slug}` | AiPage.razor | Per-artifact detail page with raw markdown viewer |
| `/404` | Error404.razor | Custom 404, prerendered to `/404/index.html` |
| `*` | Error404.razor | Via App.razor NotFound → LayoutView wrapper |

App slugs in `apps.json`: `carbon-suite`, `parkings`, `unblock-me`, `luxembourg-live-parking`, `infinite-minesweeper`, `water-reminder`, `quartz-dashboard`, `n8booking`, `recreo`, `labflow`, `dot-conductor`.

When adding a new app slug (`apps.json`) or AI artifact slug (`ai.json`), you MUST also add a route to `staticwebapp.config.json` under `routes[]` rewriting to `/<prefix>/<slug>/index.html` for prerendered pages. New AI artifacts also need their raw markdown at `Landing/wwwroot/ai/<slug>.md`.

## Design System

- **Dark theme only** — `#05010d` background. No light mode.
- **Primary**: `#00d4ff` (cyan), **Secondary**: `#ff007f` (magenta-pink)
- **Glassmorphism**: translucent cards with `backdrop-filter: blur(12px)`, `1px solid rgba(255,255,255,0.10)` borders
- **CSS vars** defined in `:root` in `Landing/wwwroot/css/input.css`: `--bg-dark`, `--primary`, `--secondary`, `--ease-out`, etc.
- **Fonts**: Orbitron 700/900 (brand), Outfit 600/700/800 (headings), Inter 400/500/600 (body)

## Key Behaviors

### Scroll Progress Bar
A single `#scroll-progress` div in `TopNav.razor` updated by `registerTopNavScroll` in `n8-page.js`. No duplication exists.

### Localization
- Detected from `navigator.language` (en/fr/de)
- Override via `?lang=fr` URL parameter
- No UI toggle — URL-based only
- Locale files: `Landing/wwwroot/locales/{en,fr,de}.json`

### Service Worker
- Cache name: `n8-cache-v12` (`Landing/wwwroot/service-worker.js`). Bump the version when precached asset URLs or fetch logic change.
- Cache-first for `/_framework/*` (content-fingerprinted) and `/fonts/*`; network-first for navigations and other assets
- Offline fallback to `/offline.html`; OG images deliberately not precached

### Deploy Config
- `staticwebapp.config.json` lives in `Landing/wwwroot/` — it MUST stay there: the deploy uploads `Landing/publish/wwwroot` only, so a config outside wwwroot silently never reaches production (no headers, no custom 404).

## Agent Rules

1. **Never add a backend** — static SPA only. All data from JSON files.
2. **Use AppDataService** — do not call `HttpClient.GetFromJsonAsync` directly in pages.
3. **Dark theme only** — no light mode unless explicitly requested.
4. **Prefer Tailwind utility classes** — check `input.css` for existing custom classes first.
5. **Add locale keys in all 3 languages** when adding UI text.
6. **New JS functions** go in an existing `Landing/wwwroot/js/n8-*.js` file or a new one loaded in `index.html`.
7. **Component-scoped CSS** goes in `[Component].razor.css`, NOT in `input.css`.
8. **Azure SWA routing** — update `Landing/wwwroot/staticwebapp.config.json` for new routes (file must stay inside wwwroot, see Deploy Config).
9. **Bump the service worker cache version** in `service-worker.js` when precached assets change.
10. **Do not remove existing keyboard shortcut registrations** — add, don't delete.
