Communitygithub.com

Jav1es/dsh-skills

Schnelle Erstellung oder Umbau von rein statischen, mehrseitigen Portfolios (kein Framework, kein Build) – datengesteuertes Rendern, SVG-Diagramme, 3D-Effekte, Performance-Optimierung und Abnahmeprüfung vor dem Launch.

Was ist dsh-skills?

Diese Skill-Anleitung beschreibt eine strikt framework-freie Methodik zum Bau eines mehrseitigen, statischen Portfolio-Websites mit HTML5, CSS3 und Vanilla JS. Sie ist für Entwickler konzipiert, die keine Build-Tools oder externen Bibliotheken verwenden möchten. Der Ansatz ist datengesteuert: Alle Inhalte werden einmal in einer JSON-Datei gepflegt und über eine Rendering-Schicht dynamisch in die Seiten eingebunden, was Wartungsaufwand drastisch reduziert. Ein besonderes Augenmerk liegt auf der Vermeidung von CORS-Problemen bei lokalem Öffnen durch eine eingebettete JS-Datenversion. Die Anleitung behandelt auch die Erstellung von SVG-Diagrammen als Platzhalter für fehlende Screenshots, die Integration von 3D-Perspektiveffekten und leistungsschonenden Animationen (nur Transform und Opacity). Für die Performance werden strenge Regeln genannt: Kein `filter: blur()` oder `backdrop-filter`, um Ruckeln beim Scrollen zu vermeiden. Zudem wird eine robuste Entry-Animation vorgestellt, die verhindert, dass der gesamte Seiteninhalt unsichtbar bleibt, falls JavaScript-Fehler auftreten. Abschließend enthält die Anleitung bewährte Verifikationsmethoden für Screenshots und Linkprüfungen sowie eine Checkliste für die Bereitstellung auf GitHub Pages. Diese Methodik ist ideal für minimalistische, schnelle und barrierefreie Portfolios ohne Abhängigkeiten von Drittanbietern.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Jav1es/dsh-skills/tree/HEAD/static-portfolio-build

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

static-portfolio-build — Statisches mehrseitiges Portfolio

Technische Leitlinie: Nur HTML5 + CSS3 + Vanilla JS. Keine UI-Frameworks wie Bootstrap/Tailwind, keine externen CDN-Schriftarten. Primärfarben ≤ 3, Schriftarten ≤ 2, jedes Bild muss einen alt-Attributwert haben (SVGs verwenden role="img" + aria-label). Erster Bildschirm bei 3G ≤ 3s (geschätzt bei 1,6 Mbit/s / RTT 400 ms).

1. Architektur: Datengesteuert, einmal pflegen

Schreibe Portfolio-Inhalte nicht von Hand in jedes HTML. Extrahiere sie in JSON-Daten + eine Rendering-Schicht:

works-data.json    Datenquelle (skriptgeneriert, nicht manuell bearbeiten)
works-data.js      Gleiche Daten als Inline-Version (window.__WORKS_DATA__) – für file://-Doppelklick um CORS zu umgehen
works.js           Rendering-Schicht: ROLES / cardHTML() / tileHTML() / bindFilter() / bindExpand()
extract-works.mjs  Extrahiere aus alter Einzelseite
patch-works.mjs    Neu nummerieren / Module ergänzen / Motive zuweisen (muss idempotent sein!)

Warum eine Inline-JS-Datenversion notwendig ist: fetch('./works-data.json') wird unter file://-Protokoll von CORS blockiert. Benutzer sehen beim Doppelklick einen leeren Bildschirm.

Idempotentes Patch-Skript: patch-works.mjs muss wiederholbar ausgeführt werden können. Verwende EXTRA_TITLES, um bereits gepatchte Einträge zu filtern – sonst wirft die zweite Ausführung einen Fehler wegen fehlender Zuordnungstabelle.

Modulnummern mit realen Verzeichnissen abgleichen: Nummeriere Werke nicht nach Kategorie, sondern nach der tatsächlichen Zugehörigkeit zu lokalen Verzeichnissen/GitHub-Repos. Orientiere dich an vorhandenen Verzeichnissen für deren Nummern und fülle Lücken für verzeichnislose Werke, um eine durchgehende Nummerierung ohne Lücken zu gewährleisten. Beim Ändern von Nummern alle Verweise auf der gesamten Website aktualisieren (Einstiegskarten, Banner der Rollenseiten, Kategorielisten auf der Über-Seite) – am besten die Einstiegskarten automatisch aus den Daten generieren lassen, um manuelle Inkonsistenzen zu vermeiden:

document.querySelectorAll('[data-owns]').forEach(function (el) {
  var r = WORKS.ROLES[el.getAttribute('data-owns')];
  if (r) el.textContent = 'Enthält ' + r.owns.length + ' Module (' + r.owns.join(' / ') + ')';
});

