# project-bifrost — merge notes

Site 2 (the 6-scene editorial scroll) has been merged into the Timeline's
Overview page. Timeline and Archive pages are **untouched**. Auth, CSP, and
all of `src/`, `server.js`, `deploy/` are untouched.

## Deploy — what to rsync where

From the project root on your laptop, copy the contents of this `protected/`
folder into `project-bifrost/protected/` on the VPS, preserving structure:

```
protected/
├── index.html                     ← REPLACED (timeline + 6 Bifrost scenes)
├── timeline.js                    ← REPLACED (adds `.js` class + bifrost lazy-boot)
├── bifrost.js                     ← NEW
├── vendor/
│   ├── lenis.min.js               ← NEW
│   ├── gsap.min.js                ← NEW
│   └── scrolltrigger.min.js       ← NEW
│   (d3-array, d3-geo, topojson-client, countries-110m are UNCHANGED; do not overwrite)
└── fenja/
    └── illustrations/             ← NEW FOLDER
        ├── community.svg
        ├── council.svg
        └── pilot.svg
```

After `rsync`, run the usual:
```
sudo chown -R fenja:fenja /opt/fenja
sudo systemctl restart fenja
sudo journalctl -u fenja -n 20
```

## What changed, concretely

1. **`protected/index.html`** — rewrote `#page-overview` only.
   - Europe map (`.overview-globe`) stays as absolute-positioned background.
   - Added `<div id="overview-scroll">` — the new internal scroller. All six
     Bifrost scenes live inside it. Lenis + ScrollTrigger are wired to this
     element, never `window`.
   - Removed the four editorial paragraphs + meta-strip ("Notes on a quiet
     inheritance" / entries / period / editor) — replaced by the Bifrost
     scenes in scroll order.
   - Site-1 palette applied via `#page-overview { --ink: #383831; --paper:
     #faf6ee; ...; --aurora-*: site-1 Archival Pigments; }`. Tokens are
     scoped to `#page-overview` only — they do **not** leak to the
     timeline or archive pages.
   - All `Fraunces` references replaced with `Newsreader`.
   - Site-2's top-left brand mark, top-right meta chip, and right-edge
     progress rail removed (would clash with site 1's site-mark + dot-nav).
   - Footer centre logo: swapped inlined Fenja SVG for
     `<img src="/fenja/fenja-wordmark-black.svg">`.
   - Innovationsfonden remains the redrawn placeholder — swap when the
     real asset arrives.
   - Illustration references: the big PNG data URIs in site 2's CSS were
     replaced with three `url("/fenja/illustrations/*.svg")` references
     pointing to the new illustration files.
   - Timeline section (`#page-timeline`) and Archive section
     (`#page-archive`) are byte-identical to the previous version.

2. **`protected/timeline.js`** — two changes only.
   - Added `document.documentElement.classList.add('js')` at the top so
     site-2's `.js .some-element { opacity: 0 }` hide-before-reveal rules
     work. Harmless to timeline (nothing there uses `.js`).
   - Dot-nav click handler wrapped into `activatePage(targetId)`. When
     `targetId === 'page-overview'`, it calls `window.__bifrost.init()`
     after a 60ms delay (lets the page-activation transition start).
     `init()` is idempotent — subsequent activations just trigger a
     `ScrollTrigger.refresh()`.
   - The "Read the editor's note" button's existing behaviour
     (`document.querySelector('.dot-btn[data-target="page-overview"]').click()`)
     routes through `activatePage` now, so clicking the button both
     activates the overview **and** boots the bifrost scenes.

3. **`protected/bifrost.js`** (NEW) — single-file CSP-compliant module.
   - Exposes `window.__bifrost.init()`. No auto-exec.
   - Creates a `new Lenis({ wrapper: scroller, content: scroller.firstElementChild, ... })`
     scoped to `#overview-scroll`.
   - Registers a `ScrollTrigger.scrollerProxy(scroller, ...)` and sets
     `ScrollTrigger.defaults({ scroller })` so every ScrollTrigger the
     site-2 code registers targets the overview's internal scroll.
   - Europe-map opacity is driven from Lenis's scroll event:
     - `scrollTop = 0` → 0.42 opacity (site-1's original "active" value)
     - between `0.2 × vh` and `0.8 × vh` → ramps to 0
     - scrolling back up → fades back in
     - The map does **not** rotate with scroll — it's static.
   - Reduced-motion: short-circuits to "content visible, map at 0.42, no
     animations" and bails before registering any ScrollTriggers.
   - All site-2 scene logic (HERO reveal, pinned 4-card stack, word
     fly-ins, aurora arc draw-in, treasure-map path draw-in + per-stop
     reveals, Join CTA + confirmation crossfade) transplanted verbatim.

4. **`protected/vendor/{lenis,gsap,scrolltrigger}.min.js`** (NEW) — the
   three libraries extracted verbatim from site 2's inlined vendor blobs.
   Served with `defer` so they load and execute before `bifrost.js` and
   `timeline.js`. (Script order at the bottom of `index.html`:
   lenis → gsap → scrolltrigger → bifrost → timeline.)

5. **`protected/fenja/illustrations/{community,council,pilot}.svg`** (NEW)
   — the three illustrations you uploaded, renamed for site-2's slot
   names. ~1.7 MB each (SVG wrappers around embedded PNGs).

## What I did NOT touch

- `src/` (auth, db, mail, middleware, sessions) — byte-identical.
- `server.js` — byte-identical. The request-routing order, CSP headers,
  and `requireAuth` gate are preserved exactly.
- `deploy/` — byte-identical.
- `public/entrance.html`, `public/entrance.js` — byte-identical.
- `protected/fenja/colors_and_type.css` — byte-identical. (The Bifrost
  scenes' tokens live in `index.html`'s own `<style>` block, scoped
  to `#page-overview`.)
- `protected/fenja/fonts/*` — byte-identical.
- `protected/archive.html`, `protected/archive.js` — byte-identical.
- `protected/vendor/{d3-array,d3-geo,topojson-client,countries-110m}*` —
  byte-identical. The new vendor libs are added **alongside** these.

## Security posture

CSP is `script-src 'self'` per `server.js`. The merged page contains **zero**
inline `<script>` tags — verified. All JS is in separate `.js` files served
from `/vendor/...` or `/`.

Auth gate is unchanged: `requireAuth` runs before `express.static(protected)`,
so the merged page is fully gated by the session cookie. The protected
directory's structure didn't change.

## What to spot-check after deploy (from `CHECKLIST.md` section B + D)

Primary smoke test:
- [ ] Login flow still works: `/` → email → code → welcome → click "Learn
      more" button → timeline loads as before.
- [ ] Timeline page unchanged: 23 headlines, globe rotates with wheel
      scroll, "Read the editor's note" button appears when scrolled to
      the end.
- [ ] Click "Read the editor's note" → overview activates. Europe map
      fades in on the right. Left column shows the new site-2 hero
      headline: "Secure & **Sovereign** AI, hosted where it **belongs**."
- [ ] Scroll inside the overview page — Europe map fades out between
      ~20 % and ~80 % of the first viewport scroll.
- [ ] Scroll back up — Europe map fades back in.
- [ ] Keep scrolling past the hero — the 4 architecture layer cards fall
      in, stack, then rearrange into a 2×2 grid (pinned scrub).
- [ ] Words fly in one by one.
- [ ] Project Bifrost aurora arc draws across, "Project Bifrost" headline
      appears.
- [ ] Treasure-map with 3 stops: Community / Advisory Council / Pilot
      Projects. Path draws in as you scroll. Each stop's illustration
      fades in on reach.
- [ ] Join CTA visible. Clicking "Join Project Bifrost" crossfades to
      the confirmation panel with 4 list items.
- [ ] Three-column footer at the very bottom: "Project Bifrost" (left),
      Fenja AI logo (centre), Innovationsfonden placeholder (right).
- [ ] Click dot-nav "Archive" — the 23-row archive table still works.
- [ ] Click dot-nav "Timeline" — back to the horizontal-scroll timeline.
- [ ] DevTools console: no CSP violations, no 404s. Check that
      `/bifrost.js`, `/vendor/lenis.min.js`, `/vendor/gsap.min.js`,
      `/vendor/scrolltrigger.min.js`, and all three illustration SVGs
      return 200.
- [ ] Response headers on `/timeline` still include the full six security
      headers (CSP with `script-src 'self'`, etc.). `curl -I
      https://project-bifrost.fenja.ai/timeline` should look identical
      to before.

## Known limitations

- **Innovationsfonden logo** is still a hand-drawn placeholder. Swap when
  the real asset is ready — replace the SVG + span block in the
  `.foot-innov` div in `index.html` (search for `foot-innov`).
- **Illustration file sizes** are large (~1.7 MB each — SVG wrappers
  around embedded PNGs). The gated page is slow to first-paint the
  treasure-map if bandwidth is constrained. Consider exporting flat
  PNGs later and swapping the `url(...)` in the `#page-overview
  { --illust-*: ... }` block in `index.html`.
- **Scene 2 (architecture stack)** uses `pin` with `pinType: 'transform'`
  to work inside the nested scroller. This is the correct setting for
  overflow-auto containers but means the pinned card's `position` during
  pinning is transform-based. If layout looks off on Firefox or Safari
  specifically, the first thing to investigate is the pin behaviour.
- **Resize behaviour**: we consolidated site-2's two separate resize
  listeners into one debounced `ScrollTrigger.refresh()` 220ms after the
  last resize event. If you see ScrollTrigger positions drifting on
  fast/rapid resize, the de-duper can be tuned in `bifrost.js`
  (`setTimeout(scheduleRefresh, 220)`).
- **Performance**: inlining vendor libs in site 2 was ~135 KB. They're now
  served as three separate cacheable files (~135 KB uncompressed, ~50 KB
  gzipped). Nginx `gzip` should already cover them.
