# Project Bifrost — Site Structure Reference

This document labels every editable piece of the site with a short name you can quote when asking for changes, and notes where each piece lives in the code. Use the labels (e.g. **S1 lede**, **P3 footer**) to point at changes precisely.

---

## Top-level structure

The site has **three pages**, each occupying the full viewport. A dot-nav at the bottom switches between them.

```
/timeline  →  ┌─────────────────────────────────────────────┐
              │  [P1] TIMELINE                              │
              │  [P2] OVERVIEW (Project Bifrost scenes)     │
              │  [P3] ARCHIVE                               │
              └─────────────────────────────────────────────┘
```

Only one page is visible at a time. They are mutually exclusive — switching between them is a class toggle on the page wrapper, not a navigation.

Before reaching any of the three, users see the **entrance** at `/` (email form → welcome step). The entrance is a separate HTML file in `public/`.

---

## [P1] TIMELINE — horizontal scroll catalog

**Files:** `protected/index.html` (`#page-timeline`, lines ~739–758) + `protected/timeline.js`

A horizontal mousewheel-driven scroll through 12 editorial cards, set over a slowly rotating orthographic globe. Cards alternate above and below a central spine; each is accented by one of four colours (copper/ochre/terracotta/crimson) denoting register. Card layout is a two-column grid: headline on the left (spanning both rows), body paragraph top-right, source + date bottom-right. Headlines' italic-bold emphasis uses the crimson accent across every card.

| Label | What it is | Where to change |
|---|---|---|
| **P1 page title** | *"From the promise of AI to the loss of **sovereignty.**"* | `index.html` line 740 |
| **P1 subtitle** | *"Twenty-three headlines, quietly laid across a tinted map. Scroll the wheel — the map turns with you."* | `index.html` line 741 |
| **P1 globe** | Rotating orthographic globe that tracks scroll position from N. America → Europe | `timeline.js` — `buildGlobe()` + `setRotation` in `applyScroll()` |
| **P1 events** | The 12 headline cards, from September 2024 to Q1 2026. Each has `date`, `kind`, `accent`, `hed`, `body`, `source`; the card builder concatenates source + date at render time | `timeline.js` — `EVENTS` array, lines 14–66 |
| **P1 card styles** | Two-column editorial grid (1.15fr / 0.85fr), 640px wide, crimson `<em>` accent in headline | `index.html` CSS `.evt`, `.evt h3`, `.evt p`, `.evt .source` |
| **P1 year ticks** | "2024", "2025", "2026" on the spine between groups of cards | `timeline.js` — `buildTimeline()`, year-tick loop |
| **P1 "How Fenja AI addresses this" button** | Vertically centered on the right edge, circular SVG arrow icon + italic crimson emphasis on "addresses". Fires dot-nav switch to Overview | `index.html` around line 2197 (markup) + `.continue-btn` CSS + `timeline.js` click handler |
| **P1 spine vertical position** | `--spine-y: 54%` — raised from the original 64% so the taller below-spine cards don't clip off the bottom of the viewport | `index.html` `.timeline-track` |

---

## [P2] OVERVIEW — The Project Bifrost scroll

**Files:** `protected/index.html` (`#page-overview`, lines ~2232–2619) + `protected/bifrost.js`

A vertically scrolling container that holds six scroll-bound scenes. The Europe map sits as a fixed background behind them all and fades in/out with scroll position.

```
┌─ S1 HERO ──────────────┐
│  "Secure & Sovereign   │  ← Europe map fades behind
│   AI, hosted where it  │
│   belongs."            │
└────────────────────────┘
┌─ S2 ARCHITECTURE ──────┐
│  4 layer cards fall +  │  ← pinned, scrubbed
│  morph into 2x2 grid   │
└────────────────────────┘
┌─ S3 WORDS ─────────────┐
│  "...built with them,  │  ← words fly in
│   not just for them."  │
└────────────────────────┘
┌─ S4 BIFROST ARC ───────┐
│  🌈 aurora arc draws   │  ← "Introducing Project Bifrost"
│  "Project Bifrost"     │
└────────────────────────┘
┌─ S5 TREASURE MAP ──────┐
│  Community, Advisory   │  ← 3 stops on a winding path
│  Council, Pilot Proj.  │
└────────────────────────┘
┌─ S6 JOIN CTA + FOOTER ─┐
│  "Join us in shaping…" │  ← button → confirmation panel
│  [Proj] [Fenja] [Innov]│  ← 3-column footer
└────────────────────────┘
```

