P2503 · Theme · SDC · P2503-82

Big Numbers SDC — animierte Kennzahlen

approved P2503-173 Erstellt: 2026-05-22 · Figma: 61447:26224
TL;DR: Zwei neue SDCs — big-numbers (Canvas-Block-Type, Grid-Container) und big-number-item (Slot-Child, ein Kennzahl-Element) — realisieren eine animierte Kennzahlen-Sektion. Zahlen zählen per IntersectionObserver von 0 auf den Zielwert hoch, sobald die Komponente in den Viewport scrollt. Werte wie 9/10 werden über strukturierte Props (animated_value: 9, suffix: /10) modelliert — kein clientseitiges Parsing. Figma-Frame 61447:26224.

Problem Statement

SOLCOM möchte auf Landingpages und der Startseite zentrale Kennzahlen (z. B. Kundenzufriedenheit, abgeschlossene Projekte, Vermittlungen) prominent und einprägsam darstellen. Eine animierte Hochzähl-Darstellung erzeugt Aufmerksamkeit und unterstreicht die Relevanz der Zahlen.

Bisher gibt es nur die rudimentäre result-counter-SDC: ein einzelner Integer-Zähler ohne Grid, ohne Animation, ohne Support für zusammengesetzte Werte wie 9/10 oder 95%. Sie ist semantisch an die Projekt-Listing-Seite gebunden und nicht als Editorial-Block konzipiert. Redakteure haben keine Möglichkeit, eine vollständige Kennzahlen-Sektion nach Figma-Spec im Canvas-Editor aufzubauen.

Solution

Zwei neue SDCs bilden das Container+Item-Muster ab (analog zu slider-container / slider-item):

  • big-numbers ist der Canvas-Block-Type und steuert das responsive Grid-Layout. Er hat keine eigenen Daten-Props — alle Inhalte kommen von seinen Slot-Kindern.
  • big-number-item repräsentiert eine einzelne Kennzahl mit den Props animated_value (Zielzahl), format (Tausender-Formatierung), suffix (statischer Rest nach der Zahl) und label (Beschriftung). Er trägt die JS-Library via libraryOverrides.

Beim Scrollen in den Viewport startet ein IntersectionObserver die Hochzähl-Animation. Bei prefers-reduced-motion: reduce wird der Endwert sofort ohne Animation gesetzt.

User Stories

  1. Als Redakteur:in möchte ich im Canvas-Editor einen „Big Numbers"-Block anlegen können, damit ich eine Kennzahlen-Sektion auf einer Landingpage platzieren kann.
  2. Als Redakteur:in möchte ich bis zu 5 Kennzahl-Items anlegen können, damit ich die wichtigsten Zahlen von SOLCOM gebündelt darstellen kann.
  3. Als Redakteur:in möchte ich die Reihenfolge der Items per Drag&Drop steuern können, damit ich die Gewichtung der Kennzahlen jederzeit anpassen kann.
  4. Als Redakteur:in möchte ich für jedes Item einen Zahlenwert und einen Beschriftungstext eingeben können, damit Besucher:innen verstehen, was die Zahl bedeutet.
  5. Als Redakteur:in möchte ich Werte im Format 9/10 eingeben können (animierter Teil + statischer Suffix), damit ich Bewertungszahlen wie Kundenzufriedenheitswerte korrekt darstellen kann.
  6. Als Redakteur:in möchte ich Prozentwerte wie 95% eingeben können (Zahlenwert 95 + Suffix %), damit das Prozentzeichen statisch neben der animierenden Zahl steht.
  7. Als Redakteur:in möchte ich große Zahlen wie 100.300 mit automatischem Tausender-Trenner ausgeben können, damit der formatierte Wert dem deutschen Zahlenformat entspricht.
  8. Als Besucher:in der Website möchte ich sehen, wie die Zahlen beim Scrollen von 0 auf den Endwert hochzählen, damit die Kennzahlen Aufmerksamkeit erregen und einprägsam sind.
  9. Als Besucher:in der Website möchte ich, dass der animierte Teil (9) und der statische Teil (/10) immer klar getrennt sichtbar sind — auch während der Animation.
  10. Als Besucher:in mit aktiviertem prefers-reduced-motion möchte ich, dass die Zahlen direkt als Endwert angezeigt werden, ohne Animations-Flimmern.
  11. Als Besucher:in auf einem Mobilgerät möchte ich die Kennzahlen in einer übersichtlichen 1-Spalten-Ansicht sehen, damit der Inhalt lesbar bleibt.
  12. Als Besucher:in auf einem Tablet möchte ich die Kennzahlen in einem 2-Spalten-Layout sehen, damit die Seite den Platz effizient nutzt.
  13. Als Site-Builder möchte ich big-numbers in eine Dark-Background-Section einbetten können, damit der weiße Text der Komponente korrekt auf dem dunklen Hintergrund erscheint.
  14. Als Entwickler möchte ich, dass die Animation nur einmal pro Seitenbesuch startet (kein Re-Trigger beim Hin- und Zurückscrollen), damit die UX konsistent und nicht störend ist.
  15. Als Entwickler möchte ich, dass die JS-Library per libraryOverrides am big-number-item-SDC hängt (nicht global geladen), damit kein unbenutztes JS auf Seiten ohne diese Komponente ausgeliefert wird.

