Zum Inhalt
← Hover

Ganze Karte klickbar

Fachbeiträge eines Pumpenbauers: die ganze Karte ist ein einziger Link, Datenblatt und Rubrik bleiben eigene Ziele, Bild, Code-Etikett, Unterstreichung, Pfeil und Eckmarken antworten gestaffelt.

Beitrags-, Leistungs-, Projekt- und Stellenkarten mit Bild und höchstens zwei Zweitlinks (fast alle Rezepte).

Eine Karte (Beitrag, Leistung, Projekt, Stelle) ist auf ganzer Fläche klickbar, enthält aber nur einen Link: den Titel. Sein Pseudo-Element ::after spannt sich über die Karte. Zweitlinks wie „Datenblatt“ oder eine Rubrik liegen mit position: relative und z-index darüber und bleiben einzeln klickbar. Hover und Fokus spielen dieselbe Choreografie: Bild, Titel-Unterstreichung und Pfeil reagieren gestaffelt, der Fokusring liegt um die ganze Karte. Zeigt der Zeiger auf einen Zweitlink, ruht die Karte.

Besucher klicken auf das Bild oder irgendwo in die Karte, nicht nur auf den Titel. Eine ganze Karte als Ziel ist nach Fitts schneller zu treffen. Ein einzelner Link hält die Linkliste für Screenreader kurz und lesbar, statt Bild, Titel, Text und Datum als einen langen Linktext vorzulesen. Die gestaffelte Reaktion von Bild, Titel und Pfeil sagt „das ist ein Ziel“, ohne dass die Karte wackelt.

Wann einsetzen

Beitragslisten, Leistungen, Referenzen, Stellen, Veranstaltungen, Produkte. Fast jedes Rezept braucht es; Pflicht für News, Leistungen, Projekte und Stellen.

Wann nicht

Wenn die Karte mehrere gleichwertige Ziele hat (zwei Hauptlinks): dann zwei sichtbare Links ohne Überzug. Keine Knöpfe, Formulare oder Auswahl in einer Überzug-Karte, wenn kein Abstand zu ihnen bleibt (Fehltipps). Nicht den ganzen Karteninhalt in <a> packen.

Varianten

  • Raster mit Überzug: ::after des Titel-Links über die Karte, Zweitlinks per z-index darüber, reines CSS
  • Liste mit Klick-Weiterleitung: kein Überzug, Klick auf die Zeile löst den Titel-Link aus, Text bleibt markierbar, Strg/Cmd und Mittelklick öffnen einen neuen Tab

Umsetzung

Karte als <article> mit position: relative. Darin <h3><a class="haupt" href>Titel</a></h3>; .haupt::after { content: ''; position: absolute; inset: 0; z-index: 1 }. Zweitlinks bekommen position: relative; z-index: 2 und mindestens 8 px Abstand zu anderen Zielen. Bild mit leerem alt, wenn der Titel es benennt; Datum als <time>. Choreografie über :is(:hover, :focus-within) bzw. :has(.haupt:hover, .haupt:focus-visible) an der Karte, Zweitlinks heben sie über :has(.neben:hover) wieder auf. Fokusring nicht am Titel, sondern als outline der Karte über :has(.haupt:focus-visible). Klick-Variante: kein Überzug, ein Listener auf der Karte ruft haupt.click() auf, außer bei markiertem Text, Klick auf einen anderen Link oder Knopf, Mittelklick und Strg/Cmd (dann window.open). Auf Touch zeigt :active denselben Zustand kurz an.

Robust umsetzen

Telefon. Eine Spalte, die ganze Karte ist ein Tippziel. Pfeil und Unterstreichung stehen auf Touch dauerhaft da; beim Tippen zeigt :active den Hover-Zustand kurz. Zweitlinks mindestens 44 px hoch und mit Abstand zum Kartenrand.

Ohne Bewegung. Keine Wege: Bild, Pfeil und Eckmarken bewegen sich nicht. Unterstreichung und Farbwechsel erscheinen sofort.

Barrierefrei. Ein Link je Karte mit kurzem Namen (Titel). Fokusring um die ganze Karte, mindestens 2 px und 3 : 1. Zweitlinks bleiben einzeln erreichbar und haben eigenen Fokus. Bild alt="", wenn es nur schmückt. In der Überzug-Variante ist Text nicht markierbar; wenn das wichtig ist, Klick-Variante nehmen.

Code

<!DOCTYPE html>
<!--
  Ganze Karte klickbar
  Fundus · Hover · https://cecodesign.de/lab/fundus/hover/karte-ganzflaechig-klickbar/
  Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ganze Karte klickbar · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Strichpunkt Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/strichpunkt-sans:vf@latest/latin-full-normal.woff2') format('woff'); font-weight: 400 900; font-stretch: 100% 200%; font-display: swap; }