### S0 — Europe map background

The map stays static behind all six scenes. It does not rotate with scroll; it only fades.

| Label | What it is | Where |
|---|---|---|
| **P2 Europe map** | Static orthographic globe centered on Europe, sitting behind the hero | `index.html` line 2234 (`.overview-globe`) + CSS line 441 |
| **P2 map opacity ceiling** | Maximum map opacity (currently `0.42`) | `bifrost.js` — `MAP_MAX_OPACITY` constant |
| **P2 map fade curve** | Fully visible 0–20% scroll, fades to 0 by 80% scroll, fades back in when scrolling up | `bifrost.js` — `updateMapOpacity()` function |

### S1 — Hero

| Label | What it is | Where |
|---|---|---|
| **S1 eyebrow** | *"For regulated environments"* | `index.html` line 2229 |
| **S1 headline** | *"Fenja AI — Secure & **Sovereign,** hosted where it **belongs.**"* | `index.html` lines 2230–2233 |
| **S1 lede** | *"Fenja AI is a sovereign AI platform, enabling highly advanced AI capabilities hosted within the client's own secure infrastructure."* | `index.html` lines 2234–2236 |
| **S1 supported-by** | Innovationsfonden placeholder logo + *"Supported by"* strip | `index.html` lines 2244–2254 |
| **S1 scroll hint** | "Scroll →" indicator bottom-right | `index.html` lines 2255–2258 |
| **S1 hero styles** | Type scale, colours, grid positioning, left-column anchoring. The hero is hidden via `.js .hero-wrap { opacity: 0 }` until Bifrost boots, then fades in as a single block | `index.html` CSS `#hero`, `.hero-title`, `.hero-lede`, `.hero-foot`, `.hero-wrap` |
| **S1 hero animation** | Single 1.0s `power2.out` fade of the whole `.hero-wrap` on scene entry (replaces the earlier per-word slide-in) | `bifrost.js` — search `HERO — single overall fade-in` |

### S2 — Architecture stack

Four cards fall into a stack one at a time, then rearrange into a 2x2 grid. As they do, three copy panels crossfade on the left.

| Label | What it is | Where |
|---|---|---|
| **S2 copy panel A** | *"All the capabilities to solve business use cases with AI. Four layers. One architecture. Every piece yours to own."* | `index.html` lines 2284–2288 (`data-copy="0"`) |
| **S2 copy panel B** | *"Full client **control.** Complete **sovereignty.**"* + *"Nothing proprietary above the hardware…"* | `index.html` lines 2289–2292 (`data-copy="1"`) |
| **S2 copy panel C** | *"Built in **Denmark.** For **Europe.**"* + *"Engineered against the standards that matter here…"* | `index.html` lines 2293–2297 (`data-copy="2"`) |
| **S2 card 1 — Foundation** | *"AI — An **open-source** model, running on your **own hardware.**"* | `index.html` lines 2302–2310 (`data-layer="0"`) |
| **S2 card 2 — Knowledge** | *"The **vocabulary** of your business — **learned, retained.**"* | `index.html` lines 2312–2320 (`data-layer="1"`) |
| **S2 card 3 — Tools** | *"How the AIs **act** — not just what they **know.**"* | `index.html` lines 2322–2330 (`data-layer="2"`) |
| **S2 card 4 — Agents** | *"**Specialists**, **collaborating** to solve distinct tasks."* | `index.html` lines 2332–2340 (`data-layer="3"`) |
| **S2 stack animation** | The fall, stack, morph-to-grid scrub sequence | `bifrost.js` — search `ARCHITECTURE — two-phase scrubbed sequence` |
| **S2 card styles** | Shape, padding, per-layer illustration, eyebrow, grid-mode styles | `index.html` CSS `.layer-card`, `.card-box`, `.card-brain`, `.in-grid` |
| **S2 card illustrations** | Per-layer PNGs set via `--card-illust` custom property on `.card-brain`. AI → `ai.png`, Knowledge → `lightbulb - knowledge.png`, Tools → `blocs tools.png`, Agents → `agents.png`. Rendered as `background-image` (not mask) so each PNG shows in its own colours | `protected/fenja/illustrations/*.png` + per-layer overrides in `index.html` CSS |

