2661 lines
87 KiB
HTML
2661 lines
87 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 ───────── */
|
||
.site-mark {
|
||
position: fixed;
|
||
top: 28px;
|
||
left: 36px;
|
||
width: 118px;
|
||
height: auto;
|
||
z-index: 50;
|
||
pointer-events: none;
|
||
opacity: 0.85;
|
||
}
|
||
@media (max-width: 720px) {
|
||
.site-mark { width: 90px; top: 20px; left: 22px; }
|
||
}
|
||
|
||
/* Page overline title — large, sits lower on the front matter so it reads */
|
||
.page-title {
|
||
position: absolute;
|
||
left: 80px; top: 42vh;
|
||
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(42vh + 220px);
|
||
max-width: 560px;
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-style: italic;
|
||
font-size: 19px;
|
||
line-height: 1.5;
|
||
color: var(--ink-soft);
|
||
z-index: 15;
|
||
opacity: 1;
|
||
transition: opacity 520ms var(--ease), transform 520ms var(--ease);
|
||
}
|
||
/* 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);
|
||
}
|
||
|
||
/* ───────── 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: 5px; height: 5px;
|
||
border-radius: 50%;
|
||
background: transparent;
|
||
box-shadow: inset 0 0 0 1px 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 1px var(--ink);
|
||
}
|
||
.dot-btn.is-active .dot {
|
||
background: var(--ink); /* filled ink, active */
|
||
box-shadow: inset 0 0 0 1px 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: 64%;
|
||
}
|
||
|
||
.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: 320px;
|
||
padding: 16px 20px 18px;
|
||
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: 26px;
|
||
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: 20px;
|
||
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 {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-weight: 400;
|
||
font-size: 17px;
|
||
line-height: 1.22;
|
||
letter-spacing: -0.01em;
|
||
color: var(--ink);
|
||
margin: 0 0 8px 0;
|
||
text-wrap: pretty;
|
||
}
|
||
.evt h3 em {
|
||
font-style: italic;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.evt p {
|
||
margin: 0;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
color: var(--ink-soft);
|
||
text-wrap: pretty;
|
||
}
|
||
|
||
.evt .source {
|
||
margin-top: 10px;
|
||
font-size: 9.5px;
|
||
letter-spacing: 0.2em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-dim);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ───────── Continue button ───────── */
|
||
.continue-btn {
|
||
all: unset;
|
||
position: absolute;
|
||
right: 72px;
|
||
bottom: 140px;
|
||
display: inline-flex;
|
||
align-items: baseline;
|
||
gap: 22px;
|
||
padding: 20px 28px;
|
||
background: var(--paper-high);
|
||
color: var(--ink);
|
||
cursor: pointer;
|
||
z-index: 30;
|
||
opacity: 0;
|
||
transform: translateX(36px);
|
||
pointer-events: none;
|
||
box-shadow:
|
||
0 0 0 0.5px rgba(56,56,49,0.06),
|
||
0 18px 32px -18px rgba(56,56,49,0.22),
|
||
0 2px 6px -3px rgba(56,56,49,0.08);
|
||
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: translateX(0);
|
||
pointer-events: auto;
|
||
animation: continue-breath 2800ms cubic-bezier(0.2, 0, 0, 1) infinite;
|
||
}
|
||
@keyframes continue-breath {
|
||
0%, 100% { transform: translateX(0); }
|
||
50% { transform: translateX(6px); }
|
||
}
|
||
.continue-btn:hover {
|
||
background: #fffbf2;
|
||
box-shadow:
|
||
0 0 0 0.5px rgba(56,56,49,0.10),
|
||
0 24px 40px -20px rgba(56,56,49,0.28),
|
||
0 3px 8px -4px rgba(56,56,49,0.10);
|
||
}
|
||
.continue-btn .c-label {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-size: 20px;
|
||
font-weight: 400;
|
||
letter-spacing: -0.01em;
|
||
color: var(--ink);
|
||
line-height: 1;
|
||
}
|
||
.continue-btn .c-label em {
|
||
font-style: italic; font-weight: 700;
|
||
}
|
||
.continue-btn .c-arrow {
|
||
font-family: "Newsreader", Georgia, serif;
|
||
font-style: italic;
|
||
font-size: 22px;
|
||
color: var(--crimson);
|
||
line-height: 1;
|
||
transition: transform var(--dur) var(--ease);
|
||
}
|
||
.continue-btn:hover .c-arrow {
|
||
transform: translateX(4px);
|
||
}
|
||
|
||
/* ───────── 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: 38vh; }
|
||
.page-title + .page-sub { font-size: 16px; top: calc(38vh + 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;
|
||
}
|
||
#page-overview #hero .hero-wrap {
|
||
/* Constrain to the left column so Europe is visible to its right. */
|
||
max-width: 62ch;
|
||
/* Reduced from clamp(6rem, 16vh, 12rem) to move hero text up into
|
||
the upper half of the viewport. Same intent as the #hero padding
|
||
reduction above — the more-specific selector takes precedence. */
|
||
padding-top: clamp(3rem, 8vh, 6rem);
|
||
}
|
||
|
||
/* 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(2.4rem, 6.2vw, 5.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);
|
||
}
|
||
|
||
.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.02;
|
||
letter-spacing: -0.03em;
|
||
color: var(--ink);
|
||
margin: 0;
|
||
max-width: 22ch;
|
||
}
|
||
.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;
|
||
gap: 0.75rem;
|
||
font-size: var(--step-sm);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.18em;
|
||
}
|
||
.support svg { height: 18px; width: auto; }
|
||
|
||
.scroll-hint {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.6rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.22em;
|
||
font-size: var(--step-sm);
|
||
color: var(--ink-soft);
|
||
}
|
||
/* Arrow reoriented to point DOWN — a vertical 1px line with a chevron
|
||
cap at the bottom. Animation moved from translateX to translateY so
|
||
the hint visually "drops" downward, matching its meaning. */
|
||
.scroll-hint .arrow {
|
||
width: 1px; height: 28px; background: currentColor;
|
||
position: relative;
|
||
animation: hint 2.2s ease-in-out infinite;
|
||
}
|
||
.scroll-hint .arrow::after {
|
||
content: ""; position: absolute; bottom: -1px; left: -3px;
|
||
width: 7px; height: 7px;
|
||
border-right: 1px solid currentColor;
|
||
border-bottom: 1px solid currentColor;
|
||
transform: rotate(45deg);
|
||
}
|
||
@keyframes hint {
|
||
0%, 100% { transform: translateY(0); opacity: 0.5; }
|
||
50% { transform: translateY(6px); 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;
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
/* Brain icon — CSS mask filled with cream color; looks good on any card tint */
|
||
.card-brain {
|
||
width: 100%;
|
||
aspect-ratio: 20 / 17;
|
||
background-color: var(--paper);
|
||
-webkit-mask-image: var(--brain-mask);
|
||
mask-image: var(--brain-mask);
|
||
-webkit-mask-size: contain;
|
||
mask-size: contain;
|
||
-webkit-mask-position: center right;
|
||
mask-position: center right;
|
||
-webkit-mask-repeat: no-repeat;
|
||
mask-repeat: no-repeat;
|
||
opacity: 0.9;
|
||
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.
|
||
Dark ink text + cream brain both read well on each. */
|
||
.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 */
|
||
|
||
/* 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;
|
||
}
|
||
|
||
.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; }
|
||
|
||
/* Brain fills remaining space, centered. */
|
||
.in-grid .card-brain {
|
||
margin: 0;
|
||
flex: 1 1 auto;
|
||
width: 100%;
|
||
aspect-ratio: auto;
|
||
-webkit-mask-position: center;
|
||
mask-position: center;
|
||
-webkit-mask-size: 90% auto;
|
||
mask-size: 90% auto;
|
||
opacity: 0.85;
|
||
}
|
||
|
||
/* 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);
|
||
line-height: 0.95;
|
||
letter-spacing: -0.04em;
|
||
color: var(--ink);
|
||
margin: 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 0.15em;
|
||
flex-wrap: wrap;
|
||
}
|
||
.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; }
|
||
}
|
||
|
||
@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);
|
||
font-weight: 330;
|
||
font-size: clamp(2rem, 5vw, 3.6rem);
|
||
line-height: 1.06;
|
||
letter-spacing: -0.03em;
|
||
color: var(--ink);
|
||
margin: 0 auto;
|
||
max-width: 22ch;
|
||
}
|
||
.join-confirmation .confirm-headline em {
|
||
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">
|
||
|
||
<img class="site-mark" src="/fenja/fenja-wordmark-black.svg" alt="Fenja" aria-hidden="true" />
|
||
|
||
<!-- ───── Page 1 : TIMELINE ───── -->
|
||
<section class="page page-timeline is-active" id="page-timeline" data-screen-label="01 Timeline">
|
||
<div class="page-title">From the promise of AI to the loss of <em>sovereignty.</em></div>
|
||
<div class="page-sub">Twenty-three headlines, quietly laid across a tinted map. Scroll the wheel — the map turns with you.</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-label">Read the editor’s <em>note</em></span>
|
||
<span class="c-arrow" aria-hidden="true">→</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>
|
||
</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">
|
||
<div>
|
||
<div class="eyebrow" data-reveal>For regulated environments</div>
|
||
<h1 id="hero-title" class="hero-title" data-reveal-lines>
|
||
Secure & <em>Sovereign</em> AI,<br/>
|
||
hosted where it <em>belongs.</em>
|
||
</h1>
|
||
<p class="hero-lede" data-reveal>
|
||
Enabling highly advanced AI capabilities hosted within the client's own secure infrastructure.
|
||
</p>
|
||
|
||
<!-- Hero foot: "Supported 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="Supported by Innovationsfonden" data-reveal>
|
||
<span>Supported 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>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================================
|
||
SCENE 2 — ARCHITECTURE (pinned, scrubbed)
|
||
============================================================ -->
|
||
<section id="stack-scene" aria-label="The Fenja AI architecture">
|
||
<div class="stack-pin">
|
||
<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-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-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-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-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">Hero</span>
|
||
</button>
|
||
<button class="dot-btn" data-target="page-overview" data-scroll-to="stack-scene">
|
||
<span class="dot"></span>
|
||
<span class="label">Architecture</span>
|
||
</button>
|
||
<button class="dot-btn" data-target="page-overview" data-scroll-to="words-scene">
|
||
<span class="dot"></span>
|
||
<span class="label">Words</span>
|
||
</button>
|
||
<button class="dot-btn" data-target="page-overview" data-scroll-to="bifrost">
|
||
<span class="dot"></span>
|
||
<span class="label">Bifrost</span>
|
||
</button>
|
||
<button class="dot-btn" data-target="page-overview" data-scroll-to="bifrost-meaning">
|
||
<span class="dot"></span>
|
||
<span class="label">Participate</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>
|