@font-face { font-family: 'Archivo'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/archivo:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-stretch: 62% 125%; font-display: swap; }

/* Welt: Rezept industrie-b2b-mittelstand · Wellbrock Pumpenbau, Plettenberg (erfunden) */
:root {
  --grund: #F3F3F0; --text: #111417; --leise: #4E555B; --flaeche: #E4E5E1; --raster: rgba(17, 20, 23, .13);
  --stahl: #16202A; --signal: #0E518D; --weiss: #FBFBF9;
  --ein: cubic-bezier(0.25, 1, 0.5, 1);   /* power3.out, token-swiss-knackig */
  --aus: cubic-bezier(0.5, 0, 0.75, 0);   /* power3.in */
  --t1: 200ms; --t2: 350ms; --t1-aus: 140ms; --t2-aus: 245ms; --versatz: 40ms;
  --headline: 'Strichpunkt Sans', system-ui, sans-serif; --schrift: 'Archivo', system-ui, sans-serif;
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--grund); color: var(--text); font-family: var(--schrift); -webkit-text-size-adjust: 100%; }
:root:has(:focus-visible) { scroll-behavior: auto; }
body { min-height: 100svh; }
:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; }
a { color: inherit; }
.code { font: 600 .75rem/1.2 var(--schrift); font-stretch: 75%; letter-spacing: .08em; text-transform: uppercase; color: var(--leise); font-variant-numeric: tabular-nums; }
.nur-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* Kopf und Raster-Hintergrund (Signatur blueprint-raster-sichtbar, ruhig) */
.kopf { display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; padding: 18px clamp(20px, 4vw, 48px); background: var(--stahl); color: #fff; }
.marke { font: 600 1.0625rem/1 var(--headline); font-stretch: 116%; letter-spacing: -.01em; }
.kopf nav { display: flex; gap: 24px; font-size: .9375rem; }
.kopf nav a { text-decoration: none; color: rgba(255, 255, 255, .86); }
.kopf nav a[aria-current] { color: #fff; box-shadow: inset 0 -2px 0 #fff; padding-bottom: 4px; }
main { background-image: linear-gradient(var(--raster) 1px, transparent 1px), linear-gradient(90deg, var(--raster) 1px, transparent 1px); background-size: 100% 8rem, calc((100% - 2 * clamp(20px, 4vw, 48px)) / 12) 100%; background-position: 0 -1px, clamp(20px, 4vw, 48px) 0; }
.sektion { max-width: 1320px; margin: 0 auto; padding: clamp(32px, 5vw, 64px) clamp(20px, 4vw, 48px) 110px; }
.sektion-kopf { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px 32px; align-items: end; margin-bottom: clamp(24px, 3vw, 40px); }
.sektion-kopf h1 { font: 600 clamp(1.75rem, 1.3rem + 1.8vw, 2.75rem)/1.02 var(--headline); font-stretch: 110%; letter-spacing: -.02em; margin-top: 10px; max-width: 22ch; text-wrap: balance; }
.alle { display: inline-flex; align-items: center; gap: 10px; height: 44px; padding: 0 14px; font-weight: 600; text-decoration: none; position: relative; }
.alle::before, .alle::after { content: ''; position: absolute; width: 8px; height: 8px; border: 0 solid var(--text); }
.alle::before { left: 0; top: 0; border-width: 1.5px 0 0 1.5px; } .alle::after { right: 0; bottom: 0; border-width: 0 1.5px 1.5px 0; }

/* ---------- Variante Raster: Überzug-Karte ---------- */
.raster { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(16px, 2vw, 24px); list-style: none; padding: 0; }
.karte { position: relative; display: flex; flex-direction: column; height: 100%; background: var(--weiss); border: 1px solid var(--raster); }
.karte .bild { position: relative; aspect-ratio: 3 / 2; overflow: hidden; background: var(--flaeche); }
.karte .bild img { position: absolute; left: 0; top: 0; width: 100%; height: calc(100% + 12px); object-fit: cover; transition: transform var(--t2-aus) var(--aus); }
.karte .bild .code { position: absolute; left: 0; bottom: 0; background: var(--weiss); color: var(--text); padding: 7px 10px 6px; transition: background-color var(--t1-aus) var(--aus), color var(--t1-aus) var(--aus); }
.karte .inhalt { display: flex; flex-direction: column; gap: 12px; padding: 18px 20px 20px; flex: 1; }
.meta { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; font-size: .875rem; color: var(--leise); }
.karte h2 { font: 600 1.25rem/1.25 var(--schrift); letter-spacing: -.005em; text-wrap: balance; }
.haupt { text-decoration: none; background: linear-gradient(var(--signal), var(--signal)) 0 100% / 0 2px no-repeat; transition: background-size var(--t1-aus) var(--aus), color var(--t1-aus) var(--aus); padding-bottom: 2px; }
.haupt::after { content: ''; position: absolute; inset: 0; z-index: 1; }
.haupt:focus-visible { outline: none; } /* Fokusring liegt um die ganze Karte */
.karte p.anriss { font-size: 1rem; line-height: 1.55; color: var(--leise); }
.fuss { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--raster); display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.neben { position: relative; z-index: 2; display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 2px; font-size: .9375rem; font-weight: 600; text-underline-offset: 4px; text-decoration-thickness: 1px; }
.neben:hover { color: var(--signal); text-decoration-thickness: 2px; }
.meta .neben { min-height: 32px; font-weight: 600; color: var(--text); text-decoration: none; border: 1px solid var(--raster); padding: 0 9px; }
.meta .neben:hover { border-color: var(--signal); }
.pfeil { position: relative; width: 40px; height: 40px; flex: none; overflow: hidden; border: 1px solid var(--raster); transition: background-color var(--t1-aus) var(--aus), border-color var(--t1-aus) var(--aus); }
.pfeil svg { position: absolute; left: 11px; top: 11px; transition: transform var(--t1-aus) var(--aus); }
.pfeil svg + svg { transform: translateX(-28px); }
.ecke { position: absolute; width: 11px; height: 11px; pointer-events: none; transition: transform var(--t1-aus) var(--aus); }
.ecke::before, .ecke::after { content: ''; position: absolute; background: var(--text); }
.ecke::before { left: 5px; top: 0; width: 1px; height: 11px; } .ecke::after { top: 5px; left: 0; height: 1px; width: 11px; }
.ecke.lo { left: -6px; top: -6px; } .ecke.ro { right: -6px; top: -6px; } .ecke.lu { left: -6px; bottom: -6px; } .ecke.ru { right: -6px; bottom: -6px; }

/* Aktiv: Zeiger über der Karte (nicht über einem Zweitlink), Tastaturfokus auf dem Titel, oder Vorführung */
.karte:hover:not(:has(.neben:hover)), .karte:has(.haupt:focus-visible), .karte.sim-an { --aktiv: 1; }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .bild img { transform: translateY(-12px); transition: transform var(--t2) var(--ein); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .bild .code { background: var(--signal); color: #fff; transition: background-color var(--t1) var(--ein), color var(--t1) var(--ein); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .haupt { background-size: 100% 2px; color: var(--signal); transition: background-size var(--t1) var(--ein) var(--versatz), color var(--t1) var(--ein) var(--versatz); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .pfeil { background: var(--signal); border-color: var(--signal); color: #fff; transition: background-color var(--t1) var(--ein) calc(var(--versatz) * 2), border-color var(--t1) var(--ein) calc(var(--versatz) * 2); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .pfeil svg { transform: translateX(28px); transition: transform var(--t1) var(--ein) calc(var(--versatz) * 2); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .pfeil svg + svg { transform: translateX(0); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .ecke.lo { transform: translate(-4px, -4px); transition: transform var(--t1) var(--ein); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .ecke.ro { transform: translate(4px, -4px); transition: transform var(--t1) var(--ein); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .ecke.lu { transform: translate(-4px, 4px); transition: transform var(--t1) var(--ein); }
.karte:is(:hover:not(:has(.neben:hover)), :has(.haupt:focus-visible), .sim-an) .ecke.ru { transform: translate(4px, 4px); transition: transform var(--t1) var(--ein); }
.karte:has(.haupt:focus-visible), .karte.sim-fokus { outline: 2px solid var(--signal); outline-offset: 5px; }
.karte .neben.sim-an { color: var(--signal); text-decoration-thickness: 2px; }
.karte .meta .neben.sim-an { border-color: var(--signal); }

/* Klickflächen zeigen (Pult) */
.zeige-flaechen .haupt::after { background: repeating-linear-gradient(135deg, rgba(14, 81, 141, .16) 0 6px, transparent 6px 12px); outline: 2px dashed var(--signal); outline-offset: -2px; }
.zeige-flaechen .neben { background: #FFD9A8; outline: 2px solid #8A4B00; }
.zeige-flaechen .z-zeile { background: repeating-linear-gradient(135deg, rgba(14, 81, 141, .12) 0 6px, transparent 6px 12px); }
.zeige-flaechen .z-zeile .anriss, .zeige-flaechen .z-zeile h2 { background: rgba(255, 255, 255, .7); }

/* ---------- Variante Liste: Klick-Weiterleitung, Text markierbar ---------- */
.liste { display: none; list-style: none; padding: 0; border-top: 2px solid var(--text); }
[data-variante=liste] .raster { display: none; }
[data-variante=liste] .liste { display: block; }
.z-zeile { position: relative; display: grid; grid-template-columns: 7.5rem minmax(0, 1fr) 11rem 44px; gap: 8px 28px; align-items: start; padding: 22px 16px 22px 0; border-bottom: 1px solid rgba(17, 20, 23, .22); cursor: pointer; isolation: isolate; }
.z-zeile::before { content: ''; position: absolute; inset: 0 -16px; background: var(--weiss); z-index: -1; transform: scaleX(0); transform-origin: left; transition: transform var(--t2-aus) var(--aus); }
.z-zeile time { font: 600 .9375rem/1.4 var(--schrift); font-stretch: 87%; font-variant-numeric: tabular-nums; padding-top: 3px; }
.z-zeile h2 { font: 600 clamp(1.125rem, 1rem + .6vw, 1.5rem)/1.25 var(--schrift); }
.z-zeile .haupt-l { text-decoration: none; background: linear-gradient(var(--signal), var(--signal)) 0 100% / 0 2px no-repeat; transition: background-size var(--t1-aus) var(--aus), color var(--t1-aus) var(--aus); }
.z-zeile .anriss { font-size: 1rem; line-height: 1.55; color: var(--leise); margin-top: 6px; max-width: 64ch; }
.z-zeile .seite { display: flex; flex-direction: column; gap: 4px; font-size: .875rem; color: var(--leise); padding-top: 3px; }
.z-zeile .seite a { color: var(--text); font-weight: 600; position: relative; z-index: 2; min-height: 32px; display: inline-flex; align-items: center; text-underline-offset: 4px; width: fit-content; }
.z-zeile .seite a:hover { color: var(--signal); }
.z-zeile .pfeil { margin-top: 0; }
.z-zeile:is(:hover:not(:has(.seite a:hover)), :has(.haupt-l:focus-visible), .sim-an)::before { transform: scaleX(1); transition: transform var(--t2) var(--ein); }
.z-zeile:is(:hover:not(:has(.seite a:hover)), :has(.haupt-l:focus-visible), .sim-an) .haupt-l { background-size: 100% 2px; color: var(--signal); transition: background-size var(--t1) var(--ein) var(--versatz), color var(--t1) var(--ein) var(--versatz); }
.z-zeile:is(:hover:not(:has(.seite a:hover)), :has(.haupt-l:focus-visible), .sim-an) .pfeil { background: var(--signal); border-color: var(--signal); color: #fff; transition: background-color var(--t1) var(--ein) calc(var(--versatz) * 2); }
.z-zeile:is(:hover:not(:has(.seite a:hover)), :has(.haupt-l:focus-visible), .sim-an) .pfeil svg { transform: translateX(28px); transition: transform var(--t1) var(--ein) calc(var(--versatz) * 2); }
.z-zeile:is(:hover:not(:has(.seite a:hover)), :has(.haupt-l:focus-visible), .sim-an) .pfeil svg + svg { transform: translateX(0); }
.z-zeile .haupt-l:focus-visible { outline: none; }
.z-zeile:has(.haupt-l:focus-visible), .z-zeile.sim-fokus { outline: 2px solid var(--signal); outline-offset: 2px; }
.markiert-hinweis { font-size: .875rem; color: var(--leise); margin-top: 14px; }
::selection { background: rgba(14, 81, 141, .22); }

/* Rückmeldung, welches Ziel getroffen wurde */
.ziel { position: fixed; right: 16px; bottom: 16px; z-index: 40; max-width: min(26rem, calc(100vw - 32px)); background: var(--stahl); color: #fff; padding: 12px 16px; font-size: .9375rem; line-height: 1.4; transform: translateY(12px); opacity: 0; transition: opacity var(--t1-aus) var(--aus), transform var(--t1-aus) var(--aus); pointer-events: none; }
.ziel.an { opacity: 1; transform: none; transition: opacity var(--t1) var(--ein), transform var(--t1) var(--ein); }
.ziel b { display: block; font: 600 .75rem/1.2 var(--schrift); font-stretch: 75%; letter-spacing: .08em; text-transform: uppercase; color: rgba(255, 255, 255, .75); margin-bottom: 4px; }

/* Pult */
.pult { position: fixed; right: 16px; top: 72px; z-index: 50; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; max-width: calc(100vw - 32px); padding: 10px 12px; background: rgba(17, 20, 23, .92); color: #f3f2ee; font: 600 11px/1 var(--schrift); font-stretch: 87%; letter-spacing: .1em; text-transform: uppercase; }
.pult .gruppe { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.pult .gruppe > span { color: rgba(243, 242, 238, .72); margin-right: 4px; }
.pult button { background: transparent; color: inherit; border: 1px solid rgba(243, 242, 238, .3); height: 28px; padding: 0 9px; font: inherit; letter-spacing: inherit; text-transform: inherit; cursor: pointer; }
.pult button[aria-pressed=true] { background: #f3f2ee; color: #111417; border-color: #f3f2ee; }
.pult button:focus-visible { outline: 2px solid #f3f2ee; outline-offset: 2px; }
.zeiger { position: fixed; left: 0; top: 0; z-index: 60; width: 22px; height: 22px; pointer-events: none; opacity: 0; }
.vorfuehrung .zeiger { opacity: 1; }
.vorschau body { overflow: hidden; }
.vorschau .kopf nav, .vorschau .ziel { display: none; }

@media (max-width: 1000px) { .raster { grid-template-columns: repeat(2, minmax(0, 1fr)); } .raster li:nth-child(3) { display: none; } .z-zeile { grid-template-columns: 6.5rem minmax(0, 1fr) 44px; } .z-zeile .seite { grid-column: 2; grid-row: 2; flex-direction: row; flex-wrap: wrap; gap: 4px 18px; } .z-zeile .pfeil { grid-column: 3; grid-row: 1; } }
@media (max-width: 640px) {
  .raster { grid-template-columns: minmax(0, 1fr); } .raster li:nth-child(3) { display: block; }
  .kopf nav { display: none; }
  .sektion-kopf { grid-template-columns: minmax(0, 1fr); }
  .z-zeile { grid-template-columns: minmax(0, 1fr) 44px; padding-right: 0; }
  .z-zeile time { grid-column: 1; } .z-zeile .z-text { grid-column: 1 / -1; grid-row: 2; } .z-zeile .seite { grid-column: 1 / -1; grid-row: 3; } .z-zeile .pfeil { grid-column: 2; grid-row: 1; }
  .ziel { left: 16px; right: auto; bottom: 64px; }
}
@media (hover: none) {
  .karte .pfeil, .z-zeile .pfeil { background: var(--signal); border-color: var(--signal); color: #fff; color: #fff; }
  .karte .pfeil svg, .z-zeile .pfeil svg { transform: translateX(28px); } .karte .pfeil svg + svg, .z-zeile .pfeil svg + svg { transform: none; }
  .karte:active .bild img { transform: translateY(-12px); transition-duration: var(--t1); }
  .karte:active .haupt, .z-zeile:active .haupt-l { background-size: 100% 2px; color: var(--signal); }
  .z-zeile:active::before { transform: scaleX(1); }
}
:root.reduziert *, :root.reduziert *::before, :root.reduziert *::after { transition-duration: .01ms !important; transition-delay: 0ms !important; animation-duration: .01ms !important; }
:root.reduziert .karte .bild img, :root.reduziert .ecke { transform: none !important; }
@media (forced-colors: active) {
  button, [role=button] { border: 1px solid ButtonText !important; }
  .karte, .z-zeile { border: 1px solid CanvasText; }
  .karte:has(.haupt:focus-visible), .z-zeile:has(.haupt-l:focus-visible) { outline: 3px solid Highlight; }
}
</style>
</head>
<body>
<header class="kopf">
  <p class="marke">Wellbrock Pumpenbau</p>
  <nav aria-label="Hauptmenü"><a href="#produkte">Produkte</a><a href="#beitraege" aria-current="page">Anwendungen</a><a href="#service">Service</a><a href="#kontakt">Kontakt</a></nav>
</header>
<main>
  <section class="sektion" id="beitraege" aria-labelledby="titel">
    <div class="sektion-kopf">
      <div>
        <p class="code">02 · Anwendungen und Fachbeiträge</p>
        <h1 id="titel">Wo unsere Pumpen laufen, und was wir dabei gelernt haben</h1>
      </div>
      <a class="alle" href="#alle">Alle 24 Beiträge <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M1 8h12M9 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a>
    </div>

    <ul class="raster" role="list">
      <li>
        <article class="karte">
          <span class="ecke lo"></span><span class="ecke ro"></span><span class="ecke lu"></span><span class="ecke ru"></span>
          <div class="bild"><img src="bilder/bild-hotspots-pumpe.webp" alt="" width="1860" height="1085"><span class="code">A-03 · Gießerei</span></div>
          <div class="inhalt">
            <p class="meta"><a class="neben" href="#rubrik-industrie">Industrie</a><time datetime="2026-10-28">28.10.2026</time><span>6 Min. Lesezeit</span></p>
            <h2><a class="haupt" href="#beitrag-attendorn">Kühlwasser für die Druckgießerei in Attendorn</a></h2>
            <p class="anriss">Zwei Normpumpen WB-NK 80 ersetzen fünf Altgeräte. Ein Kreislauf, eine Regelung, Umbau an einem Wochenende.</p>
            <div class="fuss"><a class="neben" href="#datenblatt-nk80">Datenblatt WB-NK 80 <span class="code">PDF · 1,4 MB</span></a><span class="pfeil" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></span></div>
          </div>
        </article>
      </li>
      <li>
        <article class="karte">
          <span class="ecke lo"></span><span class="ecke ro"></span><span class="ecke lu"></span><span class="ecke ru"></span>
          <div class="bild"><img src="bilder/handwerk-heizraum-hoch.webp" alt="" width="800" height="1000" style="object-position: 50% 30%"><span class="code">A-07 · Gebäudetechnik</span></div>
          <div class="inhalt">
            <p class="meta"><a class="neben" href="#rubrik-gebaeude">Gebäudetechnik</a><time datetime="2026-10-09">09.10.2026</time><span>4 Min. Lesezeit</span></p>
            <h2><a class="haupt" href="#beitrag-lennestadt">Acht Heizkreise im Schulzentrum Lennestadt</a></h2>
            <p class="anriss">Austausch in den Herbstferien: wie die Hausmeister und unser Service die Heizung in zehn Tagen umgestellt haben.</p>
            <div class="fuss"><a class="neben" href="#person-brune">Ansprechperson: Ilka Brune</a><span class="pfeil" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></span></div>
          </div>
        </article>
      </li>
      <li>
        <article class="karte">
          <span class="ecke lo"></span><span class="ecke ro"></span><span class="ecke lu"></span><span class="ecke ru"></span>
          <div class="bild"><img src="bilder/handwerk-waermepumpe.webp" alt="" width="1200" height="800"><span class="code">A-11 · Wärme</span></div>
          <div class="inhalt">
            <p class="meta"><a class="neben" href="#rubrik-waerme">Wärme</a><time datetime="2026-09-17">17.09.2026</time><span>8 Min. Lesezeit</span></p>
            <h2><a class="haupt" href="#beitrag-altbau">Umwälzpumpe im Altbau, wenn der Vorlauf auf 45 °C sinkt</a></h2>
            <p class="anriss">Was die Pumpe nach dem Umstieg auf die Wärmepumpe leisten muss, und warum größer selten besser ist.</p>
            <div class="fuss"><a class="neben" href="#rechner">Rechner Förderhöhe</a><span class="pfeil" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></span></div>
          </div>
        </article>
      </li>
    </ul>

    <ol class="liste" role="list">
      <li class="z-zeile">
        <time datetime="2026-10-28">28.10.2026</time>
        <div class="z-text"><h2><a class="haupt-l" href="#beitrag-attendorn">Kühlwasser für die Druckgießerei in Attendorn</a></h2><p class="anriss">Zwei Normpumpen WB-NK 80 ersetzen fünf Altgeräte. Ein Kreislauf, eine Regelung, Umbau an einem Wochenende.</p></div>
        <div class="seite"><a href="#rubrik-industrie">Industrie</a><span>6 Min. Lesezeit</span></div>
        <span class="pfeil" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></span>
      </li>
      <li class="z-zeile">
        <time datetime="2026-10-09">09.10.2026</time>
        <div class="z-text"><h2><a class="haupt-l" href="#beitrag-lennestadt">Acht Heizkreise im Schulzentrum Lennestadt</a></h2><p class="anriss">Austausch in den Herbstferien: wie die Hausmeister und unser Service die Heizung in zehn Tagen umgestellt haben.</p></div>
        <div class="seite"><a href="#rubrik-gebaeude">Gebäudetechnik</a><span>4 Min. Lesezeit</span></div>
        <span class="pfeil" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></span>
      </li>
      <li class="z-zeile">
        <time datetime="2026-09-17">17.09.2026</time>
        <div class="z-text"><h2><a class="haupt-l" href="#beitrag-altbau">Umwälzpumpe im Altbau, wenn der Vorlauf auf 45 °C sinkt</a></h2><p class="anriss">Was die Pumpe nach dem Umstieg auf die Wärmepumpe leisten muss, und warum größer selten besser ist.</p></div>
        <div class="seite"><a href="#rubrik-waerme">Wärme</a><a href="#rechner">Rechner Förderhöhe</a></div>
        <span class="pfeil" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></span>
      </li>
      <li class="z-zeile">
        <time datetime="2026-08-26">26.08.2026</time>
        <div class="z-text"><h2><a class="haupt-l" href="#beitrag-trockenlauf">Trockenlauf erkennen, bevor die Gleitringdichtung leidet</a></h2><p class="anriss">Drei Anzeichen, die jede Betriebsleitung kennen sollte, und eine Nachrüstung für 40 Jahre alte Anlagen.</p></div>
        <div class="seite"><a href="#rubrik-service">Service</a><a href="#datenblatt-sensor">Datenblatt TL-2</a></div>
        <span class="pfeil" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><svg width="18" height="18" viewBox="0 0 18 18"><path d="M1 9h14M10 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></span>
      </li>
    </ol>
    <p class="markiert-hinweis" id="markiert" aria-live="polite"></p>
  </section>
</main>
<div class="ziel" id="ziel" role="status"><b>Geöffnet</b><span data-ziel></span></div>

<div class="pult">
  <div class="gruppe" role="group" aria-label="Variante"><span>Variante</span>
    <button type="button" data-v="raster" aria-pressed="true">Raster · Überzug</button>
    <button type="button" data-v="liste" aria-pressed="false">Liste · Klick</button>
  </div>
  <button type="button" data-flaechen aria-pressed="false">Klickflächen zeigen</button>
</div>
<svg class="zeiger" viewBox="0 0 22 22" aria-hidden="true"><path d="M3 2l15 8.5-6.6 1.6L8.4 19z" fill="#111417" stroke="#F3F3F0" stroke-width="1.5" stroke-linejoin="round"/></svg>
<script>
(() => {
  const h = document.documentElement, $ = s => document.querySelector(s), $$ = s => [...document.querySelectorAll(s)];
  const reduziert = () => h.classList.contains('reduziert');
  const VARIANTEN = ['raster', 'liste'];
  const q = new URLSearchParams(location.search);
  let variante = VARIANTEN.includes(q.get('v')) ? q.get('v') : 'raster';

  /* Rückmeldung: welches Ziel wurde getroffen? (In der Demo führen die Links nirgendwohin.) */
  const ziel = $('#ziel');
  let zielT = null;
  function zeigeZiel(text) {
    ziel.querySelector('[data-ziel]').textContent = text;
    ziel.classList.add('an'); clearTimeout(zielT); zielT = setTimeout(() => ziel.classList.remove('an'), 1800);
  }
  document.querySelector('main').addEventListener('click', e => {
    const a = e.target.closest('a[href^="#"]');
    if (!a) return;
    e.preventDefault();
    const haupt = a.matches('.haupt, .haupt-l');
    zeigeZiel((haupt ? 'Beitrag: ' : 'Zweitlink: ') + a.textContent.replace(/\s+/g, ' ').trim() + (e.ctrlKey || e.metaKey ? ' (neuer Tab)' : ''));
  });

  /* Variante Liste: Klick auf die Zeile löst den Titel-Link aus, außer bei markiertem Text oder eigenem Link */
  const markiert = $('#markiert');
  $$('.z-zeile').forEach(z => {
    const a = z.querySelector('.haupt-l');
    z.addEventListener('click', e => {
      if (e.target.closest('a, button')) return;
      const sel = getSelection();
      if (sel && sel.toString().trim().length && z.contains(sel.anchorNode)) { markiert.textContent = 'Text markiert: Der Klick öffnet nichts, damit Sie kopieren können.'; return; }
      markiert.textContent = '';
      if (e.ctrlKey || e.metaKey || e.shiftKey) { zeigeZiel('Beitrag: ' + a.textContent.trim() + ' (neuer Tab)'); return; } /* im Projekt: window.open(a.href, '_blank') */
      a.click();
    });
    z.addEventListener('auxclick', e => { if (e.button === 1 && !e.target.closest('a')) { e.preventDefault(); zeigeZiel('Beitrag: ' + a.textContent.trim() + ' (neuer Tab)'); } });
  });

  /* Pult */
  function setzeVariante(v, schreiben) {
    variante = v; document.body.dataset.variante = v;
    $$('[data-v]').forEach(b => b.setAttribute('aria-pressed', b.dataset.v === v ? 'true' : 'false'));
    if (schreiben) { const p = new URLSearchParams(location.search); p.set('v', v); history.replaceState(null, '', location.pathname + '?' + p + location.hash); }
  }
  $$('[data-v]').forEach(b => b.addEventListener('click', () => { pause(); setzeVariante(b.dataset.v, true); neustart(); }));
  const fb = $('[data-flaechen]');
  const flaechen = an => { document.body.classList.toggle('zeige-flaechen', an); fb.setAttribute('aria-pressed', an ? 'true' : 'false'); markiert.textContent = an ? (variante === 'raster' ? 'Schraffiert: ein einziger Link (der Titel) über die ganze Karte. Orange: Zweitlinks, einzeln klickbar.' : 'Schraffiert: Klick auf die Zeile löst den Titel-Link aus. Orange: Zweitlinks.') : ''; };
  fb.addEventListener('click', () => flaechen(fb.getAttribute('aria-pressed') !== 'true'));

  function neustart() {
    $$('.sim-an, .sim-fokus').forEach(e => e.classList.remove('sim-an', 'sim-fokus'));
    flaechen(false); ziel.classList.remove('an'); markiert.textContent = '';
    const s = getSelection(); if (s) s.removeAllRanges();
  }

  /* Vorführung: gedachter Zeiger über Karte, dann Zweitlink (Karte ruht), dann Tastaturfokus, dann Klickflächen */
  const zeiger = $('.zeiger');
  let lauf = 0, zx = innerWidth * .5, zy = innerHeight * .85;
  const warte = (ms, id) => new Promise((ok, nein) => setTimeout(() => (id === lauf ? ok() : nein('stopp')), ms));
  function zeigeAuf(el, id, fx = .5, fy = .5) {
    const r = el.getBoundingClientRect(), x = r.left + r.width * fx, y = r.top + r.height * fy, dauer = reduziert() ? 0 : 600;
    zeiger.animate([{ transform: `translate(${zx}px, ${zy}px)` }, { transform: `translate(${x}px, ${y}px)` }], { duration: dauer, easing: 'cubic-bezier(0.25, 1, 0.5, 1)', fill: 'forwards' });
    zx = x; zy = y; return warte(dauer + 60, id);
  }
  const an = (el, ja = true) => el.classList.toggle('sim-an', ja);
  async function runde(id) {
    neustart(); await warte(400, id);
    if (variante === 'raster') {
      const k = $$('.raster .karte').filter(x => x.offsetParent);
      const [k1, k2, k3] = [k[0], k[1] || k[0], k[2] || k[1] || k[0]];
      await zeigeAuf(k1.querySelector('.bild'), id, .55, .6); an(k1); await warte(1300, id);
      const d = k1.querySelector('.fuss .neben'); await zeigeAuf(d, id, .4); an(k1, false); an(d); await warte(1300, id);
      an(d, false); await zeigeAuf(k2.querySelector('.anriss'), id, .5, .5); an(k2); await warte(700, id);
      zeigeZiel('Beitrag: ' + k2.querySelector('.haupt').textContent.trim()); await warte(1300, id); an(k2, false);
      zeiger.style.opacity = '0'; k3.classList.add('sim-fokus'); an(k3); await warte(1700, id);
      k3.classList.remove('sim-fokus'); an(k3, false); flaechen(true); await warte(2200, id); flaechen(false); zeiger.style.opacity = '';
    } else {
      const z = $$('.z-zeile');
      await zeigeAuf(z[0], id, .6, .45); an(z[0]); await warte(1200, id);
      const r = z[0].querySelector('.seite a'); await zeigeAuf(r, id, .4); an(z[0], false); await warte(1000, id);
      await zeigeAuf(z[1].querySelector('.anriss'), id, .3, .5); an(z[1]);
      const t = z[1].querySelector('.anriss').firstChild, rg = document.createRange(); rg.setStart(t, 0); rg.setEnd(t, 33);
      await warte(500, id); getSelection().removeAllRanges(); getSelection().addRange(rg);
      markiert.textContent = 'Text markiert: Der Klick öffnet nichts, damit Sie kopieren können.'; await warte(1800, id);
      getSelection().removeAllRanges(); markiert.textContent = ''; an(z[1], false);
      await zeigeAuf(z[2], id, .7, .4); an(z[2]); await warte(600, id); zeigeZiel('Beitrag: ' + z[2].querySelector('.haupt-l').textContent.trim()); await warte(1200, id); an(z[2], false);
      zeiger.style.opacity = '0'; z[3].classList.add('sim-fokus'); an(z[3]); await warte(1500, id); z[3].classList.remove('sim-fokus'); an(z[3], false);
      flaechen(true); await warte(2000, id); flaechen(false); zeiger.style.opacity = '';
    }
    runde(id).catch(() => {});
  }
  function vorfuehren() { const id = ++lauf; h.classList.add('vorfuehrung'); zeiger.style.opacity = ''; runde(id).catch(() => {}); }
  function pause() { lauf++; h.classList.remove('vorfuehrung'); }
  function weiter() { vorfuehren(); }

  setzeVariante(variante, false);

})();
</script>
</body>
</html>