2. Was tun ohne Screenshots: Inline-SVG-Diagramme

Die meisten Werke haben keine Screenshots. Weise jeder Kategorie ein geometrisches Motiv zu und generiere Inline-SVGs (keine externen Bilder, keine Bildanfragen beim ersten Laden):

KategorieMotivElemente
PlanungRoadmapKnoten + gestrichelte Verbindungen + Meilensteine
WorkflowPipelineEingabe → Verarbeitung → Prüfung → Ausgabe + Pfeil-Marker
AgentNeuralGeschichtete Knoten + vollständig verbundene Kanten
DatenMetrikenBalken + Liniendiagramm-Trend
Open SourceTerminalDunkles Fenster + farbige Codezeilen + Cursor
BrancheGridBlaupausen-Raster

Jedes muss role="img" + semantischen aria-label haben.

Fallstricke:

  • <text> in SVGs wird von Kartenverläufen verdeckt → Text aus SVG auslagern und mit HTML-Overlay .w-cover-tag (position:absolute) darstellen
  • preserveAspectRatio verwende meet nicht slice, um Beschnitt zu vermeiden
  • Array-Index-Verschiebung: Motivdaten als [x,y,Text,Farbe] geschrieben, aber Rendering liest l[2] als [x,y,width,color] → Rechtecke sind alle deaktiviert, nur Cursor wird angezeigt. Stelle vor dem Definieren von Motiven sicher, dass die Index-Semantik konsistent ist.

3. 3D und Effekte (schön, aber ohne Ruckeln)

Hintergrund: SpaceX-artiges Perspektiv-Gitter

.bg-grid-floor {
  position: absolute; left: -30%; right: -30%; top: 16%; height: 90%;
  background-image:
    repeating-linear-gradient(90deg,  rgba(10,10,10,.16) 0 1px, transparent 1px 74px),
    repeating-linear-gradient(180deg, rgba(10,10,10,.15) 0 1px, transparent 1px 40px);
  transform: perspective(520px) rotateX(72deg) translateZ(0);
  transform-origin: 50% 0%;
  mask-image: linear-gradient(to bottom, transparent 0%, #000 11%, #000 52%, transparent 90%);
  animation: floorDrift 30s linear infinite;   /* background-position ändern, nicht transform */
}

Darüber eine Horizont-Lichtlinie und drei driftende Lichtflecken (jeweils 24/29/34s Atmung).

Performance-Eisernes Gesetz (Entscheidend für Ruckelfreiheit)

Verwende niemals filter: blur() und backdrop-filter. Diese beiden rendern bei jedem Frame den gesamten Hintergrund neu – die Hauptursache für Ruckeln beim Scrollen:

  • Sanfte Lichtflecken mit radial-gradient(circle, rgba(...).10, transparent 68%) – der Verlauf selbst ist bereits weich, kein Blur nötig
  • Milchglas-Navigation durch fast undurchsichtigen Hintergrund rgba(255,255,255,.96) ersetzen
  • Abschnitte mit halbtransparentem weißen Hintergrund polstern, kein backdrop-filter

Animationen nur auf transform / opacity (vom Compositor verarbeitet, lösen kein Reflow/Repaint aus). Scroll-Parallaxe mit requestAnimationFrame throttlen und translate3d.

3D-Kippen von Karten

pointermove schreibt CSS-Variablen, ohne das DOM zu berühren:

.tilt { transform: perspective(1000px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)); }
.tilt.is-tilting { transition: transform .06s linear; }  /* Reduzierte Transition bei Berührung */

.tilt .w-body { transform: translateZ(18px); } erzeugt schwebenden Parallax-Effekt; ::before radialer Glanz folgt --mx/--my.

Hover-Verschiebung und Eintritts-Animation konkurrieren um transform → Hover ändert nur box-shadow/border-color, Verschiebung wird komplett von .tilt übernommen.

4. Eintritts-Animation: Niemals reines CSS opacity:0

Das ist die größte Falle. Reines CSS versteckt initial + JS fügt .is-revealed hinzu – wenn IntersectionObserver nicht zurückruft (Headless-Browser, JS-Fehler, anormaler Pfad), bleibt der gesamte Seiteninhalt dauerhaft unsichtbar.

Richtige Vorgehensweise: JS fügt bei Bedarf die versteckte Klasse hinzu, Elemente ohne Klasse sind standardmäßig sichtbar:

.w-card.reveal-armed { opacity: 0; transform: translateY(18px); }
.is-revealed { opacity: 1 !important; transform: none !important; }
list.forEach(function (el) {
  var r = el.getBoundingClientRect();
  var inView = r.top < innerHeight + 150 && r.bottom > -150;
  if (!inView) el.classList.add('reveal-armed');   // Nur außerhalb des sichtbaren Bereichs verstecken
  io.observe(el);
});
// Fallback: Nach 3 Sekunden noch nicht sichtbare Elemente erzwingen
setTimeout(function () { /* force reveal */ }, 3000);

