diff --git a/public/assets/fonts/orbitron-bold.ttf b/public/assets/fonts/orbitron-bold.ttf deleted file mode 100644 index 374f46f..0000000 Binary files a/public/assets/fonts/orbitron-bold.ttf and /dev/null differ diff --git a/public/assets/fonts/space-mono-bold.ttf b/public/assets/fonts/space-mono-bold.ttf deleted file mode 100644 index 51cd703..0000000 Binary files a/public/assets/fonts/space-mono-bold.ttf and /dev/null differ diff --git a/public/assets/models3d/bmu-assembly.glb b/public/assets/models3d/bmu-assembly.glb deleted file mode 100644 index 7ad6d55..0000000 Binary files a/public/assets/models3d/bmu-assembly.glb and /dev/null differ diff --git a/public/assets/models3d/capacitor_0805.glb b/public/assets/models3d/capacitor_0805.glb deleted file mode 100644 index c6f5bdc..0000000 Binary files a/public/assets/models3d/capacitor_0805.glb and /dev/null differ diff --git a/public/assets/models3d/inductor_0805.glb b/public/assets/models3d/inductor_0805.glb deleted file mode 100644 index 05d3dc8..0000000 Binary files a/public/assets/models3d/inductor_0805.glb and /dev/null differ diff --git a/public/assets/models3d/led_0603.glb b/public/assets/models3d/led_0603.glb deleted file mode 100644 index 5eb0b04..0000000 Binary files a/public/assets/models3d/led_0603.glb and /dev/null differ diff --git a/public/assets/models3d/qfp32.glb b/public/assets/models3d/qfp32.glb deleted file mode 100644 index 45719ba..0000000 Binary files a/public/assets/models3d/qfp32.glb and /dev/null differ diff --git a/public/assets/models3d/resistor_0603.glb b/public/assets/models3d/resistor_0603.glb deleted file mode 100644 index 7634e8c..0000000 Binary files a/public/assets/models3d/resistor_0603.glb and /dev/null differ diff --git a/public/assets/models3d/soic8.glb b/public/assets/models3d/soic8.glb deleted file mode 100644 index 1e4064c..0000000 Binary files a/public/assets/models3d/soic8.glb and /dev/null differ diff --git a/src/components/AtomGlitch.tsx b/src/components/AtomGlitch.tsx deleted file mode 100644 index 9dbfbcf..0000000 --- a/src/components/AtomGlitch.tsx +++ /dev/null @@ -1,640 +0,0 @@ -import { useRef, useMemo, useEffect, useState, useCallback } from 'react'; -import { Canvas, useFrame, useThree } from '@react-three/fiber'; -import { Text } from '@react-three/drei'; -import { EffectComposer, Bloom, ChromaticAberration, Scanline, Glitch as GlitchEffect } from '@react-three/postprocessing'; -import { GlitchMode, BlendFunction } from 'postprocessing'; -import * as THREE from 'three'; - -/* =================================================================== - ATOM GLITCH — cinematic WebGL countdown for L'Electron Rare - - Nucleus core with pulsing energy - - 3 electron orbits with trail particles - - Particle cloud (electrons as sparks) - - Post-processing: bloom, chromatic aberration, glitch, scanlines - - Mouse-reactive + auto-orbit - =================================================================== */ - -const COLORS = { - cyan: new THREE.Color('#5bd1d8'), - amber: new THREE.Color('#f1c27a'), - green: new THREE.Color('#b6d18f'), - electric: new THREE.Color('#0071e3'), - white: new THREE.Color('#ffffff'), - nucleus: new THREE.Color('#ff6b35'), -}; - -/* ---------- Particle cloud around the atom ---------- */ -function ParticleField({ count = 600 }) { - const ref = useRef(null); - - const { positions, velocities, colors } = useMemo(() => { - const pos = new Float32Array(count * 3); - const vel = new Float32Array(count * 3); - const col = new Float32Array(count * 3); - const palette = [COLORS.cyan, COLORS.amber, COLORS.green, COLORS.electric]; - - for (let i = 0; i < count; i++) { - const r = 2 + Math.random() * 6; - const theta = Math.random() * Math.PI * 2; - const phi = Math.acos(2 * Math.random() - 1); - pos[i * 3] = r * Math.sin(phi) * Math.cos(theta); - pos[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); - pos[i * 3 + 2] = r * Math.cos(phi); - - vel[i * 3] = (Math.random() - 0.5) * 0.004; - vel[i * 3 + 1] = (Math.random() - 0.5) * 0.004; - vel[i * 3 + 2] = (Math.random() - 0.5) * 0.004; - - const c = palette[Math.floor(Math.random() * palette.length)]; - col[i * 3] = c.r; - col[i * 3 + 1] = c.g; - col[i * 3 + 2] = c.b; - } - return { positions: pos, velocities: vel, colors: col }; - }, [count]); - - useFrame(({ clock }) => { - if (!ref.current) return; - const posAttr = ref.current.geometry.attributes.position as THREE.BufferAttribute; - const arr = posAttr.array as Float32Array; - const t = clock.getElapsedTime(); - - for (let i = 0; i < count; i++) { - arr[i * 3] += velocities[i * 3] + Math.sin(t * 0.5 + i) * 0.002; - arr[i * 3 + 1] += velocities[i * 3 + 1] + Math.cos(t * 0.3 + i) * 0.002; - arr[i * 3 + 2] += velocities[i * 3 + 2]; - - // respawn far particles - const dist = Math.sqrt(arr[i * 3] ** 2 + arr[i * 3 + 1] ** 2 + arr[i * 3 + 2] ** 2); - if (dist > 8) { - const r = 2 + Math.random() * 2; - const theta = Math.random() * Math.PI * 2; - const phi = Math.acos(2 * Math.random() - 1); - arr[i * 3] = r * Math.sin(phi) * Math.cos(theta); - arr[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); - arr[i * 3 + 2] = r * Math.cos(phi); - } - } - posAttr.needsUpdate = true; - }); - - return ( - - - - - - - - ); -} - -/* ---------- Orbit trail (instanced tube of spheres) ---------- */ -function OrbitTrail({ tilt, speed, color, trailCount = 30 }: { tilt: number[]; speed: number; color: THREE.Color; trailCount?: number }) { - const meshRef = useRef(null); - const phase = useMemo(() => Math.random() * Math.PI * 2, []); - const dummy = useMemo(() => new THREE.Object3D(), []); - const orbitA = 3.2; - const orbitB = orbitA * 0.38; - - useFrame(({ clock }) => { - if (!meshRef.current) return; - const t = clock.getElapsedTime() * speed + phase; - - for (let i = 0; i < trailCount; i++) { - const age = i / trailCount; - const angle = t - age * 0.8; - dummy.position.set(Math.cos(angle) * orbitA, Math.sin(angle) * orbitB, 0); - const s = (1 - age) * 0.14 + 0.02; - dummy.scale.setScalar(s); - dummy.updateMatrix(); - meshRef.current.setMatrixAt(i, dummy.matrix); - } - meshRef.current.instanceMatrix.needsUpdate = true; - }); - - return ( - - - - - - - ); -} - -/* ---------- Orbit ring (thin glowing line) ---------- */ -function OrbitRing({ tilt, color }: { tilt: number[]; color: THREE.Color }) { - const points = useMemo(() => { - const pts: THREE.Vector3[] = []; - for (let i = 0; i <= 128; i++) { - const t = (i / 128) * Math.PI * 2; - pts.push(new THREE.Vector3(Math.cos(t) * 3.2, Math.sin(t) * 3.2 * 0.38, 0)); - } - return pts; - }, []); - const geo = useMemo(() => new THREE.BufferGeometry().setFromPoints(points), [points]); - - return ( - - - - - - ); -} - -/* ---------- Nucleus: pulsing energy core ---------- */ -function Nucleus() { - const coreRef = useRef(null); - const glowRef = useRef(null); - const outerRef = useRef(null); - - useFrame(({ clock, pointer }) => { - const t = clock.getElapsedTime(); - const mouseDist = Math.sqrt(pointer.x ** 2 + pointer.y ** 2); - const mouseProximity = Math.max(0, 1 - mouseDist); // 1 at center, 0 at edges - - if (coreRef.current) { - const s = 1 + Math.sin(t * 3) * 0.08 + Math.sin(t * 7.3) * 0.03 + mouseProximity * 0.15; - coreRef.current.scale.setScalar(s); - (coreRef.current.material as THREE.MeshStandardMaterial).emissiveIntensity = 2 + mouseProximity * 3; - } - if (glowRef.current) { - const s = 1 + Math.sin(t * 2) * 0.15 + mouseProximity * 0.3; - glowRef.current.scale.setScalar(s); - (glowRef.current.material as THREE.MeshStandardMaterial).opacity = 0.15 + Math.sin(t * 4) * 0.08 + mouseProximity * 0.2; - } - if (outerRef.current) { - outerRef.current.rotation.y = t * (0.5 + mouseProximity * 2); - outerRef.current.rotation.x = t * (0.3 + mouseProximity * 1.5); - } - }); - - // Logo refs — 3 copies at different rotations - const logo1Ref = useRef(null); - const logo2Ref = useRef(null); - const logo3Ref = useRef(null); - - useFrame(({ clock, camera }) => { - const t = clock.getElapsedTime(); - // Each logo copy rotates on a different axis - if (logo1Ref.current) { - logo1Ref.current.rotation.y = t * 0.8; - logo1Ref.current.quaternion.copy(camera.quaternion); - } - if (logo2Ref.current) { - logo2Ref.current.rotation.set(t * 0.5, t * 0.3, 0); - } - if (logo3Ref.current) { - logo3Ref.current.rotation.set(0, t * -0.4, t * 0.6); - } - }); - - return ( - - {/* outer energy shell */} - - - - - {/* glow sphere */} - - - - - {/* solid core */} - - - - - - {/* ER logo — 3 copies at center, different rotations */} - - - ER - - - - - - ER - - - - - - ER - - - - - ); -} - -/* ---------- ER logo (PNG) orbiting close to nucleus ---------- */ -function LogoOrbit({ tilt, speed, radius, color }: { - tilt: number[]; speed: number; radius: number; color: string; -}) { - const ref = useRef(null); - const planeRef = useRef(null); - const [texture, setTexture] = useState(null); - const phase = useMemo(() => Math.random() * Math.PI * 2, []); - const eccentricity = 0.35; - const threeColor = useMemo(() => new THREE.Color(color), [color]); - - useEffect(() => { - new THREE.TextureLoader().load('/assets/brand/logo-mark.png', (tex) => { - tex.minFilter = THREE.LinearFilter; - tex.magFilter = THREE.LinearFilter; - setTexture(tex); - }); - }, []); - - useFrame(({ clock, camera }) => { - if (!ref.current || !planeRef.current) return; - const t = clock.getElapsedTime() * speed + phase; - - ref.current.position.set( - Math.cos(t) * radius, - Math.sin(t) * radius * eccentricity, - 0, - ); - - // Billboard — always face camera - planeRef.current.quaternion.copy(camera.quaternion); - }); - - if (!texture) return null; - - return ( - - - - {/* Logo PNG on a plane */} - - - - - - {/* Glow sphere behind logo */} - - - - - - - ); -} - -/* ---------- Energy arcs (random lightning) ---------- */ -function EnergyArc({ color }: { color: THREE.Color }) { - const ref = useRef(null); - const [visible, setVisible] = useState(true); - - useFrame(({ clock }) => { - const t = clock.getElapsedTime(); - // flash randomly - setVisible(Math.sin(t * 12 + Math.random() * 100) > 0.7); - - if (ref.current && visible) { - const geo = ref.current.geometry; - const pts: THREE.Vector3[] = []; - const segments = 8; - const startAngle = Math.sin(t * 2) * Math.PI; - for (let i = 0; i <= segments; i++) { - const frac = i / segments; - const r = 0.4 + frac * 2.5; - const angle = startAngle + frac * 1.5; - pts.push(new THREE.Vector3( - Math.cos(angle) * r + (Math.random() - 0.5) * 0.3, - Math.sin(angle) * r * 0.4 + (Math.random() - 0.5) * 0.2, - (Math.random() - 0.5) * 0.4, - )); - } - geo.setFromPoints(pts); - } - }); - - if (!visible) return null; - - return ( - - - - - ); -} - -/* ---------- 3D Circular / Orbital text ---------- */ -const LAUNCH = new Date('2026-05-01T00:00:00+02:00').getTime(); -const FONT_URL = '/assets/fonts/orbitron-bold.ttf'; - -function pad(n: number) { return String(n).padStart(2, '0'); } - -/* Each character placed on a circle arc, rotating together */ -function CircularText({ text, radius, speed, tilt, fontSize, color, emissive, opacity = 0.9, spread = 0.8 }: { - text: string; radius: number; speed: number; tilt: number[]; - fontSize: number; color: string; emissive: string; opacity?: number; spread?: number; -}) { - const groupRef = useRef(null); - const chars = useMemo(() => text.split(''), [text]); - const charWidth = fontSize * 0.55; - - useFrame(({ clock }) => { - if (!groupRef.current) return; - const t = clock.getElapsedTime() * speed; - groupRef.current.rotation.y = t; - }); - - return ( - - - {chars.map((char, i) => { - const angle = ((i - chars.length / 2) * charWidth * spread) / radius; - const x = Math.sin(angle) * radius; - const z = Math.cos(angle) * radius; - return ( - - {char} - - - ); - })} - - - ); -} - -/* Countdown — characters on a spinning ring */ -function CountdownRing() { - const [time, setTime] = useState(''); - const groupRef = useRef(null); - const radius = 2.5; - - useEffect(() => { - function tick() { - const diff = Math.max(0, LAUNCH - Date.now()); - const d = Math.floor(diff / 86400000); - const h = Math.floor((diff % 86400000) / 3600000); - const m = Math.floor((diff % 3600000) / 60000); - const s = Math.floor((diff % 60000) / 1000); - setTime(`${pad(d)}J ${pad(h)}H ${pad(m)}M ${pad(s)}S`); - } - tick(); - const id = setInterval(tick, 1000); - return () => clearInterval(id); - }, []); - - useFrame(({ clock }) => { - if (!groupRef.current) return; - groupRef.current.rotation.y = clock.getElapsedTime() * -0.2; - }); - - const chars = time.split(''); - const fontSize = 0.28; - const charWidth = fontSize * 0.5; - - return ( - - - {chars.map((char, i) => { - const angle = ((i - chars.length / 2) * charWidth * 0.9) / radius; - const x = Math.sin(angle) * radius; - const z = Math.cos(angle) * radius; - return ( - - {char} - - - ); - })} - - - ); -} - -/* All circular texts assembled */ -function CountdownText() { - return ( - - {/* "L'electron" — tight elliptical orbit, close to nucleus */} - - - {/* "rare" — opposite tilt, close */} - - - {/* Subtitle — medium ring */} - - - {/* "LANCEMENT DANS" — small tight ring */} - - - {/* 3x ER logo orbiting close to nucleus like inner electrons */} - - - - - {/* Countdown timer — spinning ring */} - - - {/* Extra decorative text rings */} - - - ); -} - -/* ---------- Main scene ---------- */ -function AtomScene() { - const groupRef = useRef(null); - - const orbits = useMemo(() => [ - { tilt: [-0.5, 0, 0], speed: 1.0, color: COLORS.cyan }, - { tilt: [0.5, 0.3, 0], speed: 0.72, color: COLORS.amber }, - { tilt: [1.57, 0.2, 0.4], speed: 0.55, color: COLORS.green }, - ], []); - - useFrame(({ clock, pointer }) => { - if (!groupRef.current) return; - const t = clock.getElapsedTime(); - // Mouse distance from center = speed boost - const mouseDist = Math.sqrt(pointer.x ** 2 + pointer.y ** 2); - const speedMul = 1 + mouseDist * 0.8; - groupRef.current.rotation.y = t * 0.12 * speedMul + pointer.x * 0.6; - groupRef.current.rotation.x = -0.2 + pointer.y * 0.4; - groupRef.current.rotation.z = pointer.x * 0.1; - }); - - return ( - - - {orbits.map((o, i) => ( - - ))} - {orbits.map((o, i) => ( - - ))} - - - - {/* Textes orbitants — dans le groupe, tournent avec l'atome */} - - - ); -} - -/* ---------- Camera with mouse parallax ---------- */ -function CameraRig() { - const { camera } = useThree(); - const mouse = useRef({ x: 0, y: 0 }); - const smooth = useRef({ x: 0, y: 0 }); - - useFrame(({ clock, pointer }) => { - const t = clock.getElapsedTime(); - - // Smooth mouse follow (lerp) - mouse.current.x = pointer.x; - mouse.current.y = pointer.y; - smooth.current.x += (mouse.current.x - smooth.current.x) * 0.05; - smooth.current.y += (mouse.current.y - smooth.current.y) * 0.05; - - // Camera orbits gently + follows mouse - camera.position.x = Math.sin(t * 0.08) * 0.5 + smooth.current.x * 2.5; - camera.position.y = Math.cos(t * 0.06) * 0.3 + smooth.current.y * 1.5; - camera.position.z = 9 + Math.sin(t * 0.1) * 0.3; - camera.lookAt(smooth.current.x * 0.5, smooth.current.y * 0.3, 0); - }); - - return null; -} - -/* ---------- Exported component ---------- */ -export default function AtomGlitch() { - return ( -
- - - - - - - - - - - - - - - - - - - -
- ); -} diff --git a/src/components/WebGLBackground.tsx b/src/components/WebGLBackground.tsx deleted file mode 100644 index a5ad658..0000000 --- a/src/components/WebGLBackground.tsx +++ /dev/null @@ -1,513 +0,0 @@ -import { useRef, useMemo, useEffect, useState, Suspense } from 'react'; -import { Canvas, useFrame, useThree } from '@react-three/fiber'; -import { Text, useGLTF } from '@react-three/drei'; -import { EffectComposer, Bloom, ChromaticAberration, Scanline } from '@react-three/postprocessing'; -import { BlendFunction } from 'postprocessing'; -import * as THREE from 'three'; - -/* =================================================================== - PCB JOURNEY v2 — Real KiCad PCB + real component GLBs - - The BMU v2 PCB is loaded as the central 3D object. - Camera orbits around it, zooming into component zones on scroll. - Real GLB components from KiCad library placed on the board. - Current flows along the copper traces. - =================================================================== */ - -const COLORS = { - copper: new THREE.Color('#c87533'), - copperBright: new THREE.Color('#e8a040'), - current: new THREE.Color('#5bd1d8'), - currentWarm: new THREE.Color('#f1c27a'), - silkscreen: new THREE.Color('#e8e8d0'), -}; - -const FONT_URL = '/assets/fonts/orbitron-bold.ttf'; - -/* ---------- Scroll state ---------- */ -let scrollProgress = 0; - -/* ---------- Real PCB Board (full 3D with components from FreeCAD) ---------- */ -function RealPCB() { - let glb: any = null; - try { glb = useGLTF('/assets/models3d/bmu-assembly.glb'); } catch {} - - if (!glb?.scene) return null; - - // Deep clone with materials preserved - const cloned = useMemo(() => { - const clone = glb.scene.clone(true); - clone.traverse((child: any) => { - if (child.isMesh && child.material) { - child.material = child.material.clone(); - } - }); - return clone; - }, [glb.scene]); - - return ( - - ); -} - - -/* ---------- GLB Component placement ---------- */ -function GLBComponent({ modelUrl, position, scale = 1, rotation = [0, 0, 0] as [number, number, number] }: { - modelUrl: string; position: [number, number, number]; scale?: number; rotation?: [number, number, number]; -}) { - let glb: any = null; - try { glb = useGLTF(modelUrl); } catch {} - if (!glb?.scene) return null; - return ( - - ); -} - -/* ---------- Section label (silkscreen on board, clickable) ---------- */ -function SectionLabel({ position, text, subtitle, scrollTarget }: { - position: [number, number, number]; text: string; subtitle?: string; scrollTarget?: string; -}) { - const ref = useRef(null); - const [hovered, setHovered] = useState(false); - - useFrame(({ camera }) => { - if (!ref.current) return; - const worldPos = new THREE.Vector3(); - ref.current.getWorldPosition(worldPos); - const dist = camera.position.distanceTo(worldPos); - const glow = Math.max(0, 1 - dist / 12); - // Dynamic opacity based on camera distance - ref.current.children.forEach(child => { - if ((child as any).material) { - (child as any).material.opacity = hovered ? 0.9 : 0.3 + glow * 0.4; - } - }); - }); - - const handleClick = () => { - if (!scrollTarget) return; - if (scrollTarget === '#contact') { - // Open contact popup - document.getElementById('contact')?.classList.add('contact-3d-popup--open'); - } else { - const el = document.querySelector(scrollTarget); - if (el) el.scrollIntoView({ behavior: 'smooth' }); - } - }; - - return ( - { setHovered(true); document.body.style.cursor = scrollTarget ? 'pointer' : 'default'; }} - onPointerOut={() => { setHovered(false); document.body.style.cursor = 'default'; }} - > - {/* Clickable hit area (invisible plane) */} - - - - - - {/* Main label */} - - {text} - - - - {/* Subtitle */} - {subtitle && ( - - {subtitle} - - )} - - {/* Hover glow ring */} - {hovered && ( - - - - - )} - - ); -} - -/* ---------- Current flow particles ---------- */ -function CurrentFlow({ count = 80 }) { - const ref = useRef(null); - const dummy = useMemo(() => new THREE.Object3D(), []); - - // Particles follow a circular path around the PCB - const phases = useMemo(() => Array.from({ length: count }, () => Math.random()), [count]); - const radii = useMemo(() => Array.from({ length: count }, () => 3 + Math.random() * 8), [count]); - const heights = useMemo(() => Array.from({ length: count }, () => 0.3 + Math.random() * 0.5), [count]); - - useFrame(({ clock }) => { - if (!ref.current) return; - const t = clock.getElapsedTime(); - - for (let i = 0; i < count; i++) { - const angle = phases[i] * Math.PI * 2 + t * (0.1 + phases[i] * 0.1); - const r = radii[i]; - dummy.position.set( - Math.cos(angle) * r, - heights[i] + Math.sin(t * 2 + i) * 0.1, - Math.sin(angle) * r * 0.6, - ); - const proximity = Math.max(0.2, 1 - Math.abs(scrollProgress - phases[i]) * 3); - dummy.scale.setScalar(0.04 * proximity); - dummy.updateMatrix(); - ref.current.setMatrixAt(i, dummy.matrix); - } - ref.current.instanceMatrix.needsUpdate = true; - }); - - return ( - - - - - ); -} - -/* ---------- Warm current (second layer) ---------- */ -function WarmCurrent({ count = 40 }) { - const ref = useRef(null); - const dummy = useMemo(() => new THREE.Object3D(), []); - const phases = useMemo(() => Array.from({ length: count }, () => Math.random()), [count]); - - useFrame(({ clock }) => { - if (!ref.current) return; - const t = clock.getElapsedTime(); - for (let i = 0; i < count; i++) { - const angle = phases[i] * Math.PI * 2 + t * 0.06 + Math.PI; - const r = 4 + phases[i] * 6; - dummy.position.set( - Math.cos(angle) * r, - 0.2 + Math.sin(t + i) * 0.05, - Math.sin(angle) * r * 0.5, - ); - dummy.scale.setScalar(0.025); - dummy.updateMatrix(); - ref.current.setMatrixAt(i, dummy.matrix); - } - ref.current.instanceMatrix.needsUpdate = true; - }); - - return ( - - - - - ); -} - -/* ---------- Ambient dust ---------- */ -function AmbientDust({ count = 150 }) { - const ref = useRef(null); - const positions = useMemo(() => { - const pos = new Float32Array(count * 3); - for (let i = 0; i < count; i++) { - pos[i * 3] = (Math.random() - 0.5) * 30; - pos[i * 3 + 1] = Math.random() * 4; - pos[i * 3 + 2] = (Math.random() - 0.5) * 20; - } - return pos; - }, [count]); - - useFrame(({ clock }) => { - if (!ref.current) return; - const arr = (ref.current.geometry.attributes.position as THREE.BufferAttribute).array as Float32Array; - const t = clock.getElapsedTime(); - for (let i = 0; i < count; i++) { - arr[i * 3 + 1] += Math.sin(t * 0.3 + i * 0.5) * 0.001; - } - ref.current.geometry.attributes.position.needsUpdate = true; - }); - - return ( - - - - - - - ); -} - -/* ---------- Component clusters around the PCB ---------- */ -function ComponentCluster() { - const R = [-Math.PI / 2, 0, 0] as [number, number, number]; - const S_RES = 8; - const S_CAP = 10; - const S_LED = 8; - const S_IC_SM = 6; - const S_IC_LG = 3; - - return ( - - {/* Zone 1 — MCU area (top-right of board) */} - - - - - - - - - - - {/* Zone 2 — Analog / Op-amp area (left of board) */} - - - - - - - - - - - {/* Zone 3 — Power stage (bottom-right) */} - - - - - - - - - - - {/* Zone 4 — Communication (bottom-left) */} - - - - - - - - - - {/* Zone 5 — DSP / Neural (center-top) */} - - - - - - - - - {/* Zone 6 — Contact area (center) */} - - - {/* Scattered passives */} - {Array.from({ length: 15 }).map((_, i) => ( - - ))} - {Array.from({ length: 10 }).map((_, i) => ( - - ))} - - ); -} - -/* ---------- Board title silkscreen ---------- */ -/* ---------- Light that follows the camera ---------- */ -function CameraLight() { - const lightRef = useRef(null); - const { camera } = useThree(); - - useFrame(() => { - if (!lightRef.current) return; - lightRef.current.position.copy(camera.position); - lightRef.current.position.y += 0.03; - }); - - return ( - - ); -} - -/* ---------- Camera — orbits around PCB, zooms on scroll ---------- */ -// STEP units = METERS. Board ~0.17m wide, ~0.10m tall. After -π/2 X rotation: -// model Y→scene -Z, model Z→scene Y. Board center ≈ (0, 0.04, 0). -const CAMERA_STOPS = [ - { scroll: 0.00, pos: [0, 0.3, 0.2], look: [0, 0.04, 0] }, // Overview - { scroll: 0.12, pos: [0.06, 0.12, 0.02], look: [0.04, 0.04, 0] }, // MCU (right) - { scroll: 0.28, pos: [-0.06, 0.12, 0.02], look: [-0.04, 0.04, 0] }, // Analog (left) - { scroll: 0.42, pos: [0.05, 0.08, -0.03], look: [0.03, 0.04, -0.02] }, // Power - { scroll: 0.56, pos: [-0.05, 0.08, -0.03], look: [-0.03, 0.04, -0.02] }, // Formation - { scroll: 0.70, pos: [0, 0.15, 0.05], look: [0, 0.06, 0] }, // Missions - { scroll: 0.85, pos: [0, 0.08, 0.01], look: [0, 0.04, 0] }, // Contact (close) - { scroll: 1.00, pos: [0, 0.4, 0.25], look: [0, 0.04, 0] }, // Pull back -]; - -function OrbitCamera() { - const { camera } = useThree(); - const smooth = useRef({ x: 0, y: 0, z: 0, lx: 0, ly: 0, lz: 0 }); - - useFrame(({ pointer }) => { - const s = scrollProgress; - - let a = CAMERA_STOPS[0], b = CAMERA_STOPS[1]; - for (let i = 0; i < CAMERA_STOPS.length - 1; i++) { - if (s >= CAMERA_STOPS[i].scroll && s <= CAMERA_STOPS[i + 1].scroll) { - a = CAMERA_STOPS[i]; - b = CAMERA_STOPS[i + 1]; - break; - } - } - - const range = b.scroll - a.scroll; - const t = range > 0 ? (s - a.scroll) / range : 0; - const ease = t * t * (3 - 2 * t); - - const tx = a.pos[0] + (b.pos[0] - a.pos[0]) * ease + pointer.x * 0.03; - const ty = a.pos[1] + (b.pos[1] - a.pos[1]) * ease + pointer.y * 0.01; - const tz = a.pos[2] + (b.pos[2] - a.pos[2]) * ease; - const lx = a.look[0] + (b.look[0] - a.look[0]) * ease + pointer.x * 0.005; - const ly = a.look[1] + (b.look[1] - a.look[1]) * ease; - const lz = a.look[2] + (b.look[2] - a.look[2]) * ease; - - smooth.current.x += (tx - smooth.current.x) * 0.05; - smooth.current.y += (ty - smooth.current.y) * 0.05; - smooth.current.z += (tz - smooth.current.z) * 0.05; - smooth.current.lx += (lx - smooth.current.lx) * 0.05; - smooth.current.ly += (ly - smooth.current.ly) * 0.05; - smooth.current.lz += (lz - smooth.current.lz) * 0.05; - - camera.position.set(smooth.current.x, smooth.current.y, smooth.current.z); - camera.lookAt(smooth.current.lx, smooth.current.ly, smooth.current.lz); - }); - - return null; -} - -/* ---------- Full scene ---------- */ -function PCBScene() { - return ( - - - - - - - - - - ); -} - -/* ---------- Exported component ---------- */ -export default function WebGLBackground() { - useEffect(() => { - const onScroll = () => { - const el = document.documentElement; - const max = Math.max(1, el.scrollHeight - el.clientHeight); - scrollProgress = el.scrollTop / max; - }; - window.addEventListener('scroll', onScroll, { passive: true }); - onScroll(); - return () => window.removeEventListener('scroll', onScroll); - }, []); - - return ( - - ); -} - -/* Preload all models */ -useGLTF.preload('/assets/models3d/bmu-assembly.glb'); -useGLTF.preload('/assets/models3d/resistor_0603.glb'); -useGLTF.preload('/assets/models3d/capacitor_0805.glb'); -useGLTF.preload('/assets/models3d/inductor_0805.glb'); -useGLTF.preload('/assets/models3d/led_0603.glb'); -useGLTF.preload('/assets/models3d/soic8.glb'); -useGLTF.preload('/assets/models3d/qfp32.glb'); diff --git a/src/components/WebGLCards.tsx b/src/components/WebGLCards.tsx deleted file mode 100644 index 3bea13e..0000000 --- a/src/components/WebGLCards.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { useRef, useEffect, useState, useCallback } from 'react'; -import * as THREE from 'three'; - -/* =================================================================== - WEBGL CARDS — Scroll-triggered tile animations + media lightbox - - Features: - - Cards materialize with particle burst on scroll-in - - Cards dissolve with particle scatter on scroll-out - - Photos/videos appear in a front-view lightbox on click - - Energy ring orbits around focused media - =================================================================== */ - -/* ---------- Particle burst effect (CSS + JS, no Three.js needed) ---------- */ -function createParticleBurst(el: HTMLElement, color: string = '#5bd1d8') { - const rect = el.getBoundingClientRect(); - const cx = rect.left + rect.width / 2; - const cy = rect.top + rect.height / 2; - const count = 12; - - for (let i = 0; i < count; i++) { - const particle = document.createElement('div'); - particle.className = 'webgl-particle-burst'; - const angle = (i / count) * Math.PI * 2; - const dist = 40 + Math.random() * 60; - const dx = Math.cos(angle) * dist; - const dy = Math.sin(angle) * dist; - const size = 2 + Math.random() * 4; - - Object.assign(particle.style, { - position: 'fixed', - left: `${cx}px`, - top: `${cy}px`, - width: `${size}px`, - height: `${size}px`, - borderRadius: '50%', - background: color, - boxShadow: `0 0 ${size * 2}px ${color}`, - pointerEvents: 'none', - zIndex: '9999', - transition: 'all 0.6s cubic-bezier(0.16, 1, 0.3, 1)', - opacity: '1', - }); - - document.body.appendChild(particle); - - requestAnimationFrame(() => { - particle.style.transform = `translate(${dx}px, ${dy}px) scale(0)`; - particle.style.opacity = '0'; - }); - - setTimeout(() => particle.remove(), 700); - } -} - -/* ---------- Scroll-triggered card wrapper ---------- */ -export function WebGLCard({ children, color = '#5bd1d8', delay = 0 }: { - children: React.ReactNode; - color?: string; - delay?: number; -}) { - const ref = useRef(null); - const [visible, setVisible] = useState(false); - const hasAnimated = useRef(false); - - useEffect(() => { - const el = ref.current; - if (!el) return; - - const observer = new IntersectionObserver( - ([entry]) => { - if (entry.isIntersecting && !hasAnimated.current) { - setTimeout(() => { - setVisible(true); - hasAnimated.current = true; - createParticleBurst(el, color); - }, delay); - } - }, - { threshold: 0.2 } - ); - - observer.observe(el); - return () => observer.disconnect(); - }, [color, delay]); - - return ( -
- {children} -
- ); -} - -/* ---------- Media lightbox (photo/video front view) ---------- */ -export function MediaLightbox() { - const [active, setActive] = useState<{ type: 'img' | 'video'; src: string; alt?: string } | null>(null); - const overlayRef = useRef(null); - - useEffect(() => { - // Listen for clicks on photos and videos - const handler = (e: MouseEvent) => { - const target = e.target as HTMLElement; - - // Photo click - if (target.matches('.photo-strip-img, .webgl-media-trigger img')) { - e.preventDefault(); - e.stopPropagation(); - const img = target as HTMLImageElement; - setActive({ type: 'img', src: img.src, alt: img.alt }); - createParticleBurst(target, '#5bd1d8'); - } - - // Video click - if (target.matches('.video-strip-clip, .webgl-media-trigger video')) { - e.preventDefault(); - e.stopPropagation(); - const video = target as HTMLVideoElement; - setActive({ type: 'video', src: video.src }); - createParticleBurst(target, '#f1c27a'); - } - }; - - document.addEventListener('click', handler, true); - return () => document.removeEventListener('click', handler, true); - }, []); - - useEffect(() => { - if (!active) return; - const onKey = (e: KeyboardEvent) => { - if (e.key === 'Escape') setActive(null); - }; - window.addEventListener('keydown', onKey); - return () => window.removeEventListener('keydown', onKey); - }, [active]); - - if (!active) return null; - - return ( -
{ if (e.target === overlayRef.current) setActive(null); }} - > - {/* Energy ring animation */} -
-
-
- -
- {active.type === 'img' ? ( - {active.alt - ) : ( -
-
- ); -} - -/* ---------- Scroll-triggered section with atomic reveal ---------- */ -export function WebGLSection({ children, id, className = '' }: { - children: React.ReactNode; - id?: string; - className?: string; -}) { - const ref = useRef(null); - const [visible, setVisible] = useState(false); - - useEffect(() => { - const el = ref.current; - if (!el) return; - - const observer = new IntersectionObserver( - ([entry]) => { - setVisible(entry.isIntersecting); - }, - { threshold: 0.1 } - ); - - observer.observe(el); - return () => observer.disconnect(); - }, []); - - return ( -
- {children} -
- ); -} diff --git a/src/pages/index.astro b/src/pages/index.astro index a000164..ee6af17 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,6 +1,5 @@ --- import BaseLayout from '@/layouts/BaseLayout.astro'; -import AtomGlitch from '@/components/AtomGlitch'; import '@/styles/global.css'; import '@/styles/home-workbench.css'; import SiteHeader from '@/components/SiteHeader.astro'; @@ -30,114 +29,72 @@ const title = isLaunched ? "Systèmes électroniques spécifiques | L'électron rare" : "L'Electron Rare — Bientôt"; const description = isLaunched - ? "L'Électron Rare conçoit, met au point et fiabilise des systèmes électroniques spécifiques en électronique, automatisme, énergie et stockage." + ? "Conception, mise au point et fiabilisation de systèmes électroniques spécifiques." : "L'Electron Rare arrive. Systèmes électroniques spécifiques, formations, prototypage."; --- {isLaunched ? ( - -
- -
-
- -
-
+
+
-
-
- -
-
- PCB custom Teensy LED — projet spectacle vivant KXKM - Câblage automate industriel — intervention terrain - Banc de test BMS batteries — prototypage - Automate Siemens S7 — diagnostic industriel - Soudure PCB — formation et prototypage - Instrumentation et mesure — oscilloscope - Rack CBTC transport — connectique industrielle - Carte embarquée en test — validation prototype - Armoire Schneider — automatisme industriel - Atelier professionnel — outillage organisé -
-
- -
-
- - - -
-
-
-
-
-
) : ( - +
-
- -
+ +
-
+
L'ELECTRON RARE
@@ -153,11 +110,10 @@ const description = isLaunched mai 2026
-

L'électron rare

© 2026 Clément Saillant — systèmes électroniques spécifiques, conseil, formation — industrie, culture et projets multi-techniques.

-

Contact direct : LinkedIn DM ou contact@lelectronrare.fr

+

Contact : LinkedIn · contact@lelectronrare.fr

@@ -186,8 +142,9 @@ const description = isLaunched let msgIndex = Math.floor(Math.random() * messages.length); function rotateMessage() { - const bubble = document.getElementById('tagline-bubble')!; - const el = document.getElementById('tagline')!; + const bubble = document.getElementById('tagline-bubble'); + const el = document.getElementById('tagline'); + if (!bubble || !el) return; bubble.style.opacity = '0'; bubble.style.transform = 'translateY(4px)'; setTimeout(() => { @@ -198,8 +155,8 @@ const description = isLaunched }, 400); } - const sprite = document.getElementById('logo-sprite')!; - sprite.style.backgroundImage = `url(/assets/brand/logo-mark.png)`; + const sprite = document.getElementById('logo-sprite'); + if (sprite) sprite.style.backgroundImage = 'url(/assets/brand/logo-mark.png)'; rotateMessage(); setInterval(rotateMessage, 8000); @@ -207,181 +164,25 @@ const description = isLaunched )} diff --git a/src/pages/preview.astro b/src/pages/preview.astro index 2b07657..655041b 100644 --- a/src/pages/preview.astro +++ b/src/pages/preview.astro @@ -1,12 +1,8 @@ --- import BaseLayout from '@/layouts/BaseLayout.astro'; -import WebGLBackground from '@/components/WebGLBackground'; -import { MediaLightbox } from '@/components/WebGLCards'; import '@/styles/global.css'; import '@/styles/home-workbench.css'; -import '@/styles/webgl-cards.css'; import SiteHeader from '@/components/SiteHeader.astro'; -// Hero removed — PCB 3D is the hero import { About } from '@/components/sections/About'; import { CaseStudies } from '@/components/sections/CaseStudies'; import { GraphicSprints } from '@/components/sections/GraphicSprints'; @@ -27,7 +23,7 @@ const headerItems = [ --- - +
BMU v2 Assembly @@ -35,124 +31,67 @@ const headerItems = [
-

Chargement du PCB 3D...

+

Chargement...

Version sans 3D →

électronique · automatisme · énergie

- - - - + +
- - -
-
+
+
- -
-
+
+
- -