### S3 — Words fly in

Sixteen individual words fly in from random directions as the user scrolls, assembling into one full sentence. Two words (`with` and `them,`) are given extra visual weight.

| Label | What it is | Where |
|---|---|---|
| **S3 full sentence** | *"But a platform for regulated organisations has to be built with them, not just for them."* | `index.html` lines 2361–2376 |
| **S3 highlight words** | `with`, `them,` — the two words with emphasis class `hi` | `index.html` lines 2371–2372 |
| **S3 word styles + animation** | Individual word reveal with scatter/scale/blur | `bifrost.js` — search `WORDS fly in`; CSS `.words`, `.w`, `.w.hi` |

### S4 — Bifrost aurora arc

A rainbow arc draws across the scene, then the "Project Bifrost" title fades in below it. This is the only place on the site where a gradient is used.

| Label | What it is | Where |
|---|---|---|
| **S4 eyebrow** | *"Introducing"* | `index.html` line 2441 |
| **S4 name** | *"**Project Bifrost**"* (split into two tokens — "Project" + "Bifrost") | `index.html` lines 2442–2445 |
| **S4 subtitle** | *"The bridge **between** an industrial-grade AI platform and the realities of regulated organisations — built **with** them, not just for them."* | `index.html` lines 2446–2448 |
| **S4 arc** | The rainbow arc that draws across the scene | `index.html` lines 2391–2436 — `<svg>` with `#arcHalo`, `#arcMain`, `#arcThin` |
| **S4 arc colors** | The aurora gradient stops (ochre → terracotta → indigo → heather) | `index.html` lines 2393–2408 — `<linearGradient>` stops |
| **S4 arc animation** | Stroke-dashoffset draw-in on scroll | `bifrost.js` — search `PROJECT BIFROST REVEAL` |

### S5 — Treasure map (3 stops)

A winding path draws down the scene, passing through three "stops" — each an illustrated card about one way to participate in Project Bifrost. Stops alternate left / right of the path.

| Label | What it is | Where |
|---|---|---|
| **S5 intro title** | *"What being part of **Project Bifrost** means"* | `index.html` lines 2469–2471 |
| **S5 intro lede** | *"Three ways to **shape**, to **influence**, and to **build with** the platform from the inside — a journey through what participation actually looks like."* | `index.html` lines 2472–2474 |
| **S5 winding path** | The bezier curve drawn between stops; rebuilt at runtime to pass through dot positions | `index.html` lines 2482–2494 (SVG shell); `bifrost.js` — `buildMapPath()` |
| **S5 intro stop** | *"Being part of **Project Bifrost** means **three** things — a community to shape the future with, a council to influence the platform through, and pilot projects that put it in your hands first."* (centered introductory copy) | `index.html` lines 2497–2504 |
| **S5 stop 1 — Community** | Eyebrow *"Be part of a"* + title ***"Community"*** + sub *"Shape the future together"* + body copy + illustration of six people in discussion | `index.html` lines 2507–2518; illustration `fenja/illustrations/community.svg` |
| **S5 stop 2 — Advisory Council** | Eyebrow *"Be part of an"* + title ***"Advisory Council"*** + sub *"Turn insight into influence"* + body copy + illustration of a man and a woman in conversation | `index.html` lines 2521–2532; illustration `fenja/illustrations/council.svg` |
| **S5 stop 3 — Pilot Projects** | Eyebrow *"Be part of"* + title ***"Pilot Projects"*** + sub *"Access the platform before others"* + body copy + illustration of two people at a computer | `index.html` lines 2535–2546; illustration `fenja/illustrations/pilot.svg` |
| **S5 path animation + per-stop reveals** | Draw-in of path; dot pops in + text/image fade on reach | `bifrost.js` — search `Treasure-map path draw` |
| **S5 styles** | Layout, alternating left/right stops, dots, path styling | `index.html` CSS `.map-intro`, `.map-canvas`, `.map-stop`, `.dot-anchor`, `.stop-*` |

