# 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 — `` with `#arcHalo`, `#arcMain`, `#arcThin` | | **S4 arc colors** | The aurora gradient stops (ochre → terracotta → indigo → heather) | `index.html` lines 2393–2408 — `` 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** | `` 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 `
` | | **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 [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 ```