customer-presentation/protected/index.html
2026-04-22 14:39:16 +02:00

862 lines
25 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" />
<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>
: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%);
}
/* ───── 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;
}
/* Masthead and folio removed for a cleaner page — no corner chrome. */
/* Page overline title — large, sits lower on the front matter so it reads */
.page-title {
position: absolute;
left: 80px; top: 42vh;
font-family: "Newsreader", Georgia, serif;
font-weight: 400;
font-size: 54px;
letter-spacing: -0.022em;
color: var(--ink);
line-height: 1.08;
z-index: 15;
max-width: 820px;
text-wrap: pretty;
opacity: 1;
transition: opacity 520ms var(--ease), transform 520ms var(--ease);
}
.page-title em {
font-style: italic; font-weight: 700;
}
.page-title + .page-sub {
position: absolute;
left: 80px; top: calc(42vh + 220px);
max-width: 560px;
font-family: "Newsreader", Georgia, serif;
font-style: italic;
font-size: 19px;
line-height: 1.5;
color: var(--ink-soft);
z-index: 15;
opacity: 1;
transition: opacity 520ms var(--ease), transform 520ms var(--ease);
}
/* Once the timeline has been advanced, the front matter steps aside */
.page-timeline.is-scrolled .page-title,
.page-timeline.is-scrolled .page-sub {
opacity: 0;
pointer-events: none;
transform: translateY(-12px);
}
/* ───────── Dot-nav ───────── */
.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%);
}
.dot-nav {
position: fixed;
bottom: 36px; left: 50%;
transform: translateX(-50%);
display: flex; gap: 44px;
z-index: 40;
}
.dot-btn {
all: unset;
display: flex; flex-direction: column; align-items: center;
gap: 10px;
cursor: pointer;
color: var(--ink-dim);
transition: color var(--dur) var(--ease);
}
.dot-btn:hover { color: var(--ink); }
.dot-btn .dot {
width: 7px; height: 7px;
border-radius: 50%;
background: var(--ink-dim);
transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.dot-btn.is-active { color: var(--ink); }
.dot-btn.is-active .dot {
background: var(--crimson);
transform: scale(1.15);
}
.dot-btn .label {
font-size: 10.5px;
letter-spacing: 0.24em;
text-transform: uppercase;
font-weight: 500;
}
/* ───────── Globe ghost ───────── */
.globe-wrap {
position: absolute;
/* 15% larger than the original 58vw ≈ 66.7vw.
Shifted ~20% of its width toward the page center: from left:-8%
to roughly left:+5%. */
left: 5%; top: 0;
width: 66.7vw;
height: 100%;
pointer-events: none;
z-index: 1;
opacity: 0.5;
transition: opacity 280ms var(--ease);
/* Mask top and fade the bottom third so the timeline rests on clean paper */
-webkit-mask-image: linear-gradient(to bottom,
transparent 0%, #000 22%, #000 58%, transparent 72%);
mask-image: linear-gradient(to bottom,
transparent 0%, #000 22%, #000 58%, transparent 72%);
}
.globe-wrap svg {
width: 100%; height: 100%;
display: block;
}
/* ───────── Timeline ───────── */
.timeline-viewport {
position: absolute; inset: 0;
z-index: 5;
}
.timeline-track {
position: absolute;
top: 0; left: 0; height: 100%;
will-change: transform;
transform: translate3d(0,0,0);
display: flex; align-items: center;
padding: 0 120px;
--spine-y: 64%;
}
.spine {
position: absolute;
top: var(--spine-y); left: 0;
height: 1px;
width: 100%;
background: linear-gradient(to right,
transparent 0,
rgba(56,56,49,0.22) 60px,
rgba(56,56,49,0.22) calc(100% - 60px),
transparent);
z-index: 2;
}
.year-tick {
position: absolute;
top: var(--spine-y);
transform: translate(-50%, -50%);
display: flex; flex-direction: column; align-items: center;
gap: 10px;
z-index: 3;
color: var(--ink-dim);
}
.year-tick::before {
content: "";
display: block;
width: 1px; height: 28px;
background: rgba(56,56,49,0.28);
}
.year-tick .y {
font-family: "Newsreader", Georgia, serif;
font-style: italic;
font-size: 22px;
color: var(--ink-soft);
letter-spacing: 0;
font-weight: 400;
}
/* Card */
.evt {
position: absolute;
width: 320px;
padding: 16px 20px 18px;
background: var(--paper-high);
/* Tonal surface shifts instead of 1px borders */
box-shadow:
0 0 0 0.5px rgba(56,56,49,0.05),
0 14px 28px -18px rgba(56,56,49,0.18),
0 2px 6px -3px rgba(56,56,49,0.08);
color: var(--ink);
opacity: 0;
/* Pop-in: small scale + downward lift for a more tactile entrance */
transform: translateY(28px) scale(0.96);
transform-origin: center top;
}
.evt.above {
transform: translateY(-28px) scale(0.96);
transform-origin: center bottom;
}
/* Only animate after first paint — prevents the initial card from
getting stuck at opacity 0 while the transition starts pre-layout. */
.evt.can-animate {
transition:
opacity 640ms var(--ease),
transform 640ms cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 320ms var(--ease);
}
.evt.is-near {
opacity: 1;
transform: translateY(0) scale(1);
}
.evt.above { bottom: calc(100% - var(--spine-y) + 48px); }
.evt.below { top: calc(var(--spine-y) + 48px); }
/* Connector from card to spine */
.evt::after {
content: "";
position: absolute;
left: 26px;
width: 1px;
background: rgba(56,56,49,0.28);
}
.evt.above::after { top: 100%; height: 40px; }
.evt.below::after { bottom: 100%; height: 40px; }
/* Node on the spine — tiny dot */
.evt .node {
position: absolute;
left: 20px;
width: 13px; height: 13px;
border-radius: 50%;
background: var(--paper-high);
z-index: 1;
}
.evt.above .node { top: calc(100% + 40px - 6px); }
.evt.below .node { bottom: calc(100% + 40px - 6px); }
.evt .node::after {
content: "";
position: absolute;
inset: 3.5px;
border-radius: 50%;
background: var(--ink-soft);
}
.evt[data-accent="copper"] .node::after { background: var(--copper); }
.evt[data-accent="ochre"] .node::after { background: var(--ochre); }
.evt[data-accent="terracotta"] .node::after { background: var(--terracotta); }
.evt[data-accent="crimson"] .node::after { background: var(--crimson); }
.evt .tag-row {
display: flex; gap: 12px; align-items: center;
margin-bottom: 8px;
}
.evt .date {
font-family: "Newsreader", Georgia, serif;
font-style: italic;
font-size: 13px;
color: var(--ink-soft);
letter-spacing: 0;
}
.evt .kind {
font-size: 9px;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--ink-dim);
font-weight: 600;
}
.evt[data-accent="copper"] .kind { color: var(--copper); }
.evt[data-accent="ochre"] .kind { color: var(--ochre); }
.evt[data-accent="terracotta"] .kind { color: var(--terracotta); }
.evt[data-accent="crimson"] .kind { color: var(--crimson); }
.evt h3 {
font-family: "Newsreader", Georgia, serif;
font-weight: 400;
font-size: 17px;
line-height: 1.22;
letter-spacing: -0.01em;
color: var(--ink);
margin: 0 0 8px 0;
text-wrap: pretty;
}
.evt h3 em {
font-style: italic;
font-weight: 700;
}
.evt p {
margin: 0;
font-size: 12px;
line-height: 1.5;
color: var(--ink-soft);
text-wrap: pretty;
}
.evt .source {
margin-top: 10px;
font-size: 9.5px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--ink-dim);
font-weight: 500;
}
/* ───────── Continue button ───────── */
.continue-btn {
all: unset;
position: absolute;
right: 72px;
bottom: 140px;
display: inline-flex;
align-items: baseline;
gap: 22px;
padding: 20px 28px;
background: var(--paper-high);
color: var(--ink);
cursor: pointer;
z-index: 30;
opacity: 0;
transform: translateX(36px);
pointer-events: none;
box-shadow:
0 0 0 0.5px rgba(56,56,49,0.06),
0 18px 32px -18px rgba(56,56,49,0.22),
0 2px 6px -3px rgba(56,56,49,0.08);
transition:
opacity 520ms var(--ease),
transform 520ms var(--ease),
box-shadow var(--dur) var(--ease),
background var(--dur) var(--ease);
}
.continue-btn.is-visible {
opacity: 1;
transform: translateX(0);
pointer-events: auto;
animation: continue-breath 2800ms cubic-bezier(0.2, 0, 0, 1) infinite;
}
@keyframes continue-breath {
0%, 100% { transform: translateX(0); }
50% { transform: translateX(6px); }
}
.continue-btn:hover {
background: #fffbf2;
box-shadow:
0 0 0 0.5px rgba(56,56,49,0.10),
0 24px 40px -20px rgba(56,56,49,0.28),
0 3px 8px -4px rgba(56,56,49,0.10);
}
.continue-btn .c-label {
font-family: "Newsreader", Georgia, serif;
font-size: 20px;
font-weight: 400;
letter-spacing: -0.01em;
color: var(--ink);
line-height: 1;
}
.continue-btn .c-label em {
font-style: italic; font-weight: 700;
}
.continue-btn .c-arrow {
font-family: "Newsreader", Georgia, serif;
font-style: italic;
font-size: 22px;
color: var(--crimson);
line-height: 1;
transition: transform var(--dur) var(--ease);
}
.continue-btn:hover .c-arrow {
transform: translateX(4px);
}
/* ───────── Overview page ───────── */
/* 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; }
/* ───────── Archive page ───────── */
.archive {
position: absolute; inset: 0;
overflow: auto;
padding: 120px 80px 180px;
}
.archive .inner {
max-width: 1180px; margin: 0 auto;
}
.archive .headline {
font-family: "Newsreader", Georgia, serif;
font-weight: 400;
font-size: 60px;
line-height: 1.05;
letter-spacing: -0.02em;
margin: 0 0 14px 0;
text-wrap: balance;
}
.archive .headline em { font-style: italic; font-weight: 700; }
.archive .sub {
font-family: "Newsreader", Georgia, serif;
font-style: italic;
font-size: 19px;
color: var(--ink-soft);
margin: 0 0 48px 0;
max-width: 700px;
}
.archive table {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
}
.archive thead th {
text-align: left;
padding: 10px 12px;
font-size: 10.5px;
letter-spacing: 0.24em;
text-transform: uppercase;
color: var(--ink-dim);
font-weight: 600;
background:
linear-gradient(to right, rgba(56,56,49,0.28), rgba(56,56,49,0.28))
bottom / 100% 1px no-repeat;
}
.archive tbody tr {
transition: background var(--dur) var(--ease);
background:
linear-gradient(to right, rgba(56,56,49,0.10), rgba(56,56,49,0.10))
bottom / 100% 1px no-repeat;
}
.archive tbody tr:hover {
background:
var(--paper-high)
linear-gradient(to right, rgba(56,56,49,0.10), rgba(56,56,49,0.10))
bottom / 100% 1px no-repeat;
}
.archive tbody td {
padding: 18px 12px;
vertical-align: top;
color: var(--ink);
line-height: 1.45;
}
.archive td.num {
font-family: "Newsreader", Georgia, serif;
font-style: italic;
color: var(--ink-dim);
font-size: 13px;
width: 48px;
}
.archive td.date {
font-family: "Newsreader", Georgia, serif;
font-style: italic;
color: var(--ink-soft);
width: 130px;
white-space: nowrap;
}
.archive td.kind {
width: 150px;
font-size: 10.5px;
letter-spacing: 0.22em;
text-transform: uppercase;
font-weight: 600;
color: var(--ink-dim);
}
.archive tr[data-accent="copper"] td.kind { color: var(--copper); }
.archive tr[data-accent="ochre"] td.kind { color: var(--ochre); }
.archive tr[data-accent="terracotta"] td.kind { color: var(--terracotta); }
.archive tr[data-accent="crimson"] td.kind { color: var(--crimson); }
.archive td.hed {
font-family: "Newsreader", Georgia, serif;
font-size: 16px;
letter-spacing: -0.005em;
color: var(--ink);
max-width: 520px;
}
.archive td.hed em { font-style: italic; font-weight: 700; }
.archive td.src {
color: var(--ink-dim);
font-size: 12px;
font-family: "Newsreader", Georgia, serif;
font-style: italic;
width: 160px;
white-space: nowrap;
}
/* Mark on key rows — tonal, no border */
.archive tr[data-accent] td:first-child {
position: relative;
}
.archive tr[data-accent] td:first-child::before {
content: "";
position: absolute;
left: -10px; top: 50%;
transform: translateY(-50%);
width: 5px; height: 5px;
border-radius: 50%;
background: var(--ink-dim);
}
.archive tr[data-accent="copper"] td:first-child::before { background: var(--copper); }
.archive tr[data-accent="ochre"] td:first-child::before { background: var(--ochre); }
.archive tr[data-accent="terracotta"] td:first-child::before { background: var(--terracotta); }
.archive tr[data-accent="crimson"] td:first-child::before { background: var(--crimson); }
/* Archive footer */
.archive .footer {
margin-top: 72px;
padding-top: 28px;
background:
linear-gradient(to right, rgba(56,56,49,0.18), rgba(56,56,49,0.18))
top / 100% 1px no-repeat;
display: flex; justify-content: space-between;
color: var(--ink-dim);
font-size: 11px;
letter-spacing: 0.22em;
text-transform: uppercase;
font-weight: 500;
}
.archive .footer em {
font-family: "Newsreader", Georgia, serif;
font-style: italic;
font-size: 13px;
color: var(--ink-soft);
letter-spacing: 0;
text-transform: none;
font-weight: 400;
}
/* Short-viewport safety: collapse the page-title block so cards never collide */
@media (max-height: 620px) {
.page-title { font-size: 36px; max-width: 640px; top: 38vh; }
.page-title + .page-sub { font-size: 16px; top: calc(38vh + 160px); }
}
@media (max-height: 500px) {
.page-title { display: none; }
.page-sub { display: none; }
}
.overview::-webkit-scrollbar,
.archive::-webkit-scrollbar { width: 6px; }
.overview::-webkit-scrollbar-thumb,
.archive::-webkit-scrollbar-thumb {
background: rgba(56,56,49,0.18);
border-radius: 3px;
}
/* Folio marks removed for a cleaner page. */
</style>
</head>
<body data-screen-label="01 Timeline">
<!-- ───── Page 1 : TIMELINE ───── -->
<section class="page page-timeline is-active" id="page-timeline" data-screen-label="01 Timeline">
<div class="page-title">From the promise of AI to the loss of <em>sovereignty.</em></div>
<div class="page-sub">Twenty-three headlines, quietly laid across a tinted map. Scroll the wheel — the map turns with you.</div>
<!-- Globe ghost -->
<div class="globe-wrap" id="globe-wrap"></div>
<!-- Continue to the next page -->
<button class="continue-btn" id="continue-btn" type="button">
<span class="c-label">Read the editor&rsquo;s <em>note</em></span>
<span class="c-arrow" aria-hidden="true"></span>
</button>
<div class="timeline-viewport" id="tl-viewport">
<div class="timeline-track" id="tl-track">
<div class="spine" id="spine"></div>
<!-- Year ticks and events injected by JS -->
</div>
</div>
</section>
<!-- ───── Page 2 : OVERVIEW ───── -->
<section class="page page-overview" id="page-overview" data-screen-label="02 Overview">
<!-- Globe background, zoomed onto Europe — animated in when entering -->
<div class="overview-globe" id="overview-globe"></div>
<div class="overview">
<div class="col-wrap">
<h1>Notes on a quiet <em>inheritance.</em></h1>
<div class="lede">
The following pages gather twenty-three headlines published between 2022 and 2026.
Taken together, they describe a slow transfer — of infrastructure, of agency,
of the right to set one's own terms — from the public institutions of Europe
to the private servers of a handful of foreign firms.
</div>
<div class="rule"></div>
<p class="drop">
None of the events in this catalog are, on their own, remarkable. A procurement
decision here; a press release there; a minister quietly conceding, at a
conference in late autumn, that the ministry's new assistant is hosted in
Virginia. Read in sequence, they are something else — a pattern, patient and
unhurried, of institutions agreeing to hold their most sensitive work on
infrastructure they do not own.
</p>
<p>
The timeline opposite is arranged horizontally so the reader can move through
the years at the pace of a slow walk, rather than the pace of a feed. Each
card carries a date, a short headline set in the italic-bold of our
masthead, and a sentence of context. Four muted pigments mark the nature of
the entry: <em>copper green</em> for a step toward sovereignty, <em>ochre</em>
for caution, <em>terracotta</em> for friction, <em>crimson</em> for rupture.
</p>
<p>
The globe drifting behind the timeline is not a chart; it is a weather system.
As the reader scrolls east from North America to Europe, the map turns with
them — a small, analogue gesture, reminding the eye that these events
happened over oceans, on real ground, to real institutions whose names
appear in the right-hand archive.
</p>
<p>
Readers in a hurry may prefer the archive, which lists every entry in
tabular form with its source. Readers with time may prefer to scroll. Either
is a legitimate way to read the catalog; neither, the editors suspect, will
leave the reader unchanged.
</p>
<div class="meta-strip">
<div class="cell">
<div class="k">Entries</div>
<div class="v">Twenty-<em>three</em></div>
</div>
<div class="cell">
<div class="k">Period</div>
<div class="v">2022 <em>2026</em></div>
</div>
<div class="cell">
<div class="k">Compiled</div>
<div class="v">Copenhagen, <em>Spring '26</em></div>
</div>
<div class="cell">
<div class="k">Editor</div>
<div class="v">F. Jørgensen, <em>pro tem.</em></div>
</div>
</div>
</div>
</div>
</section>
<!-- ───── Page 3 : ARCHIVE ───── -->
<section class="page page-archive" id="page-archive" data-screen-label="03 Archive">
<div class="archive">
<div class="inner">
<h1 class="headline">All twenty-three entries, in order of <em>publication.</em></h1>
<p class="sub">
Dates, sources and plate numbers for every card in the catalog. Hover a row to
lift it from the paper.
</p>
<table id="archive-table">
<thead>
<tr>
<th></th>
<th>Date</th>
<th>Register</th>
<th>Headline</th>
<th>Source</th>
</tr>
</thead>
<tbody id="archive-body"><!-- filled by JS --></tbody>
</table>
<div class="footer">
<div>Fenja AI&nbsp;&middot;&nbsp;Field Notes, No.&nbsp;IV</div>
<em>Catalog closed 14 April 2026.</em>
<div>Page III of III</div>
</div>
</div>
</div>
</section>
<!-- Dot-nav tray + nav (shared) -->
<div class="dot-nav-tray"></div>
<nav class="dot-nav">
<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">
<span class="dot"></span>
<span class="label">Overview</span>
</button>
<button class="dot-btn" data-target="page-archive">
<span class="dot"></span>
<span class="label">Archive</span>
</button>
</nav>
<script src="timeline.js" defer></script>
</body>
</html>