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