### S6 — Join CTA + footer

The final scene is a large call-to-action headline with a single button. Clicking the button crossfades to a confirmation panel listing what happens next. Below, a three-column footer with brand marks.

| Label | What it is | Where |
|---|---|---|
| **S6 CTA eyebrow** | *"Ready?"* | `index.html` line 2565 |
| **S6 CTA headline** | *"Join us in shaping the future of **trusted sovereign AI.**"* | `index.html` lines 2566–2568 |
| **S6 CTA button text** | *"Join Project Bifrost"* | `index.html` lines 2569–2572 |
| **S6 CTA subtext** | *"Built in Denmark. Supported by the Innovation Fund."* | `index.html` line 2573 |
| **S6 confirmation eyebrow** | *"You're in"* | `index.html` line 2578 |
| **S6 confirmation headline** | *"Thank you for joining **Project Bifrost**."* | `index.html` lines 2579–2581 |
| **S6 confirmation list item 1** | *"The **Fenja AI team** will reach out to you shortly."* | `index.html` line 2583 |
| **S6 confirmation list item 2** | *"You'll receive an invitation to the **project portal** soon…"* | `index.html` line 2584 |
| **S6 confirmation list item 3** | *"We're currently setting the date for the **first advisory council meeting**…"* | `index.html` line 2585 |
| **S6 confirmation list item 4** | *"We'll be in touch shortly about your participation in the **pilot project**."* | `index.html` line 2586 |
| **S6 footer left — "Project Bifrost"** | Wordmark rendered in Newsreader with italic emphasis on "Bifrost" | `index.html` line 2593 |
| **S6 footer center — Fenja logo** | `<img>` pointing to `/fenja/fenja-wordmark-black.svg` | `index.html` lines 2595–2599 |
| **S6 footer right — Innovationsfonden** | Placeholder slanted-I mark + "nnovationsfonden" text (to be swapped for real asset) | `index.html` lines 2601–2612 |
| **S6 click handler** | CTA → confirmation crossfade + staggered checkmarks on list items. Also fires `POST /api/bifrost-join` (fire-and-forget, credentials:same-origin) so the click is logged in `bifrost_joins` | `bifrost.js` — search `SCENE 6 — Join section` |
| **S6 click tracking** | Every click of the CTA is appended to the `bifrost_joins` table (id, email, clicked_at, session_id). Read via `bin/joins.js list | summary | for | stats` | `src/db.js` (schema + `q.recordJoin`), `server.js` (`POST /api/bifrost-join`), `bin/joins.js` |

---

## [P3] ARCHIVE — tabular view

**Files:** `protected/index.html` (`#page-archive`, lines ~2622–2651) + `protected/timeline.js` archive-builder IIFE

A tabular view of the same 12 events from the timeline, sorted chronologically. Each row fades its background on hover.

| Label | What it is | Where |
|---|---|---|
| **P3 headline** | *"All twenty-three entries, in order of **publication.**"* (copy still references 23 — update if this becomes user-facing again) | `index.html` line 2625 |
| **P3 sub** | *"Dates, sources and plate numbers for every card in the catalog. Hover a row to lift it from the paper."* | `index.html` lines 2626–2629 |
| **P3 table** | 5 columns: №, Date, Register, Headline, Source | `index.html` table markup + `timeline.js` archive-builder IIFE |
| **P3 data** | Same `EVENTS` array as the timeline | `timeline.js` — `EVENTS` |
| **P3 footer** | *"Fenja AI · Field Notes, No. IV / Catalog closed 14 April 2026 / Page III of III"* | `index.html` lines 2644–2648 |

---

