- "Meet Fenja AI" eyebrow removed — headline + wordmark pairing is enough to anchor the scene. - Type scale reduced inside #hero: title clamp(1.65rem, 4vw, 3.4rem) (was clamp(2rem, 5.1vw, 4.4rem) via --step-hero), lede clamp(1rem, 1.7vw, 1.4rem). Reads calmer and keeps the headline from overshadowing the lede. - .hero-copy gets padding-left: clamp(2rem, 8vw, 8rem) so the text block floats in the same way the wordmark does in its column — symmetric breathing room on both sides of the viewport rather than the copy hugging the left edge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2972 lines
100 KiB
HTML
2972 lines
100 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<title>A Catalog of Sovereignty — 2022–2026</title>
|
||
<link rel="stylesheet" href="/fenja/colors_and_type.css" />
|
||
<script src="/vendor/d3-array.min.js"></script>
|
||
<script src="/vendor/d3-geo.min.js"></script>
|
||
<script src="/vendor/topojson-client.min.js"></script>
|
||
<style>
|
||
@view-transition { navigation: auto; }
|
||
|
||
:root{
|
||
--paper: #faf6ee;
|
||
--paper-high: #fffcf7;
|
||
--paper-mid: #f4efe2;
|
||
--paper-low: #ece5d2;
|
||
--ink: #383831;
|
||
--ink-soft: #5f5e5e;
|
||
--ink-dim: #8a887f;
|
||
|
||
--copper: #6d8c7c; /* copper green */
|
||
--ochre: #c29d59;
|
||
--terracotta: #b96b58;
|
||
--crimson: #8a3a2f; /* deep crimson */
|
||
|
||
--ease: cubic-bezier(0.2, 0, 0, 1);
|
||
--dur: 240ms;
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; }
|
||
|
||
html, body {
|
||
margin: 0; padding: 0;
|
||
height: 100%;
|
||
background: var(--paper);
|
||
color: var(--ink);
|
||
font-family: "Manrope", system-ui, sans-serif;
|
||
overflow: hidden;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
body {
|
||
/* Subtle tonal shift across the entire surface — not a gradient on chrome,
|
||
just the paper catching light. */
|
||
background:
|
||
radial-gradient(1200px 800px at 18% 45%, #fffcf7 0%, var(--paper) 55%, #f4efe2 100%);
|
||
view-transition-name: paper;
|
||
}
|
||
|
||
/* ───── Page scaffolding ───── */
|
||
.page {
|
||
position: fixed; inset: 0;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 380ms var(--ease);
|
||
will-change: opacity;
|
||
}
|
||
.page.is-active {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
/* ───────── Site wordmark — top-left masthead, clickable "home" link ───────── */
|
||
.site-mark,
|
||
.site-mark:link,
|
||
.site-mark:visited,
|
||
.site-mark:hover,
|
||
.site-mark:active,
|
||
.site-mark:focus,
|
||
.site-mark:focus-visible {
|
||
text-decoration: none;
|
||
border-bottom: 0;
|
||
}
|
||
.site-mark {
|
||
position: fixed;
|
||
top: 28px;
|
||
left: 36px;
|
||
width: 118px;
|
||
height: auto;
|
||
z-index: 50;
|
||
opacity: 0.85;
|
||
display: block;
|
||
cursor: pointer;
|
||
transition: opacity var(--dur, 200ms) var(--ease, ease);
|
||
}
|
||
.site-mark img {
|
||
display: block;
|
||
width: 100%;
|
||
height: auto;
|
||
pointer-events: none; /* keeps clicks on the anchor, not the image */
|
||
}
|
||
.site-mark:hover,
|
||
.site-mark:focus-visible {
|
||
opacity: 1;
|
||
}
|
||
.site-mark:focus-visible {
|
||
outline: 2px solid var(--walnut, #785f53);
|
||
outline-offset: 6px;
|
||
}
|
||
@media (max-width: 720px) {
|
||
.site-mark { width: 90px; top: 20px; left: 22px; }
|
||
}
|
||
|
||
/* Page overline title — large, sits in the upper third of the viewport
|
||
so the long page-sub body below it has room without clipping. */
|
||
.page-title {
|
||
position: absolute;
|
||
left: 80px; top: 22vh;
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-weight: 400;
|
||
font-size: 54px;
|
||
letter-spacing: -0.022em;
|
||
color: var(--ink);
|
||
line-height: 1.08;
|
||
z-index: 15;
|
||
max-width: 820px;
|
||
text-wrap: pretty;
|
||
opacity: 1;
|
||
transition: opacity 520ms var(--ease), transform 520ms var(--ease);
|
||
}
|
||
.page-title em {
|
||
font-style: italic; font-weight: 700;
|
||
}
|
||
.page-title + .page-sub {
|
||
position: absolute;
|
||
left: 80px; top: calc(22vh + 160px);
|
||
max-width: 720px;
|
||
/* Upright serif editorial body, ink colour, 23px (about 15%
|
||
larger than the welcome-page body) so the long intro reads
|
||
at ease. Only the <em> spans carry italics for emphasis. */
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-weight: 400;
|
||
font-size: 23px;
|
||
line-height: 1.52;
|
||
color: var(--ink);
|
||
z-index: 15;
|
||
opacity: 1;
|
||
transition: opacity 520ms var(--ease), transform 520ms var(--ease);
|
||
}
|
||
.page-sub em { font-style: italic; font-weight: 700; color: var(--ink); }
|
||
/* Final sentence of the front-matter is pulled up in crimson so the
|
||
rhetorical question reads as the emphatic beat of the block. Applied
|
||
to the whole sentence, not just the <em> on "Washington?", so the
|
||
colour change cues the reader's eye before they reach the italic. */
|
||
.page-sub-accent,
|
||
.page-sub-accent em { color: var(--crimson); }
|
||
/* Once the timeline has been advanced, the front matter steps aside */
|
||
.page-timeline.is-scrolled .page-title,
|
||
.page-timeline.is-scrolled .page-sub {
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transform: translateY(-12px);
|
||
}
|
||
|
||
/* ───────── First-load scroll hint ─────────
|
||
Right-edge prompt telling the reader to begin scrolling the
|
||
horizontal timeline. Vertically centered, horizontal layout
|
||
(label then arrow), editorial ink colour. Arrow pulses
|
||
rightward to hint at the scroll direction. Fades out as soon
|
||
as the reader advances — same `.is-scrolled` class the
|
||
front-matter uses. */
|
||
.timeline-scroll-hint {
|
||
position: absolute;
|
||
right: clamp(32px, 3.8vw, 72px);
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
z-index: 20;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 22px;
|
||
color: var(--ink);
|
||
pointer-events: none;
|
||
opacity: 0;
|
||
animation: ts-fade-in 700ms 500ms var(--ease) forwards;
|
||
transition: opacity 360ms var(--ease), transform 360ms var(--ease);
|
||
}
|
||
.ts-label {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-style: italic;
|
||
font-size: clamp(22px, 2.1vw, 30px);
|
||
letter-spacing: -0.005em;
|
||
line-height: 1;
|
||
white-space: nowrap;
|
||
}
|
||
.ts-arrow {
|
||
width: clamp(84px, 8vw, 120px);
|
||
height: auto;
|
||
color: var(--ink);
|
||
animation: ts-arrow-nudge 1800ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||
}
|
||
@keyframes ts-fade-in {
|
||
from { opacity: 0; transform: translate(14px, -50%); }
|
||
to { opacity: 1; transform: translate(0, -50%); }
|
||
}
|
||
@keyframes ts-arrow-nudge {
|
||
0%, 100% { transform: translateX(0); opacity: 0.9; }
|
||
50% { transform: translateX(14px); opacity: 1; }
|
||
}
|
||
/* Fade out on the very first wheel tick (`.hint-dismissed` is set
|
||
in timeline.js's onWheel handler, independent of the 40px
|
||
`.is-scrolled` threshold so the hint leaves instantly).
|
||
`animation: none` is required to override the entry ts-fade-in
|
||
animation (which has forwards fill-mode and outranks this rule
|
||
otherwise); ts-arrow-nudge is also stopped so the pulse doesn't
|
||
keep ticking behind the fade. */
|
||
.page-timeline.hint-dismissed .timeline-scroll-hint {
|
||
animation: none;
|
||
opacity: 0;
|
||
transform: translate(14px, -50%);
|
||
}
|
||
.page-timeline.hint-dismissed .ts-arrow {
|
||
animation: none;
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.timeline-scroll-hint { animation: none; opacity: 1; }
|
||
.ts-arrow { animation: none; }
|
||
}
|
||
|
||
/* ───────── Dot-nav ─────────
|
||
5px dots, filled when active, outlined ring otherwise. Labels hidden
|
||
by default and appear as a floating tooltip above the dot on hover.
|
||
|
||
The `.dot-nav-tray` (bottom paper-fade behind the nav) is still
|
||
declared but suppressed on #page-overview so the S6 footer reads
|
||
as a hard terminus — see the #page-overview.is-active rule further
|
||
down. */
|
||
.dot-nav-tray {
|
||
position: fixed;
|
||
left: 0; right: 0; bottom: 0;
|
||
height: 110px;
|
||
z-index: 35;
|
||
pointer-events: none;
|
||
background: linear-gradient(to bottom,
|
||
rgba(250,246,238,0) 0%,
|
||
rgba(250,246,238,0.88) 45%,
|
||
rgba(250,246,238,0.98) 100%);
|
||
transition: opacity var(--dur) var(--ease);
|
||
}
|
||
/* Fade the tray away on the Overview page so the S6 footer meets the
|
||
bottom of the viewport cleanly without a paper wash over the logos. */
|
||
body:has(#page-overview.is-active) .dot-nav-tray { opacity: 0; }
|
||
|
||
.dot-nav {
|
||
position: fixed;
|
||
bottom: 36px; left: 50%;
|
||
transform: translateX(-50%);
|
||
display: flex; gap: 22px;
|
||
z-index: 40;
|
||
}
|
||
.dot-btn {
|
||
all: unset;
|
||
position: relative;
|
||
padding: 10px; /* invisible hit target — the dot itself is 5px */
|
||
cursor: pointer;
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.dot-btn .dot {
|
||
width: 10px; height: 10px;
|
||
border-radius: 50%;
|
||
background: transparent;
|
||
box-shadow: inset 0 0 0 1.5px var(--ink-dim); /* outlined ring, default */
|
||
transition: background var(--dur) var(--ease),
|
||
box-shadow var(--dur) var(--ease);
|
||
}
|
||
.dot-btn:hover .dot {
|
||
box-shadow: inset 0 0 0 1.5px var(--ink);
|
||
}
|
||
.dot-btn.is-active .dot {
|
||
background: var(--ink); /* filled ink, active */
|
||
box-shadow: inset 0 0 0 1.5px var(--ink);
|
||
}
|
||
|
||
/* Label tooltip — rises above the dot on hover or keyboard focus. */
|
||
.dot-btn .label {
|
||
position: absolute;
|
||
bottom: calc(100% - 6px); /* sit just above the hit area */
|
||
left: 50%;
|
||
transform: translate(-50%, 4px);
|
||
background: #fffcf7;
|
||
color: var(--ink);
|
||
font-size: 10.5px;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
font-weight: 500;
|
||
font-family: "Manrope", system-ui, sans-serif;
|
||
padding: 7px 11px;
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
opacity: 0;
|
||
box-shadow:
|
||
0 0 0 0.5px rgba(56,56,49,0.08),
|
||
0 10px 18px -10px rgba(56,56,49,0.2),
|
||
0 2px 6px -3px rgba(56,56,49,0.08);
|
||
transition: opacity var(--dur) var(--ease),
|
||
transform var(--dur) var(--ease);
|
||
}
|
||
.dot-btn:hover .label,
|
||
.dot-btn:focus-visible .label {
|
||
opacity: 1;
|
||
transform: translate(-50%, 0);
|
||
}
|
||
|
||
/* ───────── Globe ghost ───────── */
|
||
.globe-wrap {
|
||
position: absolute;
|
||
/* 15% larger than the original 58vw ≈ 66.7vw.
|
||
Shifted ~20% of its width toward the page center: from left:-8%
|
||
to roughly left:+5%. */
|
||
left: 5%; top: 0;
|
||
width: 66.7vw;
|
||
height: 100%;
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
opacity: 0.5;
|
||
transition: opacity 280ms var(--ease);
|
||
/* Mask top and fade the bottom third so the timeline rests on clean paper */
|
||
-webkit-mask-image: linear-gradient(to bottom,
|
||
transparent 0%, #000 22%, #000 58%, transparent 72%);
|
||
mask-image: linear-gradient(to bottom,
|
||
transparent 0%, #000 22%, #000 58%, transparent 72%);
|
||
}
|
||
.globe-wrap svg {
|
||
width: 100%; height: 100%;
|
||
display: block;
|
||
}
|
||
|
||
/* ───────── Timeline ───────── */
|
||
.timeline-viewport {
|
||
position: absolute; inset: 0;
|
||
z-index: 5;
|
||
}
|
||
|
||
.timeline-track {
|
||
position: absolute;
|
||
top: 0; left: 0; height: 100%;
|
||
will-change: transform;
|
||
transform: translate3d(0,0,0);
|
||
display: flex; align-items: center;
|
||
padding: 0 120px;
|
||
--spine-y: 54%;
|
||
}
|
||
|
||
.spine {
|
||
position: absolute;
|
||
top: var(--spine-y); left: 0;
|
||
height: 1px;
|
||
width: 100%;
|
||
background: linear-gradient(to right,
|
||
transparent 0,
|
||
rgba(56,56,49,0.22) 60px,
|
||
rgba(56,56,49,0.22) calc(100% - 60px),
|
||
transparent);
|
||
z-index: 2;
|
||
}
|
||
|
||
.year-tick {
|
||
position: absolute;
|
||
top: var(--spine-y);
|
||
transform: translate(-50%, -50%);
|
||
display: flex; flex-direction: column; align-items: center;
|
||
gap: 10px;
|
||
z-index: 3;
|
||
color: var(--ink-dim);
|
||
}
|
||
.year-tick::before {
|
||
content: "";
|
||
display: block;
|
||
width: 1px; height: 28px;
|
||
background: rgba(56,56,49,0.28);
|
||
}
|
||
.year-tick .y {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-style: italic;
|
||
font-size: 22px;
|
||
color: var(--ink-soft);
|
||
letter-spacing: 0;
|
||
font-weight: 400;
|
||
}
|
||
|
||
/* Card */
|
||
.evt {
|
||
position: absolute;
|
||
width: 640px;
|
||
padding: 32px 40px 36px;
|
||
/* Two-column editorial layout: headline on the left (15% wider),
|
||
body paragraph + source stacked on the right. Keeps cards shorter
|
||
so they don't clip below the timeline spine. */
|
||
display: grid;
|
||
grid-template-columns: 1.15fr 0.85fr;
|
||
grid-template-areas:
|
||
"head body"
|
||
"head source";
|
||
column-gap: 32px;
|
||
row-gap: 14px;
|
||
background: var(--paper-high);
|
||
/* Tonal surface shifts instead of 1px borders */
|
||
box-shadow:
|
||
0 0 0 0.5px rgba(56,56,49,0.05),
|
||
0 14px 28px -18px rgba(56,56,49,0.18),
|
||
0 2px 6px -3px rgba(56,56,49,0.08);
|
||
color: var(--ink);
|
||
opacity: 0;
|
||
/* Pop-in: small scale + downward lift for a more tactile entrance */
|
||
transform: translateY(28px) scale(0.96);
|
||
transform-origin: center top;
|
||
}
|
||
.evt.above {
|
||
transform: translateY(-28px) scale(0.96);
|
||
transform-origin: center bottom;
|
||
}
|
||
/* Only animate after first paint — prevents the initial card from
|
||
getting stuck at opacity 0 while the transition starts pre-layout. */
|
||
.evt.can-animate {
|
||
transition:
|
||
opacity 640ms var(--ease),
|
||
transform 640ms cubic-bezier(0.16, 1, 0.3, 1),
|
||
box-shadow 320ms var(--ease);
|
||
}
|
||
.evt.is-near {
|
||
opacity: 1;
|
||
transform: translateY(0) scale(1);
|
||
}
|
||
.evt.above { bottom: calc(100% - var(--spine-y) + 48px); }
|
||
.evt.below { top: calc(var(--spine-y) + 48px); }
|
||
|
||
/* Connector from card to spine */
|
||
.evt::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 52px;
|
||
width: 1px;
|
||
background: rgba(56,56,49,0.28);
|
||
}
|
||
.evt.above::after { top: 100%; height: 40px; }
|
||
.evt.below::after { bottom: 100%; height: 40px; }
|
||
|
||
/* Node on the spine — tiny dot */
|
||
.evt .node {
|
||
position: absolute;
|
||
left: 46px;
|
||
width: 13px; height: 13px;
|
||
border-radius: 50%;
|
||
background: var(--paper-high);
|
||
z-index: 1;
|
||
}
|
||
.evt.above .node { top: calc(100% + 40px - 6px); }
|
||
.evt.below .node { bottom: calc(100% + 40px - 6px); }
|
||
.evt .node::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 3.5px;
|
||
border-radius: 50%;
|
||
background: var(--ink-soft);
|
||
}
|
||
|
||
.evt[data-accent="copper"] .node::after { background: var(--copper); }
|
||
.evt[data-accent="ochre"] .node::after { background: var(--ochre); }
|
||
.evt[data-accent="terracotta"] .node::after { background: var(--terracotta); }
|
||
.evt[data-accent="crimson"] .node::after { background: var(--crimson); }
|
||
|
||
.evt .tag-row {
|
||
display: flex; gap: 12px; align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
.evt .date {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-style: italic;
|
||
font-size: 13px;
|
||
color: var(--ink-soft);
|
||
letter-spacing: 0;
|
||
}
|
||
.evt .kind {
|
||
font-size: 9px;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-dim);
|
||
font-weight: 600;
|
||
}
|
||
.evt[data-accent="copper"] .kind { color: var(--copper); }
|
||
.evt[data-accent="ochre"] .kind { color: var(--ochre); }
|
||
.evt[data-accent="terracotta"] .kind { color: var(--terracotta); }
|
||
.evt[data-accent="crimson"] .kind { color: var(--crimson); }
|
||
|
||
.evt h3 {
|
||
grid-area: head;
|
||
align-self: start;
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-weight: 400;
|
||
font-size: 41px;
|
||
line-height: 1.12;
|
||
letter-spacing: -0.015em;
|
||
color: var(--ink);
|
||
margin: 0;
|
||
text-wrap: pretty;
|
||
}
|
||
/* Bold-italic emphasis in headlines gets the red accent so each card
|
||
carries a consistent touch of colour while staying editorial. */
|
||
.evt h3 em {
|
||
font-style: italic;
|
||
font-weight: 700;
|
||
color: var(--crimson);
|
||
}
|
||
|
||
.evt p {
|
||
grid-area: body;
|
||
margin: 0;
|
||
font-size: 16px;
|
||
line-height: 1.5;
|
||
color: var(--ink-soft);
|
||
text-wrap: pretty;
|
||
align-self: start;
|
||
}
|
||
|
||
.evt .source {
|
||
grid-area: source;
|
||
align-self: end;
|
||
margin-top: 0;
|
||
font-size: 9.5px;
|
||
letter-spacing: 0.2em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-dim);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ───────── Continue button ─────────
|
||
Right-side anchor, vertically centered so readers crossing the
|
||
timeline can't miss it. Larger editorial block with a circular
|
||
icon on the left and the label on the right. Breath animation
|
||
translates horizontally only; `translateY(-50%)` is applied via
|
||
`top: 50%` centering and kept stable across keyframes. */
|
||
.continue-btn {
|
||
all: unset;
|
||
position: absolute;
|
||
right: 72px;
|
||
top: 50%;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 28px;
|
||
padding: 32px 44px;
|
||
/* Inverted palette — crimson field, paper ink — so the button
|
||
pops out against the timeline's warm paper background. */
|
||
background: var(--crimson);
|
||
color: var(--paper);
|
||
cursor: pointer;
|
||
z-index: 30;
|
||
opacity: 0;
|
||
transform: translate(36px, -50%);
|
||
pointer-events: none;
|
||
box-shadow:
|
||
0 0 0 0.5px rgba(138,58,47,0.22),
|
||
0 28px 52px -20px rgba(138,58,47,0.45),
|
||
0 4px 12px -5px rgba(138,58,47,0.24);
|
||
transition:
|
||
opacity 520ms var(--ease),
|
||
transform 520ms var(--ease),
|
||
box-shadow var(--dur) var(--ease),
|
||
background var(--dur) var(--ease);
|
||
}
|
||
.continue-btn.is-visible {
|
||
opacity: 1;
|
||
transform: translate(0, -50%);
|
||
pointer-events: auto;
|
||
animation: continue-breath 2800ms cubic-bezier(0.2, 0, 0, 1) infinite;
|
||
}
|
||
@keyframes continue-breath {
|
||
0%, 100% { transform: translate(0, -50%); }
|
||
50% { transform: translate(6px, -50%); }
|
||
}
|
||
.continue-btn:hover {
|
||
/* Slight darken on hover so the inverted card still signals it's interactive. */
|
||
background: #7a3229;
|
||
box-shadow:
|
||
0 0 0 0.5px rgba(122,50,41,0.32),
|
||
0 34px 60px -20px rgba(122,50,41,0.55),
|
||
0 5px 14px -5px rgba(122,50,41,0.32);
|
||
}
|
||
.continue-btn .c-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 72px;
|
||
height: 72px;
|
||
color: var(--paper);
|
||
flex: 0 0 auto;
|
||
transition: transform var(--dur) var(--ease);
|
||
}
|
||
.continue-btn .c-icon svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
.continue-btn:hover .c-icon {
|
||
transform: translateX(4px);
|
||
}
|
||
.continue-btn .c-label {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-size: 36px;
|
||
font-weight: 400;
|
||
letter-spacing: -0.015em;
|
||
color: var(--paper);
|
||
line-height: 1.08;
|
||
max-width: 13ch;
|
||
}
|
||
.continue-btn .c-label em {
|
||
font-style: italic;
|
||
font-weight: 700;
|
||
/* Italic emphasis stays on paper colour so it reads against the crimson field. */
|
||
color: var(--paper);
|
||
}
|
||
|
||
/* ───────── Overview page ───────── */
|
||
|
||
/* Topography layer — concentric ring pattern, parallax-scrolled,
|
||
sitting behind the Europe map. Reads as a visual sibling of the
|
||
entrance page's "currents" pattern but rotated and repositioned so
|
||
it doesn't look like a duplicate. The SVG contents are drawn at
|
||
runtime by drawTopography() in bifrost.js.
|
||
|
||
Transform is driven by JS from Lenis's scroll position (parallax
|
||
speed 0.15× — very slow). z-index 0 so it sits behind the map
|
||
(z-index 1) and content (z-index 2+). */
|
||
.overview-topography {
|
||
position: absolute; inset: 0;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
overflow: hidden;
|
||
opacity: 0;
|
||
transition: opacity 900ms var(--ease);
|
||
}
|
||
.overview-topography svg {
|
||
position: absolute;
|
||
/* Offset to the opposite corner of the entrance-page currents
|
||
(which sit top-right). Here we anchor bottom-left and extend
|
||
well beyond the viewport so parallax translation never reveals
|
||
an edge. */
|
||
left: -20vw;
|
||
top: -10vh;
|
||
width: 140vw;
|
||
height: 140vh;
|
||
display: block;
|
||
/* Rotate 40° so the rings don't read as an exact copy of the
|
||
entrance's pattern; the viewer registers this as "related but
|
||
distinct". */
|
||
transform: rotate(40deg);
|
||
transform-origin: 50% 50%;
|
||
will-change: transform;
|
||
}
|
||
.page-overview.is-active .overview-topography {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Globe background behind the overview — same SVG style as the timeline's,
|
||
but centered on Europe. It begins at the timeline's size/position so
|
||
that when the page is entered, the CSS transition zooms it into place. */
|
||
.overview-globe {
|
||
position: absolute; inset: 0;
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
overflow: hidden;
|
||
/* Soft fade at top + bottom so the paper reads as the surface, not the sphere */
|
||
-webkit-mask-image: linear-gradient(to bottom,
|
||
transparent 0%, #000 10%, #000 82%, transparent 100%);
|
||
mask-image: linear-gradient(to bottom,
|
||
transparent 0%, #000 10%, #000 82%, transparent 100%);
|
||
}
|
||
.overview-globe svg {
|
||
position: absolute;
|
||
left: 65%; top: 55%;
|
||
/* Smaller than before — 92vmax is enough to show Europe at the framing we want,
|
||
and it paints fast enough not to block the page fade-in. */
|
||
width: 92vmax;
|
||
height: 92vmax;
|
||
max-width: none;
|
||
transform: translate(-50%, -50%) scale(0.78);
|
||
transform-origin: 50% 50%;
|
||
opacity: 0.22;
|
||
transition:
|
||
transform 1200ms cubic-bezier(0.22, 1, 0.36, 1),
|
||
opacity 900ms var(--ease);
|
||
}
|
||
/* When the overview page becomes active, zoom onto Europe */
|
||
.page-overview.is-active .overview-globe svg {
|
||
transform: translate(-50%, -50%) scale(1.35);
|
||
opacity: 0.42;
|
||
}
|
||
|
||
.overview {
|
||
position: absolute; inset: 0;
|
||
overflow: auto;
|
||
padding: 160px 80px 180px;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: rgba(56,56,49,0.18) transparent;
|
||
z-index: 5;
|
||
}
|
||
.overview .col-wrap {
|
||
max-width: 1280px; margin: 0 auto;
|
||
/* Text on the left; globe occupies the right half of the spread. */
|
||
display: grid;
|
||
grid-template-columns: minmax(420px, 560px) 1fr;
|
||
column-gap: 80px;
|
||
row-gap: 24px;
|
||
align-items: start;
|
||
}
|
||
.overview h1 {
|
||
grid-column: 1;
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-weight: 400;
|
||
font-size: 56px;
|
||
line-height: 1.05;
|
||
letter-spacing: -0.025em;
|
||
margin: 0 0 18px 0;
|
||
text-wrap: balance;
|
||
color: var(--ink);
|
||
}
|
||
.overview h1 em {
|
||
font-style: italic; font-weight: 700;
|
||
}
|
||
.overview .lede {
|
||
grid-column: 1;
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-style: italic;
|
||
font-size: 20px;
|
||
line-height: 1.5;
|
||
color: var(--ink-soft);
|
||
max-width: 780px;
|
||
margin-bottom: 28px;
|
||
}
|
||
.overview .rule {
|
||
grid-column: 1;
|
||
height: 1px;
|
||
background: rgba(56,56,49,0.18);
|
||
margin: 14px 0 8px 0;
|
||
}
|
||
.overview p {
|
||
grid-column: 1;
|
||
font-size: 15px;
|
||
line-height: 1.7;
|
||
color: var(--ink);
|
||
margin: 0 0 14px 0;
|
||
text-wrap: pretty;
|
||
}
|
||
.overview p.drop::first-letter {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-weight: 700;
|
||
font-size: 58px;
|
||
line-height: 0.9;
|
||
float: left;
|
||
padding: 4px 10px 0 0;
|
||
color: var(--ink);
|
||
}
|
||
.overview .meta-strip {
|
||
grid-column: 1;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 28px 40px;
|
||
margin-top: 32px;
|
||
padding-top: 24px;
|
||
border-top: 0;
|
||
background:
|
||
linear-gradient(to right, rgba(56,56,49,0.18), rgba(56,56,49,0.18)) top / 100% 1px no-repeat;
|
||
}
|
||
.overview .meta-strip .cell {
|
||
display: flex; flex-direction: column; gap: 8px;
|
||
}
|
||
.overview .meta-strip .k {
|
||
font-size: 10.5px;
|
||
letter-spacing: 0.24em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-dim);
|
||
font-weight: 600;
|
||
}
|
||
.overview .meta-strip .v {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-size: 22px;
|
||
letter-spacing: -0.01em;
|
||
color: var(--ink);
|
||
}
|
||
.overview .meta-strip .v em { font-style: italic; font-weight: 700; }
|
||
|
||
/* Short-viewport safety: collapse the page-title block so cards never collide */
|
||
@media (max-height: 620px) {
|
||
.page-title { font-size: 36px; max-width: 640px; top: 18vh; }
|
||
.page-title + .page-sub { font-size: 21px; top: calc(18vh + 160px); }
|
||
}
|
||
@media (max-height: 500px) {
|
||
.page-title { display: none; }
|
||
.page-sub { display: none; }
|
||
}
|
||
.overview::-webkit-scrollbar { width: 6px; }
|
||
.overview::-webkit-scrollbar-thumb {
|
||
background: rgba(56,56,49,0.18);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
/* ============================================================
|
||
BIFROST OVERLAY — scenes inside the Overview page
|
||
============================================================ */
|
||
|
||
/* Internal scroller — sits inside #page-overview. Hosts the six
|
||
scenes. Scrolls vertically. The Europe map (overview-globe)
|
||
stays as fixed-position background behind it. */
|
||
#overview-scroll {
|
||
position: absolute;
|
||
inset: 0;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
z-index: 5;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: rgba(56,56,49,0.18) transparent;
|
||
}
|
||
#overview-scroll::-webkit-scrollbar { width: 6px; }
|
||
#overview-scroll::-webkit-scrollbar-thumb {
|
||
background: rgba(56,56,49,0.18);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
/* Site-1 overview-globe base rule kept; bifrost.js drives opacity
|
||
inline after init, so transition is suppressed by JS. Until JS
|
||
runs, CSS handles the 900ms fade-in on page activation. */
|
||
|
||
/* Hero scene anchored over the Europe map. The map's right-of-
|
||
centre framing is preserved from site 1 (overview-globe CSS at
|
||
left:65%, top:55%, scale 1.35 when .is-active). The hero text
|
||
lives in the left column; the map fills the right ~2/3. */
|
||
#page-overview #hero {
|
||
min-height: 100vh;
|
||
padding-inline: var(--edge);
|
||
display: grid;
|
||
align-items: center;
|
||
position: relative;
|
||
/* Asymmetric block padding — the larger padding-bottom shifts the
|
||
centered hero content upward, leaving breathing room between the
|
||
hero-foot row and the dot-nav at the bottom of the viewport. */
|
||
padding-top: clamp(1.5rem, 3vh, 2.5rem);
|
||
padding-bottom: clamp(6rem, 16vh, 11rem);
|
||
}
|
||
#page-overview #hero .hero-wrap {
|
||
/* Two-column layout: editorial copy on the left, Fenja wordmark
|
||
on the right — the right column anchors the logo at the same
|
||
viewport position the welcome page uses (~75% across). */
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 58%) minmax(0, 42%);
|
||
gap: clamp(2rem, 5vw, 4rem);
|
||
align-items: center;
|
||
max-width: none;
|
||
padding-top: 0;
|
||
}
|
||
#page-overview #hero .hero-copy {
|
||
/* Indent from the left so the text block floats with breathing
|
||
room on both sides of the viewport, mirroring the space that
|
||
the right-column wordmark has around it. */
|
||
padding-left: clamp(2rem, 8vw, 8rem);
|
||
max-width: 58ch;
|
||
}
|
||
/* Tighter type specifically inside the hero — the lockup reads
|
||
clearer when the headline isn't shouting at 4.4rem. */
|
||
#page-overview #hero .hero-title {
|
||
font-size: clamp(1.65rem, 4vw, 3.4rem);
|
||
line-height: 1.08;
|
||
}
|
||
#page-overview #hero .hero-lede {
|
||
font-size: clamp(1rem, 1.7vw, 1.4rem);
|
||
line-height: 1.45;
|
||
max-width: 44ch;
|
||
}
|
||
|
||
/* Right-column wordmark — sized to match the welcome page's
|
||
.welcome-logo (280px at the top end). Decorative only; the
|
||
heading remains the left-column <h1>. */
|
||
#page-overview #hero .hero-mark {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
pointer-events: none;
|
||
}
|
||
#page-overview #hero .hero-mark img {
|
||
width: clamp(200px, 22vw, 320px);
|
||
height: auto;
|
||
display: block;
|
||
opacity: 0.92;
|
||
}
|
||
/* No separate reveal/opacity rule needed: the whole .hero-wrap
|
||
fades in as one via bifrost.js's hero tween, so both columns
|
||
arrive together. */
|
||
|
||
/* Make sure scenes don't accidentally inherit `main { position: relative }` */
|
||
#overview-scroll > section { position: relative; z-index: 2; }
|
||
|
||
|
||
/* ============================================================
|
||
BIFROST SCENES — tokens (scoped to #page-overview only, so
|
||
they never leak to the timeline page).
|
||
Palette reconciled with site 1's Nordic Editorial system.
|
||
============================================================ */
|
||
#page-overview {
|
||
--ink: #383831; /* site 1 --on-surface (charcoal slate) */
|
||
--ink-soft: #5f5e5e; /* site 1 --on-surface-variant */
|
||
--ink-mute: #8a887f; /* site 1 --on-surface-muted */
|
||
--ink-faint: #ddd6c3; /* site 1 --surface-container-highest */
|
||
--paper: #faf6ee; /* site 1 --background */
|
||
--paper-2: #f6f2e8; /* site 1 --surface-container-low */
|
||
--paper-3: #efeadc; /* site 1 --surface-container */
|
||
--accent: #b96b58; /* site 1 --pigment-terracotta */
|
||
--ring: #6d8c7c; /* site 1 --pigment-copper */
|
||
|
||
/* aurora gradient — site-1 Archival Pigments, kept exclusive to Scene 4 */
|
||
--aurora-1: #c29d59; /* site 1 --pigment-ochre */
|
||
--aurora-2: #b96b58; /* site 1 --pigment-terracotta */
|
||
--aurora-3: #5a6d83; /* site 1 --pigment-indigo */
|
||
--aurora-4: #8d7a85; /* site 1 --pigment-heather */
|
||
|
||
--type-body: "Manrope", ui-sans-serif, system-ui, sans-serif;
|
||
--type-display: "Newsreader", Georgia, serif;
|
||
|
||
--step-hero: clamp(2rem, 5.1vw, 4.4rem);
|
||
--step-xl: clamp(1.8rem, 4.8vw, 4rem);
|
||
--step-lg: clamp(1.35rem, 3vw, 2.2rem);
|
||
--step-md: clamp(1rem, 1.4vw, 1.15rem);
|
||
--step-sm: clamp(0.85rem, 1vw, 0.95rem);
|
||
|
||
--edge: clamp(1.5rem, 4vw, 4rem);
|
||
}
|
||
|
||
/* ============================================================
|
||
TOKENS
|
||
============================================================ */
|
||
*, *::before, *::after { box-sizing: border-box; }
|
||
html {
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
text-rendering: optimizeLegibility;
|
||
scroll-behavior: auto; /* Lenis handles it */
|
||
}
|
||
|
||
/* Paper-grain noise overlay for tactile warmth */
|
||
/* Faint contour lines in the background for the whole page — Nordic-map motif */
|
||
/* ============================================================
|
||
LAYOUT PRIMITIVES
|
||
============================================================ */
|
||
.scene {
|
||
position: relative;
|
||
min-height: 100vh;
|
||
padding-inline: var(--edge);
|
||
display: grid;
|
||
align-items: center;
|
||
}
|
||
|
||
/* ============================================================
|
||
SCENE 1 — HERO
|
||
============================================================ */
|
||
#hero {
|
||
min-height: 100vh;
|
||
/* Reduced from clamp(7rem, 14vh, 11rem) to pull the hero text up
|
||
into the upper half of the viewport. The original value centered
|
||
the text too low when measured against the site mark at top. */
|
||
padding-top: clamp(3.5rem, 7vh, 5.5rem);
|
||
grid-template-columns: 1fr;
|
||
align-items: start;
|
||
}
|
||
.hero-wrap {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: clamp(2rem, 6vw, 4rem);
|
||
align-items: end;
|
||
max-width: 1600px;
|
||
width: 100%;
|
||
margin-inline: auto;
|
||
padding-top: clamp(2rem, 6vh, 4rem);
|
||
}
|
||
/* Hide the hero on first paint while JS is booting so it doesn't
|
||
flash in raw form during the page-activation transition. The
|
||
Bifrost init fades it in once ScrollTriggers are wired up. */
|
||
.js .hero-wrap { opacity: 0; }
|
||
|
||
.eyebrow {
|
||
font-size: var(--step-sm);
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-mute);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
margin-bottom: clamp(1.4rem, 4vh, 2.4rem);
|
||
font-weight: 500;
|
||
}
|
||
.eyebrow::before {
|
||
content: "";
|
||
width: 28px; height: 1px;
|
||
background: var(--ink-mute);
|
||
}
|
||
|
||
.hero-title {
|
||
font-family: var(--type-display);
|
||
font-weight: 330;
|
||
font-size: var(--step-hero);
|
||
line-height: 1.06;
|
||
letter-spacing: -0.025em;
|
||
color: var(--ink);
|
||
margin: 0;
|
||
max-width: 30ch;
|
||
}
|
||
.hero-title em {
|
||
font-style: italic;
|
||
font-weight: 340;
|
||
color: var(--accent);
|
||
}
|
||
|
||
.hero-lede {
|
||
max-width: 46ch;
|
||
font-size: var(--step-lg);
|
||
font-weight: 300;
|
||
line-height: 1.35;
|
||
color: var(--ink-soft);
|
||
margin-top: clamp(1.5rem, 4vh, 2.5rem);
|
||
letter-spacing: -0.01em;
|
||
}
|
||
|
||
/* Hero foot row — lives INSIDE the left column at the bottom of the
|
||
paragraph block. Displays "Supported by Innovationsfonden" on the
|
||
left and the scroll-down indicator on the right, sharing a single
|
||
baseline. The row sits immediately after the lede paragraph. */
|
||
.hero-foot {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: baseline;
|
||
gap: 1.5rem;
|
||
margin-top: clamp(2rem, 5vh, 3.5rem);
|
||
font-size: var(--step-sm);
|
||
color: var(--ink-mute);
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.support {
|
||
display: flex;
|
||
align-items: center;
|
||
/* All three axes bumped ~50% from the original: gap 0.75→1.1rem,
|
||
font-size ×1.5 via a calc on --step-sm, and the svg wordmark
|
||
grows 18→27px so the Innovationsfonden lockup reads with more
|
||
weight. */
|
||
gap: 1.1rem;
|
||
font-size: calc(var(--step-sm) * 1.5);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.18em;
|
||
}
|
||
.support svg { height: 27px; width: auto; }
|
||
|
||
.scroll-hint {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.7rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.22em;
|
||
/* Bumped from --step-sm for legibility. The hint is the primary
|
||
"what next?" cue at the bottom of the hero and needs to read
|
||
confidently, not whisper. */
|
||
font-size: calc(var(--step-sm) * 1.15);
|
||
font-weight: 600;
|
||
/* Switched from --ink-soft to --ink so the icon reads against the
|
||
paper background. The hint animation still breathes opacity so
|
||
it doesn't shout. */
|
||
color: var(--ink);
|
||
}
|
||
/* Arrow reoriented to point DOWN — a vertical line with a chevron
|
||
cap at the bottom. Animation moved from translateX to translateY so
|
||
the hint visually "drops" downward, matching its meaning. Weight
|
||
and length both bumped (1px → 2px, 28px → 44px) so the icon has
|
||
more visual presence; chevron arms thickened and enlarged to match. */
|
||
.scroll-hint .arrow {
|
||
width: 2px; height: 44px; background: currentColor;
|
||
position: relative;
|
||
animation: hint 2.2s ease-in-out infinite;
|
||
}
|
||
.scroll-hint .arrow::after {
|
||
content: ""; position: absolute; bottom: -1px; left: -5px;
|
||
width: 11px; height: 11px;
|
||
border-right: 2px solid currentColor;
|
||
border-bottom: 2px solid currentColor;
|
||
transform: rotate(45deg);
|
||
}
|
||
@keyframes hint {
|
||
0%, 100% { transform: translateY(0); opacity: 0.75; }
|
||
50% { transform: translateY(8px); opacity: 1; }
|
||
}
|
||
|
||
/* ============================================================
|
||
SCENE 2 — ARCHITECTURE (pinned, scrubbed)
|
||
4 layer-cards that fall in, stack with an offset revealing
|
||
each previous layer's bottom strip, then rearrange into a
|
||
2x2 grid on the right while explanatory copy appears on
|
||
the left.
|
||
============================================================ */
|
||
#stack-scene { position: relative; }
|
||
.stack-pin {
|
||
position: relative;
|
||
height: 100vh;
|
||
padding-inline: var(--edge);
|
||
max-width: none;
|
||
margin-inline: auto;
|
||
display: grid;
|
||
place-items: center;
|
||
padding-top: clamp(6rem, 11vh, 8.5rem); /* keep clear of brand mark */
|
||
}
|
||
|
||
/* Theatre — holds cards absolutely positioned. GSAP drives positions. */
|
||
.layer-theatre {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
max-width: none;
|
||
}
|
||
|
||
/* ───────── Stack scene title bar ─────────
|
||
Sits above the card theatre and rides with the pin so it remains
|
||
visible through all four card landings and the grid rearrange.
|
||
Centered horizontally; positioned lower so the title anchors
|
||
visually to the cards below rather than floating at the top of
|
||
the viewport. Per-card counter (1/4, 2/4, …) now lives inside
|
||
each .layer-card rather than being slaved to scroll progress. */
|
||
.stack-title-bar {
|
||
position: absolute;
|
||
top: clamp(5.5rem, 14vh, 10rem);
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 20;
|
||
text-align: center;
|
||
padding-inline: clamp(1rem, 4vw, 3rem);
|
||
pointer-events: none;
|
||
}
|
||
.stack-title {
|
||
margin: 0;
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-weight: 400;
|
||
font-size: clamp(2rem, 3.6vw, 3rem);
|
||
letter-spacing: -0.015em;
|
||
line-height: 1.12;
|
||
color: var(--ink);
|
||
}
|
||
|
||
/* LEFT COPY (visible only during grid phase) */
|
||
.copy-stage {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0; /* full theatre height so copy-layer can vertically center */
|
||
width: 42%;
|
||
max-width: 46ch;
|
||
z-index: 10;
|
||
pointer-events: none;
|
||
}
|
||
.copy-layer {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 50%; /* vertically center */
|
||
will-change: opacity, transform;
|
||
}
|
||
.js .copy-layer { opacity: 0; }
|
||
.copy-layer h2 {
|
||
font-family: var(--type-display);
|
||
font-weight: 340;
|
||
font-size: var(--step-xl);
|
||
line-height: 1.04;
|
||
letter-spacing: -0.025em;
|
||
color: var(--ink);
|
||
margin: 0 0 1rem;
|
||
}
|
||
.copy-layer h2 em { font-style: italic; color: var(--accent); font-weight: 400; }
|
||
.copy-layer h2 strong { font-weight: 600; font-style: normal; }
|
||
.copy-layer p {
|
||
font-size: var(--step-lg);
|
||
font-weight: 300;
|
||
line-height: 1.35;
|
||
color: var(--ink-soft);
|
||
margin: 0;
|
||
max-width: 38ch;
|
||
letter-spacing: -0.005em;
|
||
}
|
||
.copy-layer .tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.6rem;
|
||
font-size: var(--step-sm);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.22em;
|
||
color: var(--ink-mute);
|
||
margin-bottom: 1rem;
|
||
font-weight: 500;
|
||
}
|
||
.copy-layer .tag::before {
|
||
content: "";
|
||
width: 20px; height: 1px;
|
||
background: currentColor;
|
||
}
|
||
|
||
/* -------- Layer cards -------- */
|
||
.layer-card {
|
||
position: absolute;
|
||
/* 7.5% margin on each side = 15% total width reduction from the
|
||
original edge-to-edge layout. */
|
||
left: 7.5%;
|
||
right: 7.5%;
|
||
top: 50%;
|
||
width: auto;
|
||
transform: translateY(-50%);
|
||
transform-origin: center center;
|
||
will-change: transform, opacity;
|
||
}
|
||
.layer-card .card-eyebrow {
|
||
display: block;
|
||
font-size: var(--step-sm);
|
||
letter-spacing: 0.28em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-soft);
|
||
margin: 0 0 0.9rem 0.25rem;
|
||
font-weight: 500;
|
||
will-change: opacity;
|
||
}
|
||
|
||
.card-box {
|
||
position: relative;
|
||
border-radius: 22px;
|
||
/* Reduced 15% from the original clamp(1.75rem, 3.2vw, 2.8rem) for a
|
||
slimmer, quieter card presence. */
|
||
padding: clamp(1.5rem, 2.7vw, 2.4rem);
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.9fr);
|
||
gap: clamp(0.85rem, 2.1vw, 1.9rem);
|
||
align-items: center;
|
||
overflow: hidden;
|
||
/* contain: paint forces transformed children (with will-change:
|
||
transform creating compositing layers) to respect this box's
|
||
overflow clipping. Without it, the brain's counter-scale
|
||
transform during the morph escapes the box bounds. */
|
||
contain: paint;
|
||
box-shadow: 0 22px 48px -18px rgba(46,46,40,0.28), 0 8px 22px -8px rgba(46,46,40,0.16);
|
||
/* 15% reduction from the original 240px — matches the lateral shrink */
|
||
min-height: 204px;
|
||
}
|
||
.card-content { min-width: 0; }
|
||
.card-title {
|
||
font-family: var(--type-display);
|
||
font-weight: 330;
|
||
font-size: clamp(2.08rem, 4.3vw, 3.32rem);
|
||
line-height: 1.06;
|
||
letter-spacing: -0.018em;
|
||
margin: 0 0 1.1rem;
|
||
color: #fffcf7;
|
||
}
|
||
.card-title b { font-weight: 640; font-style: normal; }
|
||
.card-title em { font-style: italic; font-weight: 640; }
|
||
.card-body {
|
||
font-size: clamp(1.2rem, 1.5vw, 1.4rem);
|
||
line-height: 1.4;
|
||
color: #fffcf7;
|
||
margin: 0;
|
||
max-width: 38ch;
|
||
font-weight: 400;
|
||
opacity: 0.88;
|
||
will-change: opacity;
|
||
}
|
||
|
||
/* Card illustration — per-layer PNG set via --card-illust custom property. */
|
||
.card-brain {
|
||
width: 100%;
|
||
aspect-ratio: 20 / 17;
|
||
background-image: var(--card-illust);
|
||
background-size: contain;
|
||
background-position: center right;
|
||
background-repeat: no-repeat;
|
||
opacity: 0.95;
|
||
margin-right: clamp(-3.5rem, -3vw, -1.5rem); /* bleed off right edge */
|
||
pointer-events: none;
|
||
will-change: transform;
|
||
}
|
||
|
||
/* Per-layer colours — muted Nordic mid-tones. */
|
||
.layer-card[data-layer="0"] .card-box { background: #7a8c70; } /* sage — AI Model */
|
||
.layer-card[data-layer="1"] .card-box { background: #7b9399; } /* slate — Knowledge */
|
||
.layer-card[data-layer="2"] .card-box { background: #b07556; } /* clay — Tools */
|
||
.layer-card[data-layer="3"] .card-box { background: #8a7a92; } /* plum — Agents */
|
||
|
||
/* Per-layer illustrations — URL-encode spaces in filenames. */
|
||
.layer-card[data-layer="0"] .card-brain { --card-illust: url('/fenja/illustrations/ai.png'); }
|
||
.layer-card[data-layer="1"] .card-brain { --card-illust: url('/fenja/illustrations/lightbulb%20-%20knowledge.png'); }
|
||
.layer-card[data-layer="2"] .card-brain { --card-illust: url('/fenja/illustrations/blocs%20tools.png'); }
|
||
.layer-card[data-layer="3"] .card-brain { --card-illust: url('/fenja/illustrations/agents.png'); }
|
||
|
||
/* z-stacking — later layers appear on top */
|
||
.layer-card[data-layer="0"] { z-index: 1; }
|
||
.layer-card[data-layer="1"] { z-index: 2; }
|
||
.layer-card[data-layer="2"] { z-index: 3; }
|
||
.layer-card[data-layer="3"] { z-index: 4; }
|
||
|
||
/* -------- GRID PHASE — cards become aligned SQUARES -------- */
|
||
/* When the .in-grid class is toggled on .layer-theatre, each card-box
|
||
becomes a 20vw square (via .in-grid .card-box), centered in its
|
||
full-width parent. Inside, the layout switches: the outside eyebrow
|
||
hides; a dedicated grid-label shows at the top; the long title and
|
||
body hide; the brain fills the rest centered.
|
||
|
||
The grid-label is ALWAYS in the DOM (absolutely-positioned inside
|
||
card-box with opacity:0 by default) so GSAP can fade it in smoothly
|
||
during the morph transition, rather than it snapping on when the
|
||
.in-grid class applies. */
|
||
|
||
.card-grid-label {
|
||
position: absolute;
|
||
left: clamp(1rem, 1.4vw, 1.4rem);
|
||
top: clamp(1rem, 1.4vw, 1.4rem);
|
||
font-size: clamp(0.75rem, 0.95vw, 0.95rem);
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: #fffcf7;
|
||
opacity: 0;
|
||
font-weight: 500;
|
||
text-align: left;
|
||
line-height: 1;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
|
||
/* Per-card counter — lives in the top-right of each card-box and
|
||
reads "1 / 4" through "4 / 4". Paper-toned to contrast against
|
||
the sage/slate/clay/plum card backgrounds. */
|
||
.card-counter {
|
||
position: absolute;
|
||
top: clamp(1rem, 1.4vw, 1.4rem);
|
||
right: clamp(1rem, 1.4vw, 1.4rem);
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-weight: 500;
|
||
font-size: clamp(0.85rem, 1.05vw, 1.05rem);
|
||
letter-spacing: 0.04em;
|
||
color: rgba(255, 252, 247, 0.85);
|
||
font-variant-numeric: tabular-nums;
|
||
line-height: 1;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
.in-grid .card-counter {
|
||
/* Keep the counter visible in the grid phase but smaller so it
|
||
doesn't compete with the grid-label. */
|
||
top: clamp(0.5rem, 0.7vw, 0.8rem);
|
||
right: clamp(0.5rem, 0.7vw, 0.8rem);
|
||
font-size: clamp(0.6rem, 0.7vw, 0.75rem);
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.in-grid .card-box {
|
||
/* 15% reduction from the original 20vw — matches the drop-phase shrink.
|
||
Also matches cellSize and targetW in bifrost.js (both are vw * 0.17). */
|
||
max-width: 17vw;
|
||
width: 17vw;
|
||
aspect-ratio: 1 / 1;
|
||
margin: 0 auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
justify-content: flex-start;
|
||
grid-template-columns: unset;
|
||
grid-template-rows: unset;
|
||
padding: clamp(0.85rem, 1.2vw, 1.2rem);
|
||
gap: 0;
|
||
min-height: 0;
|
||
border-radius: 16px;
|
||
}
|
||
|
||
/* In grid state the label is visible; position resets inside the
|
||
flex column */
|
||
.in-grid .card-grid-label {
|
||
opacity: 0.88;
|
||
position: relative;
|
||
left: auto;
|
||
top: auto;
|
||
margin: 0 0 0.5rem;
|
||
}
|
||
|
||
/* Hide long title + body in grid phase */
|
||
.in-grid .card-content { display: none; }
|
||
|
||
/* Illustration fills remaining space, centered. */
|
||
.in-grid .card-brain {
|
||
margin: 0;
|
||
flex: 1 1 auto;
|
||
width: 100%;
|
||
aspect-ratio: auto;
|
||
background-position: center;
|
||
background-size: 90% auto;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* Hide the outside-box eyebrow during grid phase */
|
||
.in-grid .layer-card .card-eyebrow {
|
||
opacity: 0 !important;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* ============================================================
|
||
SCENE 3 — SLIDE 11 — words fly in
|
||
============================================================ */
|
||
#words-scene {
|
||
position: relative;
|
||
height: 260vh;
|
||
}
|
||
.words-pin {
|
||
position: sticky;
|
||
top: 0;
|
||
height: 100vh;
|
||
display: grid;
|
||
place-items: center;
|
||
padding-inline: var(--edge);
|
||
}
|
||
.words {
|
||
font-family: var(--type-display);
|
||
font-weight: 320;
|
||
font-size: clamp(2rem, 6vw, 5.2rem);
|
||
line-height: 1.06;
|
||
letter-spacing: -0.025em;
|
||
color: var(--ink);
|
||
max-width: 22ch;
|
||
margin: 0 auto;
|
||
text-align: left;
|
||
}
|
||
.words .w {
|
||
display: inline-block;
|
||
will-change: transform, opacity;
|
||
margin-right: 0.25em;
|
||
}
|
||
.js .words .w { opacity: 0; }
|
||
.words .w.hi {
|
||
font-style: italic;
|
||
color: var(--accent);
|
||
font-weight: 420;
|
||
}
|
||
|
||
/* ============================================================
|
||
SCENE 4 — PROJECT BIFROST REVEAL
|
||
============================================================ */
|
||
#bifrost {
|
||
position: relative;
|
||
min-height: 200vh;
|
||
}
|
||
.bifrost-pin {
|
||
position: sticky;
|
||
top: 0;
|
||
height: 100vh;
|
||
display: grid;
|
||
place-items: center;
|
||
overflow: hidden;
|
||
}
|
||
.bifrost-stage {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
display: grid;
|
||
place-items: center;
|
||
}
|
||
|
||
/* the arc — bifrost bridge */
|
||
.arc-wrap {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
width: min(120vw, 1400px);
|
||
translate: -50% -50%;
|
||
pointer-events: none;
|
||
will-change: opacity, transform;
|
||
}
|
||
.js .arc-wrap { opacity: 0; }
|
||
.arc-wrap svg { display: block; width: 100%; height: auto; overflow: visible; }
|
||
|
||
.bifrost-text {
|
||
position: relative;
|
||
z-index: 2;
|
||
text-align: center;
|
||
max-width: 90vw;
|
||
padding: 0 var(--edge);
|
||
}
|
||
.bifrost-eyebrow {
|
||
font-size: var(--step-sm);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.32em;
|
||
color: var(--ink-soft);
|
||
margin-bottom: clamp(1rem, 3vh, 1.8rem);
|
||
will-change: opacity, transform;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.8rem;
|
||
}
|
||
.js .bifrost-eyebrow { opacity: 0; }
|
||
.bifrost-eyebrow::before,
|
||
.bifrost-eyebrow::after {
|
||
content: "";
|
||
width: 28px; height: 1px;
|
||
background: currentColor;
|
||
}
|
||
.bifrost-name {
|
||
font-family: var(--type-display);
|
||
font-weight: 320;
|
||
font-size: clamp(3rem, 10vw, 9rem);
|
||
/* Loosened from 0.95 to 1.12 and padded so the italic "Bifrost"
|
||
token's ascenders/descenders clear the .bifrost-pin's
|
||
overflow:hidden (which exists to clip the aurora arc). */
|
||
line-height: 1.12;
|
||
letter-spacing: -0.04em;
|
||
color: var(--ink);
|
||
margin: 0;
|
||
padding: 0.12em 0.08em;
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 0.15em;
|
||
flex-wrap: wrap;
|
||
overflow: visible;
|
||
}
|
||
.bifrost-name .token {
|
||
display: inline-block;
|
||
will-change: transform, opacity, filter;
|
||
}
|
||
.js .bifrost-name .token { opacity: 0; }
|
||
.bifrost-name .token.accent {
|
||
font-style: italic;
|
||
background: linear-gradient(100deg, var(--aurora-1) 0%, var(--aurora-2) 32%, var(--aurora-3) 68%, var(--aurora-4) 100%);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
}
|
||
.bifrost-sub {
|
||
margin-top: clamp(1.4rem, 4vh, 2.2rem);
|
||
font-size: var(--step-lg);
|
||
font-weight: 300;
|
||
color: var(--ink-soft);
|
||
max-width: 44ch;
|
||
margin-inline: auto;
|
||
line-height: 1.35;
|
||
will-change: opacity, transform;
|
||
}
|
||
.js .bifrost-sub { opacity: 0; }
|
||
.bifrost-sub em {
|
||
font-style: italic;
|
||
color: var(--ink);
|
||
}
|
||
|
||
/* Credits */
|
||
.credits {
|
||
position: relative;
|
||
padding: clamp(4rem, 12vh, 8rem) var(--edge) clamp(2rem, 4vh, 3rem);
|
||
border-top: 1px solid rgba(46,46,40,0.1);
|
||
margin-top: 8vh;
|
||
font-size: var(--step-sm);
|
||
color: var(--ink-mute);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 2rem;
|
||
flex-wrap: wrap;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
.credits .col a {
|
||
color: var(--ink-soft);
|
||
text-decoration: none;
|
||
border-bottom: 1px solid rgba(46,46,40,0.2);
|
||
padding-bottom: 1px;
|
||
transition: border-color 0.2s, color 0.2s;
|
||
}
|
||
.credits .col a:hover { color: var(--accent); border-color: var(--accent); }
|
||
|
||
/* ============================================================
|
||
RESPONSIVE
|
||
============================================================ */
|
||
@media (max-width: 900px) {
|
||
#page-overview { --edge: 1.25rem; }
|
||
|
||
.meta { display: none; }
|
||
.rail { display: none; }
|
||
.brand-sub { display: none; } /* too cramped on small screens */
|
||
|
||
.stack-pin {
|
||
padding-top: 5rem;
|
||
}
|
||
.copy-stage {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
position: relative;
|
||
top: auto;
|
||
transform: none;
|
||
}
|
||
.copy-layer h2 { font-size: clamp(1.6rem, 5.5vw, 2.2rem); }
|
||
.copy-layer p { font-size: 1rem; }
|
||
|
||
/* Cards shrink on mobile — single column body + smaller brain */
|
||
.layer-card { width: 92%; }
|
||
.card-box {
|
||
grid-template-columns: 1fr;
|
||
min-height: 0;
|
||
padding: 1.4rem;
|
||
}
|
||
.card-title { font-size: clamp(1.25rem, 5vw, 1.7rem); }
|
||
.card-body { font-size: 0.92rem; }
|
||
.card-brain {
|
||
max-width: 180px;
|
||
justify-self: end;
|
||
margin-right: -1rem;
|
||
aspect-ratio: 20 / 14;
|
||
}
|
||
|
||
.hero-foot { margin-top: 2rem; flex-direction: column; gap: 1rem; align-items: flex-start; }
|
||
#hero { padding-top: 5rem; padding-bottom: 3rem; min-height: auto; }
|
||
/* Narrow viewports: collapse the hero's 2-column grid so the
|
||
wordmark sits below the copy instead of beside it. */
|
||
#page-overview #hero .hero-wrap {
|
||
grid-template-columns: 1fr;
|
||
gap: 2rem;
|
||
}
|
||
#page-overview #hero .hero-mark { justify-content: flex-start; }
|
||
#page-overview #hero .hero-mark img { width: clamp(160px, 40vw, 220px); }
|
||
}
|
||
|
||
@media (max-width: 520px) {
|
||
.hero-title { font-size: clamp(2.2rem, 10vw, 3.2rem); }
|
||
.hero-lede { font-size: 1.05rem; }
|
||
.card-eyebrow { font-size: 0.72rem !important; letter-spacing: 0.2em; }
|
||
}
|
||
|
||
/* ============================================================
|
||
REDUCED MOTION — degraded version reveals all content
|
||
============================================================ */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
#words-scene, #bifrost { height: auto !important; min-height: 0 !important; }
|
||
.stack-pin, .words-pin, .bifrost-pin {
|
||
position: relative !important;
|
||
height: auto !important;
|
||
min-height: auto;
|
||
padding-block: 4rem;
|
||
display: block !important;
|
||
}
|
||
.layer-theatre {
|
||
position: relative !important;
|
||
height: auto !important;
|
||
}
|
||
.layer-card {
|
||
position: relative !important;
|
||
left: auto !important; top: auto !important;
|
||
transform: none !important;
|
||
margin: 0 auto 2rem !important;
|
||
width: 100% !important;
|
||
max-width: 900px;
|
||
}
|
||
.card-body { opacity: 1 !important; }
|
||
.card-eyebrow { opacity: 1 !important; }
|
||
.copy-stage {
|
||
position: relative !important;
|
||
top: auto !important;
|
||
transform: none !important;
|
||
width: 100% !important;
|
||
max-width: 900px;
|
||
margin: 2rem auto 0;
|
||
}
|
||
.copy-layer { position: static !important; opacity: 1 !important; transform: none !important; margin-bottom: 3rem; }
|
||
.brand-sub { opacity: 1 !important; transform: none !important; }
|
||
.words .w { opacity: 1 !important; transform: none !important; }
|
||
.bifrost-eyebrow, .bifrost-sub, .arc-wrap { opacity: 1 !important; transform: translate(-50%, -50%) !important; }
|
||
.arc-wrap { transform: translate(-50%, -50%) !important; }
|
||
.bifrost-name .token { opacity: 1 !important; transform: none !important; filter: none !important; }
|
||
.scroll-hint .arrow { animation: none; }
|
||
}
|
||
/* Illustration data URIs — defined once, referenced by both
|
||
the treasure-map stops and the summary cards below. */
|
||
/* Illustration paths — real SVG files, self-hosted under protected/fenja/illustrations/ */
|
||
#page-overview {
|
||
--illust-community: url("/fenja/illustrations/community.svg");
|
||
--illust-council: url("/fenja/illustrations/council.svg");
|
||
--illust-pilot: url("/fenja/illustrations/pilot.svg");
|
||
}
|
||
/* ============================================================
|
||
SCENE 5 — PROJECT BIFROST · WHAT IT MEANS (treasure-map)
|
||
A meandering path drawn down the page with one intro stop
|
||
and three component stops revealed sequentially as the user
|
||
scrolls. Each component pairs body copy with an illustration
|
||
that fades in alongside it.
|
||
============================================================ */
|
||
#bifrost-meaning {
|
||
position: relative;
|
||
padding: clamp(6rem, 14vh, 12rem) var(--edge) clamp(4rem, 10vh, 8rem);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.map-intro {
|
||
max-width: 60ch;
|
||
margin: 0 auto clamp(5rem, 12vh, 9rem);
|
||
text-align: center;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.map-intro .map-eyebrow {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
font-size: var(--step-sm);
|
||
letter-spacing: 0.32em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-mute);
|
||
margin-bottom: clamp(1.4rem, 3.5vh, 2rem);
|
||
font-weight: 500;
|
||
}
|
||
.map-intro .map-eyebrow::before,
|
||
.map-intro .map-eyebrow::after {
|
||
content: "";
|
||
width: 28px; height: 1px;
|
||
background: currentColor;
|
||
}
|
||
.map-title {
|
||
font-family: var(--type-display);
|
||
font-weight: 330;
|
||
font-size: var(--step-xl);
|
||
line-height: 1.04;
|
||
letter-spacing: -0.025em;
|
||
color: var(--ink);
|
||
margin: 0 0 clamp(1rem, 2.5vh, 1.5rem);
|
||
}
|
||
.map-title em {
|
||
font-style: italic;
|
||
color: var(--accent);
|
||
font-weight: 400;
|
||
}
|
||
.map-lede {
|
||
font-size: var(--step-lg);
|
||
font-weight: 300;
|
||
line-height: 1.4;
|
||
color: var(--ink-soft);
|
||
margin: 0 auto;
|
||
max-width: 56ch;
|
||
letter-spacing: -0.005em;
|
||
}
|
||
.map-lede em {
|
||
font-style: italic;
|
||
color: var(--ink);
|
||
font-weight: 400;
|
||
}
|
||
|
||
/* The map canvas — relative container holding the path SVG and stops */
|
||
.map-canvas {
|
||
position: relative;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* Wandering path — SVG stretched to canvas dimensions */
|
||
.map-path {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
overflow: visible;
|
||
}
|
||
.map-path .path-bg {
|
||
fill: none;
|
||
stroke: var(--ink);
|
||
stroke-opacity: 0.18;
|
||
stroke-width: 1.2;
|
||
stroke-dasharray: 4 6;
|
||
stroke-linecap: round;
|
||
}
|
||
.map-path .path-draw {
|
||
fill: none;
|
||
stroke: var(--accent);
|
||
stroke-opacity: 0.7;
|
||
stroke-width: 1.6;
|
||
stroke-linecap: round;
|
||
}
|
||
|
||
/* A stop — three-column grid: text | dot | image (alternating sides) */
|
||
.map-stop {
|
||
position: relative;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||
align-items: center;
|
||
gap: clamp(2rem, 5vw, 4.5rem);
|
||
margin-bottom: clamp(7rem, 14vh, 11rem);
|
||
z-index: 2;
|
||
}
|
||
.map-stop:last-child { margin-bottom: 0; }
|
||
|
||
/* Intro stop is single-column, centered, no image */
|
||
.map-stop--intro {
|
||
grid-template-columns: 1fr;
|
||
text-align: center;
|
||
margin-bottom: clamp(7rem, 14vh, 11rem);
|
||
justify-items: center;
|
||
}
|
||
.map-stop--intro .stop-content {
|
||
max-width: 46ch;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* Dot anchor — sits on the path */
|
||
.dot-anchor {
|
||
position: relative;
|
||
width: 16px;
|
||
height: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
z-index: 3;
|
||
}
|
||
.map-stop[data-side="left"] .dot-anchor { grid-column: 2; }
|
||
.map-stop[data-side="right"] .dot-anchor { grid-column: 2; }
|
||
|
||
.dot {
|
||
position: relative;
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
background: var(--paper);
|
||
border: 2px solid var(--accent);
|
||
/* paper-coloured ring masks the path passing under the dot */
|
||
box-shadow: 0 0 0 6px var(--paper);
|
||
z-index: 2;
|
||
}
|
||
/* Soft pulse halo on the dot */
|
||
.dot::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: -10px;
|
||
border-radius: 50%;
|
||
border: 1px solid var(--accent);
|
||
opacity: 0.35;
|
||
z-index: -1;
|
||
}
|
||
|
||
.map-stop--intro .dot-anchor {
|
||
margin-bottom: clamp(1.5rem, 4vh, 2.5rem);
|
||
}
|
||
|
||
/* Text and image columns — alternating sides */
|
||
.map-stop[data-side="left"] .stop-content { grid-column: 1; text-align: left; }
|
||
.map-stop[data-side="left"] .stop-image { grid-column: 3; justify-self: start; }
|
||
.map-stop[data-side="right"] .stop-image { grid-column: 1; justify-self: end; }
|
||
.map-stop[data-side="right"] .stop-content { grid-column: 3; text-align: left; }
|
||
|
||
.stop-content {
|
||
font-family: var(--type-body);
|
||
}
|
||
.stop-eyebrow {
|
||
font-size: var(--step-sm);
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-mute);
|
||
font-weight: 500;
|
||
display: block;
|
||
margin-bottom: 0.6rem;
|
||
}
|
||
.stop-title {
|
||
font-family: var(--type-display);
|
||
font-weight: 340;
|
||
font-size: clamp(2rem, 4.2vw, 3.2rem);
|
||
line-height: 1.04;
|
||
letter-spacing: -0.025em;
|
||
color: var(--ink);
|
||
margin: 0 0 clamp(0.6rem, 1.5vh, 1rem);
|
||
}
|
||
.stop-title em {
|
||
font-style: italic;
|
||
color: var(--accent);
|
||
font-weight: 400;
|
||
}
|
||
.stop-sub {
|
||
font-family: var(--type-display);
|
||
font-style: italic;
|
||
font-size: clamp(1.15rem, 1.8vw, 1.5rem);
|
||
font-weight: 300;
|
||
color: var(--ink-soft);
|
||
margin: 0 0 1.1rem;
|
||
letter-spacing: -0.005em;
|
||
line-height: 1.3;
|
||
}
|
||
.stop-body {
|
||
font-size: var(--step-md);
|
||
line-height: 1.55;
|
||
color: var(--ink-soft);
|
||
margin: 0;
|
||
max-width: 42ch;
|
||
font-weight: 400;
|
||
}
|
||
.stop-intro-text {
|
||
font-family: var(--type-display);
|
||
font-style: italic;
|
||
font-weight: 300;
|
||
font-size: clamp(1.4rem, 2.4vw, 2rem);
|
||
line-height: 1.35;
|
||
color: var(--ink-soft);
|
||
margin: 0;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
.stop-intro-text em {
|
||
font-style: normal;
|
||
color: var(--accent);
|
||
font-weight: 400;
|
||
}
|
||
|
||
.stop-image {
|
||
width: 100%;
|
||
max-width: 380px;
|
||
}
|
||
.stop-image img {
|
||
display: block;
|
||
width: 100%;
|
||
height: auto;
|
||
/* faint blend with the cream paper */
|
||
mix-blend-mode: multiply;
|
||
}
|
||
|
||
/* Initial hidden state — only when JS is enabled */
|
||
.js .map-stop .dot {
|
||
opacity: 0;
|
||
transform: scale(0.2);
|
||
transform-origin: center;
|
||
will-change: opacity, transform;
|
||
}
|
||
.js .map-stop .stop-content > *,
|
||
.js .map-stop .stop-image {
|
||
opacity: 0;
|
||
transform: translateY(28px);
|
||
will-change: opacity, transform;
|
||
}
|
||
|
||
/* ============================================================
|
||
SCENE 6 — PROJECT BIFROST · JOIN
|
||
A large call-to-action that, on click, crossfades to a
|
||
confirmation panel listing what happens next. Below, a
|
||
three-column footer row: "Project Bifrost" wordmark (left),
|
||
Fenja AI logo (centre), Innovationsfonden mark (right).
|
||
============================================================ */
|
||
#bifrost-join {
|
||
position: relative;
|
||
padding: clamp(5rem, 12vh, 10rem) var(--edge) clamp(2rem, 5vh, 3.5rem);
|
||
overflow: hidden;
|
||
min-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Stage holds BOTH the CTA and the confirmation panel, stacked in
|
||
the SAME grid cell. The cell auto-sizes to whichever panel is
|
||
taller (on mobile the confirmation list is much taller than the
|
||
CTA), so neither panel ever overflows into the footer. GSAP's
|
||
opacity/y tweens handle the crossfade. */
|
||
.join-stage {
|
||
position: relative;
|
||
flex: 1 1 auto;
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
display: grid;
|
||
place-items: center;
|
||
padding: clamp(1rem, 4vh, 3rem) 0;
|
||
}
|
||
|
||
.join-panel {
|
||
grid-column: 1;
|
||
grid-row: 1;
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
text-align: center;
|
||
}
|
||
|
||
/* ---------- CTA state ---------- */
|
||
.join-cta .join-eyebrow {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
font-size: var(--step-sm);
|
||
letter-spacing: 0.32em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-mute);
|
||
margin-bottom: clamp(1.4rem, 3.5vh, 2rem);
|
||
font-weight: 500;
|
||
}
|
||
.join-cta .join-eyebrow::before,
|
||
.join-cta .join-eyebrow::after {
|
||
content: "";
|
||
width: 28px; height: 1px;
|
||
background: currentColor;
|
||
}
|
||
|
||
.join-cta .join-headline {
|
||
font-family: var(--type-display);
|
||
font-weight: 320;
|
||
font-size: clamp(2.4rem, 6.2vw, 5.4rem);
|
||
line-height: 1.04;
|
||
letter-spacing: -0.035em;
|
||
color: var(--ink);
|
||
margin: 0 auto clamp(2.6rem, 6vh, 4rem);
|
||
max-width: 20ch;
|
||
}
|
||
.join-cta .join-headline em {
|
||
font-style: italic;
|
||
background: linear-gradient(100deg, var(--aurora-1) 0%, var(--aurora-2) 32%, var(--aurora-3) 68%, var(--aurora-4) 100%);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
font-weight: 340;
|
||
}
|
||
|
||
/* The button itself — pill shape with the terracotta accent */
|
||
.join-button {
|
||
font-family: var(--type-body);
|
||
font-size: clamp(1.02rem, 1.35vw, 1.18rem);
|
||
font-weight: 600;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--paper);
|
||
background: var(--accent);
|
||
border: none;
|
||
padding: clamp(1.1rem, 2.2vh, 1.45rem) clamp(2rem, 3.8vw, 2.8rem);
|
||
border-radius: 100px;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.85rem;
|
||
box-shadow: 0 14px 34px -14px rgba(164, 85, 59, 0.55),
|
||
0 4px 12px -4px rgba(46, 46, 40, 0.2);
|
||
transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1),
|
||
box-shadow 0.25s ease,
|
||
background-color 0.25s ease;
|
||
}
|
||
.join-button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 22px 44px -14px rgba(164, 85, 59, 0.65),
|
||
0 8px 18px -4px rgba(46, 46, 40, 0.25);
|
||
background: #b55e42;
|
||
}
|
||
.join-button:active {
|
||
transform: translateY(0);
|
||
}
|
||
.join-button:focus-visible {
|
||
outline: 2px solid var(--ring);
|
||
outline-offset: 4px;
|
||
}
|
||
.join-button:disabled {
|
||
cursor: default;
|
||
}
|
||
.join-button .arrow {
|
||
display: inline-block;
|
||
width: 18px; height: 1.5px;
|
||
background: currentColor;
|
||
position: relative;
|
||
transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
.join-button .arrow::after {
|
||
content: "";
|
||
position: absolute;
|
||
right: -1px; top: -4px;
|
||
width: 9px; height: 9px;
|
||
border-right: 1.5px solid currentColor;
|
||
border-bottom: 1.5px solid currentColor;
|
||
transform: rotate(-45deg);
|
||
}
|
||
.join-button:hover .arrow {
|
||
transform: translateX(4px);
|
||
}
|
||
|
||
.join-cta .join-subtext {
|
||
margin: clamp(1.6rem, 4vh, 2.4rem) auto 0;
|
||
font-size: var(--step-sm);
|
||
color: var(--ink-mute);
|
||
letter-spacing: 0.04em;
|
||
max-width: 40ch;
|
||
}
|
||
|
||
/* ---------- Confirmation state ---------- */
|
||
.join-confirmation {
|
||
pointer-events: none; /* enabled by JS after fade-in completes */
|
||
}
|
||
.join-confirmation .confirm-eyebrow {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
font-size: var(--step-sm);
|
||
letter-spacing: 0.32em;
|
||
text-transform: uppercase;
|
||
color: var(--accent);
|
||
margin-bottom: clamp(1.4rem, 3vh, 1.8rem);
|
||
font-weight: 500;
|
||
}
|
||
.join-confirmation .confirm-eyebrow::before,
|
||
.join-confirmation .confirm-eyebrow::after {
|
||
content: "";
|
||
width: 22px; height: 1px;
|
||
background: currentColor;
|
||
opacity: 0.7;
|
||
}
|
||
.join-confirmation .confirm-headline {
|
||
font-family: var(--type-display);
|
||
/* Matched to .join-cta .join-headline so pressing the CTA doesn't
|
||
shrink the page's visual anchor — the confirmation keeps the
|
||
same stature, only the copy changes. */
|
||
font-weight: 320;
|
||
font-size: clamp(2.4rem, 6.2vw, 5.4rem);
|
||
line-height: 1.04;
|
||
letter-spacing: -0.035em;
|
||
color: var(--ink);
|
||
margin: 0 auto;
|
||
max-width: 20ch;
|
||
}
|
||
.join-confirmation .confirm-headline em {
|
||
/* Italics preserved for emphasis; accent colour keeps the editorial
|
||
weight without the aurora gradient (which is reserved for S4
|
||
and the pre-click CTA). */
|
||
font-style: italic;
|
||
color: var(--accent);
|
||
font-weight: 380;
|
||
}
|
||
|
||
.confirm-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: clamp(2rem, 5vh, 3.2rem) auto 0;
|
||
max-width: 62ch;
|
||
text-align: left;
|
||
}
|
||
.confirm-list li {
|
||
padding: clamp(1rem, 2.2vh, 1.4rem) 0 clamp(1rem, 2.2vh, 1.4rem) clamp(2.6rem, 4vw, 3.2rem);
|
||
position: relative;
|
||
font-size: var(--step-md);
|
||
line-height: 1.55;
|
||
color: var(--ink-soft);
|
||
border-top: 1px solid rgba(46, 46, 40, 0.12);
|
||
}
|
||
.confirm-list li:first-child { border-top: none; }
|
||
.confirm-list li em {
|
||
font-family: var(--type-display);
|
||
font-style: italic;
|
||
font-weight: 400;
|
||
color: var(--ink);
|
||
}
|
||
/* Terracotta circle marker + cream check — staged with a CSS
|
||
transition that fires when JS adds `.is-checked` to each <li>. */
|
||
.confirm-list li::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: clamp(0.95rem, 2.2vh, 1.35rem);
|
||
width: clamp(1.4rem, 2.3vw, 1.7rem);
|
||
height: clamp(1.4rem, 2.3vw, 1.7rem);
|
||
background: var(--accent);
|
||
border-radius: 50%;
|
||
opacity: 0;
|
||
transform: scale(0.4);
|
||
transition: opacity 0.35s ease,
|
||
transform 0.4s cubic-bezier(0.3, 1.5, 0.5, 1);
|
||
}
|
||
.confirm-list li::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: clamp(0.42rem, 0.7vw, 0.55rem);
|
||
top: clamp(1.5rem, 3vh, 1.95rem);
|
||
width: clamp(0.58rem, 0.9vw, 0.72rem);
|
||
height: clamp(0.3rem, 0.5vw, 0.38rem);
|
||
border-left: 2px solid var(--paper);
|
||
border-bottom: 2px solid var(--paper);
|
||
transform: rotate(-45deg) scale(0.4);
|
||
transform-origin: center;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease 0.15s,
|
||
transform 0.3s cubic-bezier(0.3, 1.5, 0.5, 1) 0.15s;
|
||
}
|
||
.confirm-list li.is-checked::before {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
}
|
||
.confirm-list li.is-checked::after {
|
||
opacity: 1;
|
||
transform: rotate(-45deg) scale(1);
|
||
}
|
||
|
||
/* ---------- Footer row (three brand marks, equal weight) ---------- */
|
||
.join-footer {
|
||
margin-top: auto;
|
||
padding-top: clamp(2.5rem, 6vh, 4rem);
|
||
/* Generous bottom padding — the dot-nav sits 36px from the bottom of
|
||
the viewport, plus its own hit area; logos need clear room below
|
||
them. Without this, the rightmost footer items can appear to slide
|
||
under the nav on short viewports. */
|
||
padding-bottom: clamp(5rem, 10vh, 8rem);
|
||
border-top: 1px solid rgba(46, 46, 40, 0.1);
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr 1fr;
|
||
align-items: center;
|
||
gap: clamp(1rem, 3vw, 3rem);
|
||
max-width: 1200px;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
width: 100%;
|
||
/* Single source of truth for how tall each footer item should be.
|
||
All three items are sized against this — the Fenja SVG fills it,
|
||
the Innovationsfonden mark scales its icon and text against it,
|
||
and the "Project Bifrost" text picks a font-size from it. */
|
||
--foot-h: clamp(34px, 4.4vh, 52px);
|
||
}
|
||
.join-footer > * {
|
||
height: var(--foot-h);
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
/* Left: "Project Bifrost" in Newsreader, italic accent on Bifrost */
|
||
.join-footer .foot-project {
|
||
justify-self: start;
|
||
font-family: var(--type-display);
|
||
font-weight: 340;
|
||
font-size: calc(var(--foot-h) * 0.54);
|
||
letter-spacing: -0.01em;
|
||
color: var(--ink);
|
||
white-space: nowrap;
|
||
line-height: 1;
|
||
}
|
||
.join-footer .foot-project em {
|
||
font-style: italic;
|
||
color: var(--accent);
|
||
font-weight: 360;
|
||
margin-left: 0.2em;
|
||
}
|
||
|
||
/* Centre: the Fenja AI SVG logo.
|
||
Same height target (--foot-h) as the "Project Bifrost" wordmark on
|
||
the left and the Innovationsfonden mark on the right, so all three
|
||
read as equal-weight brand marks. */
|
||
.join-footer .foot-fenja {
|
||
justify-self: center;
|
||
}
|
||
.join-footer .foot-fenja img,
|
||
.join-footer .foot-fenja svg {
|
||
height: 100%;
|
||
width: auto;
|
||
display: block;
|
||
}
|
||
|
||
/* Right: Innovationsfonden — hybrid of a small SVG slanted "I" mark
|
||
plus HTML text. Using HTML text instead of SVG <text> avoids
|
||
text-metric overflow clipping at the section's right edge. */
|
||
.join-footer .foot-innov {
|
||
justify-self: end;
|
||
gap: calc(var(--foot-h) * 0.12);
|
||
color: #0e3a48;
|
||
font-family: var(--type-body);
|
||
font-weight: 700;
|
||
font-size: calc(var(--foot-h) * 0.46);
|
||
letter-spacing: -0.015em;
|
||
line-height: 1;
|
||
white-space: nowrap;
|
||
}
|
||
.join-footer .foot-innov .innov-mark {
|
||
height: calc(var(--foot-h) * 0.88);
|
||
width: auto;
|
||
display: block;
|
||
fill: currentColor;
|
||
}
|
||
|
||
/* Hidden initial states when JS enabled (scroll-triggered reveals) */
|
||
.js .join-cta,
|
||
.js .join-confirmation,
|
||
.js .join-footer > * {
|
||
opacity: 0;
|
||
transform: translateY(22px);
|
||
will-change: opacity, transform;
|
||
}
|
||
|
||
/* ============================================================
|
||
RESPONSIVE — additions for the new sections
|
||
(independent of the existing @media block above)
|
||
============================================================ */
|
||
@media (max-width: 900px) {
|
||
/* Treasure map collapses to a centred vertical timeline */
|
||
.map-stop,
|
||
.map-stop--intro {
|
||
grid-template-columns: 1fr;
|
||
text-align: center;
|
||
margin-bottom: clamp(4.5rem, 10vh, 7rem);
|
||
gap: clamp(1.2rem, 3vw, 2rem);
|
||
justify-items: center;
|
||
}
|
||
.map-stop .dot-anchor {
|
||
grid-column: 1 !important;
|
||
margin: 0 auto;
|
||
order: 1;
|
||
}
|
||
.map-stop .stop-image {
|
||
grid-column: 1 !important;
|
||
justify-self: center !important;
|
||
max-width: 280px;
|
||
margin: 0 auto;
|
||
order: 2;
|
||
/* Paper-coloured backdrop hides the centred rail behind the
|
||
illustration on mobile. The illustration uses multiply blend
|
||
which then composites against this paper colour (same as the
|
||
page background) — visually unchanged, but no rail bleed. */
|
||
background: var(--paper);
|
||
}
|
||
.map-stop .stop-content {
|
||
grid-column: 1 !important;
|
||
text-align: center !important;
|
||
order: 3;
|
||
/* Same trick for the text block — keeps the rail from showing
|
||
through inter-line spacing. Generous vertical padding so the
|
||
rail "ducks under" the whole text region cleanly. */
|
||
background: var(--paper);
|
||
padding: 0.75rem 0.5rem;
|
||
max-width: 90%;
|
||
}
|
||
.stop-body {
|
||
max-width: 42ch;
|
||
margin: 0 auto !important;
|
||
}
|
||
/* Same backdrop for the intro paragraph */
|
||
.map-stop--intro .stop-content {
|
||
background: var(--paper);
|
||
padding: 0.75rem 1rem;
|
||
}
|
||
/* Dot already has a paper-coloured ring (box-shadow) so the
|
||
rail doesn't show through it — no extra work needed there. */
|
||
|
||
/* Replace the curving SVG path with a clean vertical rail */
|
||
.map-path { display: none; }
|
||
.map-canvas::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 1px;
|
||
background: rgba(46, 46, 40, 0.18);
|
||
transform: translateX(-50%);
|
||
z-index: 0;
|
||
}
|
||
.map-canvas::after {
|
||
/* the "drawn" overlay rail in accent — height set by JS */
|
||
content: "";
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 0;
|
||
width: 1.5px;
|
||
height: var(--rail-progress, 0%);
|
||
background: var(--accent);
|
||
opacity: 0.7;
|
||
transform: translateX(-50%);
|
||
z-index: 1;
|
||
transition: none;
|
||
}
|
||
|
||
/* Join footer stacks vertically on mobile, each mark centred */
|
||
.join-footer {
|
||
grid-template-columns: 1fr;
|
||
gap: clamp(1.8rem, 4vh, 2.4rem);
|
||
justify-items: center;
|
||
text-align: center;
|
||
}
|
||
.join-footer > * {
|
||
justify-self: center !important;
|
||
}
|
||
|
||
/* CTA headline + button tighter on phones */
|
||
.join-cta .join-headline {
|
||
font-size: clamp(2rem, 7vw, 3rem);
|
||
}
|
||
.confirm-list {
|
||
max-width: 100%;
|
||
padding: 0 0.25rem;
|
||
}
|
||
#bifrost-join {
|
||
min-height: auto;
|
||
padding: clamp(4rem, 10vh, 7rem) var(--edge) clamp(2rem, 4vh, 3rem);
|
||
}
|
||
.join-stage {
|
||
/* Mobile content (esp. confirmation with 4 wrapping bullets) is
|
||
taller than on desktop; size for the tallest panel so neither
|
||
one overflows into the footer below. */
|
||
min-height: clamp(560px, 82vh, 720px);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 520px) {
|
||
.stop-title { font-size: clamp(1.6rem, 7vw, 2.2rem); }
|
||
.stop-sub { font-size: 1.05rem; }
|
||
.stop-intro-text { font-size: clamp(1.1rem, 4.5vw, 1.4rem); }
|
||
}
|
||
|
||
/* Reduced motion — show everything statically */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.map-stop .dot,
|
||
.map-stop .stop-content > *,
|
||
.map-stop .stop-image,
|
||
.join-cta,
|
||
.join-confirmation,
|
||
.join-footer > *,
|
||
.confirm-list li::before,
|
||
.confirm-list li::after {
|
||
opacity: 1 !important;
|
||
transform: none !important;
|
||
}
|
||
.map-path .path-draw { stroke-dashoffset: 0 !important; }
|
||
.map-canvas::after { height: 100% !important; }
|
||
}
|
||
|
||
/* Bind illustration custom properties to the rendered surfaces.
|
||
Each surface uses background-image so the underlying base64 data
|
||
stays defined once in :root. */
|
||
.stop-illust {
|
||
width: 100%;
|
||
aspect-ratio: 1 / 1;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
background-size: contain;
|
||
mix-blend-mode: multiply;
|
||
}
|
||
.stop-illust[data-illust="community"] { background-image: var(--illust-community); }
|
||
.stop-illust[data-illust="council"] { background-image: var(--illust-council); }
|
||
.stop-illust[data-illust="pilot"] { background-image: var(--illust-pilot); }
|
||
|
||
|
||
</style>
|
||
</head>
|
||
<body data-screen-label="01 Timeline">
|
||
|
||
<a class="site-mark" href="/" aria-label="Back to the front page">
|
||
<img src="/fenja/fenja-wordmark-black.svg" alt="Fenja" />
|
||
</a>
|
||
|
||
<!-- ───── Page 1 : TIMELINE ───── -->
|
||
<section class="page page-timeline is-active" id="page-timeline" data-screen-label="01 Timeline">
|
||
<div class="page-title">When AI runs Europe, who runs the <em>AI?</em></div>
|
||
<div class="page-sub">
|
||
We’ve spent years building data and AI across Denmark and Europe, watching one dependency harden after another. AI is different. The United States has made that clear. China has made that clear. You cannot stand strong in this century on AI you do not control — and for the first time in a generation, Europe has both the reason and the moment to build its own. The window is closing faster than most realise. It is open now. It will not be open long.<br/><br/>
|
||
<span class="page-sub-accent">As AI moves into our hospitals, our courts, our defence, our schools — can we afford for the switch to sit in <em>Washington?</em></span>
|
||
</div>
|
||
|
||
<!-- Globe ghost -->
|
||
<div class="globe-wrap" id="globe-wrap"></div>
|
||
|
||
<!-- Continue to the next page -->
|
||
<button class="continue-btn" id="continue-btn" type="button">
|
||
<span class="c-icon" aria-hidden="true">
|
||
<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4">
|
||
<circle cx="24" cy="24" r="22.5"/>
|
||
<path d="M16 24 H32 M26 17.5 L32.5 24 L26 30.5"
|
||
stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</span>
|
||
<span class="c-label">How Fenja AI <em>addresses</em> this</span>
|
||
</button>
|
||
|
||
<div class="timeline-viewport" id="tl-viewport">
|
||
<div class="timeline-track" id="tl-track">
|
||
<div class="spine" id="spine"></div>
|
||
<!-- Year ticks and events injected by JS -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- First-load scroll hint. Vertically centered on the right edge;
|
||
fades out the moment the reader starts scrolling the timeline
|
||
(piggybacks on `.is-scrolled`, set in timeline.js after ~40px
|
||
of scroll travel). -->
|
||
<div class="timeline-scroll-hint" aria-hidden="true">
|
||
<span class="ts-label">Scroll to begin</span>
|
||
<svg class="ts-arrow" viewBox="0 0 56 14" fill="none"
|
||
stroke="currentColor" stroke-width="1.6"
|
||
stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M2 7 H50"/>
|
||
<path d="M44 2 L50 7 L44 12"/>
|
||
</svg>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ───── Page 2 : OVERVIEW ───── -->
|
||
<section class="page page-overview" id="page-overview" data-screen-label="02 Overview">
|
||
<!-- Topography background — concentric rings, parallax-scrolling behind
|
||
the Europe map. Drawn at runtime by bifrost.js's drawTopography()
|
||
into the SVG slot. Lives at z-index 0 so it sits behind the map
|
||
(z-index 1) and all content (z-index 2+). -->
|
||
<div class="overview-topography" id="overview-topography" aria-hidden="true"></div>
|
||
<div class="overview-globe" id="overview-globe"></div>
|
||
|
||
<!-- Internal scroller: the six Project Bifrost scenes live inside this.
|
||
Lenis and ScrollTrigger are wired to this element, not the window. -->
|
||
<div id="overview-scroll">
|
||
|
||
<!-- ============================================================
|
||
SCENE 1 — HERO
|
||
============================================================ -->
|
||
<section id="hero" class="scene" aria-labelledby="hero-title">
|
||
<div class="hero-wrap">
|
||
<!-- Left column: headline + lede + foot row. The right column
|
||
(.hero-mark) holds the Fenja wordmark, mirroring the
|
||
welcome page's right-side lockup. No eyebrow here — the
|
||
headline + logo pairing is enough to anchor the scene. -->
|
||
<div class="hero-copy">
|
||
<h1 id="hero-title" class="hero-title" data-reveal-lines>
|
||
Trusted & <em>Sovereign AI</em><br/>
|
||
built in Denmark, for <em>Europe.</em>
|
||
</h1>
|
||
<p class="hero-lede" data-reveal>
|
||
Fenja AI is both our company and our platform — one mission, one name. An entirely client-managed AI platform built in Denmark, so Danish and European organisations can take full control of their own AI.
|
||
</p>
|
||
|
||
<!-- Hero foot: "Backed by Innovationsfonden" on the left and
|
||
the scroll-down indicator on the right, both inside the
|
||
left column at the bottom of the paragraph block. Shared
|
||
baseline via display:flex + align-items:baseline on
|
||
.hero-foot. The scroll arrow points DOWN and gently bounces
|
||
downward (see @keyframes hint below). -->
|
||
<div class="hero-foot">
|
||
<div class="support" aria-label="Backed by Innovationsfonden" data-reveal>
|
||
<span>Backed by</span>
|
||
<!-- Simplified Innovationsfonden wordmark (redrawn — not their official logo, a respectful representation) -->
|
||
<svg viewBox="0 0 190 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||
<g fill="#3c6b6b">
|
||
<path d="M4 2 L12 18 L10 2 Z" />
|
||
<text x="18" y="15" font-family="Manrope, sans-serif" font-weight="600" font-size="13" letter-spacing="0.2" fill="#3c6b6b">nnovationsfonden</text>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="scroll-hint" aria-hidden="true" data-reveal>
|
||
<span>Scroll</span>
|
||
<span class="arrow"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right column: the Fenja wordmark, anchored to the right
|
||
half of the viewport — same placement the welcome page
|
||
uses for its .welcome-logo. Decorative; the <h1> above
|
||
is still the semantic heading. -->
|
||
<div class="hero-mark" aria-hidden="true" data-reveal>
|
||
<img src="/fenja/fenja-wordmark-black.svg" alt="" />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================================
|
||
SCENE 2 — ARCHITECTURE (pinned, scrubbed)
|
||
============================================================ -->
|
||
<section id="stack-scene" aria-label="The Fenja AI architecture">
|
||
<div class="stack-pin">
|
||
<!-- Title bar: rides along with the pin so it stays visible while
|
||
the reader scrolls through all 4 capability cards. The per-card
|
||
counter now lives inside each .layer-card (see .card-counter
|
||
below), so the title here is a standalone centered lockup. -->
|
||
<div class="stack-title-bar" aria-hidden="true">
|
||
<h2 class="stack-title">The Fenja AI platform in four steps</h2>
|
||
</div>
|
||
<div class="layer-theatre">
|
||
|
||
<!-- LEFT SIDE — explanatory copy, visible only during the grid phase. -->
|
||
<div class="copy-stage" aria-live="polite">
|
||
<div class="copy-layer" data-copy="0">
|
||
<span class="tag">One complete platform</span>
|
||
<h2>Everything you need <em>in one place.</em></h2>
|
||
<p>Fenja AI brings models, knowledge, tools, and agents together in one platform for using and scaling AI across your organisation.</p>
|
||
</div>
|
||
<div class="copy-layer" data-copy="1">
|
||
<span class="tag">Full control</span>
|
||
<h2>Your <strong>infrastructure.</strong><br/>Your <em>rules.</em></h2>
|
||
<p>Fenja AI is installed in your own client-managed environment, giving you full control over data, security, and governance.</p>
|
||
</div>
|
||
<div class="copy-layer" data-copy="2">
|
||
<span class="tag">Sovereignty</span>
|
||
<h2>Built in <strong>Denmark.</strong><br/>Ready for <em>Europe.</em></h2>
|
||
<p>Fenja AI is built in Denmark for European organisations that want trusted, sovereign AI on their own terms.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LAYER CARDS — drop in, stack, then rearrange to grid -->
|
||
<article class="layer-card" data-layer="0" aria-label="Layer 1: the AI">
|
||
<span class="card-eyebrow">The AI</span>
|
||
<div class="card-box">
|
||
<span class="card-counter" aria-hidden="true">1 / 4</span>
|
||
<span class="card-grid-label" aria-hidden="true">The AI</span>
|
||
<div class="card-content">
|
||
<h3 class="card-title">An <b>open-source</b> model, running on your <em>own hardware.</em></h3>
|
||
<p class="card-body">A state-of-the-art open-source language model deployed directly in your environment. It gives you powerful AI capabilities with full control over data, performance, and security.</p>
|
||
</div>
|
||
<div class="card-brain" aria-hidden="true"></div>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="layer-card" data-layer="1" aria-label="Layer 2: Knowledge">
|
||
<span class="card-eyebrow">The Knowledge</span>
|
||
<div class="card-box">
|
||
<span class="card-counter" aria-hidden="true">2 / 4</span>
|
||
<span class="card-grid-label" aria-hidden="true">The Knowledge</span>
|
||
<div class="card-content">
|
||
<h3 class="card-title">The business context that makes <em>AI understand your world.</em></h3>
|
||
<p class="card-body">A built-in knowledge layer that helps the platform understand your terminology, processes, and data. It retains what matters, improves over time, and gives the AI the context needed to deliver relevant and accurate results.</p>
|
||
</div>
|
||
<div class="card-brain" aria-hidden="true"></div>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="layer-card" data-layer="2" aria-label="Layer 3: Tools">
|
||
<span class="card-eyebrow">The Tools</span>
|
||
<div class="card-box">
|
||
<span class="card-counter" aria-hidden="true">3 / 4</span>
|
||
<span class="card-grid-label" aria-hidden="true">The Tools</span>
|
||
<div class="card-content">
|
||
<h3 class="card-title">How AI <b>acts</b> — not just what it <em>knows.</em></h3>
|
||
<p class="card-body">The capabilities that let the platform do real work across your environment. From search and retrieval to data access, automation, and analysis, these are the tools the AI uses to solve tasks in practice.</p>
|
||
</div>
|
||
<div class="card-brain" aria-hidden="true"></div>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="layer-card" data-layer="3" aria-label="Layer 4: Agents">
|
||
<span class="card-eyebrow">The Agents</span>
|
||
<div class="card-box">
|
||
<span class="card-counter" aria-hidden="true">4 / 4</span>
|
||
<span class="card-grid-label" aria-hidden="true">The Agents</span>
|
||
<div class="card-content">
|
||
<h3 class="card-title">Specialized AI agents <b>working together</b> around <em>real tasks.</em></h3>
|
||
<p class="card-body">Purpose-built agents designed to handle distinct roles and workflows. Fenja AI includes both ready-made agents and the framework to build new ones, so you can orchestrate AI the same way your organisation already works — through specialisation and coordination.</p>
|
||
</div>
|
||
<div class="card-brain" aria-hidden="true"></div>
|
||
</div>
|
||
</article>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
|
||
<!-- ============================================================
|
||
SCENE 3 — SLIDE 11 — WORDS FLY IN ONE AT A TIME
|
||
============================================================ -->
|
||
<section id="words-scene" aria-labelledby="words-head">
|
||
<h3 id="words-head" class="sr-only" style="position:absolute;left:-9999px;">
|
||
This is why we've invited you. To ensure Fenja AI is not just built for you, but with you.
|
||
</h3>
|
||
<div class="words-pin">
|
||
<!-- The words sentence is rebuilt client-side when a first name is
|
||
available from /auth/me, so the user sees their own name fly in
|
||
as the "Erik." token. Static text below is the no-JS / no-name
|
||
fallback, used verbatim if /auth/me returns no firstName.
|
||
|
||
Spans must be laid out verbatim so the word-fly-in animation
|
||
has known DOM targets; the injected name variant swaps them in
|
||
place with the same .w structure preserved.
|
||
|
||
When a first name is present:
|
||
"This is why we've invited you, [Name]. To ensure Fenja AI is
|
||
not just built for you — but with you."
|
||
→ .hi on "[Name]." (the personalization beat)
|
||
→ .hi on "with" + "you." (the thesis)
|
||
|
||
When no first name:
|
||
"This is why we've invited you. To ensure Fenja AI is not
|
||
just built for you — but with you."
|
||
→ .hi on "you." (after invited)
|
||
→ .hi on "with" + "you." (the thesis) -->
|
||
<p class="words" aria-hidden="true" id="words-sentence">
|
||
<span class="w">This</span>
|
||
<span class="w">is</span>
|
||
<span class="w">why</span>
|
||
<span class="w">we’ve</span>
|
||
<span class="w">invited</span>
|
||
<span class="w hi">you.</span>
|
||
<span class="w">To</span>
|
||
<span class="w">ensure</span>
|
||
<span class="w">Fenja</span>
|
||
<span class="w">AI</span>
|
||
<span class="w">is</span>
|
||
<span class="w">not</span>
|
||
<span class="w">just</span>
|
||
<span class="w">built</span>
|
||
<span class="w">for</span>
|
||
<span class="w">you</span>
|
||
<span class="w">—</span>
|
||
<span class="w">but</span>
|
||
<span class="w hi">with</span>
|
||
<span class="w hi">you.</span>
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================================
|
||
SCENE 4 — PROJECT BIFROST REVEAL
|
||
============================================================ -->
|
||
<section id="bifrost" aria-labelledby="bifrost-head">
|
||
<div class="bifrost-pin">
|
||
<div class="bifrost-stage">
|
||
|
||
<!-- The bridge arc. Uses a restrained aurora gradient — the ONLY
|
||
place colour appears in the entire site. Norse mythology:
|
||
Bifrost is the bridge between worlds, rendered here as a
|
||
single luminous arc spanning the stage. -->
|
||
<div class="arc-wrap" aria-hidden="true">
|
||
<svg viewBox="0 0 1400 500" xmlns="http://www.w3.org/2000/svg">
|
||
<defs>
|
||
<linearGradient id="auroraGrad" x1="0" x2="1" y1="0" y2="0">
|
||
<stop offset="0%" stop-color="#b48755" stop-opacity="0"/>
|
||
<stop offset="15%" stop-color="#b48755" stop-opacity="0.95"/>
|
||
<stop offset="40%" stop-color="#a4553b" stop-opacity="0.95"/>
|
||
<stop offset="65%" stop-color="#5c7b8e" stop-opacity="0.95"/>
|
||
<stop offset="85%" stop-color="#6e5a86" stop-opacity="0.95"/>
|
||
<stop offset="100%" stop-color="#6e5a86" stop-opacity="0"/>
|
||
</linearGradient>
|
||
<linearGradient id="auroraGradSoft" x1="0" x2="1" y1="0" y2="0">
|
||
<stop offset="0%" stop-color="#b48755" stop-opacity="0"/>
|
||
<stop offset="15%" stop-color="#b48755" stop-opacity="0.35"/>
|
||
<stop offset="40%" stop-color="#a4553b" stop-opacity="0.35"/>
|
||
<stop offset="65%" stop-color="#5c7b8e" stop-opacity="0.35"/>
|
||
<stop offset="85%" stop-color="#6e5a86" stop-opacity="0.35"/>
|
||
<stop offset="100%" stop-color="#6e5a86" stop-opacity="0"/>
|
||
</linearGradient>
|
||
<filter id="softGlow" x="-20%" y="-50%" width="140%" height="200%">
|
||
<feGaussianBlur stdDeviation="8"/>
|
||
</filter>
|
||
</defs>
|
||
|
||
<!-- soft halo -->
|
||
<path id="arcHalo" d="M 60 420 Q 700 -40 1340 420"
|
||
fill="none"
|
||
stroke="url(#auroraGradSoft)"
|
||
stroke-width="24"
|
||
stroke-linecap="round"
|
||
filter="url(#softGlow)"/>
|
||
|
||
<!-- main arc -->
|
||
<path id="arcMain" d="M 60 420 Q 700 -40 1340 420"
|
||
fill="none"
|
||
stroke="url(#auroraGrad)"
|
||
stroke-width="3"
|
||
stroke-linecap="round"/>
|
||
|
||
<!-- secondary thin highlight arc -->
|
||
<path id="arcThin" d="M 80 420 Q 700 -20 1320 420"
|
||
fill="none"
|
||
stroke="url(#auroraGrad)"
|
||
stroke-width="1"
|
||
stroke-linecap="round"
|
||
opacity="0.6"/>
|
||
</svg>
|
||
</div>
|
||
|
||
<div class="bifrost-text">
|
||
<div class="bifrost-eyebrow">Introducing</div>
|
||
<h2 id="bifrost-head" class="bifrost-name">
|
||
<span class="token">Project</span>
|
||
<span class="token accent">Bifrost</span>
|
||
</h2>
|
||
<p class="bifrost-sub">
|
||
The bridge <em>between</em> an industrial-grade AI platform and the realities of regulated organisations — built <em>with</em> them, not just for them.
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================================
|
||
SCENE 5 — PROJECT BIFROST · WHAT IT MEANS
|
||
A treasure-map of participation: an intro stop and three
|
||
component stops revealed sequentially as the user scrolls
|
||
a meandering ink path down the page.
|
||
============================================================ -->
|
||
<section id="bifrost-meaning" aria-labelledby="bifrost-meaning-head">
|
||
<div class="map-intro">
|
||
<span class="map-eyebrow">The invitation</span>
|
||
<h2 id="bifrost-meaning-head" class="map-title">
|
||
What being part of <em>Project Bifrost</em> means
|
||
</h2>
|
||
<p class="map-lede">
|
||
Three ways to <em>shape</em>, to <em>influence</em>, and to <em>build with</em> the platform from the inside — a journey through what participation actually looks like.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="map-canvas">
|
||
|
||
<!-- Wandering path. Stretched to fill the canvas via
|
||
preserveAspectRatio="none". The accent overlay is drawn
|
||
as the user scrolls down through the stops. -->
|
||
<svg class="map-path" viewBox="0 0 100 200" preserveAspectRatio="none" aria-hidden="true">
|
||
<!-- The d attributes are computed at runtime by buildMapPath()
|
||
based on the rendered Y positions of the dots, so the path
|
||
always passes through them regardless of content height.
|
||
The placeholder values below are valid fallback geometry
|
||
that ships if JS fails. -->
|
||
<path id="mapPathBg" class="path-bg"
|
||
vector-effect="non-scaling-stroke"
|
||
d="M 50 6 C 72 28, 72 52, 50 70 C 28 88, 28 112, 50 130 C 72 148, 72 172, 50 194"/>
|
||
<path id="mapPathDraw" class="path-draw"
|
||
vector-effect="non-scaling-stroke"
|
||
d="M 50 6 C 72 28, 72 52, 50 70 C 28 88, 28 112, 50 130 C 72 148, 72 172, 50 194"/>
|
||
</svg>
|
||
|
||
<!-- INTRO STOP -->
|
||
<article class="map-stop map-stop--intro" data-stop="0">
|
||
<div class="dot-anchor"><span class="dot"></span></div>
|
||
<div class="stop-content">
|
||
<p class="stop-intro-text">
|
||
Being part of <em>Project Bifrost</em> means <em>three</em> 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.
|
||
</p>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- STOP 1 — Community (text on left, image on right) -->
|
||
<article class="map-stop" data-stop="1" data-side="left" aria-labelledby="stop-1-title">
|
||
<div class="stop-content">
|
||
<span class="stop-eyebrow">Be part of a</span>
|
||
<h3 id="stop-1-title" class="stop-title"><em>Community</em></h3>
|
||
<p class="stop-sub">Shape the future together</p>
|
||
<p class="stop-body">Join a select community of organisations helping define the future of trusted sovereign AI in Denmark and Europe. At a time when Europe needs greater technological independence, this is an opportunity to contribute to an AI platform built on trust, shared ambition, and a common mission.</p>
|
||
</div>
|
||
<div class="dot-anchor"><span class="dot"></span></div>
|
||
<div class="stop-image">
|
||
<div class="stop-illust" data-illust="community" role="img" aria-label="Six people in discussion around a table"></div>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- STOP 2 — Advisory Council (image on left, text on right) -->
|
||
<article class="map-stop" data-stop="2" data-side="right" aria-labelledby="stop-2-title">
|
||
<div class="stop-image">
|
||
<div class="stop-illust" data-illust="council" role="img" aria-label="A man and a woman in conversation"></div>
|
||
</div>
|
||
<div class="dot-anchor"><span class="dot"></span></div>
|
||
<div class="stop-content">
|
||
<span class="stop-eyebrow">Be part of an</span>
|
||
<h3 id="stop-2-title" class="stop-title"><em>Advisory Council</em></h3>
|
||
<p class="stop-sub">Turn insight into influence</p>
|
||
<p class="stop-body">Take part in regular advisory council sessions where your input directly shapes the product and platform roadmap. Gain first-hand insight into cutting-edge AI developments and help influence what is built, which capabilities are prioritised, and how the platform evolves to meet real organisational needs.</p>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- STOP 3 — Pilot Projects (text on left, image on right) -->
|
||
<article class="map-stop" data-stop="3" data-side="left" aria-labelledby="stop-3-title">
|
||
<div class="stop-content">
|
||
<span class="stop-eyebrow">Be part of</span>
|
||
<h3 id="stop-3-title" class="stop-title"><em>Pilot Projects</em></h3>
|
||
<p class="stop-sub">Access the platform before others</p>
|
||
<p class="stop-body">A select number of Project Bifrost participants will have the opportunity to join pilot projects and gain early access to the platform at a significantly reduced price, subsidised by the Innovation Fund. This gives your organisation the chance to explore cutting-edge sovereign AI early, realise value at low cost, and help shape the platform through real-world use.</p>
|
||
</div>
|
||
<div class="dot-anchor"><span class="dot"></span></div>
|
||
<div class="stop-image">
|
||
<div class="stop-illust" data-illust="pilot" role="img" aria-label="Two people working together at a computer"></div>
|
||
</div>
|
||
</article>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================================
|
||
SCENE 6 — PROJECT BIFROST · JOIN
|
||
A large call-to-action ("Join us in shaping the future...")
|
||
that, when clicked, crossfades to a confirmation panel listing
|
||
what happens next. Below, a three-column footer row with three
|
||
equal-weight brand marks: "Project Bifrost" (left-aligned),
|
||
Fenja AI (centred), and Innovationsfonden (right-aligned).
|
||
============================================================ -->
|
||
<section id="bifrost-join" aria-labelledby="bifrost-join-head">
|
||
|
||
<div class="join-stage">
|
||
|
||
<!-- CTA state (visible initially) -->
|
||
<div class="join-panel join-cta" id="joinCTA">
|
||
<div class="join-eyebrow">Ready?</div>
|
||
<h2 id="bifrost-join-head" class="join-headline">
|
||
Join us in shaping the future of <em>trusted sovereign AI.</em>
|
||
</h2>
|
||
<button type="button" class="join-button" id="joinBtn" aria-controls="joinConfirm">
|
||
Join Project Bifrost
|
||
<span class="arrow" aria-hidden="true"></span>
|
||
</button>
|
||
<p class="join-subtext">Built in Denmark. Supported by the Innovation Fund.</p>
|
||
</div>
|
||
|
||
<!-- Confirmation state (revealed after the CTA is clicked) -->
|
||
<div class="join-panel join-confirmation" id="joinConfirm" aria-hidden="true">
|
||
<div class="confirm-eyebrow">You're in</div>
|
||
<h2 class="confirm-headline">
|
||
Thank you for joining <em>Project Bifrost</em>.
|
||
</h2>
|
||
<ul class="confirm-list" role="list">
|
||
<li>The <em>Fenja AI team</em> will reach out to you shortly.</li>
|
||
<li>You’ll receive an invitation to the <em>project portal</em> soon — where all project communication, materials, and updates will live.</li>
|
||
<li>We’re currently setting the date for the <em>first advisory council meeting</em>. You’ll be invited as soon as it’s confirmed.</li>
|
||
<li>We’ll be in touch shortly about your participation in the <em>pilot project</em>.</li>
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="join-footer">
|
||
<div class="foot-project" aria-label="Project Bifrost">Project <em>Bifrost</em></div>
|
||
|
||
<div class="foot-fenja" aria-label="Fenja AI">
|
||
<!-- Fenja AI wordmark. Sized via .join-footer .foot-fenja img CSS,
|
||
which makes it match the height of the other two footer items. -->
|
||
<img src="/fenja/fenja-wordmark-black.svg" alt="Fenja AI" />
|
||
</div>
|
||
|
||
<div class="foot-innov" aria-label="Innovationsfonden">
|
||
<!-- PLACEHOLDER: redrawn Innovationsfonden mark.
|
||
Hybrid: a small SVG holds the characteristic slanted "I"
|
||
and an HTML <span> renders the rest of the wordmark with
|
||
native text metrics (avoids SVG <text> overflowing its
|
||
viewBox at arbitrary widths). Swap this block for the
|
||
real PNG/SVG when available — the container already
|
||
sizes correctly via --foot-h. -->
|
||
<svg class="innov-mark" viewBox="0 0 60 100" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||
<path d="M 22 4 L 55 4 L 38 96 L 5 96 Z"/>
|
||
</svg><span class="innov-text">nnovationsfonden</span>
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
|
||
</div><!-- /#overview-scroll -->
|
||
</section>
|
||
|
||
<!-- Dot-nav tray + nav (shared across all pages)
|
||
Seven entries, flat. The first targets the Timeline page (P1). The
|
||
next five each target a scene inside the Overview page (P2) — clicking
|
||
switches to Overview AND scrolls the overview's internal scroller to
|
||
that scene. The last (Join) goes to the final scene of Overview.
|
||
|
||
data-target : page id to activate
|
||
data-scroll-to : (optional) element id inside #overview-scroll to
|
||
scroll to AFTER the page switch. Scroll runs on the
|
||
Overview's internal scroller via Lenis (if booted)
|
||
or scroller.scrollTo() as a fallback. -->
|
||
<div class="dot-nav-tray"></div>
|
||
<nav class="dot-nav">
|
||
<button class="dot-btn" data-target="external-welcome" data-href="/" aria-label="Return to welcome page">
|
||
<span class="dot"></span>
|
||
<span class="label">Welcome</span>
|
||
</button>
|
||
<button class="dot-btn is-active" data-target="page-timeline">
|
||
<span class="dot"></span>
|
||
<span class="label">Timeline</span>
|
||
</button>
|
||
<button class="dot-btn" data-target="page-overview" data-scroll-to="hero">
|
||
<span class="dot"></span>
|
||
<span class="label">Fenja introduction</span>
|
||
</button>
|
||
<button class="dot-btn" data-target="page-overview" data-scroll-to="stack-scene">
|
||
<span class="dot"></span>
|
||
<span class="label">Capabilities</span>
|
||
</button>
|
||
<button class="dot-btn" data-target="page-overview" data-scroll-to="bifrost">
|
||
<span class="dot"></span>
|
||
<span class="label">Project Bifrost</span>
|
||
</button>
|
||
<button class="dot-btn" data-target="page-overview" data-scroll-to="bifrost-join">
|
||
<span class="dot"></span>
|
||
<span class="label">Join</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<script src="/vendor/lenis.min.js" defer></script>
|
||
<script src="/vendor/gsap.min.js" defer></script>
|
||
<script src="/vendor/scrolltrigger.min.js" defer></script>
|
||
<script src="/bifrost.js" defer></script>
|
||
<script src="/timeline.js" defer></script>
|
||
|
||
</body>
|
||
</html>
|