customer-presentation/MERGE_NOTES.md
Arlind Ukshini f2f0f8a43e Merge Project Bifrost scenes into the Overview page
Six scroll-bound scenes (hero, architecture stack, words fly-in,
aurora arc, treasure-map, join CTA) now live inside page-overview,
above the existing 23-headline timeline. The Europe map stays as a
static background that fades with scroll.

- protected/index.html: rewrote #page-overview only; timeline and
  archive sections unchanged. Site-2 palette re-mapped to site-1
  Nordic Editorial tokens, Fraunces to Newsreader, tokens scoped
  to #page-overview.
- protected/timeline.js: dot-nav boots window.__bifrost.init()
  on first Overview activation. Added .js class on documentElement.
- protected/bifrost.js (new): Lenis + ScrollTrigger wired to the
  overview's internal scroller via scrollerProxy; drives Europe
  map opacity on scroll.
- protected/vendor/{lenis,gsap,scrolltrigger}.min.js (new):
  extracted from site-2's inlined vendor blobs; CSP-compliant.
- protected/fenja/illustrations/{community,council,pilot}.svg
  (new): treasure-map stop images.

No changes to src/, server.js, deploy/, or public/. CSP stays
strict (script-src 'self'); zero inline scripts added. Auth gate
and session model untouched.
2026-04-22 17:48:44 +02:00

189 lines
9.6 KiB
Markdown
Raw 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 — 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.