## Shared chrome — visible on all three pages

**Files:** `protected/index.html` (lines 2207 + 2653–2668) + `protected/timeline.js`

| Label | What it is | Where |
|---|---|---|
| **Site wordmark** | Top-left Fenja logo, 118px wide | `index.html` line 2207 |
| **Dot-nav tray** | Subtle gradient fade at the bottom edge of the viewport | `index.html` `.dot-nav-tray` |
| **Dot-nav** | Bottom-center "Timeline / Overview / Archive" page switcher | `index.html` lines 2653–2668 |
| **Dot-nav logic** | Page activation + bifrost lazy-init on first Overview visit | `timeline.js` — `activatePage()` function, lines 439–457 |

---

## Entrance — before login

**Files:** `public/entrance.html` + `public/entrance.js`

The only public-facing page. A single email field; on submit, if the email is on the invite list, advances to a welcome step. Otherwise shows an inline "not invited" message.

| Label | What it is | Where |
|---|---|---|
| **Entrance tagline** | *"Thank you for your commitment and willingness to contribute."* | `entrance.html` step-email `.tagline` |
| **Entrance email input** | The single input field with "your email" placeholder | `entrance.html` step-email `<form>` |
| **Entrance topographic currents** | Concentric-circle SVG background in the top-right | `entrance.js` — `drawCurrents()` IIFE |
| **Entrance error messages** | *"Please enter a valid email address."* / *"This email is not on the invite list."* / *"Too many attempts…"* | `entrance.js` — inside the submit handler |
| **Welcome title** | *"Thanks for your interest, **[Name].**"* (or *"Thank you for your **interest.**"* when no first name) — set by JS after a successful login | `entrance.js` — `setWelcomeTitle()` |
| **Welcome body, paragraph 1** | *"Thank you for joining and for your interest in enabling sovereign AI in Denmark and Europe. Project Bifrost is a deliberate effort to advance it — the conviction that how we build these systems, and where, will shape the next decades."* | `entrance.html` step-welcome `.welcome-body` (first one) |
| **Welcome body, paragraph 2** | *"What follows is a timeline: twelve moments that explain why this matters now, and — at the end — a note on how Fenja AI addresses it."* | `entrance.html` step-welcome `.welcome-body` (second one) |
| **"Learn more" button** | The button that routes to `/timeline` | `entrance.html` step-welcome `#welcome-continue` |
| **Welcome logo ghost** | Faint Fenja wordmark occupying the right half of the viewport | `entrance.html` `.welcome-logo` |

---

## Auth — server-side

**Files:** `src/auth.js` + `src/sessions.js` + `src/db.js` + `bin/invite.js`

For completeness — not UI, but labelled here so you can point at it when relevant.

| Label | What it is | Where |
|---|---|---|
| **Login endpoint** | `POST /auth/login` — accepts `{email}`, returns `{ok, firstName}` on success or `{error: "not_invited"}` with 403 | `src/auth.js` |
| **Login rate limit** | 30 attempts per IP per hour | `src/auth.js` — `rateLimit({…})` on `/login` route |
| **Logout endpoint** | `POST /auth/logout` — clears session cookie | `src/auth.js` |
| **Me endpoint** | `GET /auth/me` — returns `{email, firstName}` for current session or 401 | `src/auth.js` |
| **Invite CLI** | `node bin/invite.js add <email> [FirstName]` / `remove` / `list` | `bin/invite.js` |
| **Joins CLI** | `node bin/joins.js list` / `summary` / `for <email>` / `stats` — reads the CTA click log | `bin/joins.js` |
| **Invite schema** | `invites(email, first_name, invited_at, invited_by)` | `src/db.js` |
| **Bifrost joins schema** | `bifrost_joins(id, email, clicked_at, session_id)` — one row per CTA click, not per user | `src/db.js` |
| **Join tracking endpoint** | `POST /api/bifrost-join` (behind `requireAuth`) — called by S6 click handler | `server.js` |
| **Session duration** | 30 days | `src/sessions.js` — `SESSION_TTL_MS` |

---

## Design tokens — safe to change, affect everything cohesively

