1
<section class="archive" aria-labelledby="archive-title">
<header class="archive-header">
<p class="eyebrow">FORM / STUDIER</p>
<h1 id="archive-title">Små former.<br>Store bevægelser.</h1>
<p>Fire plakater. Ét dokument. Åbn en plakat og følg den.</p>
</header>
<div class="overview">
<div class="tools" role="group" aria-label="Organisér plakater">
<button type="button" data-action="reverse">Vend rækkefølgen <span aria-hidden="true">↔</span></button>
<button type="button" data-action="filter" aria-pressed="false">Kun favoritter <span aria-hidden="true">☆</span></button>
</div>
<div class="collection">
<button type="button" class="tile" data-action="open" data-poster="orbit" data-favorite="true" data-description="En cirkel i kredsløb. Samme motiv beholder sin identitet, selv om det nu vises i et andet DOM-element.">
<span class="poster poster--orbit" style="--poster-name: vt3-orbit" aria-hidden="true"><span>O / 01</span></span>
<span class="caption"><strong>Orbit</strong><span>01 · Cirkelstudie ↗</span></span>
</button>
<button type="button" class="tile" data-action="open" data-poster="arch" data-description="En bue bygget af gentagelser. Browseren forbinder oversigtens lille motiv med detaljevisningens større motiv.">
<span class="poster poster--arch" style="--poster-name: vt3-arch" aria-hidden="true"><span>A / 02</span></span>
<span class="caption"><strong>Arc</strong><span>02 · Rytmestudie ↗</span></span>
</button>
<button type="button" class="tile" data-action="open" data-poster="steps" data-favorite="true" data-description="Tre trin i balance. Prøv også favoritter: elementer uden et match får deres egen ind- eller ud-animation.">
<span class="poster poster--steps" style="--poster-name: vt3-steps" aria-hidden="true"><span>S / 03</span></span>
<span class="caption"><strong>Steps</strong><span>03 · Balancestudie ↗</span></span>
</button>
<button type="button" class="tile" data-action="open" data-poster="wave" data-description="En bølge på papir. Brug tilbageknappen: det fælles navn virker i begge retninger, uden at siden genindlæses.">
<span class="poster poster--wave" style="--poster-name: vt3-wave" aria-hidden="true"><span>W / 04</span></span>
<span class="caption"><strong>Wave</strong><span>04 · Kontraststudie ↗</span></span>
</button>
</div>
</div>
<section class="detail" hidden aria-labelledby="detail-title">
<button type="button" data-action="back">← Til samlingen</button>
<div class="detail-art"></div>
<div class="detail-copy">
<p class="eyebrow">UDVALGT STUDIE</p>
<h2 id="detail-title" tabindex="-1"></h2>
<p class="detail-description"></p>
</div>
</section>
<p class="status" role="status">4 plakater · Vælg et motiv</p>
</section>
1
const archive = document.querySelector(".archive");
const overview = document.querySelector(".overview");
const collection = document.querySelector(".collection");
const detail = document.querySelector(".detail");
const detailArt = document.querySelector(".detail-art");
const title = document.querySelector("#detail-title");
const description = document.querySelector(".detail-description");
const status = document.querySelector(".status");
const filterButton = document.querySelector('[data-action="filter"]');
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
let selected;
let activeTransition;
let request = 0;
async function changeView(update, focusTarget) {
const currentRequest = ++request;
if (activeTransition) {
activeTransition.skipTransition();
await activeTransition.updateCallbackDone.catch(() => {});
if (currentRequest !== request) return;
}
if (!document.startViewTransition || reducedMotion.matches) {
update();
focusTarget?.focus({ preventScroll: true });
return;
}
// DOM-opdateringen er den samme med og uden animation.
const transition = document.startViewTransition(update);
activeTransition = transition;
transition.ready.catch(() => {});
try {
await transition.finished;
} finally {
if (currentRequest === request) {
activeTransition = undefined;
focusTarget?.focus({ preventScroll: true });
}
}
}
function handleClick(event) {
const button = event.target.closest("button[data-action]");
if (!button || !archive.contains(button)) return;
let update;
let focusTarget;
switch (button.dataset.action) {
case "reverse":
update = () => {
collection.append(...Array.from(collection.children).reverse());
status.textContent = "Rækkefølgen er vendt · Følg de samme motiver";
};
break;
case "filter":
update = () => {
const favoritesOnly = filterButton.getAttribute("aria-pressed") !== "true";
filterButton.setAttribute("aria-pressed", String(favoritesOnly));
for (const tile of collection.children) {
tile.hidden = favoritesOnly && tile.dataset.favorite !== "true";
}
status.textContent = favoritesOnly ? "2 favoritter · To motiver forlader oversigten" : "4 plakater · Alle motiver er tilbage";
};
break;
case "open":
focusTarget = title;
update = () => {
selected = button;
// Et nyt DOM-element overtager det samme view-transition-name.
detailArt.replaceChildren(button.querySelector(".poster").cloneNode(true));
title.textContent = button.querySelector("strong").textContent;
description.textContent = button.dataset.description;
overview.hidden = true;
detail.hidden = false;
status.textContent = "Detaljevisning · Samme dokument, nyt layout";
};
break;
case "back":
focusTarget = selected;
update = () => {
detail.hidden = true;
overview.hidden = false;
status.textContent = "Tilbage i samlingen · Rækkefølge og filter er bevaret";
};
break;
default:
return;
}
changeView(update, focusTarget).catch(console.error);
}
archive.addEventListener("click", handleClick);
return () => {
request++;
activeTransition?.skipTransition();
archive.removeEventListener("click", handleClick);
};