Elemente im sichtbaren Bereich sind sofort zu sehen, außerhalb werden sie erst bewaffnet – der erste Bildschirm wird nicht durch Animationen verlangsamt, und anormale Pfade führen nicht zu einem leeren Bildschirm.

5. Skript-Injection: Prüfe <script>-Paarung

Beim Batch-Injizieren von Code in mehrere Seiten muss überprüft werden, dass die Anzahl von <script> und </script> gleich ist.

Realer Vorfall: Beim Injizieren ein öffnendes <script>-Tag zu viel geschrieben → der nachfolgende Inline-Skriptblock wurde vom JS-Parser verschluckt → der Rendering-Code aller vier Rollenseiten war komplett deaktiviert. Die Seite sah völlig normal aus (Titel, Navigation, Aufgabenliste vorhanden), nur der Werkbereich war leer – kaum erkennbar.

Beachte bei der Verwendung von regulären Ausdrücken: LastIndexOf('<script>') könnte <script src=...> treffen, wodurch die Definition außerhalb des Tags platziert und als sichtbarer Text angezeigt wird. Verwende stattdessen einen expliziten Anker-String.

6. Verifikationsmethoden (wiederverwendbar)

Screenshot-Verifikation (Wichtig: Gleichnamige Dateien werden gecached)

vision_html_screenshot cached bei gleichem Dateinamen. Nach CSS-Änderungen muss die Datei mit Copy-Item in ein temporäres Verzeichnis .verify/ kopiert und dann als Screenshot aufgenommen werden. Nach der Prüfung die Sandbox löschen.

Synchroner Probe (Nicht load + setTimeout verwenden)

In Headless-Umgebungen sind window.load + setTimeout unzuverlässig. Schreibe den Probe direkt in den Render-Callback (synchron), ausgegeben in ein <pre> mit position:fixed;bottom:0:

WORKS.loadData(function (data) {
  root.innerHTML = WORKS.listByRole(data.items, 'fde');
  var d = document.createElement('pre');
  d.style.cssText='position:fixed;bottom:0;left:0;right:0;z-index:99999;background:#000;color:#0f0;font:11px monospace;padding:4px;margin:0';
  d.textContent = 'CARDS=' + root.querySelectorAll('.w-card').length;
  document.body.appendChild(d);
});

⚠️ Verwende für den Probe nicht top-Positionierung – das 3D-Kippen der Karten (transform) könnte das fixed-Element verschieben.

Batch-Prüfung (Schneller als seitenweises Lesen)

  • Tote Links: Regex ./xx/yy.md extrahieren → Test-Path einzeln prüfen
  • Nummern-Kontinuität: Compare-Object erwartete Sequenz vs. tatsächliche
  • Verzeichnisabgleich: PowerShell vergleicht '01' als int 1 – muss mit [string] umwandeln
  • Teure Features übrig: [regex]::Matches($css,'filter:\s*blur|backdrop-filter'), dabei Kommentare unterscheiden

7. Deployment auf GitHub Pages

gh repo create <user>/<repo> --public --description "..."
git remote add origin https://github.com/<user>/<repo>.git
git push -u origin main

Vor dem Deployment prüfen:

  1. git ls-files überprüfen, ob die Abhängigkeitsverzeichnisse wirklich getrackt werden (lokale Dateien ≠ git getrackt)
  2. Bei bereits vorhandenem entfernten Inhalt zuerst git ls-tree --name-only origin/main vergleichen, nichts überschreiben
  3. Lokale relative Pfade zu benachbarten Projektverzeichnissen: Entweder beide pushen oder in GitHub-Repo-Adressen ändern
  4. GitHub Pages benötigt Settings → Pages → Source muss Branch auswählen
  5. Bei Sub-Apps (z. B. hitech-self-assessment/) darauf achten, dass sie nicht durch die Hauptseite überschrieben werden

Performance-Schätzung: Gesamtbytes / 8 / 1.6e6 + 4 * 0.4 (Sekunden)

8. Selbstcheck vor Auslieferung (Abgleich mit web-delivery-checklist)

  • Alle vier/fünf Seiten per Screenshot verifiziert (nicht nur die Startseite)
  • Tote Links: 0, fehlende alt: 0
  • <script>-Paarung geschlossen, CSS-Klammern ausgeglichen
  • Tatsächliche Anzahl von filter: blur / backdrop-filter = 0
  • 3G-Schätzung < 3s
  • Primärfarben ≤ 3, Schriftarten ≤ 2, null externe CDN-Ressourcen
  • Auf mobilen Geräten (390px) Hamburger-Menü auf-/zuklappbar, aria-expanded synchronisiert
  • Bei prefers-reduced-motion alle Animationen deaktiviert

Verwandte Skills