+ );
+}
+
diff --git a/website/src/components/DiamondScene/facetBadge.module.css b/website/src/components/DiamondScene/facetBadge.module.css
new file mode 100644
index 00000000..daaee9cb
--- /dev/null
+++ b/website/src/components/DiamondScene/facetBadge.module.css
@@ -0,0 +1,51 @@
+.badgeContainer {
+ position: absolute;
+ top: 70%; /* Position below the diamond roughly */
+ right: 15%; /* Align to the right side where diamond floats on desktop */
+ transform: translateY(20px);
+ opacity: 0;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ pointer-events: none;
+ z-index: 10;
+ text-align: center;
+ min-width: 180px;
+}
+
+.visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.badgeLabel {
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--ifm-color-primary-light);
+ margin-bottom: 0.25rem;
+ font-weight: 600;
+ text-shadow: 0 2px 4px rgba(0,0,0,0.5);
+}
+
+.badgeValue {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: #fff;
+ text-shadow: 0 0 20px rgba(59, 130, 246, 0.6);
+ background: linear-gradient(to right, #fff, #bfdbfe);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+.badgeLine {
+ width: 40px;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, var(--ifm-color-primary), transparent);
+ margin: 0.5rem auto 0;
+}
+
+/* Mobile Adjustment */
+@media (max-width: 1024px) {
+ .badgeContainer {
+ display: none;
+ }
+}
diff --git a/website/src/components/DiamondScene/geometry.js b/website/src/components/DiamondScene/geometry.js
new file mode 100644
index 00000000..0fed73df
--- /dev/null
+++ b/website/src/components/DiamondScene/geometry.js
@@ -0,0 +1,176 @@
+import * as THREE from 'three';
+
+// GEOMETRY: HIGH-FIDELITY ROUND BRILLIANT CUT
+// Constructed procedurally to ensure perfect symmetry and sharp facet edges
+export function createDiamondGeometry(radius = 1.5) {
+ const geometry = new THREE.BufferGeometry();
+
+ const rTable = radius * 0.54;
+ const rGirdle = radius;
+ const rMidCrown = radius * 0.82;
+ const rMidPav = radius * 0.35;
+
+ const hCrown = radius * 0.30;
+ const hMidCrown = radius * 0.12;
+ const hGirdle = 0;
+ const hTip = -radius * 0.75;
+ const hMidPav = -radius * 0.45;
+
+ const vertices = [];
+ const indices = [];
+
+ const tableVerts = [];
+ for (let i = 0; i < 8; i++) {
+ const theta = (i / 8) * Math.PI * 2;
+ vertices.push(Math.cos(theta) * rTable, hCrown, Math.sin(theta) * rTable);
+ tableVerts.push(i);
+ }
+
+ const midCrownVerts = [];
+ const midCrownStart = 8;
+ for (let i = 0; i < 8; i++) {
+ const theta = ((i + 0.5) / 8) * Math.PI * 2;
+ vertices.push(Math.cos(theta) * rMidCrown, hMidCrown, Math.sin(theta) * rMidCrown);
+ midCrownVerts.push(midCrownStart + i);
+ }
+
+ const girdleVerts = [];
+ const girdleStart = 16;
+ for (let i = 0; i < 16; i++) {
+ const theta = (i / 16) * Math.PI * 2;
+ vertices.push(Math.cos(theta) * rGirdle, hGirdle, Math.sin(theta) * rGirdle);
+ girdleVerts.push(girdleStart + i);
+ }
+
+ const pavMidVerts = [];
+ const pavMidStart = 32;
+ for (let i = 0; i < 16; i++) {
+ const theta = (i / 16) * Math.PI * 2;
+ vertices.push(Math.cos(theta) * (rGirdle * 0.5), hGirdle + (hTip - hGirdle) * 0.5, Math.sin(theta) * (rGirdle * 0.5));
+ pavMidVerts.push(pavMidStart + i);
+ }
+
+ const tipIdx = 48;
+ vertices.push(0, hTip, 0);
+
+ const topCenterIdx = 49;
+ vertices.push(0, hCrown, 0);
+
+ // Table Fan
+ for (let i = 0; i < 8; i++) {
+ indices.push(topCenterIdx, tableVerts[i], tableVerts[(i + 1) % 8]);
+ }
+
+ // Crown
+ for (let i = 0; i < 8; i++) {
+ const t1 = tableVerts[i];
+ const t2 = tableVerts[(i + 1) % 8];
+ const m = midCrownVerts[i];
+
+ const gLeft = girdleVerts[(i * 2) % 16];
+ const gMid = girdleVerts[(i * 2 + 1) % 16];
+ const gRight = girdleVerts[(i * 2 + 2) % 16];
+
+ const nextI = (i + 1) % 8;
+ const prevI = (i + 7) % 8;
+ const T_curr = tableVerts[i];
+ const T_next = tableVerts[nextI];
+ const M_curr = midCrownVerts[i];
+ const G_curr = girdleVerts[i * 2];
+ const G_mid = girdleVerts[i * 2 + 1];
+ const G_next = girdleVerts[(i * 2 + 2) % 16];
+
+ indices.push(T_curr, M_curr, T_next); // Star
+ indices.push(M_curr, G_curr, G_mid); // Upper Girdle 1
+ indices.push(M_curr, G_mid, G_next); // Upper Girdle 2
+
+ const M_prev = midCrownVerts[prevI];
+ indices.push(T_curr, M_prev, G_curr); // Bezel 1
+ indices.push(T_curr, G_curr, M_curr); // Bezel 2
+ }
+
+ // Pavilion
+ for (let i = 0; i < 16; i++) {
+ const G_curr = girdleVerts[i];
+ const G_next = girdleVerts[(i + 1) % 16];
+ const P_curr = pavMidVerts[i];
+ const P_next = pavMidVerts[(i + 1) % 16];
+
+ indices.push(G_curr, P_curr, G_next);
+ indices.push(G_next, P_curr, P_next);
+ indices.push(P_curr, tipIdx, P_next);
+ }
+
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+ geometry.setIndex(indices);
+ geometry.computeVertexNormals();
+
+ return geometry;
+}
+
+// Helper to map triangles to logical Facet IDs for interaction
+export function addFacetIds(nonIndexedGeometry) {
+ const positionAttribute = nonIndexedGeometry.getAttribute('position');
+ const vertexCount = positionAttribute.count;
+ // nonIndexedGeometry has unique vertices for each triangle, so count is multiple of 3
+
+ const facetIds = new Float32Array(vertexCount);
+
+ let triIndex = 0;
+
+ // MUST MATCH THE ORDER OF INDICES PUSHED IN createDiamondGeometry
+
+ // 1. Table: 8 triangles (Fan)
+ // ID 1: Table
+ for (let i = 0; i < 8; i++) {
+ const id = 1;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = id;
+ triIndex++;
+ }
+
+ // 2. Crown: 8 sections * 5 triangles
+ for (let i = 0; i < 8; i++) {
+ // Star: 1 triangle
+ const starId = 100 + i;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = starId;
+ triIndex++;
+
+ // Upper Girdle 1: 1 triangle
+ const upGirdle1Id = 200 + i * 2;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = upGirdle1Id;
+ triIndex++;
+
+ // Upper Girdle 2: 1 triangle
+ const upGirdle2Id = 200 + i * 2 + 1;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = upGirdle2Id;
+ triIndex++;
+
+ // Bezel: 2 triangles (Kite) -> Same ID
+ const bezelId = 300 + i;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = bezelId;
+ triIndex++;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = bezelId;
+ triIndex++;
+ }
+
+ // 3. Pavilion: 16 sections * 3 triangles
+ for (let i = 0; i < 16; i++) {
+ // Lower Girdle / Upper Pav 1
+ const lowGirdle1Id = 400 + i;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = lowGirdle1Id;
+ triIndex++;
+
+ // Lower Girdle / Upper Pav 2
+ const lowGirdle2Id = 500 + i;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = lowGirdle2Id;
+ triIndex++;
+
+ // Pavilion Main (Tip)
+ const pavMainId = 600 + i;
+ for (let v = 0; v < 3; v++) facetIds[triIndex * 3 + v] = pavMainId;
+ triIndex++;
+ }
+
+ nonIndexedGeometry.setAttribute('aFacetId', new THREE.BufferAttribute(facetIds, 1));
+ return nonIndexedGeometry;
+}
diff --git a/website/src/components/DiamondScene/index.js b/website/src/components/DiamondScene/index.js
new file mode 100644
index 00000000..07b35f73
--- /dev/null
+++ b/website/src/components/DiamondScene/index.js
@@ -0,0 +1,273 @@
+import React, { useEffect, useRef } from 'react';
+import * as THREE from 'three';
+import gsap from 'gsap';
+import { DiamondShader, ParticleShader, FacetHighlightShader } from './shaders';
+import { createDiamondGeometry, addFacetIds } from './geometry';
+
+export default function DiamondScene({ className, onHoverChange }) {
+ const canvasContainerRef = useRef(null);
+
+ useEffect(() => {
+ if (!canvasContainerRef.current) return;
+
+ const container = canvasContainerRef.current;
+
+ // Scene setup
+ const scene = new THREE.Scene();
+
+ // Camera
+ const camera = new THREE.PerspectiveCamera(22, container.clientWidth / container.clientHeight, 0.1, 1000);
+ camera.position.z = 12;
+
+ // Renderer
+ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); // Alpha true for transparency
+ renderer.setSize(container.clientWidth, container.clientHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+ container.appendChild(renderer.domElement);
+
+ // GEOMETRY
+ // Use slightly smaller radius to fit composition
+ const diamondGeometry = createDiamondGeometry(1.7);
+
+ // Flat Normals for Faceted Look (Critical for diamond shader)
+ let flatGeometry = diamondGeometry.toNonIndexed();
+ flatGeometry.computeVertexNormals();
+
+ // ADD FACET IDs for Interaction
+ flatGeometry = addFacetIds(flatGeometry);
+
+ // SHADER MATERIAL (Main Body)
+ const material = new THREE.ShaderMaterial({
+ uniforms: THREE.UniformsUtils.clone(DiamondShader.uniforms),
+ vertexShader: DiamondShader.vertexShader,
+ fragmentShader: DiamondShader.fragmentShader,
+ side: THREE.DoubleSide,
+ transparent: true,
+ extensions: { derivatives: true }
+ });
+
+ const mesh = new THREE.Mesh(flatGeometry, material);
+
+ // HIGHLIGHT MESH (Overlay for EIP-2535 Facets)
+ const highlightUniforms = THREE.UniformsUtils.clone(FacetHighlightShader.uniforms);
+ const highlightMaterial = new THREE.ShaderMaterial({
+ uniforms: highlightUniforms,
+ vertexShader: FacetHighlightShader.vertexShader,
+ fragmentShader: FacetHighlightShader.fragmentShader,
+ side: THREE.DoubleSide,
+ transparent: true,
+ depthTest: true,
+ depthWrite: false,
+ blending: THREE.AdditiveBlending,
+ });
+
+ // Create a slightly scaled up mesh or use polygon offset to avoid z-fighting
+ const highlightMesh = new THREE.Mesh(flatGeometry, highlightMaterial);
+ highlightMesh.scale.setScalar(1.001); // Tiny scale up to sit on top
+
+ // WIREFRAME (Subtle Structure)
+ // Angle threshold reduced to 15 to catch the top table edges (which are shallow)
+ const edges = new THREE.EdgesGeometry(diamondGeometry, 15);
+ const lineMat = new THREE.LineBasicMaterial({
+ color: 0xffffff,
+ transparent: true,
+ opacity: 0.3,
+ blending: THREE.AdditiveBlending
+ });
+ const wireframe = new THREE.LineSegments(edges, lineMat);
+
+ const diamondGroup = new THREE.Group();
+ diamondGroup.add(mesh);
+ diamondGroup.add(highlightMesh);
+ diamondGroup.add(wireframe);
+
+ // PARTICLES: FLOATING WAVE (PRO GRADE - DENSE & UNORDERED)
+ const particlesGeometry = new THREE.BufferGeometry();
+ const countX = 200;
+ const countZ = 100;
+ const particlesCount = countX * countZ;
+ const posArray = new Float32Array(particlesCount * 3);
+
+ let i = 0;
+ const separation = 0.5;
+ const offsetX = (countX * separation) / 2;
+ const offsetZ = (countZ * separation) / 2;
+
+ for(let x = 0; x < countX; x++) {
+ for(let z = 0; z < countZ; z++) {
+ posArray[i] = (x * separation) - offsetX + (Math.random() - 0.5) * separation * 0.8;
+ posArray[i+1] = 0;
+ posArray[i+2] = (z * separation) - offsetZ + (Math.random() - 0.5) * separation * 0.8;
+ i += 3;
+ }
+ }
+
+ particlesGeometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
+
+ const particlesMaterial = new THREE.ShaderMaterial({
+ uniforms: {
+ ...ParticleShader.uniforms,
+ uWidth: { value: countX * separation },
+ uDepth: { value: countZ * separation }
+ },
+ vertexShader: ParticleShader.vertexShader,
+ fragmentShader: ParticleShader.fragmentShader,
+ transparent: true,
+ blending: THREE.AdditiveBlending,
+ depthWrite: false
+ });
+
+ const particlesMesh = new THREE.Points(particlesGeometry, particlesMaterial);
+
+ const particlesGroup = new THREE.Group();
+ particlesGroup.add(particlesMesh);
+
+ particlesGroup.position.y = -1;
+ particlesGroup.position.z = -1;
+ particlesGroup.rotation.x = 0.05;
+
+ scene.add(diamondGroup);
+ scene.add(particlesGroup);
+
+ // INITIAL POSITION
+ diamondGroup.position.x = window.innerWidth > 1024 ? 2.2 : 0;
+ diamondGroup.position.y = 0.1;
+ diamondGroup.rotation.x = 0.25;
+
+ // ANIMATION
+ // Rotation
+ gsap.to(diamondGroup.rotation, {
+ y: Math.PI * 2,
+ duration: 40,
+ repeat: -1,
+ ease: "none"
+ });
+
+ // Float
+ gsap.to(diamondGroup.position, {
+ y: 0.4,
+ duration: 4,
+ yoyo: true,
+ repeat: -1,
+ ease: "sine.inOut"
+ });
+
+ // RAYCASTER SETUP
+ const raycaster = new THREE.Raycaster();
+ const mouse = new THREE.Vector2(-100, -100); // Start off-screen
+
+ const onMouseMove = (event) => {
+ // Disable interaction on mobile
+ if (window.innerWidth <= 1024) {
+ mouse.x = -100;
+ mouse.y = -100;
+ return;
+ }
+
+ const rect = renderer.domElement.getBoundingClientRect();
+ mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+ };
+
+ window.addEventListener('mousemove', onMouseMove);
+
+ // Time Uniform & Loop
+ const clock = new THREE.Clock();
+ let animationId;
+ let currentHoverId = -1;
+
+ const animate = () => {
+ animationId = requestAnimationFrame(animate);
+ const elapsedTime = clock.getElapsedTime();
+
+ // Update Uniforms
+ mesh.material.uniforms.uTime.value = elapsedTime;
+ highlightMesh.material.uniforms.uTime.value = elapsedTime;
+ particlesMaterial.uniforms.uTime.value = elapsedTime;
+
+ // Rotate wave slightly
+ particlesGroup.rotation.y = Math.sin(elapsedTime * 0.1) * 0.1;
+
+ // Raycasting logic
+ raycaster.setFromCamera(mouse, camera);
+
+ // Intersect with the highlight mesh (which has the same geometry as the diamond)
+ const intersects = raycaster.intersectObject(highlightMesh);
+
+ if (intersects.length > 0) {
+ const intersect = intersects[0];
+ const faceIndex = intersect.faceIndex;
+
+ // Retrieve Facet ID from geometry attribute
+ if (flatGeometry.attributes.aFacetId) {
+ const facetId = flatGeometry.attributes.aFacetId.getX(faceIndex * 3);
+
+ if (highlightMesh.material.uniforms.uHoverFacetId.value !== facetId) {
+ highlightMesh.material.uniforms.uHoverFacetId.value = facetId;
+
+ // Trigger callback only if changed
+ if (currentHoverId !== facetId) {
+ currentHoverId = facetId;
+ if (onHoverChange) onHoverChange(facetId);
+ }
+ }
+ }
+ } else {
+ // Reset if no intersection
+ if (highlightMesh.material.uniforms.uHoverFacetId.value !== -1.0) {
+ highlightMesh.material.uniforms.uHoverFacetId.value = -1.0;
+
+ if (currentHoverId !== -1) {
+ currentHoverId = -1;
+ if (onHoverChange) onHoverChange(-1);
+ }
+ }
+ }
+
+ renderer.render(scene, camera);
+ };
+ animate();
+
+ // RESIZE
+ const handleResize = () => {
+ if (!container) return;
+ const width = container.clientWidth;
+ const height = container.clientHeight;
+
+ camera.aspect = width / height;
+ camera.updateProjectionMatrix();
+ renderer.setSize(width, height);
+
+ const isDesktop = window.innerWidth > 1024;
+
+ if (isDesktop) {
+ gsap.to(diamondGroup.position, { x: 2.2, duration: 0.5 });
+ mesh.material.opacity = 1.0;
+ wireframe.material.opacity = 0.2;
+ } else {
+ gsap.to(diamondGroup.position, { x: 0, duration: 0.5 });
+ mesh.material.opacity = 0.1;
+ wireframe.material.opacity = 0.05;
+ }
+ };
+
+ handleResize();
+ window.addEventListener('resize', handleResize);
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ window.removeEventListener('mousemove', onMouseMove);
+ cancelAnimationFrame(animationId);
+ if (container && renderer.domElement && container.contains(renderer.domElement)) {
+ container.removeChild(renderer.domElement);
+ }
+ diamondGeometry.dispose();
+ flatGeometry.dispose();
+ edges.dispose();
+ particlesGeometry.dispose();
+ renderer.dispose();
+ };
+ }, [onHoverChange]); // Depend on callback
+
+ return ;
+}
diff --git a/website/src/components/DiamondScene/shaders.js b/website/src/components/DiamondScene/shaders.js
new file mode 100644
index 00000000..13db4279
--- /dev/null
+++ b/website/src/components/DiamondScene/shaders.js
@@ -0,0 +1,358 @@
+import * as THREE from 'three';
+
+// ULTRA-REALISTIC DIAMOND SHADER
+// Implements physical dispersion (chromatic aberration), internal reflection simulation,
+// and a crystalline normal map perturbation for that "crushed ice" look.
+export const DiamondShader = {
+ uniforms: {
+ uTime: { value: 0 },
+ // Color Palette: Deep rich blues for shadow, bright electric blues for highlights
+ uColor1: { value: new THREE.Color('#020617') }, // Almost black navy (Depth)
+ uColor2: { value: new THREE.Color('#1d4ed8') }, // Rich Blue (Body)
+ uColor3: { value: new THREE.Color('#bfdbfe') }, // Ice White (Sparkle)
+ uEnvRotation: { value: 0 },
+ uPixelSize: { value: 2.0 } // Tighter dithering for definition
+ },
+ vertexShader: `
+ varying vec2 vUv;
+ varying vec3 vNormal;
+ varying vec3 vPosition;
+ varying vec3 vViewPosition;
+ varying vec3 vWorldPosition;
+ varying vec3 vReflect;
+
+ void main() {
+ vUv = uv;
+ vNormal = normalize(normalMatrix * normal);
+ vPosition = position;
+
+ vec4 worldPosition = modelMatrix * vec4(position, 1.0);
+ vWorldPosition = worldPosition.xyz;
+
+ vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
+ vViewPosition = -mvPosition.xyz;
+
+ // Calculate reflection vector in view space for env mapping
+ vReflect = reflect(-normalize(vViewPosition), vNormal);
+
+ gl_Position = projectionMatrix * mvPosition;
+ }
+ `,
+ fragmentShader: `
+ uniform float uTime;
+ uniform vec3 uColor1;
+ uniform vec3 uColor2;
+ uniform vec3 uColor3;
+ uniform float uPixelSize;
+
+ varying vec2 vUv;
+ varying vec3 vNormal;
+ varying vec3 vViewPosition;
+ varying vec3 vReflect;
+
+ // Ordered dithering matrix 4x4
+ float dither4x4(vec2 position, float brightness) {
+ int x = int(mod(position.x, 4.0));
+ int y = int(mod(position.y, 4.0));
+ int index = x + y * 4;
+ float limit = 0.0;
+
+ if (x < 8) {
+ if (index == 0) limit = 0.0625;
+ if (index == 1) limit = 0.5625;
+ if (index == 2) limit = 0.1875;
+ if (index == 3) limit = 0.6875;
+ if (index == 4) limit = 0.8125;
+ if (index == 5) limit = 0.3125;
+ if (index == 6) limit = 0.9375;
+ if (index == 7) limit = 0.4375;
+ if (index == 8) limit = 0.25;
+ if (index == 9) limit = 0.75;
+ if (index == 10) limit = 0.125;
+ if (index == 11) limit = 0.625;
+ if (index == 12) limit = 1.0;
+ if (index == 13) limit = 0.5;
+ if (index == 14) limit = 0.875;
+ if (index == 15) limit = 0.375;
+ }
+ return brightness < limit ? 0.0 : 1.0;
+ }
+
+ // Fast pseudo-random
+ float rand(vec2 co){
+ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
+ }
+
+ // Cheap environment map simulation (Studio Lights)
+ float getEnvLight(vec3 dir) {
+ float light = 0.0;
+ // Key Light (Top Right) - Boosted for brightness
+ light += pow(max(0.0, dot(dir, normalize(vec3(1.0, 1.5, 1.0)))), 32.0) * 3.5;
+ // Top Light (Direct Overhead) - Added for extra brilliance
+ light += pow(max(0.0, dot(dir, vec3(0.0, 0.95, 0.1))), 20.0) * 2.0;
+ // Fill Light (Left)
+ light += pow(max(0.0, dot(dir, normalize(vec3(-1.0, 0.5, 0.2)))), 16.0) * 1.2;
+ // Rim Light (Back/Bottom) - Reduced intensity to darken bottom
+ light += pow(max(0.0, dot(dir, normalize(vec3(0.0, -1.0, -1.0)))), 8.0) * 0.2;
+
+ return light;
+ }
+
+ void main() {
+ vec3 viewDir = normalize(vViewPosition);
+ vec3 normal = normalize(vNormal);
+
+ // 1. Fresnel (The "Glass" Edge)
+ float fresnel = pow(1.0 - max(0.0, dot(viewDir, normal)), 4.0);
+
+ // 2. Reflection (External bounce)
+ vec3 refDir = reflect(-viewDir, normal);
+
+ // 3. Refraction/Internal (The "Fire")
+ // We simulate internal bounces by distorting the reflection vector
+ // This creates the "scrambled" look of a diamond interior
+ vec3 internalDir = refDir;
+ internalDir.x += sin(uTime * 0.5 + vViewPosition.y * 10.0) * 0.1;
+ internalDir.y += cos(uTime * 0.3 + vViewPosition.x * 10.0) * 0.1;
+ internalDir = normalize(internalDir);
+
+ // 4. Dispersion (Chromatic Aberration - The Rainbow)
+ // We sample the environment light at slightly offset angles for R, G, B
+ float rLight = getEnvLight(normalize(internalDir + vec3(0.02, 0.0, 0.0)));
+ float gLight = getEnvLight(normalize(internalDir));
+ float bLight = getEnvLight(normalize(internalDir - vec3(0.02, 0.0, 0.0)));
+
+ vec3 sparkles = vec3(rLight, gLight, bLight);
+
+ // 5. Edge Definition (Facet Cuts)
+ // Use derivatives to find sharp geometric edges
+ vec3 dNx = dFdx(vNormal);
+ vec3 dNy = dFdy(vNormal);
+ float edgeStrength = length(dNx) + length(dNy);
+ // Lower threshold to catch fainter edges (like the top table angles)
+ float edge = smoothstep(0.01, 0.06, edgeStrength);
+
+ // COMPOSITING
+
+ // Base Body: Deep blue to mid blue gradient based on view angle
+ float bodyTerm = dot(normal, vec3(0.0, 1.0, 0.0)) * 0.5 + 0.5;
+ // Brighter top mix: 0.8 influence instead of 0.6
+ vec3 bodyColor = mix(uColor1, uColor2, bodyTerm * 0.8 + fresnel * 0.4);
+
+ // Add Sparkles (Dispersion)
+ // Sparkles are masked by the body density to feel "internal"
+ vec3 finalColor = bodyColor + (sparkles * uColor3 * 1.5);
+
+ // Add extra top face brightness (Table highlight)
+ float topGlow = smoothstep(0.8, 1.0, normal.y); // Widen the range to catch more top angles
+ finalColor += uColor3 * topGlow * 0.25; // Boosted intensity
+
+ // Add crisp white edges - Changed to Light Blue for definition
+ vec3 edgeColor = mix(uColor3, uColor2, 0.2); // Whiter blue
+ finalColor += edgeColor * edge * 2.0; // Stronger edge definition
+
+ // DITHERING (The Retro-Tech Style)
+ // We dither based on luminance to create texture
+ float luminance = dot(finalColor, vec3(0.299, 0.587, 0.114));
+
+ // Boost dither input at edges and highlights - Increased weight to fill body
+ float ditherInput = luminance * 1.2 + edge * 0.4 + max(rLight, max(gLight, bLight)) * 0.2;
+
+ vec2 pixelCoord = gl_FragCoord.xy / uPixelSize;
+ float dither = dither4x4(pixelCoord, ditherInput);
+
+ // Final Mix:
+ // Balanced mix: Use calculated color for both states to preserve form
+ // Shadow state: Dimmer but colored (0.6)
+ // Light state: Boosted (1.4)
+ vec3 pixelColor = mix(finalColor * 0.6, finalColor * 1.4, dither);
+
+ // Add pure white sparkle post-dither for "blinding" hits
+ float superHighlight = step(0.95, max(rLight, max(gLight, bLight)));
+ pixelColor = mix(pixelColor, vec3(1.0), superHighlight * 0.8);
+
+ // MOBILE VISIBILITY FIX:
+ // On small screens, the diamond often sits BEHIND the text.
+ // We need to fade it out or darken it significantly when it might interfere with text readability.
+
+ gl_FragColor = vec4(pixelColor, 0.9); // Slight transparency for blending
+ }
+`
+};
+
+// PARTICLE SHADER - Floating diamond dust (Pro Wave with Flow)
+export const ParticleShader = {
+ uniforms: {
+ uTime: { value: 0 },
+ uColor: { value: new THREE.Color('#3b82f6') }, // Blue-500
+ uPixelSize: { value: 2.0 },
+ uWidth: { value: 100.0 } // Width of the field for wrapping
+ },
+ vertexShader: `
+ uniform float uTime;
+ uniform float uWidth;
+ varying vec3 vPos;
+ varying float vDist;
+
+ void main() {
+ vPos = position;
+ vec3 pos = position;
+
+ // CONTINUOUS FLOW:
+ // Move particles to the right over time
+ float speed = 0.5;
+ pos.x += uTime * speed;
+
+ // Infinite Scroll Logic:
+ // If pos.x goes beyond half width, wrap it back to start
+ // Assumes initial grid is centered at 0
+ // uWidth is total width. Range is [-uWidth/2, uWidth/2]
+ float halfWidth = uWidth * 0.5;
+
+ // Modulo math for GLSL to wrap around [-halfWidth, halfWidth]
+ // We add halfWidth first to shift to [0, uWidth], mod it, then shift back
+ pos.x = mod(pos.x + halfWidth, uWidth) - halfWidth;
+
+ // WAVE MOVEMENT (Vertical):
+ // Use the new wrapped X for wave calculation so the wave stays cohesive
+ float time = uTime * 0.3;
+
+ // Layered sine waves for "water" look
+ float wave1 = sin(pos.x * 0.4 + time) * cos(pos.z * 0.3 + time) * 0.6;
+ float wave2 = sin(pos.x * 0.8 - time * 1.2) * 0.2;
+ float wave3 = cos((pos.x + pos.z) * 0.2) * 0.3;
+
+ pos.y += wave1 + wave2 + wave3;
+
+ vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
+ gl_Position = projectionMatrix * mvPosition;
+
+ // Calculate distance for depth fade
+ vDist = length(mvPosition.xyz);
+
+ // Size attenuation - Dust Size
+ gl_PointSize = (4.0 * 12.0) / -mvPosition.z;
+ }
+ `,
+ fragmentShader: `
+ uniform vec3 uColor;
+ uniform float uPixelSize;
+ varying float vDist;
+
+ // Reusing dither logic for consistency
+ float dither4x4(vec2 position, float brightness) {
+ int x = int(mod(position.x, 4.0));
+ int y = int(mod(position.y, 4.0));
+ int index = x + y * 4;
+ float limit = 0.0;
+ if (x < 8) {
+ if (index == 0) limit = 0.0625;
+ if (index == 1) limit = 0.5625;
+ if (index == 2) limit = 0.1875;
+ if (index == 3) limit = 0.6875;
+ if (index == 4) limit = 0.8125;
+ if (index == 5) limit = 0.3125;
+ if (index == 6) limit = 0.9375;
+ if (index == 7) limit = 0.4375;
+ if (index == 8) limit = 0.25;
+ if (index == 9) limit = 0.75;
+ if (index == 10) limit = 0.125;
+ if (index == 11) limit = 0.625;
+ if (index == 12) limit = 1.0;
+ if (index == 13) limit = 0.5;
+ if (index == 14) limit = 0.875;
+ if (index == 15) limit = 0.375;
+ }
+ return brightness < limit ? 0.0 : 1.0;
+ }
+
+ void main() {
+ vec2 center = gl_PointCoord - 0.5;
+ float dist = length(center);
+ if (dist > 0.5) discard;
+
+ // Softer, more diffuse edge for "dust" look
+ float alpha = 1.0 - smoothstep(0.4, 0.8, dist);
+
+ // Depth Fade
+ float depthFade = 1.0 - smoothstep(8.0, 22.0, vDist);
+ alpha *= depthFade;
+
+ vec2 pixelCoord = gl_FragCoord.xy / uPixelSize;
+ float dither = dither4x4(pixelCoord, alpha * 1.5);
+
+ if (dither < 0.1) discard;
+
+ // Slightly more varied color for dust (mostly blue with faint white)
+ vec3 finalColor = mix(uColor, vec3(1.0), 0.1);
+
+ gl_FragColor = vec4(finalColor, alpha * 0.6);
+ }
+ `
+};
+
+// HIGHLIGHT SHADER - For highlighting specific facets (Diamond Standard / EIP-2535 Visualization)
+export const FacetHighlightShader = {
+ uniforms: {
+ uTime: { value: 0 },
+ uColor: { value: new THREE.Color('#488FF8') }, // Blue-400 (Bright Blue)
+ uActiveFacetId: { value: -1.0 }, // ID of the facet group to highlight (-1 = none)
+ uHoverFacetId: { value: -1.0 } // ID of the facet currently hovered (optional)
+ },
+ vertexShader: `
+ attribute float aFacetId;
+ varying float vFacetId;
+ varying vec3 vNormal;
+
+ void main() {
+ vFacetId = aFacetId;
+ vNormal = normalize(normalMatrix * normal);
+ vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
+ gl_Position = projectionMatrix * mvPosition;
+ }
+ `,
+ fragmentShader: `
+ uniform float uTime;
+ uniform vec3 uColor;
+ uniform float uActiveFacetId;
+ uniform float uHoverFacetId;
+
+ varying float vFacetId;
+ varying vec3 vNormal;
+ varying vec3 vPosition; // Added for rim calculation
+
+ void main() {
+ // Check if this fragment belongs to the active facet group
+ float isActive = 1.0 - step(0.1, abs(vFacetId - uActiveFacetId));
+ float isHover = 1.0 - step(0.1, abs(vFacetId - uHoverFacetId));
+
+ float totalActive = max(isActive, isHover);
+
+ if (totalActive < 0.1) discard;
+
+ // Clean, steady glow instead of frantic pulsing
+ // Small subtle breathe for life
+ float breathe = sin(uTime * 3.0) * 0.1 + 0.95; // Faster, brighter base
+
+ // Add Rim Light for definition (Fresnel-like)
+ // View vector is roughly along Z in local space for this simple rim
+ float rim = 1.5 - abs(dot(normalize(vNormal), vec3(0.0, 0.0, 1.0)));
+ rim = pow(rim, 3.0);
+
+ // Combine
+ vec3 finalColor = uColor * 1.4; // Boost base color brightness
+
+ // Mix solid fill with extra bright rim
+ finalColor += vec3(0.6) * rim; // Brighter rim
+
+ // Base alpha: steady and clean
+ // Increased opacity significantly for brighter appearance
+ float alpha = totalActive * breathe * 0.85;
+
+ // Boost alpha at rim for "glassy" edge
+ alpha += rim * 0.4;
+
+ gl_FragColor = vec4(finalColor, alpha);
+ }
+ `
+};
diff --git a/website/src/components/DiamondScene/useFacetBadges.js b/website/src/components/DiamondScene/useFacetBadges.js
new file mode 100644
index 00000000..b1049498
--- /dev/null
+++ b/website/src/components/DiamondScene/useFacetBadges.js
@@ -0,0 +1,42 @@
+import { useState, useCallback } from 'react';
+
+// A list of realistic facet names related to EIP-2535 Diamond Standard
+const FACET_NAMES = [
+ "DiamondCutFacet",
+ "DiamondLoupeFacet",
+ "OwnerFacet",
+ "AccessControlFacet",
+ "ERC20Facet",
+ "ERC721Facet",
+ "ERC721EnumerableFacet",
+ "ERC1155Facet",
+ "RoyaltyFacet",
+ "ERC165Facet",
+ "AccessControlPausableFacet",
+ "AccessControlTemporalFacet"
+];
+
+export function useFacetBadges() {
+ const [activeFacetName, setActiveFacetName] = useState(null);
+
+ // Map random names to IDs to keep them consistent during a session if we wanted,
+ // but for now we just pick a random one on hover entry if it's not already set.
+ const [facetMap] = useState(() => new Map());
+
+ const handleHover = useCallback((facetId) => {
+ if (facetId === -1) {
+ setActiveFacetName(null);
+ return;
+ }
+
+ // If we haven't assigned a name to this ID yet, pick one randomly
+ if (!facetMap.has(facetId)) {
+ const randomName = FACET_NAMES[Math.floor(Math.random() * FACET_NAMES.length)];
+ facetMap.set(facetId, randomName);
+ }
+
+ setActiveFacetName(facetMap.get(facetId));
+ }, [facetMap]);
+
+ return { activeFacetName, handleHover };
+}
diff --git a/website/src/pages/home/HomepageHeader.js b/website/src/pages/home/HomepageHeader.js
index 71add6ee..d1482b22 100644
--- a/website/src/pages/home/HomepageHeader.js
+++ b/website/src/pages/home/HomepageHeader.js
@@ -1,19 +1,29 @@
+import React from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Heading from '@theme/Heading';
-import GitHubStarButton from '@site/src/components/navigation/GitHubStarButton';
import Icon from '../../components/ui/Icon';
import styles from './homepageHeader.module.css';
+import DiamondScene from '../../components/DiamondScene';
+import { useFacetBadges } from '../../components/DiamondScene/useFacetBadges';
+import { FacetBadge } from '../../components/DiamondScene/FacetBadge';
export default function HomepageHeader() {
- const {siteConfig} = useDocusaurusContext();
+ const { activeFacetName, handleHover } = useFacetBadges();
+
return (
+
+
+ {/* Floating Badge for Diamond Interaction */}
+
+
+
@@ -25,36 +35,30 @@ export default function HomepageHeader() {
Build the future of Smart Contracts
-
- {siteConfig.tagline}
-
-
- A smart contract library for building diamond-based systems with an onchain
- standard library of facets. Write code that's designed to be understood,
- maintained, and scaled.
-
+
+
+ Compose is a smart contract library for building diamond-based systems with an onchain
+ standard library of facets.
+
+
+ Write code that's designed to be understood, maintained, and scaled.
+