customer-presentation/SITE_STRUCTURE_REFERENCE.md
Arlind Ukshini d5f578a581 update docs
2026-04-23 15:00:53 +02:00

334 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 ~739758) + `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 1466 |
| **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 ~22322619) + `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 020% 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 22302233 |
| **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 22342236 |
| **S1 supported-by** | Innovationsfonden placeholder logo + *"Supported by"* strip | `index.html` lines 22442254 |
| **S1 scroll hint** | "Scroll →" indicator bottom-right | `index.html` lines 22552258 |
| **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 22842288 (`data-copy="0"`) |
| **S2 copy panel B** | *"Full client **control.** Complete **sovereignty.**"* + *"Nothing proprietary above the hardware…"* | `index.html` lines 22892292 (`data-copy="1"`) |
| **S2 copy panel C** | *"Built in **Denmark.** For **Europe.**"* + *"Engineered against the standards that matter here…"* | `index.html` lines 22932297 (`data-copy="2"`) |
| **S2 card 1 — Foundation** | *"AI — An **open-source** model, running on your **own hardware.**"* | `index.html` lines 23022310 (`data-layer="0"`) |
| **S2 card 2 — Knowledge** | *"The **vocabulary** of your business — **learned, retained.**"* | `index.html` lines 23122320 (`data-layer="1"`) |
| **S2 card 3 — Tools** | *"How the AIs **act** — not just what they **know.**"* | `index.html` lines 23222330 (`data-layer="2"`) |
| **S2 card 4 — Agents** | *"**Specialists**, **collaborating** to solve distinct tasks."* | `index.html` lines 23322340 (`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 23612376 |
| **S3 highlight words** | `with`, `them,` — the two words with emphasis class `hi` | `index.html` lines 23712372 |
| **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 24422445 |
| **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 24462448 |
| **S4 arc** | The rainbow arc that draws across the scene | `index.html` lines 23912436 — `<svg>` with `#arcHalo`, `#arcMain`, `#arcThin` |
| **S4 arc colors** | The aurora gradient stops (ochre → terracotta → indigo → heather) | `index.html` lines 23932408 — `<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 24692471 |
| **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 24722474 |
| **S5 winding path** | The bezier curve drawn between stops; rebuilt at runtime to pass through dot positions | `index.html` lines 24822494 (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 24972504 |
| **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 25072518; 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 25212532; 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 25352546; 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 25662568 |
| **S6 CTA button text** | *"Join Project Bifrost"* | `index.html` lines 25692572 |
| **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 25792581 |
| **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 25952599 |
| **S6 footer right — Innovationsfonden** | Placeholder slanted-I mark + "nnovationsfonden" text (to be swapped for real asset) | `index.html` lines 26012612 |
| **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 ~26222651) + `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 26262629 |
| **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 26442648 |
---
## Shared chrome — visible on all three pages
**Files:** `protected/index.html` (lines 2207 + 26532668) + `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 26532668 |
| **Dot-nav logic** | Page activation + bifrost lazy-init on first Overview visit | `timeline.js``activatePage()` function, lines 439457 |
---
## 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
```