**File:** `protected/fenja/colors_and_type.css`

| Token | Value | Meaning |
|---|---|---|
| `--paper` / `--background` | `#faf6ee` | Primary paper background |
| `--ink` / `--on-surface` | `#383831` | Primary text colour |
| `--ink-soft` / `--on-surface-variant` | `#5f5e5e` | Muted text |
| `--ink-dim` / `--on-surface-muted` | `#8a887f` | Very muted text |
| `--walnut` / `--secondary` | `#785f53` | Primary accent (links, focus rings) |
| `--crimson` | `#8a3a2f` | Destructive / error |
| `--pigment-terracotta` | `#b96b58` | Warnings, S4 Bifrost accent |
| `--pigment-copper` | `#6d8c7c` | Success, organic / growth |
| `--pigment-ochre` | `#c29d59` | Cautions, tertiary |
| `--pigment-indigo` | `#5a6d83` | Info (only "blue" allowed) |
| `--pigment-heather` | `#8d7a85` | Categorical, supportive |
| Aurora gradient stops | ochre → terracotta → indigo → heather | S4 arc only |

Fonts: **Newsreader** (serif, for intent) and **Manrope** (sans, for execution). Both self-hosted as variable fonts in `protected/fenja/fonts/`.

The "no line" rule applies throughout: depth comes from tonal surface shifts, not 1px borders. Emphasis is applied via the Fenja rule — last keyword in Newsreader Bold Italic, followed by the absolute period.

---

## How to ask for changes

Pick a label from the tables above and tell me what you want changed. Examples:

- *"Change **S1 lede** to say …"*
- *"Swap the colour of the **S4 arc** from terracotta to copper"*
- *"Remove **S5 stop 2** entirely; renumber the path"*
- *"Add a 5th item to the **S6 confirmation list**"*
- *"Reorder: move **S3 Words** to come after **S4 Bifrost Arc**"*
- *"Make the **P2 Europe map** fully fade out by 40% scroll instead of 80%"*
- *"Change the **Welcome title** to read differently when the user has no first name"*
- *"Update **P1 events** entry #14 (the November 2024 rupture) to say …"*

If a change crosses multiple sections (for example, "add a fourth treasure map stop" or "reorganise the six scenes into five"), I'll lay out the pieces that need to move in a plan before writing code, so you can sign off.

If something you want to change isn't on this map, quote a phrase from the site and I'll find it.

---

## File index — where each label's code lives

```
protected/
├── index.html                          [P1], [P2 except animations], [P3], shared chrome
├── timeline.js                         [P1 events + globe + card builder], [P3 table builder],
│                                       dot-nav logic + bifrost lazy-init
├── bifrost.js                          All [P2] scene animations + Europe map fade
├── vendor/
│   ├── lenis.min.js                    Smooth scroll (for [P2])
│   ├── gsap.min.js                     Animation engine
│   ├── scrolltrigger.min.js            Scroll-bound timelines
│   ├── d3-array.min.js                 Globe math (used by timeline.js)
│   ├── d3-geo.min.js                   Globe projection
│   ├── topojson-client.min.js          World topology parser
│   └── countries-110m.json             World country shapes
└── fenja/
    ├── colors_and_type.css             Design tokens
    ├── fonts/                          Manrope + Newsreader (variable fonts)
    ├── fenja-wordmark-black.svg        Logo — used by site-mark + S6 footer
    └── illustrations/
        ├── community.svg               S5 stop 1
        ├── council.svg                 S5 stop 2
        └── pilot.svg                   S5 stop 3

public/
├── entrance.html                       Entrance + welcome steps (before login)
└── entrance.js                         Entrance form behaviour, welcome title logic

src/
├── auth.js                             /auth/login, /auth/logout, /auth/me
├── db.js                               SQLite schema + prepared queries
├── sessions.js                         Session cookie lifecycle
└── middleware.js                       rateLimit() + requireAuth()

bin/
├── invite.js                           CLI: add / remove / list invites
└── joins.js                            CLI: read the Join-CTA click log

server.js                               Entry point, CSP, routing
```
