customer-presentation/protected/index.html
Jonathan Hvid b4babd82d5 credits: remove AI Lab / BioInnovation Institute affiliation
Drop the "Part of AI Lab" credit block from the entrance welcome
lockup, desktop hero foot, and mobile credits. Flex-column gap
reflows the remaining two credits with no CSS changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:27:16 +02:00

3804 lines
144 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 — 20222026</title>
<link rel="stylesheet" href="/fenja/colors_and_type.css" />
<link rel="stylesheet" href="/platform.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;
/* Balanced block padding so the grid-centered content sits on
the true vertical midline instead of being pushed upward by a
big padding-bottom (the previous asymmetric rule was tuned for
a taller headline + more foot clearance to the dot-nav). */
padding-top: clamp(3rem, 8vh, 6rem);
padding-bottom: clamp(4rem, 10vh, 7rem);
}
#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);
/* Widened ~20% from 58ch so the column gets more horizontal
room to breathe; the lede follows to 53ch. */
max-width: 70ch;
}
/* Hero type — tighter than --step-hero, with a +10% bump from the
previous clamp so the headline has a little more presence at
this wider column width. */
#page-overview #hero .hero-title {
font-size: clamp(1.8rem, 4.4vw, 3.75rem);
line-height: 1.08;
}
#page-overview #hero .hero-lede {
font-size: clamp(1.1rem, 1.87vw, 1.54rem);
line-height: 1.45;
max-width: 53ch;
}
/* 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;
}
/* Backer credits — funder + affiliations, in the deck's editorial
voice: a small uppercase Manrope label over each entity name set
in upright Newsreader serif, with the parent body / issuing
authority in a quieter serif beneath. Left-aligned variant of the
entrance page's centred .welcome-credits lockup. */
.support-stack {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.support-credit {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.support-credit-label {
font-family: "Manrope", system-ui, sans-serif;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--ink-dim);
}
.support-credit-name {
font-family: "Newsreader", Georgia, serif;
font-style: normal;
font-weight: 500;
font-size: 19px;
line-height: 1.2;
letter-spacing: 0.005em;
color: var(--ink);
}
.support-credit-auth {
font-family: "Newsreader", Georgia, serif;
font-style: normal;
font-size: 13.5px;
line-height: 1.3;
color: var(--ink-soft);
}
.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;
}
/* ============================================================
ADVISORY BOARD — dedicated reveal, placed right after the
final treasure-map stop (Pilot Projects) and before the
platform-architecture explainer. Editorial 4×2 portrait grid
adapted from the LinkedIn "board reveal" Layout A. Portraits
are served from /fenja/board/. Revealed on scroll by
bifrost.js; reduced-motion fallback is the @media block below.
============================================================ */
#board-reveal {
position: relative;
padding: clamp(5rem, 12vh, 10rem) var(--edge);
}
.board-wrap { max-width: 1180px; margin: 0 auto; }
.board-head {
text-align: center;
max-width: 62ch;
margin: 0 auto clamp(3.5rem, 8vh, 6rem);
}
.board-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);
font-weight: 500;
margin-bottom: clamp(1.2rem, 3vh, 1.8rem);
}
.board-eyebrow::before,
.board-eyebrow::after {
content: "";
width: 28px; height: 1px;
background: currentColor;
}
.board-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(0.8rem, 2vh, 1.2rem);
}
.board-title em { font-style: italic; font-weight: 400; }
.board-sub {
font-family: var(--type-display);
font-style: italic;
font-weight: 300;
font-size: var(--step-lg);
color: var(--ink-soft);
margin: 0;
letter-spacing: -0.005em;
}
.board-grid {
display: grid;
/* Fixed ~150px tiles, centred — roughly half the full-width size,
matching the reference's compact proportions. */
grid-template-columns: repeat(4, minmax(0, 150px));
justify-content: center;
gap: clamp(1.8rem, 3.2vw, 2.8rem) clamp(1.6rem, 2.6vw, 2.6rem);
}
.board-member {
margin: 0;
display: flex;
flex-direction: column;
}
.board-portrait {
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 4px;
overflow: hidden;
background: var(--surface-container-high, #e7e1d0);
}
.board-portrait img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.board-member figcaption { margin: 0; }
.bm-name {
font-family: var(--type-display);
font-weight: 700;
color: var(--ink);
font-size: clamp(1.05rem, 1.4vw, 1.3rem);
line-height: 1.15;
letter-spacing: -0.01em;
margin: clamp(0.9rem, 1.6vw, 1.1rem) 0 0.35rem;
}
.bm-title {
font-family: var(--type-body);
font-weight: 600;
color: var(--ink);
font-size: clamp(0.85rem, 1vw, 0.95rem);
line-height: 1.3;
margin: 0;
}
.bm-company {
font-family: var(--type-body);
font-weight: 400;
color: var(--ink-mute);
font-size: clamp(0.85rem, 1vw, 0.95rem);
line-height: 1.3;
margin: 0.15rem 0 0;
}
/* Initial hidden state — only when JS active (reduced-motion override below) */
.js #board-reveal .board-head > *,
.js #board-reveal .board-member {
opacity: 0;
transform: translateY(28px);
will-change: opacity, transform;
}
@media (max-width: 640px) {
.board-grid {
grid-template-columns: repeat(2, minmax(0, 150px));
}
}
@media (prefers-reduced-motion: reduce) {
#board-reveal .board-head > *,
#board-reveal .board-member {
opacity: 1 !important;
transform: none !important;
}
}
/* ============================================================
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&rsquo;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 &mdash; 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 laboratories, our boardrooms, our regulated workflows &mdash; into the data and the intellectual property our organisations have spent decades protecting &mdash; 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 &amp; <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 &mdash; 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-stack" data-reveal>
<div class="support-credit">
<span class="support-credit-label">Backed by</span>
<span class="support-credit-name">Innofounder</span>
<span class="support-credit-auth">Innovationsfonden</span>
</div>
<div class="support-credit">
<span class="support-credit-label">Part of</span>
<span class="support-credit-name">The Regulatory AI-Sandbox</span>
<span class="support-credit-auth">Datatilsynet &amp; Digitaliseringsstyrelsen</span>
</div>
</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 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 &mdash; 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 &mdash; 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 &mdash; 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>
<!-- ============================================================
ADVISORY BOARD — "Meet the Fenja AI Advisory Board"
A dedicated reveal placed right after the final Project
Bifrost stop (Pilot Projects). Editorial 4×2 portrait grid;
portraits live under /fenja/board/. Header bits + members
are staggered in on scroll by bifrost.js.
============================================================ -->
<section id="board-reveal" aria-labelledby="board-reveal-head">
<div class="board-wrap">
<div class="board-head">
<span class="board-eyebrow">Guidance &amp; counsel</span>
<h2 id="board-reveal-head" class="board-title">Meet the Fenja AI <em>Advisory Board</em></h2>
<p class="board-sub">Bridging industry &amp; sovereign AI</p>
</div>
<div class="board-grid">
<figure class="board-member">
<div class="board-portrait"><img src="/fenja/board/soren-friis.jpg" alt="Søren Friis" loading="lazy" /></div>
<figcaption>
<p class="bm-name">Søren Friis</p>
<p class="bm-title">IT Director</p>
<p class="bm-company">DSB</p>
</figcaption>
</figure>
<figure class="board-member">
<div class="board-portrait"><img src="/fenja/board/william-irving.jpg" alt="William Irving" loading="lazy" /></div>
<figcaption>
<p class="bm-name">William Irving</p>
<p class="bm-title">Chief Data &amp; Analytics Officer</p>
<p class="bm-company">Norlys</p>
</figcaption>
</figure>
<figure class="board-member">
<div class="board-portrait"><img src="/fenja/board/ulla-nygaard-eliassen.jpg" alt="Ulla Nygaard Eliassen" loading="lazy" /></div>
<figcaption>
<p class="bm-name">Ulla Nygaard Eliassen</p>
<p class="bm-title">Associate Improvement Project Director</p>
<p class="bm-company">Novo Nordisk</p>
</figcaption>
</figure>
<figure class="board-member">
<div class="board-portrait"><img src="/fenja/board/anna-jessen.jpg" alt="Anna Jessen" loading="lazy" /></div>
<figcaption>
<p class="bm-name">Anna Jessen</p>
<p class="bm-title">Director, Process Excellence &amp; Digitalization</p>
<p class="bm-company">Novo Nordisk</p>
</figcaption>
</figure>
<figure class="board-member">
<div class="board-portrait"><img src="/fenja/board/mathies-laursen.jpg" alt="Mathies Laursen" loading="lazy" /></div>
<figcaption>
<p class="bm-name">Mathies Laursen</p>
<p class="bm-title">CDO</p>
<p class="bm-company">Nationalbanken</p>
</figcaption>
</figure>
<figure class="board-member">
<div class="board-portrait"><img src="/fenja/board/torben-schutt.jpg" alt="Torben Schütt" loading="lazy" /></div>
<figcaption>
<p class="bm-name">Torben Schütt</p>
<p class="bm-title">Office Director, Center for Cyber and Digitalization</p>
<p class="bm-company">Forsvarsministeriet</p>
</figcaption>
</figure>
<figure class="board-member">
<div class="board-portrait"><img src="/fenja/board/mads-nyborg.jpg" alt="Mads Nyborg" loading="lazy" /></div>
<figcaption>
<p class="bm-name">Mads Nyborg</p>
<p class="bm-title">Chief Consultant, Department of Data and Analytics</p>
<p class="bm-company">Københavns Kommune</p>
</figcaption>
</figure>
<figure class="board-member">
<div class="board-portrait"><img src="/fenja/board/hakon-daltveit.jpg" alt="Håkon Daltveit" loading="lazy" /></div>
<figcaption>
<p class="bm-name">Håkon Daltveit</p>
<p class="bm-title">Chief Consultant, Department of Data and Analytics</p>
<p class="bm-company">Københavns Kommune</p>
</figcaption>
</figure>
</div>
</div>
</section>
<!-- ============================================================
ARCHITECTURE — Fenja AI Platform, simply explained
Inlined from the former standalone /deepdive page so the
reader can scroll straight from Project Bifrost into the
architecture explainer. Same DOM as deepdive.html's three
sections; same /platform.css. platform.js detects that it's
running inside #overview-scroll and skips its own Lenis
setup (bifrost.js owns that here).
============================================================ -->
<section id="platform-question" aria-labelledby="platform-question-head">
<div class="pq-wrap">
<h2 id="platform-question-head" class="pq-title">
Renting a few AI capabilities from American companies isn't enough.<br>
Installing an open-source language model isn't enough.
</h2>
<p class="pq-body">
You need a <em>platform you control</em> &mdash; with the
tools, the knowledge, and the framework to make AI
actually do the work your organization needs done.
</p>
</div>
</section>
<section id="platform-layers" aria-labelledby="platform-layers-head">
<h2 id="platform-layers-head" class="sr-only" style="position:absolute;left:-9999px;">The Fenja architecture, layer by layer</h2>
<div class="pl-pin">
<header class="pl-pin-header">
<p class="pl-pin-title">Fenja AI Platform Architecture</p>
<p class="pl-pin-subtitle">Simply Explained</p>
</header>
<div class="pl-pin-body">
<div class="pl-copy-stage" aria-live="polite">
<div class="pl-copy-step" data-beat="1">
<p class="pl-eyebrow">The foundation</p>
<h3 class="pl-headline"><em>A model in your environment.</em></h3>
<p class="pl-body">
A state-of-the-art open-source language model, running
entirely on your hardware. No data leaves your
perimeter. The starting point &mdash; but not yet
Fenja.
</p>
</div>
<div class="pl-copy-step" data-beat="2">
<p class="pl-eyebrow">The foundation</p>
<h3 class="pl-headline"><em>Knowledge.</em></h3>
<p class="pl-body">
What makes the model <em>Fenja</em> &mdash; an
understanding of your organization, captured in a wiki
your team can read and edit. Plus the routines and
working memory that turn Fenja into a coworker who
knows how things get done.
</p>
</div>
<div class="pl-copy-step" data-beat="3">
<p class="pl-eyebrow">What Fenja can do</p>
<h3 class="pl-headline"><em>Tools.</em></h3>
<p class="pl-body">
How knowledge becomes work. Fenja uses tools to find
documents, query data, take action across your
systems. Some are obvious; others depend on what your
work needs.
</p>
</div>
<div class="pl-copy-step" data-beat="4">
<p class="pl-eyebrow">When one becomes a team</p>
<h3 class="pl-headline"><em>Agents.</em></h3>
<p class="pl-body">
Real work isn't one task. Fenja becomes a team
&mdash; a supervisor and specialists, each focused,
each governed, all dispatched by workflows you've
designed.
</p>
</div>
<div class="pl-copy-step" data-beat="5">
<p class="pl-eyebrow">The full picture</p>
<h3 class="pl-headline"><em>Everything you need and with full control.</em></h3>
<p class="pl-body">
Fenja brings together all the pieces to solve simple
and complex AI use cases across your organisation.
Every component hosted in your infrastructure with
full traceability and governance. Secure and sovereign
by design.
</p>
</div>
</div>
<div class="pl-canvas-wrap">
<div class="pl-canvas">
<div class="pl-canvas-frame" aria-hidden="true">
<span class="pl-canvas-frame-label">Everything Client-Managed</span>
</div>
<section class="pl-group" data-layer="foundation" aria-hidden="true">
<header class="pl-group-head">
<span class="pl-group-label">Foundation</span>
<span class="pl-group-caption">Sovereign by design</span>
</header>
<div class="pl-cards pl-cards--3 pl-cards--stretch">
<article class="pl-card" data-card="lm">
<h4 class="pl-card-name">Language model</h4>
<p class="pl-card-italic">State-of-the-art, open-source</p>
<p class="pl-card-mono">On-prem</p>
</article>
<article class="pl-card" data-card="wiki">
<h4 class="pl-card-name">Wiki</h4>
<p class="pl-card-italic">Company and domain knowledge</p>
<p class="pl-card-mono">Organizational &middot; Departmental &middot; Personal</p>
</article>
<article class="pl-card" data-card="routines">
<h4 class="pl-card-name">Routines &amp; memory</h4>
<p class="pl-card-italic">How Fenja works inside it</p>
<p class="pl-card-mono">Stand-ups &middot; Recurring tasks &middot; Working memory</p>
</article>
</div>
</section>
<section class="pl-group" data-layer="tools" aria-hidden="true">
<header class="pl-group-head">
<span class="pl-group-label">Tools</span>
<span class="pl-group-caption">How Fenja acts</span>
</header>
<div class="pl-cards pl-cards--4 pl-cards--stretch">
<article class="pl-card">
<h4 class="pl-card-name">Document retrieval</h4>
<p class="pl-card-italic">Find and cite</p>
<p class="pl-card-mono">RAG</p>
</article>
<article class="pl-card">
<h4 class="pl-card-name">Structured data (ie SQL)</h4>
<p class="pl-card-italic">Query and extract</p>
<p class="pl-card-mono">NL &rarr; SQL</p>
</article>
<article class="pl-card">
<h4 class="pl-card-name">System actions</h4>
<p class="pl-card-italic">Read and write</p>
<p class="pl-card-mono">APIs &middot; integrations</p>
</article>
<article class="pl-card">
<h4 class="pl-card-name">Custom tools</h4>
<p class="pl-card-italic">Your specific work</p>
<p class="pl-card-mono">Defined by you</p>
</article>
</div>
</section>
<section class="pl-group" data-layer="agents" aria-hidden="true">
<header class="pl-group-head">
<span class="pl-group-label">Agents</span>
<span class="pl-group-caption">How Fenja scales</span>
</header>
<div class="pl-cards pl-cards--4 pl-cards--stretch">
<article class="pl-card">
<h4 class="pl-card-name">Supervisor</h4>
<p class="pl-card-italic">Plan and dispatch</p>
<p class="pl-card-mono">Orchestration</p>
</article>
<article class="pl-card">
<h4 class="pl-card-name">Specialists</h4>
<p class="pl-card-italic">Focused expertise</p>
<p class="pl-card-mono">Subagents</p>
</article>
<article class="pl-card">
<h4 class="pl-card-name">Skills</h4>
<p class="pl-card-italic">Reusable capability</p>
<p class="pl-card-mono">Portable across specialists</p>
</article>
<article class="pl-card">
<h4 class="pl-card-name">Workflows</h4>
<p class="pl-card-italic">Composed by you</p>
<p class="pl-card-mono">Governed end-to-end</p>
</article>
</div>
</section>
</div>
</div>
</div><!-- /.pl-pin-body -->
</div>
</section>
<!-- ============================================================
WIKI DEEP-DIVE — from scattered knowledge to one structured
source of truth. Pinned scrubbed five-beat section,
mirroring #platform-layers's structure (.pl-pin shell,
pl-pin-header for title, then a body of three columns).
Beats:
0 Anchor — the Wiki pl-card from the architecture grid
scales up to centre as the section enters.
1 Left — "Scattered knowledge". Document and tacit-
knowledge icons reveal in a jittered cluster.
2 Middle — "Fenja AI Compiler". Compiler block fades in
and three flow lines draw from the scatter into it.
3 Right — "Fenja Wiki" mock (nav + article + citations).
Three flow lines draw from the compiler into it; the
article's inline <sup> citation markers fade in.
4 Trust — citation [1] lights up in walnut, a faint arc
traces back to a specific document on the left. Light
touch; rewards attention.
Content invariants — do not soften:
• Only the brand name "Fenja Wiki" is used. No generic
encyclopedia-style brand names are referenced anywhere
(copy, alt text, filenames, or this comment).
• Fenja Wiki is described as the *default* structured
output — other outputs are possible (sub-caption).
• Left side stays visually chaotic, right side stays
calm. The contrast is the storytelling point.
• The Compiler is the only element earning the walnut
accent. Left is muted; right is neutral except for
citation markers (also walnut, threading the trust
story to the only other accent on the slide).
============================================================ -->
<section id="wiki-deepdive" aria-labelledby="wiki-deepdive-head">
<h2 id="wiki-deepdive-head" class="sr-only" style="position:absolute;left:-9999px;">
Wiki — from scattered knowledge to one structured source of truth
</h2>
<div class="wd-pin">
<header class="pl-pin-header">
<p class="pl-pin-title">From scattered knowledge to <em>one structured source of truth.</em></p>
<p class="pl-pin-subtitle">Sources you already have, structured the way you decide &mdash; with every fact traceable back to where it came from.</p>
</header>
<!-- Beat-0 anchor: a 1:1 copy of the Wiki pl-card from the
architecture grid, positioned over the centre column.
platform.js fades it out as Beat 1 fires. Decorative,
hidden from AT — the real content is in .wd-body. -->
<div class="wd-anchor" aria-hidden="true">
<article class="pl-card">
<h4 class="pl-card-name">Wiki</h4>
<p class="pl-card-italic">Company and domain knowledge</p>
<p class="pl-card-mono">Organizational &middot; Departmental &middot; Personal</p>
</article>
</div>
<div class="wd-body">
<!-- LEFT — Scattered knowledge ---------------------- -->
<div class="wd-zone wd-zone--scatter">
<p class="wd-zone-eyebrow">What you have today</p>
<h3 class="wd-zone-name">Scattered knowledge</h3>
<p class="wd-zone-sub">Documents &middot; Emails &middot; Notes &middot; Knowledge in people&rsquo;s heads</p>
<!-- Bounded scatter cluster.
Positions follow a loose 4-row × 4-column grid that
we then jitter from, so density is even across the
bounded zone — no top-heavy clumps or middle voids.
Foreground items (full opacity) and background-pile
items (--o ≈ 0.45) interleave row-by-row so the
layering is also even rather than stacked.
Icon vocabulary unchanged from the previous pass
(PDF / DOC / slide / note / mail outlines kept as-is);
only the tacit-knowledge symbol is new — see below. -->
<div class="wd-scatter" aria-hidden="true">
<!-- Row 1 (ty ≈ 610%) -->
<span class="wd-doc" data-doc="pdf" style="--r:-7deg; --tx:6%; --ty:10%; --s:1.05">
<svg viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg">
<path d="M 6 4 H 30 L 42 16 V 56 H 6 Z M 30 4 V 16 H 42" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>
<line x1="12" y1="26" x2="36" y2="26" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="32" x2="36" y2="32" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="38" x2="28" y2="38" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<rect x="9" y="46" width="18" height="8" rx="2" fill="currentColor" opacity="0.18"/>
<text x="18" y="52.5" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="5.4" font-weight="600" fill="currentColor">PDF</text>
</svg>
</span>
<span class="wd-doc" data-doc="doc" style="--r:5deg; --tx:34%; --ty:6%; --s:0.96">
<svg viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg">
<path d="M 6 4 H 30 L 42 16 V 56 H 6 Z M 30 4 V 16 H 42" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>
<line x1="12" y1="26" x2="36" y2="26" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="32" x2="36" y2="32" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="38" x2="30" y2="38" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<rect x="9" y="46" width="18" height="8" rx="2" fill="currentColor" opacity="0.18"/>
<text x="18" y="52.5" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="5.4" font-weight="600" fill="currentColor">DOC</text>
</svg>
</span>
<span class="wd-doc wd-doc--note" data-doc="note" style="--r:10deg; --tx:68%; --ty:8%; --s:0.78; --o:0.45">
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="5" width="38" height="38" rx="1.5" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
<line x1="11" y1="16" x2="37" y2="16" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="11" y1="23" x2="33" y2="23" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="11" y1="30" x2="29" y2="30" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
</svg>
</span>
<!-- Row 2 (ty ≈ 2632%) -->
<span class="wd-doc wd-doc--slide" data-doc="ppt" style="--r:6deg; --tx:2%; --ty:28%; --s:0.80; --o:0.45">
<svg viewBox="0 0 60 44" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="4" width="52" height="36" rx="2" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
<line x1="10" y1="14" x2="30" y2="14" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity="0.75"/>
<rect x="10" y="20" width="20" height="14" rx="1.5" fill="none" stroke="currentColor" stroke-width="1" opacity="0.55"/>
<rect x="34" y="20" width="16" height="14" rx="1.5" fill="none" stroke="currentColor" stroke-width="1" opacity="0.55"/>
</svg>
</span>
<span class="wd-doc" data-doc="pdf" style="--r:11deg; --tx:26%; --ty:32%; --s:0.86; --o:0.45">
<svg viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg">
<path d="M 6 4 H 30 L 42 16 V 56 H 6 Z M 30 4 V 16 H 42" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>
<line x1="12" y1="26" x2="36" y2="26" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="32" x2="36" y2="32" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="38" x2="28" y2="38" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<rect x="9" y="46" width="18" height="8" rx="2" fill="currentColor" opacity="0.18"/>
<text x="18" y="52.5" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="5.4" font-weight="600" fill="currentColor">PDF</text>
</svg>
</span>
<span class="wd-doc wd-doc--slide" data-doc="ppt" style="--r:-3deg; --tx:48%; --ty:26%; --s:1.0">
<svg viewBox="0 0 60 44" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="4" width="52" height="36" rx="2" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
<line x1="10" y1="14" x2="30" y2="14" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity="0.75"/>
<rect x="10" y="20" width="20" height="14" rx="1.5" fill="none" stroke="currentColor" stroke-width="1" opacity="0.55"/>
<rect x="34" y="20" width="16" height="14" rx="1.5" fill="none" stroke="currentColor" stroke-width="1" opacity="0.55"/>
</svg>
</span>
<span class="wd-doc" data-doc="doc" style="--r:-9deg; --tx:72%; --ty:30%; --s:0.84; --o:0.50">
<svg viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg">
<path d="M 6 4 H 30 L 42 16 V 56 H 6 Z M 30 4 V 16 H 42" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>
<line x1="12" y1="26" x2="36" y2="26" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="32" x2="36" y2="32" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="38" x2="30" y2="38" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<rect x="9" y="46" width="18" height="8" rx="2" fill="currentColor" opacity="0.18"/>
<text x="18" y="52.5" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="5.4" font-weight="600" fill="currentColor">DOC</text>
</svg>
</span>
<!-- Row 3 (ty ≈ 4652%) -->
<span class="wd-doc wd-doc--note" data-doc="note" style="--r:9deg; --tx:10%; --ty:50%; --s:0.98">
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="5" width="38" height="38" rx="1.5" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
<line x1="11" y1="16" x2="37" y2="16" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="11" y1="23" x2="33" y2="23" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="11" y1="30" x2="29" y2="30" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="11" y1="37" x2="25" y2="37" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
</svg>
</span>
<span class="wd-doc wd-doc--mail" data-doc="mail" style="--r:-5deg; --tx:30%; --ty:52%; --s:1.0">
<svg viewBox="0 0 56 40" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="6" width="48" height="28" rx="2" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
<path d="M 4 10 L 28 24 L 52 10" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" opacity="0.8"/>
</svg>
</span>
<!-- Tacit knowledge: clean person silhouette paired
with a plain rounded-rectangle "think box". Two
small connector dots between them act as the
thought trail. Same stroke weight (1.4) as every
other icon — drops into the icon family without
introducing a new shape vocabulary. -->
<span class="wd-doc wd-doc--tacit" data-doc="tacit" style="--r:3deg; --tx:64%; --ty:48%; --s:1.0">
<svg viewBox="0 0 60 44" xmlns="http://www.w3.org/2000/svg">
<!-- person: head + shoulders -->
<circle cx="14" cy="18" r="7" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
<path d="M 4 40 Q 14 30 24 40" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
<!-- connector dots -->
<circle cx="26" cy="14" r="1.1" fill="currentColor" opacity="0.7"/>
<circle cx="31" cy="9" r="0.85" fill="currentColor" opacity="0.55"/>
<!-- think box: plain rounded rectangle -->
<rect x="34" y="2" width="22" height="14" rx="2" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
</svg>
</span>
<!-- Row 4 (ty ≈ 6872%) -->
<span class="wd-doc wd-doc--note" data-doc="note" style="--r:-4deg; --tx:8%; --ty:72%; --s:0.82; --o:0.50">
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="5" width="38" height="38" rx="1.5" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
<line x1="11" y1="16" x2="37" y2="16" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="11" y1="23" x2="33" y2="23" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="11" y1="30" x2="29" y2="30" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="11" y1="37" x2="25" y2="37" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
</svg>
</span>
<span class="wd-doc wd-doc--mail" data-doc="mail" style="--r:8deg; --tx:36%; --ty:70%; --s:0.88; --o:0.45">
<svg viewBox="0 0 56 40" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="6" width="48" height="28" rx="2" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4"/>
<path d="M 4 10 L 28 24 L 52 10" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" opacity="0.8"/>
</svg>
</span>
<span class="wd-doc" data-doc="pdf" style="--r:-6deg; --tx:66%; --ty:72%; --s:0.82; --o:0.50">
<svg viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg">
<path d="M 6 4 H 30 L 42 16 V 56 H 6 Z M 30 4 V 16 H 42" fill="var(--surface-container-lowest)" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>
<line x1="12" y1="26" x2="36" y2="26" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="32" x2="36" y2="32" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<line x1="12" y1="38" x2="28" y2="38" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<rect x="9" y="46" width="18" height="8" rx="2" fill="currentColor" opacity="0.18"/>
<text x="18" y="52.5" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="5.4" font-weight="600" fill="currentColor">PDF</text>
</svg>
</span>
</div>
</div>
<!-- Chevron between cluster and compiler. Same SVG mark
used in #platform-roadmap; reinforces left → right
reading without bringing back curving flow lines. -->
<span class="wd-chevron" aria-hidden="true"></span>
<!-- MIDDLE — Fenja AI Compiler ---------------------- -->
<div class="wd-zone wd-zone--compiler">
<!-- The labels and the card are positioned together
so the card's vertical centre lines up with the
chevron midline on either side. The labels are
grouped in .wd-compiler-head so they can be
anchored just above the card. -->
<div class="wd-compiler-head">
<p class="wd-zone-eyebrow">Fenja Compiler</p>
<h3 class="wd-zone-name">AI Compiler</h3>
<p class="wd-zone-sub">Apply your rules and structure to fit your requirements</p>
</div>
<!-- Rules card. Outline + paper fill; brand accent is
reserved for the trust beat. Card centre is held
at zone vertical centre via absolute positioning
so it lines up exactly with the chevrons. -->
<div class="wd-compiler" aria-hidden="true">
<span class="wd-compiler-label">Rules</span>
<ul class="wd-compiler-rules">
<li><span class="wd-rule-toggle"></span><span class="wd-rule-line"></span></li>
<li><span class="wd-rule-toggle is-on"></span><span class="wd-rule-line"></span></li>
<li><span class="wd-rule-toggle"></span><span class="wd-rule-line"></span></li>
<li><span class="wd-rule-toggle is-on"></span><span class="wd-rule-line"></span></li>
<li><span class="wd-rule-toggle is-on"></span><span class="wd-rule-line"></span></li>
<li><span class="wd-rule-toggle"></span><span class="wd-rule-line"></span></li>
</ul>
</div>
</div>
<!-- Chevron between compiler and stack. -->
<span class="wd-chevron" aria-hidden="true"></span>
<!-- RIGHT — abstract layered page stack -------------- -->
<div class="wd-zone wd-zone--wiki">
<p class="wd-zone-eyebrow">Fenja Wiki</p>
<h3 class="wd-zone-name">Structured output</h3>
<p class="wd-zone-sub">Structured output &middot; easy for humans and AIs to read.</p>
<!-- Three abstract knowledge-document cards stacked
with depth. ALL THREE share the same internal
layout — title bar, two body sections with body
lines, subtitle between them, divider, three
source entries at the bottom. The front card adds
small superscript citation markers inline within
the body lines (the trust-beat payoff visible at
Beat 4); the mid and back cards skip the citation
markers so they're not legible through the blur,
but their layout matches so the format reads
consistently across the stack. -->
<div class="wd-stack" aria-hidden="true">
<div class="wd-stack-card" data-depth="back">
<span class="wd-stack-rail" aria-hidden="true"></span>
<div class="wd-stack-content">
<span class="wd-stack-title-bar"></span>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:30%"></span>
<span class="wd-stack-bracket"><span style="--bw:16px"></span></span>
<span class="wd-stack-block" style="--w:24%"></span>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:42%"></span>
<span class="wd-stack-block" style="--w:24%"></span>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:24%"></span>
<span class="wd-stack-bracket"><span style="--bw:10px"></span></span>
<span class="wd-stack-block" style="--w:20%"></span>
</div>
<span class="wd-stack-subhead"></span>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:38%"></span>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:30%"></span>
<span class="wd-stack-block" style="--w:28%"></span>
</div>
<hr class="wd-stack-divider"/>
<div class="wd-stack-sources">
<div class="wd-stack-source"><sup>1</sup><span class="wd-stack-source-line"></span></div>
<div class="wd-stack-source"><sup>2</sup><span class="wd-stack-source-line"></span></div>
<div class="wd-stack-source"><sup>3</sup><span class="wd-stack-source-line"></span></div>
</div>
</div>
</div>
<div class="wd-stack-card" data-depth="mid">
<span class="wd-stack-rail" aria-hidden="true"></span>
<div class="wd-stack-content">
<span class="wd-stack-title-bar"></span>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:34%"></span>
<span class="wd-stack-bracket"><span style="--bw:18px"></span></span>
<span class="wd-stack-block" style="--w:20%"></span>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:38%"></span>
<span class="wd-stack-block" style="--w:28%"></span>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:28%"></span>
<span class="wd-stack-bracket"><span style="--bw:12px"></span></span>
<span class="wd-stack-block" style="--w:18%"></span>
</div>
<span class="wd-stack-subhead"></span>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:44%"></span>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:26%"></span>
<span class="wd-stack-block" style="--w:30%"></span>
</div>
<hr class="wd-stack-divider"/>
<div class="wd-stack-sources">
<div class="wd-stack-source"><sup>1</sup><span class="wd-stack-source-line"></span></div>
<div class="wd-stack-source"><sup>2</sup><span class="wd-stack-source-line"></span></div>
<div class="wd-stack-source"><sup>3</sup><span class="wd-stack-source-line"></span></div>
</div>
</div>
</div>
<div class="wd-stack-card" data-depth="front">
<span class="wd-stack-rail" aria-hidden="true"></span>
<div class="wd-stack-content">
<span class="wd-stack-title-bar"></span>
<!-- Body block 1: 3 lines with internal-link
brackets ([ ── ]) and a citation marker. -->
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:30%"></span>
<span class="wd-stack-bracket"><span style="--bw:14px"></span></span>
<span class="wd-stack-block" style="--w:22%"></span>
<sup class="wd-cite" data-cite="1">1</sup>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:40%"></span>
<span class="wd-stack-block" style="--w:22%"></span>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:26%"></span>
<span class="wd-stack-bracket"><span style="--bw:18px"></span></span>
<span class="wd-stack-block" style="--w:18%"></span>
</div>
<!-- Subtitle (shorter and darker than body). -->
<span class="wd-stack-subhead"></span>
<!-- Body block 2: 2 lines with citation markers. -->
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:42%"></span>
<sup class="wd-cite" data-cite="2">2</sup>
</div>
<div class="wd-stack-line">
<span class="wd-stack-block" style="--w:28%"></span>
<span class="wd-stack-block" style="--w:24%"></span>
<sup class="wd-cite" data-cite="3">3</sup>
</div>
<hr class="wd-stack-divider"/>
<!-- Source list: numbered horizontal lines.
data-source on each entry lets Beat 4 pair
the lit in-text citation with its bottom
entry (the source that the arc traces
back to in the scatter cluster). -->
<div class="wd-stack-sources">
<div class="wd-stack-source" data-source="1"><sup>1</sup><span class="wd-stack-source-line"></span></div>
<div class="wd-stack-source" data-source="2"><sup>2</sup><span class="wd-stack-source-line"></span></div>
<div class="wd-stack-source" data-source="3"><sup>3</sup><span class="wd-stack-source-line"></span></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="platform-cards" aria-labelledby="platform-cards-head">
<header class="platform-cards-head">
<p class="platform-eyebrow">Deployment options</p>
<h2 id="platform-cards-head" class="platform-title">Choose your <em>Capability.</em></h2>
</header>
<div class="platform-card-grid" role="list">
<article class="platform-card" role="listitem">
<h3 class="platform-card-name">Fenja <em>Core.</em></h3>
<p class="platform-card-tier">Foundational</p>
<p class="platform-card-body">Essential LLM capabilities with Fenja Semantic. Your safe and custom chatbot that understands your organization.</p>
</article>
<article class="platform-card" role="listitem">
<h3 class="platform-card-name">Fenja <em>Dev.</em></h3>
<p class="platform-card-tier">Developer toolset</p>
<p class="platform-card-body">Code faster and better with your own secure AI-supported development platform.</p>
<p class="platform-card-includes">+ Core</p>
</article>
<article class="platform-card" role="listitem">
<h3 class="platform-card-name">Fenja <em>Analyze.</em></h3>
<p class="platform-card-tier">Strategic intel</p>
<p class="platform-card-body">Bring real insights to your people. You ask for an insight, and your agents will find, analyze, and present the relevant data.</p>
<p class="platform-card-includes">+ Core</p>
</article>
<article class="platform-card is-dark" role="listitem">
<h3 class="platform-card-name">Fenja <em>Agentic.</em></h3>
<p class="platform-card-tier">Automation</p>
<p class="platform-card-body">The complete framework. Fully governed and controlled agents collaborate to solve your most important processes.</p>
<p class="platform-card-includes">+ Core. Dev. Analyze.</p>
</article>
</div>
</section>
<!-- ============================================================
IMPLEMENTATION ROADMAP
A horizontal sequence of four stages followed by a
cross-cutting band that runs underneath all of them. The
four stages are bounded waves of use-case delivery; the
band is continuous infrastructure work that does NOT end.
Content-correctness note (deliberate, do not "tidy"):
Stages 24 are called "Wave" (not "Phase"). The platform
is installed ONCE at Setup; later waves bring deeper
layers (knowledge → tools → agents) into ACTIVE USE.
Card subtitles describe what is now live and in use,
not what is being deployed. An earlier "Phase 1 =
Foundation, Phase 2 = Knowledge…" framing was wrong.
============================================================ -->
<section id="platform-roadmap" aria-labelledby="platform-roadmap-head">
<header class="platform-cards-head">
<p class="platform-eyebrow">Implementation roadmap</p>
<h2 id="platform-roadmap-head" class="platform-title">
One foundation, <em>many use cases.</em>
</h2>
</header>
<!-- Row of four stage cards + chevrons between each pair. The
::after chevron on each card except the last gives the
"sequence" reading; aria-hidden because the cards' DOM
order already conveys it to assistive tech. -->
<ol class="rm-row" role="list">
<!-- Each card is the same DOM element in two states: a
collapsed face (title / subtitle / meta-at-bottom) and
an expanded panel (meta moved to the top via flex
order, then title, subtitle, intro paragraph, key
activities, all on the same walnut surface). The
transition between the two is a FLIP layout animation
driven by initRoadmap → setupRoadmapMorph in
platform.js — see that file for the animation logic. -->
<li class="rm-card" role="button" tabindex="0"
aria-expanded="false" data-detail="setup">
<h3 class="rm-name">Setup</h3>
<p class="rm-italic">Platform live in your environment</p>
<div class="rm-card-body" aria-hidden="true">
<p class="rm-card-intro">The one-time foundation. In a single sprint, the platform goes live inside your infrastructure &mdash; model deployed, identity wired in, brand voice applied, baseline policies enforced. After this, the platform is ready to receive any use case.</p>
<p class="rm-card-section-label">Key activities</p>
<ul class="rm-card-list">
<li>Foundation model installed in your environment</li>
<li>SSO and identity provider connected</li>
<li>Brand voice, tone, and visual style applied</li>
<li>Baseline governance and security policies in place</li>
</ul>
</div>
<button class="rm-card-close" type="button" aria-label="Close" tabindex="-1">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">
<path d="M3 3 L11 11"/>
<path d="M11 3 L3 11"/>
</svg>
</button>
</li>
<li class="rm-card" role="button" tabindex="0"
aria-expanded="false" data-detail="wave-1">
<h3 class="rm-name">Knowledge</h3>
<p class="rm-italic">Captured into one structured source</p>
<div class="rm-card-body" aria-hidden="true">
<p class="rm-card-intro">The first body of use cases captures what your organisation already knows. For each use case, the team scopes the target output, ingests the right files, and runs AI-led interviews to surface the knowledge that today lives only in people&rsquo;s heads.</p>
<p class="rm-card-section-label">Key activities</p>
<ul class="rm-card-list">
<li>Use case scope and target output defined</li>
<li>Relevant files ingested and structured</li>
<li>AI-led interviews capture tacit expert knowledge</li>
<li>Outputs tested and validated against real work</li>
</ul>
</div>
<button class="rm-card-close" type="button" aria-label="Close" tabindex="-1">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">
<path d="M3 3 L11 11"/>
<path d="M11 3 L3 11"/>
</svg>
</button>
</li>
<li class="rm-card" role="button" tabindex="0"
aria-expanded="false" data-detail="wave-2">
<h3 class="rm-name">Tools</h3>
<p class="rm-italic">Acting across your systems</p>
<div class="rm-card-body" aria-hidden="true">
<p class="rm-card-intro">Use cases that reach beyond knowledge into your systems. Each tool implementation wires the platform into a specific source or destination &mdash; a data warehouse, a SharePoint repository, an API, a structured report. The platform stops being read-only and starts acting on your behalf.</p>
<p class="rm-card-section-label">Key activities</p>
<ul class="rm-card-list">
<li>Ongoing retrieval from data warehouses and SharePoint</li>
<li>Read and write integrations to specific APIs</li>
<li>Custom report outputs and structured deliverables</li>
<li>Each tool scoped, built, and tested for one specific job</li>
</ul>
</div>
<button class="rm-card-close" type="button" aria-label="Close" tabindex="-1">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">
<path d="M3 3 L11 11"/>
<path d="M11 3 L3 11"/>
</svg>
</button>
</li>
<li class="rm-card" role="button" tabindex="0"
aria-expanded="false" data-detail="wave-3">
<h3 class="rm-name">Agents</h3>
<p class="rm-italic">Workflows running under your control</p>
<div class="rm-card-body" aria-hidden="true">
<p class="rm-card-intro">Use cases that compose multiple steps into one governed workflow. For each agent use case, the team defines the task, builds the specialist agents that handle it, and configures monitoring, human checkpoints, and governance at every step. The platform now does work end to end &mdash; under your control.</p>
<p class="rm-card-section-label">Key activities</p>
<ul class="rm-card-list">
<li>Agent tasks defined and scoped per workflow</li>
<li>Specialist agents built for specific jobs</li>
<li>Monitoring and audit trails configured</li>
<li>Human-in-the-loop checkpoints placed where they matter</li>
<li>Governance and security checks enforced at each step</li>
</ul>
</div>
<button class="rm-card-close" type="button" aria-label="Close" tabindex="-1">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">
<path d="M3 3 L11 11"/>
<path d="M11 3 L3 11"/>
</svg>
</button>
</li>
</ol>
<!-- Cross-cutting band — continuous infrastructure work that
spans all four stages. Visually neutral (paper surface,
not the walnut accent) to read as "always on", not as a
fifth deliverable. -->
<div class="rm-band" role="group" aria-label="Continuous work alongside every stage">
<p class="rm-band-name">Govern &amp; scale</p>
<p class="rm-band-italic">Security &middot; compliance &middot; change management &middot; training &middot; advisory council feedback</p>
<!-- Speaker note: Continuous from day one — security and
compliance, change management and training, governance
cadence (audit, model risk, policy), and (for Bifrost
members) the advisory council loop feeding back into
the roadmap. -->
</div>
<p class="rm-foot">
Setup is bounded &middot; waves of use cases continue as your needs evolve.
</p>
</section>
</div><!-- /#overview-scroll -->
</section>
<!-- Dot-nav tray + nav (shared across all pages)
Eight entries — one per major section of the customer
presentation, in scroll order:
1. Welcome — external; routes to /
2. Timeline — Timeline page (P1)
3. Fenja introduction — Overview page (P2), scene #hero
4. Project Bifrost — Overview page (P2), scene #bifrost
(also covers #bifrost-meaning, the
treasure-map continuation)
5. Architecture — Overview page (P2), scene
#platform-layers (also covers
#platform-question, the framing
lead-in)
6. Wiki — Overview page (P2), scene
#wiki-deepdive
7. Deployment — Overview page (P2), scene
#platform-cards
8. Roadmap — Overview page (P2), scene
#platform-roadmap
data-target : page id to activate, OR `external-*` for a
cross-page navigation (uses data-href).
data-scroll-to : (optional, Overview only) 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. The
sceneOrder / sceneToDot maps in bifrost.js
must include every scrollable scene so the
scroll-spy highlights the right dot as the
user moves through. -->
<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="bifrost">
<span class="dot"></span>
<span class="label">Project Bifrost</span>
</button>
<button class="dot-btn" data-target="page-overview" data-scroll-to="platform-layers">
<span class="dot"></span>
<span class="label">Architecture</span>
</button>
<button class="dot-btn" data-target="page-overview" data-scroll-to="wiki-deepdive">
<span class="dot"></span>
<span class="label">Wiki</span>
</button>
<button class="dot-btn" data-target="page-overview" data-scroll-to="platform-cards">
<span class="dot"></span>
<span class="label">Deployment</span>
</button>
<button class="dot-btn" data-target="page-overview" data-scroll-to="platform-roadmap">
<span class="dot"></span>
<span class="label">Roadmap</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="/platform.js" defer></script>
<script src="/timeline.js" defer></script>
</body>
</html>