Implementation Decisions

Modul 1 — big-numbers (Canvas-Block-Type)

Das Container-SDC hat keine eigenen Daten-Props. Es rendert einen <div> mit dem Slot-Slot items als direktem Kind. Das Grid-Layout (flex, flex-wrap: wrap, gap: 32px) ist ausschließlich CSS/Tailwind. Der Container ist transparent — Hintergrundfarbe kommt immer vom umgebenden Section-Block.

Canvas-Registrierung: big-numbers wird als Canvas-Block-Type registriert, damit Redakteure ihn als Block einfügen können. Die Items sind Canvas-Slots, die per Drag&Drop umgeordnet werden können.

Modul 2 — big-number-item (Slot-Child)

Props-Schema:

PropTypPflichtDefaultBeschreibung
animated_valuenumberjaNumerischer Zielwert der Animation (z. B. 9, 95, 100300)
formatstring enumneinnonenone: keine Formatierung; thousands: Tausender-Trenner mit . (dt.) via toLocaleString('de-DE')
suffixstringneinleerStatischer Teil nach dem Zahlenwert (z. B. %, /10)
labelstringjaBeschriftungstext unter der Zahl (z. B. „Kundenzufriedenheit")

Das Twig-Template rendert die Zahl als zwei benachbarte <span>-Elemente: einen animierbaren Span (mit data-target-Attribut für die Zielzahl und data-format für die Formatierungsoption) und einen statischen Span für den Suffix. Das JS liest data-target und data-format aus dem DOM, ohne die Twig-Props direkt zu kennen.

Die JS-Library wird per libraryOverrides im big-number-item.component.yml geladen (analog zum slider-container-Muster):

libraryOverrides:
  js:
    big-numbers.js: { attributes: { type: module } }

Damit wird das JS nur geladen, wenn mindestens ein big-number-item auf der Seite vorhanden ist.

Modul 3 — big-numbers.js (Drupal.behaviors)

Das JS-Modul registriert ein Drupal.behaviors.solcomBigNumbers-Behavior. Im attach-Hook werden alle .big-number-item-Elemente mit once() prozessiert. Für jedes Element wird ein IntersectionObserver registriert.

Beim ersten Eintreten in den Viewport (threshold: 0.3) startet die Hochzähl-Animation:

  • Animationsdauer: 1500 ms, Easing: ease-out (quadratisches Easing via requestAnimationFrame).
  • Zwischenwerte: Math.round(eased * target) für Integer; für Float-Werte (erkennbar am data-target-Typ): Dezimalstellen beibehalten.
  • Nach Abschluss: wenn data-format="thousands", Endwert via toLocaleString('de-DE') formatieren.
  • Observer wird nach dem ersten Trigger abgemeldet (einmaliger Start, kein Re-Trigger).

prefers-reduced-motion-Check vor dem Observer-Setup: window.matchMedia('(prefers-reduced-motion: reduce)').matches → Endwert sofort setzen, kein Observer.

Canvas-Registrierung

big-numbers wird als Canvas-Block-Type registriert. Die Registrierung folgt dem etablierten Muster für Container-SDCs im Projekt. big-number-item wird als Slot-Child registriert. Details zur Canvas-Konfiguration: ADR-0002, ADR-0007, ADR-0014, ADR-0016 (zusammengefasst in docs/wiki/canvas-integration.html).

Responsive Grid

BreakpointLayout
Mobile < 768 px1 Spalte — Items untereinander, volle Breite
Tablet 768–1023 px2 Spalten — flex-wrap, je calc(50% - 16px)
Desktop ≥ 1024 pxAuto — alle Items nebeneinander mit flex: 1 0 0

Kein explizites columns-Prop am Container — die Spaltenanzahl ergibt sich natürlich aus der Item-Anzahl (Figma-Varianten 3/4/5 entsprechen der Item-Anzahl).

Testing Decisions

Was einen guten Test ausmacht

Tests prüfen das gerenderte HTML-Output der Komponenten — nicht die interne Implementierung (keine Twig-Variablen, keine JS-Internals). Gute Tests verifizieren: korrekte DOM-Struktur, korrekte data-*-Attribute, korrekte Klassen für das responsive Layout. Das JS-Verhalten wird durch Browser-Tests abgedeckt (Playwright), nicht durch Unit-Tests.

Kernel-Tests (PHP)

  • big-number-item Render-Test: Rendert das SDC mit animated_value: 9, suffix: /10, label: Weiterempfehlung und verifiziert: ein Element mit data-target="9", ein statisches <span> mit /10, ein Label-Element mit „Weiterempfehlung".
  • Format-Test: animated_value: 100300, format: thousands → verifiziert data-format="thousands" im DOM.
  • Kein-Suffix-Test: animated_value: 11, kein suffix → verifiziert, dass kein Suffix-<span> gerendert wird.

Prior Art: Kernel-Tests für slider-container und slider-item unter web/modules/custom/ (Tag slider). Big-Numbers-Tests werden mit dem Tag big-numbers angelegt und per ddev exec ./vendor/bin/phpunit --group big-numbers ausgeführt.

Manuelle Browser-Tests

Das JS-Verhalten (Animation, Observer-Trigger, prefers-reduced-motion) wird manuell auf der Test-URL https://solcom.ddev.site/page/73 gemäß dem QA-Plan im Story-Hub geprüft.

Out of Scope

  • Automatische Synchronisation der Kennzahlen aus einem Drittsystem (alle Werte werden manuell redaktionell gepflegt).
  • Animationen für den Suffix-Teil (z. B. /10 einblenden) — nur der numerische Teil animiert.
  • Dezimalzahl-Animation mit sich ändernden Dezimalstellen während der Animation — der Endwert wird exakt so angezeigt, wie er via animated_value übergeben wurde.
  • Negative Zahlen oder Zahlen mit führendem Vorzeichen.
  • Mehr als 5 Items pro Container-Block.
  • Eigene Hintergrundfarbe auf der big-numbers-Komponente — das ist Aufgabe des Section-Wrappers.
  • Anpassung oder Deprecation von result-counter — diese Komponente bleibt unverändert im Code und wird für ihren bestehenden Anwendungsfall weiter genutzt.
  • Staggered Animation (Items zählen zeitlich versetzt hoch) — alle Items starten gleichzeitig.

Further Notes

  • Figma-Referenz: File CHHDfbpX8Ys2LBiGs5HVK0, Node 61447:26224 (BigNumbersHor / CounterItem). Typografie: Montserrat ExtraBold 45 px für Zahlenwerte, Scala Sans LF Regular 19 px für Labels, Gap Zahl→Label 24 px (var(--xs)), Gap Item→Item 32 px.
  • Story-Hub: docs/stories/P2503-173-big-numbers.html enthält den vollständigen QA-Plan mit Click-Pfaden pro AK.
  • result-counter-Abgrenzung: Die bestehende SDC hat eine andere semantische Rolle (Suchfilter-Counter für Projekt-Listings) und ein anderes Props-Schema (nur count: integer). Sie ist kein Vorläufer von big-number-item und wird nicht ersetzt.
  • Tausender-Trenner: Nur bei format: thousands aktiv. Der Wert 178.2 (Dezimalzahl) wird als animated_value: 178.2, format: none modelliert — kein Punkt als Tausender-Trenner.
  • Canvas-Integration: Detaillierte Canvas-Konventionen (active_version-Hash, Slot-Topologie, Block-Type-Registrierung) sind in docs/wiki/canvas-integration.html zusammengefasst.