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

9.6 KiB
Raw Blame History

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.