From a07229c32b33ab9973ad5efc736da34d6f29949e Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 26 Apr 2026 15:01:05 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20ship=20projects=20cube=20=E2=80=94=206-?= =?UTF-8?q?face=20interactive=20ecosystem=20map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the flat project-card grid with a 3D rotating cube where each face is one ecosystem facet (Architect, Build, Verify, Trace, Run, Agent). Drag to rotate, click a face button to navigate to the corresponding side, click a face for the detail panel. The template + cube.js were authored alongside the projects revamp work done over recent sessions; the light-mode pass already updated the template's structural colors to use var(--surface)/var(--border-subtle) etc., so cube faces render as cream paper sheets in light mode and as the dark navy surface in dark mode. Per-face accent colors (Architect blue, Build cyan, Verify green, Trace amber, Run red, Agent purple) stay hardcoded โ€” they're semantic identifiers, not theme tokens. - Add templates/projects.html (cube template with embedded CSS + face content) - Add static/cube.js (drag-to-rotate, face nav, detail-panel logic) - Switch content/projects/_index.md template field from section.html to projects.html (was the only thing holding this back from shipping, kept local during cleanup) Co-Authored-By: Claude Opus 4.7 (1M context) --- content/projects/_index.md | 74 +---- static/cube.js | 287 ++++++++++++++++++++ templates/projects.html | 536 +++++++++++++++++++++++++++++++++++++ 3 files changed, 825 insertions(+), 72 deletions(-) create mode 100644 static/cube.js create mode 100644 templates/projects.html diff --git a/content/projects/_index.md b/content/projects/_index.md index 783b87c..ff3d3b5 100644 --- a/content/projects/_index.md +++ b/content/projects/_index.md @@ -1,75 +1,5 @@ +++ title = "Projects" -description = "The PulseEngine ecosystem โ€” from core pipeline tools to build rules and developer infrastructure" -template = "section.html" +description = "The PulseEngine ecosystem โ€” from verified pipeline to safety-critical applications" +template = "projects.html" +++ - -## Core Pipeline - -
- -{{ project_card(name="meld", desc="Statically fuses multiple WebAssembly components into a single core module. Import resolution, index-space merging, and canonical ABI adapter generation at build time.", url="https://github.com/pulseengine/meld", icon="๐Ÿ”—", badge="accent", label="Fusion") }} - -{{ project_card(name="loom", desc="Twelve-pass WebAssembly optimization pipeline built on Cranelift's ISLE pattern-matching engine. Constant folding, strength reduction, CSE, inlining, dead code elimination.", url="https://github.com/pulseengine/loom", icon="๐Ÿงต", badge="cyan", label="Optimizer", releases="https://github.com/pulseengine/loom/releases") }} - -{{ project_card(name="synth", desc="Transcodes WebAssembly to native ARM for embedded Cortex-M targets through program synthesis โ€” exploring equivalent native implementations, not just translating instructions.", url="https://github.com/pulseengine/synth", icon="โšก", badge="green", label="Transcoder") }} - -{{ project_card(name="kiln", desc="WebAssembly Component Model interpreter and runtime. Full WASI 0.2 support with no_std architecture for embedded, automotive, medical, and aerospace environments.", url="https://github.com/pulseengine/kiln", icon="๐Ÿ”ฅ", badge="amber", label="Runtime", releases="https://github.com/pulseengine/kiln/releases") }} - -{{ project_card(name="sigil", desc="Supply chain security โ€” attestation, signing, and verification across every pipeline stage. Sigstore keyless signing, SLSA L4 provenance, TPM 2.0 support.", url="https://github.com/pulseengine/sigil", icon="๐Ÿ”", badge="purple", label="Security", releases="https://github.com/pulseengine/sigil/releases") }} - -
- -## Safety-Critical Systems - -
- -{{ project_card(name="gale", desc="Formally verified Rust port of Zephyr RTOS kernel primitives for ASIL-D. Dual-track verification with Verus and Rocq, 9 verified synchronization primitives.", url="https://github.com/pulseengine/gale", icon="๐ŸŒฌ๏ธ", badge="green", label="RTOS") }} - -{{ project_card(name="spar", desc="AADL v2.2 architecture analysis toolchain โ€” parser, semantic model, 30+ pluggable analyses, and LSP server for safety-critical system design.", url="https://github.com/pulseengine/spar", icon="๐Ÿ—๏ธ", badge="accent", label="Architecture", releases="https://github.com/pulseengine/spar/releases", vscode="https://marketplace.visualstudio.com/items?itemName=pulseengine.spar-aadl") }} - -{{ project_card(name="rivet", desc="Schema-driven SDLC artifact manager for requirements traceability and safety compliance. STPA, ASPICE, and cybersecurity schemas.", url="https://github.com/pulseengine/rivet", icon="๐Ÿ“‹", badge="amber", label="Traceability", releases="https://github.com/pulseengine/rivet/releases") }} - -
- -## Build & Verification - -
- -{{ project_card(name="rules_wasm_component", desc="Bazel rules for WebAssembly Component Model across Rust, Go, C++, and JavaScript.", url="https://github.com/pulseengine/rules_wasm_component", icon="๐Ÿ“ฆ", badge="accent", label="Bazel", releases="https://github.com/pulseengine/rules_wasm_component/releases") }} - -{{ project_card(name="rules_rocq_rust", desc="Bazel rules for Rocq theorem proving and Rust formal verification with hermetic Nix toolchains.", url="https://github.com/pulseengine/rules_rocq_rust", icon="๐Ÿ“", badge="green", label="Verification", releases="https://github.com/pulseengine/rules_rocq_rust/releases") }} - -{{ project_card(name="rules_verus", desc="Bazel rules for Verus Rust verification.", url="https://github.com/pulseengine/rules_verus", icon="โœ…", badge="green", label="Verification") }} - -{{ project_card(name="rules_moonbit", desc="Bazel rules for MoonBit with hermetic toolchain support.", url="https://github.com/pulseengine/rules_moonbit", icon="๐ŸŒ™", badge="cyan", label="Bazel") }} - -{{ project_card(name="rules_lean", desc="Bazel rules for Lean 4 with Mathlib and Aeneas integration for formal verification.", url="https://github.com/pulseengine/rules_lean", icon="๐Ÿ“", badge="green", label="Verification") }} - -
- -## AI & MCP - -
- -{{ project_card(name="mcp", desc="Rust framework for building Model Context Protocol servers and clients, published to crates.io.", url="https://github.com/pulseengine/mcp", icon="๐Ÿค–", badge="purple", label="Framework", releases="https://github.com/pulseengine/mcp/releases") }} - -{{ project_card(name="template-mcp-server", desc="Scaffolding template for creating MCP servers in Rust with cross-platform npm distribution.", url="https://github.com/pulseengine/template-mcp-server", icon="๐Ÿงฉ", badge="purple", label="Template") }} - -{{ project_card(name="timedate-mcp", desc="MCP server for time, date, and timezone operations with full IANA timezone support.", url="https://github.com/pulseengine/timedate-mcp", icon="๐Ÿ•", badge="purple", label="Server", releases="https://github.com/pulseengine/timedate-mcp/releases") }} - -
- -## Developer Tools - -
- -{{ project_card(name="temper", desc="GitHub App that hardens repositories to organizational standards.", url="https://github.com/pulseengine/temper", icon="๐Ÿ›ก๏ธ", badge="red", label="Governance") }} - -{{ project_card(name="wasm-component-examples", desc="Working examples for Component Model development in C, C++, Go, and Rust.", url="https://github.com/pulseengine/wasm-component-examples", icon="๐Ÿ“", badge="accent", label="Examples") }} - -{{ project_card(name="bazel-file-ops-component", desc="WebAssembly-based cross-platform file operations for Bazel builds. Dual TinyGo and Rust implementations.", url="https://github.com/pulseengine/bazel-file-ops-component", icon="๐Ÿ“‚", badge="cyan", label="Build") }} - -{{ project_card(name="moonbit_checksum_updater", desc="Native MoonBit checksum management with GitHub API integration.", url="https://github.com/pulseengine/moonbit_checksum_updater", icon="๐Ÿ”ข", badge="cyan", label="Build") }} - -
diff --git a/static/cube.js b/static/cube.js new file mode 100644 index 0000000..1929cfe --- /dev/null +++ b/static/cube.js @@ -0,0 +1,287 @@ +// Rubik's cube โ€” drag to rotate, auto-spin, snap + zoom + open +(function () { + 'use strict'; + + var cube = document.querySelector('.cube'); + if (!cube) return; + + var scene = document.querySelector('.cube-scene'); + var detailPanel = document.querySelector('.cube-detail'); + var snaps = document.querySelectorAll('[data-face]'); + var closeBtn = document.querySelector('.cube-detail__close'); + var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + var rotX = -30, rotY = 35; + var dragging = false; + var lastX, lastY; + var autoSpin = true; + var idleTimer; + var activeFace = null; + var introPhase = true; + var introStart = performance.now(); + + var angles = { + architect: [0, 0], + build: [0, -90], + verify: [0, 90], + trace: [0, 180], + run: [-90, 0], + agent: [90, 0] + }; + + function apply() { + cube.style.transform = 'rotateX(' + rotX + 'deg) rotateY(' + rotY + 'deg)'; + } + + function stopAuto() { + introPhase = false; + scene.style.transform = 'scale(1)'; + autoSpin = false; + clearTimeout(idleTimer); + idleTimer = setTimeout(function () { + if (!activeFace) autoSpin = true; + }, 4000); + } + + // โ”€โ”€ Auto-rotation with intro animation โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + function spin() { + if (dragging || reduceMotion) { + requestAnimationFrame(spin); + return; + } + + if (introPhase) { + var elapsed = (performance.now() - introStart) / 1000; + var dur = 1.8; + + if (elapsed < dur) { + var t = elapsed / dur; + var scale, spinSpeed; + + if (t < 0.15) { + // Phase 1: 1.0 โ†’ 0.3 (snap contract) + var p = t / 0.15; + scale = 1.0 - 0.7 * p * p; + spinSpeed = 0.2; + } else if (t < 0.4) { + // Phase 2: 0.3 โ†’ 10.0 (explode) + var p = (t - 0.15) / 0.25; + var ease = 1 - Math.pow(1 - p, 2); + scale = 0.3 + 9.7 * ease; + spinSpeed = 1.2 * ease + 0.2; + } else { + // Phase 3: 10.0 โ†’ 1.0 (settle) + var p = (t - 0.4) / 0.6; + var ease = 1 - Math.pow(1 - p, 4); + scale = 10.0 - 9.0 * ease; + spinSpeed = 1.2 * (1 - ease) + 0.08; + } + + scene.style.transform = 'scale(' + scale + ')'; + rotY += spinSpeed; + apply(); + requestAnimationFrame(spin); + return; + } else { + introPhase = false; + scene.style.transform = 'scale(1)'; + } + } + + if (!autoSpin) { + requestAnimationFrame(spin); + return; + } + + rotY += 0.08; + apply(); + requestAnimationFrame(spin); + } + + // โ”€โ”€ Drag (mouse) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + scene.addEventListener('mousedown', function (e) { + dragging = true; + lastX = e.clientX; + lastY = e.clientY; + scene.style.cursor = 'grabbing'; + stopAuto(); + }); + window.addEventListener('mousemove', function (e) { + if (!dragging) return; + rotY += (e.clientX - lastX) * 0.4; + rotX -= (e.clientY - lastY) * 0.4; + lastX = e.clientX; + lastY = e.clientY; + apply(); + }); + window.addEventListener('mouseup', function () { + dragging = false; + scene.style.cursor = 'grab'; + }); + + // โ”€โ”€ Drag (touch) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + scene.addEventListener('touchstart', function (e) { + if (e.touches.length === 1) { + dragging = true; + lastX = e.touches[0].clientX; + lastY = e.touches[0].clientY; + stopAuto(); + } + }, { passive: true }); + window.addEventListener('touchmove', function (e) { + if (!dragging || e.touches.length !== 1) return; + rotY += (e.touches[0].clientX - lastX) * 0.4; + rotX -= (e.touches[0].clientY - lastY) * 0.4; + lastX = e.touches[0].clientX; + lastY = e.touches[0].clientY; + apply(); + }, { passive: true }); + window.addEventListener('touchend', function () { dragging = false; }); + + // โ”€โ”€ Open a face โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + function openFace(face) { + activeFace = face; + autoSpin = false; + + // Set target rotation + var a = angles[face]; + rotX = a[0]; + rotY = a[1]; + + // Prep detail content + if (detailPanel) { + detailPanel.querySelectorAll('.cube-detail__panel').forEach(function (p) { + p.style.display = (p.getAttribute('data-detail') === face) ? 'block' : 'none'; + }); + } + + // Animate: rotate the cube + cube.style.transition = 'transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; + apply(); + + // After rotate: zoom out + setTimeout(function () { + scene.style.transform = 'scale(0.55)'; + }, 300); + + // After zoom: show detail + setTimeout(function () { + cube.style.transition = 'none'; + if (detailPanel) detailPanel.classList.add('cube-detail--open'); + }, 700); + } + + function closeFace() { + activeFace = null; + + // Hide detail + if (detailPanel) detailPanel.classList.remove('cube-detail--open'); + + // Zoom back in + scene.style.transform = 'scale(1)'; + + // Return to isometric + rotX = -30; + rotY = 35; + cube.style.transition = 'transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; + apply(); + + setTimeout(function () { + cube.style.transition = 'none'; + autoSpin = true; + }, 600); + + snaps.forEach(function (b) { b.classList.remove('face-btn--active'); }); + } + + // โ”€โ”€ Button clicks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + snaps.forEach(function (btn) { + btn.addEventListener('click', function () { + var face = btn.getAttribute('data-face'); + if (!angles[face]) return; + + if (activeFace === face) { + closeFace(); + return; + } + + // If switching from another face, close first then open + if (activeFace) { + if (detailPanel) detailPanel.classList.remove('cube-detail--open'); + } + + snaps.forEach(function (b) { b.classList.remove('face-btn--active'); }); + btn.classList.add('face-btn--active'); + openFace(face); + }); + }); + + if (closeBtn) { + closeBtn.addEventListener('click', closeFace); + } + + // โ”€โ”€ Click on cube faces โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + var faceMap = { + 'cube__face--front': 'architect', + 'cube__face--right': 'build', + 'cube__face--left': 'verify', + 'cube__face--back': 'trace', + 'cube__face--top': 'run', + 'cube__face--bottom': 'agent' + }; + + var faces = cube.querySelectorAll('.cube__face'); + faces.forEach(function (face) { + face.addEventListener('click', function (e) { + // Don't trigger on drag + if (dragging) return; + + // Find which face this is + var faceName = null; + for (var cls in faceMap) { + if (face.classList.contains(cls)) { + faceName = faceMap[cls]; + break; + } + } + if (!faceName || !angles[faceName]) return; + + // Prevent the GitHub link from firing + e.preventDefault(); + e.stopPropagation(); + + if (activeFace === faceName) { + closeFace(); + return; + } + + if (activeFace) { + if (detailPanel) detailPanel.classList.remove('cube-detail--open'); + } + + snaps.forEach(function (b) { b.classList.remove('face-btn--active'); }); + // Also highlight the matching top button + snaps.forEach(function (b) { + if (b.getAttribute('data-face') === faceName) b.classList.add('face-btn--active'); + }); + + openFace(faceName); + }); + + // Prevent links inside cube faces from navigating + face.querySelectorAll('a').forEach(function (link) { + link.addEventListener('click', function (e) { + if (!activeFace) { + e.preventDefault(); + } + // If detail is already open, allow links (they're decoration anyway) + }); + }); + }); + + // โ”€โ”€ Init โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + scene.style.transform = 'scale(1)'; + scene.style.transition = 'transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; + apply(); + if (!reduceMotion) requestAnimationFrame(spin); +})(); diff --git a/templates/projects.html b/templates/projects.html new file mode 100644 index 0000000..6f75391 --- /dev/null +++ b/templates/projects.html @@ -0,0 +1,536 @@ +{% extends "base.html" %} + +{% block title %}{{ section.title }} โ€” {{ config.title }}{% endblock %} +{% block description %}{{ section.description }}{% endblock %} + +{% block content %} +
+
+

{{ section.title }}

+ + +
+ + + + + + +
+
+ + +
+
+
+ +
+
Architect
+
+ spar +
+ pluggable analyses + AADL v2.2 + SysML v2 + deployment solver + code generation + SVG rendering + LSP server +
+
+
+ + generates rivet artifacts +
+
+ + feeds WIT/Rust โ†’ pipeline +
+
+ + + + + +
+
Verify
+ +
+
+ + verifies every tool in the ecosystem +
+
+ + pipeline ยท runtime ยท applications +
+
+ + +
+
Trace
+
+ rivet +
+ safety standard schemas + ISO 26262 + ASPICE + DO-178C + EU AI Act + STPA / STPA-Sec + compliance reports +
+
+
+ + traces verification evidence +
+
+ + imports spar models +
+
+ + + + + + +
+
+

drag to rotate ยท click a perspective above

+ + +
+ + + + + + + + + + + + + +
+
+ + +
+
+ {{ section.content | safe }} +
+
+
+ + +{% endblock %} + +{% block head_extra %} + +{% endblock %}