22 KiB
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 23 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.
| 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 23 headline cards. Each has date, kind, accent, hed, body, source |
timeline.js — EVENTS array, lines 4–102 |
| P1 card styles | Card layout, above/below-spine positioning, colour accents per accent value |
index.html CSS .evt, .accent-* |
| P1 year ticks | "2022", "2023", … on the spine between groups of cards | timeline.js — buildTimeline(), year-tick loop |
| P1 "Read the editor's note" button | Bottom-right, appears near the end of the catalog; fires dot-nav switch to Overview | index.html line 747 (text) + .continue-btn CSS + timeline.js click handler |
[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 2246 |
| S1 headline | "Secure & Sovereign AI, hosted where it belongs." | index.html lines 2247–2250 |
| S1 lede | "Enabling highly advanced AI capabilities hosted within the client's own secure infrastructure." | index.html lines 2251–2253 |
| S1 supported-by | Innovationsfonden placeholder logo + "Supported by" strip | index.html lines 2258–2267 |
| S1 scroll hint | "Scroll →" indicator bottom-right | index.html lines 2268–2271 |
| S1 hero styles | Type scale, colours, grid positioning, left-column anchoring | index.html CSS #hero, .hero-title, .hero-lede, .hero-foot |
| S1 hero animation | Word-by-word reveal of the headline on scene entry | bifrost.js — search HERO — staggered intro |
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, "brain" illustration, eyebrow, grid-mode styles | index.html CSS .layer-card, .card-box, .card-brain, .in-grid |
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 | bifrost.js — search SCENE 6 — Join section |
[P3] ARCHIVE — 23-row table
Files: protected/index.html (#page-archive, lines ~2622–2651) + protected/timeline.js archive-builder IIFE
A tabular view of the same 23 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." | 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 (lines 406–420) |
| 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: twenty-three moments that explain why this matters now, and what the path looks like." | 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 |
| Invite schema | invites(email, first_name, invited_at, invited_by) |
src/db.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
server.js Entry point, CSP, routing