diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d8c3fec --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,73 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Corporate site for **L'électron rare** (electronics consulting studio) — an Astro 5 SSR site with a React Three Fiber 3D product viewer as its centerpiece. Production URL: `https://www.lelectronrare.fr/` + +## Commands + +```bash +npm run dev # Astro dev server (default port 4321) +npm run build # Production build (SSR, outputs dist/) +npm run build:external # Build with PUBLIC_SITE_URL for external domain +npm run preview # Preview production build locally +npm run typecheck # astro check (TypeScript validation) +npm run storybook # Component dev on port 6006 +npm run lab:dev # Sub-app (apps/lab-interactif) dev server +npm run tracking:check # Validate GA4 tracking events +npm run image:budget # Check image sizes against budget +``` + +**Docker build & deploy (from `/home/electron/electron-rare.github.io/infra/`):** +```bash +docker compose -f docker-compose.site.yml build electron-rare-site +docker compose -f docker-compose.site.yml up -d electron-rare-site +``` + +## Architecture + +**Runtime:** Astro SSR with `@astrojs/node` adapter (standalone mode). The built site runs as `node dist/server/entry.mjs` on port 4321, behind Traefik. + +**Styling:** Tailwind CSS 4 integrated via `@tailwindcss/vite` plugin — no separate tailwind.config. Uses `cva` + `clsx` + `tailwind-merge` for component variants. + +**Path alias:** `@/*` maps to `src/*`. + +### Site URL & Base Path + +The site supports multiple deployment targets (GitHub Pages subpath, custom domain). `PUBLIC_SITE_URL` env var drives both `astro.config.mjs` and `src/lib/site.ts`. All internal links must use `withSiteBase()` from `src/lib/site.ts` to work across deployments. + +### 3D Viewer (`src/components/BmuViewer.tsx`) + +The main differentiating feature — a scroll-driven 3D PCB viewer on the `/preview/` page: +- Loads 5 GLTF models (BMU, I2C Repeater, MOSFET switches) from `public/assets/models3d/` +- 16+ named scroll sections, each defining camera position, rotation, focus card, and explode amount +- Scroll progress (0–1) interpolates between section states for smooth transitions +- Uses `@react-three/drei` Html component for interactive PCB label overlays +- Post-processing: Bloom + Vignette via `@react-three/postprocessing` +- VideoTexture meshes for PCB bottom views +- Brand color: `#5bd1d8` (cyan) + +### Pages + +- `index.astro` — Landing page with section components (Hero, About, CaseStudies, GraphicSprints, Faq, Contact) +- `preview.astro` — 3D scroll-driven viewer (1000vh scroll track, fixed overlays, dot navigation) +- `api/submit-lead.ts` — Server endpoint posting to Frappe CRM +- `formation.astro`, `portfolio.astro`, `ops.astro` — Content pages + +### CRM Integration + +Contact form submits to Frappe CRM via `src/pages/api/submit-lead.ts`. Requires `FRAPPE_URL`, `FRAPPE_API_KEY`, `FRAPPE_API_SECRET` env vars (set in `infra/.env`). + +## CI/CD + +Push to `main` triggers GitHub Actions (`.github/workflows/deploy-cloudflare.yml`): +1. `npm ci` + `npm run build` with `PUBLIC_SITE_URL=https://www.lelectronrare.fr/` +2. SCP `dist/` to Tower server +3. Docker container restart (node:22-alpine running `dist/server/entry.mjs`) +4. Health check on `http://127.0.0.1:4321/` + +## GLB Pipeline + +KiCad `.kicad_pcb` → STEP export → FreeCAD Docker tessellation → GLB with per-component materials and KiCad raytraced textures. Models stored in `public/assets/models3d/`. diff --git a/infra/Dockerfile.prebuild b/infra/Dockerfile.prebuild new file mode 100644 index 0000000..7c0ecf8 --- /dev/null +++ b/infra/Dockerfile.prebuild @@ -0,0 +1,9 @@ +FROM node:22-alpine +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev +COPY dist ./dist +ENV HOST=0.0.0.0 +ENV PORT=4321 +EXPOSE 4321 +CMD ["node", "dist/server/entry.mjs"] diff --git a/public/assets/models3d/bmu-assembly.glb b/public/assets/models3d/bmu-assembly.glb new file mode 100644 index 0000000..f5ac0ab Binary files /dev/null and b/public/assets/models3d/bmu-assembly.glb differ diff --git a/public/assets/models3d/bmu_v2.glb b/public/assets/models3d/bmu_v2.glb new file mode 100644 index 0000000..18990b7 Binary files /dev/null and b/public/assets/models3d/bmu_v2.glb differ diff --git a/public/assets/models3d/i2c_repeater.glb b/public/assets/models3d/i2c_repeater.glb new file mode 100644 index 0000000..86bba57 Binary files /dev/null and b/public/assets/models3d/i2c_repeater.glb differ diff --git a/public/assets/models3d/mosfet_switch.glb b/public/assets/models3d/mosfet_switch.glb new file mode 100644 index 0000000..95af95a Binary files /dev/null and b/public/assets/models3d/mosfet_switch.glb differ diff --git a/public/sitemap.xml b/public/sitemap.xml index 46168e3..ccc6f4c 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1,20 +1,26 @@ - https://electron-rare.github.io/ - 2026-03-02 + https://www.lelectronrare.fr/ + 2026-04-01 weekly 1.0 - https://electron-rare.github.io/formation/ - 2026-03-14 + https://www.lelectronrare.fr/formation/ + 2026-04-01 monthly 0.9 - https://electron-rare.github.io/mentions-legales/ - 2026-03-14 + https://www.lelectronrare.fr/preview/ + 2026-04-01 + weekly + 0.8 + + + https://www.lelectronrare.fr/mentions-legales/ + 2026-04-01 yearly 0.3 diff --git a/src/components/BmuViewer.tsx b/src/components/BmuViewer.tsx new file mode 100644 index 0000000..45d0e89 --- /dev/null +++ b/src/components/BmuViewer.tsx @@ -0,0 +1,586 @@ +import { useRef, Suspense, useMemo, useEffect, useState } from 'react'; +import { Canvas, useFrame, useThree } from '@react-three/fiber'; +import { useGLTF, Environment, ContactShadows, Html } from '@react-three/drei'; +import { EffectComposer, Bloom, Vignette } from '@react-three/postprocessing'; +import * as THREE from 'three'; + +const MODELS_BASE = '/assets/models3d'; +const S = 0.1; + +function fc2three(fx: number, fy: number, fz: number): [number, number, number] { + return [fx * S, fz * S, -fy * S]; +} + +// explodeDelay: 0-1 stagger (0=moves first, 1=moves last) +// explodeRotTarget: rotation when fully exploded (null = keep original) +const CARDS_DEF = [ + { file: `${MODELS_BASE}/bmu_v2.glb`, name: 'BMU v2', + position: fc2three(0, 0, 0), rotation: [-Math.PI / 2, 0, 0] as [number, number, number], + explodeDir: [0, 0.8, 0] as [number, number, number], explodeDist: 1.5, + explodeDelay: 0, explodeRotTarget: null as [number, number, number] | null }, + { file: `${MODELS_BASE}/i2c_repeater.glb`, name: 'I2C Repeater', + position: fc2three(-52, 33, 4), rotation: [-Math.PI / 2, 0, 0] as [number, number, number], + explodeDir: [-0.8, 0.2, -0.6] as [number, number, number], explodeDist: 2.0, + explodeDelay: 0.15, explodeRotTarget: null as [number, number, number] | null }, + { file: `${MODELS_BASE}/mosfet_switch.glb`, name: 'Mosfet #1', + position: fc2three(23.2, 20, -11.2), rotation: [Math.PI / 2, 0, Math.PI] as [number, number, number], + explodeDir: [0.5, -0.8, 0.4] as [number, number, number], explodeDist: 4.0, + explodeDelay: 0.25, + explodeRotTarget: [0, 0, Math.PI] as [number, number, number] }, + { file: `${MODELS_BASE}/mosfet_switch.glb`, name: 'Mosfet #2', + position: fc2three(-15.56, 20, -11.16), rotation: [Math.PI / 2, 0, Math.PI] as [number, number, number], + explodeDir: [-0.2, -0.8, 0.5] as [number, number, number], explodeDist: 4.0, + explodeDelay: 0.35, + explodeRotTarget: [0, 0, Math.PI] as [number, number, number] }, + { file: `${MODELS_BASE}/mosfet_switch.glb`, name: 'Mosfet #3', + position: fc2three(-54.72, 20, -11.12), rotation: [Math.PI / 2, 0, Math.PI] as [number, number, number], + explodeDir: [-0.6, -0.8, 0.3] as [number, number, number], explodeDist: 4.0, + explodeDelay: 0.45, + explodeRotTarget: [0, 0, Math.PI] as [number, number, number] }, + { file: `${MODELS_BASE}/mosfet_switch.glb`, name: 'Mosfet #4', + position: fc2three(62.12, 20, -11.16), rotation: [Math.PI / 2, 0, Math.PI] as [number, number, number], + explodeDir: [0.7, -0.8, 0.2] as [number, number, number], explodeDist: 4.0, + explodeDelay: 0.55, + explodeRotTarget: [0, 0, Math.PI] as [number, number, number] }, +]; + +CARDS_DEF.forEach((c) => useGLTF.preload(c.file)); + +/* ── Sections ── */ +// Camera positions are relative: offset from card center when focused +// 'focus' = which card index camera should look at (null = assembly center) +interface Section { + id: string; start: number; end: number; + camOffset: [number, number, number]; // camera position offset from target + camOffsetEnd?: [number, number, number]; + targetShift?: [number, number, number]; // shift lookAt from card center (start) + targetShiftEnd?: [number, number, number]; // shift lookAt (end) — for dolly movement + focus: number | null; // card index to focus on, null = center + focusEnd?: number | null; + explode: number; explodeEnd?: number; +} + +const SECTIONS: Section[] = [ + // 1. HERO — vue large, focus sur l'assemblage (card 0 = BMU) + { id: 'hero', start: 0.00, end: 0.10, + camOffset: [2.0, 1.5, 2.0], camOffsetEnd: [1.5, 1.2, 1.5], focus: 0, explode: 0 }, + // 1→2 TRANSITION — zoom progressif vers la zone I2C (haut-gauche) + { id: 'hero-to-exp', start: 0.10, end: 0.16, + camOffset: [1.5, 1.2, 1.5], camOffsetEnd: [0.6, 0.7, 0.7], + targetShift: [0, 0, 0], targetShiftEnd: [-0.7, 0.35, 0], + focus: 0, explode: 0 }, + // 2a. Électronique — vise la zone I2C sur la BMU + { id: 'exp-elec', start: 0.16, end: 0.20, + camOffset: [0.4, 0.6, 0.5], camOffsetEnd: [0.2, 0.5, 0.4], + targetShift: [-0.7, 0.35, 0], + focus: 0, explode: 0 }, + // 2b. BAS — tuile card-local [0.85, 0, 0.45] → Rx(-π/2) → [0.85, 0.45, 0] + { id: 'exp-auto', start: 0.20, end: 0.24, + camOffset: [-0.3, 0.35, -0.2], camOffsetEnd: [-0.15, 0.3, -0.15], + targetShift: [0.85, 0.45, 0], + focus: 0, explode: 0 }, + // 2c. HAUT — tuile card-local [-0.5, 0, -0.45] → Rx(-π/2) → [-0.5, -0.45, 0] + { id: 'exp-energie', start: 0.24, end: 0.28, + camOffset: [0.25, 0.35, 0.3], camOffsetEnd: [0.12, 0.3, 0.2], + targetShift: [-0.5, -0.45, 0], + focus: 0, explode: 0 }, + // 2d. BAS — tuile card-local [-0.85, 0, 0.45] → Rx(-π/2) → [-0.85, 0.45, 0] + { id: 'exp-dispositifs', start: 0.28, end: 0.31, + camOffset: [0.3, 0.35, -0.2], camOffsetEnd: [0.15, 0.3, -0.1], + targetShift: [-0.85, 0.45, 0], + focus: 0, explode: 0 }, + // 2e. HAUT — tuile card-local [0.3, 0, -0.45] → Rx(-π/2) → [0.3, -0.45, 0] + { id: 'exp-consulting', start: 0.31, end: 0.34, + camOffset: [-0.2, 0.35, 0.3], camOffsetEnd: [0.1, 0.3, 0.2], + targetShift: [0.3, -0.45, 0], + focus: 0, explode: 0 }, + // 3a. RECUL — rapide + { id: 'eclate-recul', start: 0.34, end: 0.36, + camOffset: [0.0, 1.0, 0.8], camOffsetEnd: [1.0, 3.0, 4.0], focus: 0, focusEnd: null, + explode: 0 }, + // 3b. SÉPARATION — rapide + { id: 'eclate-sep', start: 0.36, end: 0.39, + camOffset: [1.0, 3.0, 4.0], camOffsetEnd: [1.6, 3.6, 4.4], focus: null, + explode: 0, explodeEnd: 0.7 }, + // 3c. MISE À 90° — rapide + { id: 'eclate-plat', start: 0.39, end: 0.41, + camOffset: [1.6, 3.6, 4.4], camOffsetEnd: [2.0, 3.6, 5.0], focus: null, + explode: 0.7, explodeEnd: 1 }, + // 4. CAS — recul caméra, vue des 4 mosfets, tuiles + médias visibles + { id: 'cas', start: 0.41, end: 0.65, + camOffset: [0.5, -0.5, -3.0], camOffsetEnd: [-0.5, -0.3, -2.5], focus: null, explode: 1 }, + // 7a. MEDIA — vidéo 1, caméra de l'autre côté (Z négatif) + { id: 'media', start: 0.65, end: 0.69, + camOffset: [0.2, 0.0, -1.0], camOffsetEnd: [0.0, 0.0, -0.8], focus: 2, explode: 1 }, + // 7b. MEDIA — vidéo 2 + { id: 'media', start: 0.69, end: 0.73, + camOffset: [0.2, 0.0, -1.0], camOffsetEnd: [-0.1, 0.0, -0.8], focus: 3, explode: 1 }, + // 7c. MEDIA — vidéo 3 + { id: 'media', start: 0.73, end: 0.77, + camOffset: [-0.1, 0.0, -1.0], camOffsetEnd: [-0.3, 0.0, -0.8], focus: 4, explode: 1 }, + // 7d. MEDIA — carrousel photos + { id: 'media', start: 0.77, end: 0.81, + camOffset: [-0.2, 0.0, -1.0], camOffsetEnd: [0.1, 0.0, -0.8], focus: 5, explode: 1 }, + // 8. FORMATS + { id: 'formats', start: 0.81, end: 0.91, + camOffset: [2.4, 3.0, 4.0], camOffsetEnd: [1.6, 2.4, 3.0], focus: null, explode: 1, explodeEnd: 0 }, + // 9. CONTACT — zoom serré sur la tuile contact au centre de la BMU + { id: 'contact', start: 0.91, end: 1.00, + camOffset: [0.15, 0.3, 0.2], camOffsetEnd: [0.0, 0.25, 0.15], focus: 0, explode: 0 }, +]; + +/* ── Tuiles plaquées sur les faces PCB ── */ +interface PcbLabel { + section: string; cardIndex: number; + localOffset: [number, number, number]; + title: string; sub?: string; color?: string; + href?: string; +} + +const PCB_LABELS: PcbLabel[] = [ + // Expertise — alternance haut/bas du PCB BMU + // 1. HAUT — côté I2C (card 1) + { section: 'exp-elec', cardIndex: 1, localOffset: [0.0, 0.005, 0.0], + title: 'Électronique spécifique', sub: 'Cartes · Interfaces · Capteurs · Alimentation' }, + // 2. BAS — bas-droit BMU + { section: 'exp-auto', cardIndex: 0, localOffset: [0.85, 0.005, 0.45], + title: 'Instrumentation & Automatisme', sub: 'Bancs · Automates · Variateurs · Protocoles' }, + // 3. HAUT — haut BMU, à droite de I2C + { section: 'exp-energie', cardIndex: 0, localOffset: [-0.5, 0.005, -0.45], + title: 'Énergie & Stockage', sub: 'Batterie · Conversion · Supervision · Télémétrie' }, + // 4. BAS — bas-gauche BMU + { section: 'exp-dispositifs', cardIndex: 0, localOffset: [-0.85, 0.005, 0.45], + title: 'Dispositifs pour le réel', sub: 'Audio · LED · Scène · Robustesse terrain' }, + // 5. HAUT — centre-haut BMU + { section: 'exp-consulting', cardIndex: 0, localOffset: [0.3, 0.005, -0.45], + title: 'Consulting & Formation', sub: 'Audit · Transfert · Pédagogie · Projet' }, + // 6-9. CAS — les 4 tuiles visibles en même temps, face bottom des mosfets + { section: 'cas', cardIndex: 2, localOffset: [0.0, 0.0, -0.08], + title: 'Industries créatives', sub: 'Audio embarqué · Batterie LiFePO4 · KXKM' }, + { section: 'cas', cardIndex: 3, localOffset: [0.0, 0.0, -0.08], + title: 'Industrie', sub: 'Production V3.2 · KXKM ESP32 → STM32F030' }, + { section: 'cas', cardIndex: 4, localOffset: [0.0, 0.0, -0.08], + title: 'Formation', sub: 'PCB · KiCad · µC' }, + { section: 'cas', cardIndex: 5, localOffset: [0.0, 0.0, -0.08], + title: 'Service', sub: 'Mise en service · Maintenance · Support terrain' }, + // 9. Conception — Mosfet #1 (card 2) + { section: 'formats', cardIndex: 2, localOffset: [0.0, 0.005, 0.0], + title: 'Conception', sub: 'Schéma · PCB · Firmware · Validation', + href: '/conception/' }, + // 10. Formation — Mosfet #2 (card 3) + { section: 'formats', cardIndex: 3, localOffset: [0.0, 0.005, 0.0], + title: 'Formation', sub: 'PCB · KiCad · µC · Soudure', + href: '/formation/' }, + // 11. Consulting — Mosfet #3 (card 4) + { section: 'formats', cardIndex: 4, localOffset: [0.0, 0.005, 0.0], + title: 'Consulting', sub: 'Audit · Diagnostic · Transfert', + href: '/consulting/' }, + // 12. Contact — sur BMU (card 0) + { section: 'contact', cardIndex: 0, localOffset: [0.0, 0.005, 0.0], + title: 'Parlons de votre projet', sub: 'Premier échange sans engagement', + href: '/contact/' }, +]; + +/* ── Tuile plaquée sur la face du PCB ── */ +function PcbTile({ offset, title, sub, visible, section, href }: { + offset: [number, number, number]; + title: string; sub?: string; color?: string; visible: boolean; section?: string; href?: string; +}) { + const isContact = section === 'contact'; + const isClickable = !!href || isContact; + const linkHref = isContact ? undefined : href; + + const isBig = isContact; + + const content = ( +
+
{title}
+ {sub &&
{sub}
} +
+ ); + + return ( + + {isContact ? ( +
window.dispatchEvent(new CustomEvent('open-contact-drawer', { detail: { source: 'pcb_contact' } }))} + style={{ textDecoration: 'none', display: 'block' }}>{content}
+ ) : linkHref ? ( + {content} + ) : content} + + ); +} + +/* ── Card ── */ +function CardModel({ file }: { file: string }) { + const { scene } = useGLTF(file); + const cloned = useMemo(() => { + const c = scene.clone(true); + c.traverse((child) => { if (child instanceof THREE.Mesh && child.material) child.material.envMapIntensity = 1.5; }); + return c; + }, [scene]); + return ; +} + +/* ── Video plane on PCB bottom (real 3D mesh with VideoTexture) ── */ +function PcbVideoMesh({ offset, src, visible }: { + offset: [number, number, number]; src: string; visible: boolean; +}) { + const meshRef = useRef(null); + const videoRef = useRef(null); + const texRef = useRef(null); + + // Mosfet card local space: ~0.38 x 0.86 (XZ), Y = thickness + // Plane size: cover most of the bottom face + const planeW = 1.9, planeH = 4.275; + + useEffect(() => { + const video = document.createElement('video'); + video.src = src; + video.crossOrigin = 'anonymous'; + video.loop = true; + video.muted = true; + video.playsInline = true; + video.preload = 'metadata'; + videoRef.current = video; + + const tex = new THREE.VideoTexture(video); + tex.minFilter = THREE.LinearFilter; + tex.magFilter = THREE.LinearFilter; + tex.colorSpace = THREE.SRGBColorSpace; + texRef.current = tex; + + return () => { video.pause(); video.src = ''; tex.dispose(); }; + }, [src]); + + useEffect(() => { + if (!videoRef.current) return; + if (visible) { videoRef.current.play().catch(() => {}); } + else { videoRef.current.pause(); } + }, [visible]); + + useFrame(() => { + if (meshRef.current && texRef.current) { + (meshRef.current.material as THREE.MeshStandardMaterial).opacity = visible ? 1 : 0; + texRef.current.needsUpdate = visible; + } + }); + + return ( + + + + + ); +} + +/* ── Photo carousel plane on PCB bottom (real 3D mesh with swapping texture) ── */ +const CAROUSEL_PHOTOS = [ + '/assets/photos/pcb-teensy-led-kxkm.webp', + '/assets/photos/pcb-verso-soudures.webp', + '/assets/photos/soudure-pcb-composants.webp', + '/assets/photos/bench-bms-batteries-xt60.webp', + '/assets/photos/automate-siemens-s7.webp', + '/assets/photos/armoire-automate-schneider.webp', + '/assets/photos/oscilloscope-philips-vintage.webp', + '/assets/photos/workspace-dev-tektronix.webp', + '/assets/photos/platine-automate-cablage.webp', + '/assets/photos/bench-ampli-oscilloscope.webp', + '/assets/photos/pcb-produit-embarque.webp', + '/assets/photos/portrait-bench.webp', +]; + +function PcbCarouselMesh({ offset, visible }: { + offset: [number, number, number]; visible: boolean; +}) { + const meshRef = useRef(null); + const textures = useRef([]); + const [idx, setIdx] = useState(0); + const autoTimer = useRef(0); + const planeW = 1.9, planeH = 4.275; + + useEffect(() => { + const loader = new THREE.TextureLoader(); + textures.current = CAROUSEL_PHOTOS.map(url => { + const t = loader.load(url); + t.colorSpace = THREE.SRGBColorSpace; + t.minFilter = THREE.LinearFilter; + return t; + }); + return () => textures.current.forEach(t => t.dispose()); + }, []); + + // Auto-advance every 3s when visible + useFrame((_, delta) => { + if (!meshRef.current) return; + const mat = meshRef.current.material as THREE.MeshStandardMaterial; + mat.opacity = visible ? 1 : 0; + if (textures.current[idx]) { + mat.map = textures.current[idx]; + mat.emissiveMap = textures.current[idx]; + mat.needsUpdate = true; + } + if (visible) { + autoTimer.current += delta; + if (autoTimer.current > 3) { + autoTimer.current = 0; + setIdx(i => (i + 1) % CAROUSEL_PHOTOS.length); + } + } + }); + + return ( + + + + + ); +} + +/* ── Media definitions for mosfet bottoms ── */ +const MOSFET_VIDEOS = [ + { cardIndex: 2, src: '/assets/videos/video-test-prototype.mp4' }, + { cardIndex: 3, src: '/assets/videos/video-bench-electronique.mp4' }, + { cardIndex: 4, src: '/assets/videos/video-atelier-terrain.mp4' }, +]; +const MOSFET_CAROUSEL_CARD = 5; // mosfet #4 + +/* ── Assembly ── */ +function Assembly({ scrollRef, onSection }: { + scrollRef: React.MutableRefObject; onSection: (id: string) => void; +}) { + const assemblyGroup = useRef(null); + const cardsRef = useRef([]); + const fitted = useRef(false); + const { camera } = useThree(); + const smoothScroll = useRef(0); + const origPositions = useRef([]); + const assemblyScale = useRef(1); + const [currentSection, setCurrentSection] = useState('hero'); + + useFrame((_, delta) => { + if (!assemblyGroup.current) return; + + // Auto-fit on first valid frame + if (!fitted.current && cardsRef.current.filter(Boolean).length === CARDS_DEF.length) { + fitted.current = true; + const box = new THREE.Box3().setFromObject(assemblyGroup.current); + const center = box.getCenter(new THREE.Vector3()); + const maxDim = Math.max(...box.getSize(new THREE.Vector3()).toArray()); + const scale = 2.2 / maxDim; + assemblyScale.current = scale; + assemblyGroup.current.scale.setScalar(scale); + assemblyGroup.current.position.copy(center.multiplyScalar(-scale)); + assemblyGroup.current.updateMatrixWorld(true); + origPositions.current = cardsRef.current.map(g => g.position.clone()); + } + if (!fitted.current) return; + + // Smooth scroll + smoothScroll.current += (scrollRef.current - smoothScroll.current) * Math.min(1, delta * 2); + const t = Math.max(0, Math.min(1, smoothScroll.current)); + + // Find current section + let section = SECTIONS[SECTIONS.length - 1]; + for (const s of SECTIONS) { if (t >= s.start && t <= s.end) { section = s; break; } } + + const range = section.end - section.start || 1; + let f = Math.max(0, Math.min(1, (t - section.start) / range)); + f = f * f * (3 - 2 * f); // smoothstep + + // Explode + const explodeEnd = section.explodeEnd ?? section.explode; + const explode = section.explode + (explodeEnd - section.explode) * f; + + // Apply explode to cards with stagger delay and rotation + for (let i = 0; i < cardsRef.current.length; i++) { + if (!cardsRef.current[i] || !origPositions.current[i]) continue; + const g = cardsRef.current[i]; + const orig = origPositions.current[i]; + const def = CARDS_DEF[i]; + + // Staggered explode: each card starts moving at its delay point + const delay = def.explodeDelay; + const cardExplode = Math.max(0, Math.min(1, (explode - delay) / (1 - delay))); + + // Position + g.position.set( + orig.x + def.explodeDir[0] * def.explodeDist * cardExplode, + orig.y + def.explodeDir[1] * def.explodeDist * cardExplode, + orig.z + def.explodeDir[2] * def.explodeDist * cardExplode, + ); + + // Rotation: interpolate toward flat when explodeRotTarget is set + if (def.explodeRotTarget) { + const origRot = def.rotation; + // Mosfets flip to flat during the last phase (cardExplode > 0.5) + const rotT = Math.max(0, Math.min(1, (cardExplode - 0.5) * 2)); + const smoothRot = rotT * rotT * (3 - 2 * rotT); + g.rotation.set( + origRot[0] + (def.explodeRotTarget[0] - origRot[0]) * smoothRot, + origRot[1] + (def.explodeRotTarget[1] - origRot[1]) * smoothRot, + origRot[2] + (def.explodeRotTarget[2] - origRot[2]) * smoothRot, + ); + } + } + + // Compute camera: focus on card or center + const focusStart = section.focus; + const focusEnd = section.focusEnd !== undefined ? section.focusEnd : focusStart; + + // Get world position of focused card + function getCardWorldPos(cardIdx: number | null): THREE.Vector3 { + if (cardIdx !== null && cardsRef.current[cardIdx]) { + const wp = new THREE.Vector3(); + cardsRef.current[cardIdx].getWorldPosition(wp); + return wp; + } + return new THREE.Vector3(0, 0, 0); // assembly center + } + + const targetStart = getCardWorldPos(focusStart); + const targetEnd = getCardWorldPos(focusEnd); + const target = new THREE.Vector3().lerpVectors(targetStart, targetEnd, f); + + // Apply targetShift — interpolate between start and end for dolly effect + if (section.targetShift) { + const shiftStart = new THREE.Vector3(...section.targetShift); + const shiftEnd = new THREE.Vector3(...(section.targetShiftEnd || section.targetShift)); + const shift = new THREE.Vector3().lerpVectors(shiftStart, shiftEnd, f); + if (assemblyGroup.current) { + shift.applyMatrix4(assemblyGroup.current.matrixWorld).sub( + new THREE.Vector3().setFromMatrixPosition(assemblyGroup.current.matrixWorld) + ); + } + target.add(shift); + } + + const offStart = new THREE.Vector3(...section.camOffset); + const offEnd = new THREE.Vector3(...(section.camOffsetEnd || section.camOffset)); + const offset = new THREE.Vector3().lerpVectors(offStart, offEnd, f); + + const camPos = target.clone().add(offset); + + camera.position.lerp(camPos, Math.min(1, delta * 2)); + camera.lookAt(target); + + // Update section + if (section.id !== currentSection) { + setCurrentSection(section.id); + onSection(section.id); + } + }); + + return ( + + {CARDS_DEF.map((card, i) => ( + { if (el) cardsRef.current[i] = el; }} + position={card.position} rotation={card.rotation}> + + {/* Tuiles 3D plaquées sur les faces */} + {PCB_LABELS.filter(l => l.cardIndex === i).map((label, li) => ( + + ))} + {/* Videos on mosfet bottoms — real 3D mesh planes */} + {MOSFET_VIDEOS.filter(v => v.cardIndex === i).map((v, vi) => ( + + ))} + {/* Carousel on mosfet #4 bottom — real 3D mesh plane */} + {i === MOSFET_CAROUSEL_CARD && ( + + )} + + ))} + + ); +} + +/* ── Pulsed Bloom — machine breathing ── */ +function PulsedBloom() { + const bloomRef = useRef(null); + useFrame(({ clock }) => { + if (!bloomRef.current) return; + const t = clock.getElapsedTime(); + bloomRef.current.intensity = 0.25 + Math.sin(t * 0.8) * 0.1; + }); + return ; +} + +/* ── Export ── */ +export function BmuViewer() { + const scrollRef = useRef(0); + const [, setSectionId] = useState('hero'); + + useEffect(() => { + function onScroll() { + const max = document.documentElement.scrollHeight - window.innerHeight; + scrollRef.current = max > 0 ? window.scrollY / max : 0; + } + window.addEventListener('scroll', onScroll, { passive: true }); + onScroll(); + return () => window.removeEventListener('scroll', onScroll); + }, []); + + return ( +
+ + + + + + + + + + + +
+ ); +} diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index bdb3457..16b9231 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -321,7 +321,7 @@ const structuredDataJsonLd = JSON.stringify({ var uiAllowed = { a: 1, b: 1 }; var uiSprint = uiRaw.toLowerCase().trim() in uiAllowed ? uiRaw.toLowerCase().trim() : 'b'; - root.setAttribute('data-da-variant', 'v12'); + root.setAttribute('data-da-variant', 'classic'); root.setAttribute('data-ui-sprint', uiSprint); })(); @@ -344,7 +344,7 @@ const structuredDataJsonLd = JSON.stringify({ + +
+

Conception électronique

+

L'Électron Rare conçoit, met au point et fiabilise des systèmes électroniques spécifiques, des automatismes et des ensembles liés à l'énergie, au stockage et à l'optimisation terrain.

+ +

Formats de mission

+ +
+

28%

+

Diagnostic

+

1 à 2 semaines

+

Analyser votre besoin, identifier les sous-systèmes critiques et livrer une carte de faisabilité.

+

Livrables : analyse de faisabilité · risques identifiés · plan d'action

+
+ +
+

55%

+

Prototype

+

4 à 6 semaines

+

Concevoir et tester un prototype fonctionnel : schéma, PCB, firmware, mesures et validation.

+

Livrables : prototype testé · mesures documentées · go/no-go argumenté

+
+ +
+

100%

+

Mission complète

+

2 à 6 mois

+

Développement complet jusqu'au produit industrialisable : itérations, tests terrain, documentation et transfert.

+

Livrables : livrables par lot · dossier de production · transfert clé en main

+
+ +

Cas concrets

+ +
+

Battery Parallelator — KXKM

+

Système de gestion parallèle de 4 batteries LiFePO4 pour le spectacle vivant. Conception complète KiCad : carte BMU, 4 cartes Mosfet Switch, carte I2C Repeater. Firmware ESP32, supervision I2C, protection thermique.

+

→ 8h autonomie · OTA · 0 panne sur 2 saisons

+
+ +
+

HypnoLED — Contrôle LED embarqué

+

Carte de contrôle LED haute densité pour installations scéniques. Architecture multi-cartes, protocole DMX/Art-Net, topologie bus, alimentation distribuée. PCB 4 couches KiCad.

+

→ 512 canaux DMX · Latence <1ms · Déployé en tournée

+
+ +
+

Télémétrie ESP32 — Monitoring énergie

+

Système de supervision terrain avec ESP32 : mesure courant/tension, transmission MQTT, dashboard temps réel. Capteurs INA226, connectivité WiFi, boîtier IP65.

+

→ Autonomie 6 mois sur batterie · Dashboard Grafana

+
+ +

Domaines d'intervention

+

Électronique spécifique · Instrumentation et automatisme · Énergie et stockage · Dispositifs pour le réel · Montages multi-techniques avec partenaires.

+ + +
+ + + + diff --git a/src/pages/consulting.astro b/src/pages/consulting.astro new file mode 100644 index 0000000..fc46044 --- /dev/null +++ b/src/pages/consulting.astro @@ -0,0 +1,118 @@ +--- +--- + + + + + + Consulting technique | L'électron rare + + + + + +
+ + + +
+ + + +
+

Consulting technique

+

Je suis Clément Saillant, fondateur de L'électron rare. Mon rôle n'est pas de vendre "un peu d'embarqué" en plus, mais de rendre possible le bon système pour le besoin réel : électronique, automatisme, énergie, stockage, optimisation, diagnostic, prototype, reprise d'existant ou fiabilisation.

+ +

Familles d'intervention

+
+ Électronique spécifique + Instrumentation & Automatisme + Dispositifs pour le réel + Énergie & Stockage + Multi-techniques & Partenaires +
+ +
+

Audit & Diagnostic

+

Analyse de l'existant, identification des points critiques, recommandations chiffrées. Diagnostic terrain sur site industriel, automate, variateur, instrumentation.

+
+ +
+

Accompagnement technique

+

Pilotage de sous-traitance, revue de conception, validation de prototypes, suivi de production. Mobilisation de partenaires spécialisés quand le projet devient multi-technique.

+
+ +
+

Transfert & Documentation

+

Rédaction de dossiers techniques, procédures de maintenance, documentation de production. Transfert de compétences vers l'équipe interne.

+
+ +

Cas concret

+
+

Mise en production et correction V3.2 KXKM

+

Mise en production du hardware ESP32, correction des bugs V3.2, passage STM32F030. Tests terrain, validation embarquée, documentation technique.

+

→ Production V3.2 livrée · Migration STM32F030 validée

+
+ +

À propos

+
+

Clément Saillant — 10 ans d'expérience

+

Cyborg en systèmes électroniques, j'interviens depuis 10 ans sur des projets allant du spectacle vivant à l'industrie automatisée, en passant par l'énergie et la formation. Contributeur open-source (KiCad, KXKM), ambassadeur IA appliquée à l'électronique.

+

Secteurs : industries créatives, ferroviaire, automatisme industriel, éducation, énergie/stockage.

+
+ + +
+ + + + diff --git a/src/pages/contact.astro b/src/pages/contact.astro new file mode 100644 index 0000000..66f54d6 --- /dev/null +++ b/src/pages/contact.astro @@ -0,0 +1,170 @@ +--- +--- + + + + + + Contact | L'électron rare + + + + + +
+ + + +
+ + + +
+

Parlons de votre projet

+

Décrivez votre besoin en quelques lignes — premier échange sans engagement.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + +

+
+ +
+

Merci !

+

Je reviens vers vous sous 48h.

+
+ +
+

Ou directement : contact@lelectronrare.fr

+

LinkedIn · GitHub

+
+
+ + + + + + diff --git a/src/pages/formation.astro b/src/pages/formation.astro index ea67df9..b5c5150 100644 --- a/src/pages/formation.astro +++ b/src/pages/formation.astro @@ -1,370 +1,171 @@ --- -import BaseLayout from '@/layouts/BaseLayout.astro'; -import '@/styles/global.css'; -import '@/styles/home-workbench.css'; -import SiteHeader from '@/components/SiteHeader.astro'; -import { withSiteBase } from '@/lib/site'; -import { TRACK_EVENTS, trackAttrs } from '@/lib/tracking'; - -const title = "Formation électronique appliquée | L'électron rare"; -const description = "Formations sur mesure : électronique, automatisme, énergie, stockage, PCB, instrumentation, IoT et optimisation. 1 jour, 3 jours ou programme dédié. Écoles, entreprises."; -const homeHref = withSiteBase('/'); -const homeAboutHref = withSiteBase('/#a-propos'); -const homeMissionsHref = withSiteBase('/#graphic-sprints-title'); -const formationHref = withSiteBase('/formation/'); -const contactHref = withSiteBase('/#contact'); -const mentionsHref = withSiteBase('/mentions-legales/'); -const headerItems = [ - { href: homeAboutHref, label: 'Approche' }, - { href: homeMissionsHref, label: 'Missions' }, - { href: formationHref, label: 'Formation', current: true }, - { href: contactHref, label: 'Contact', desktopOnly: true } -]; --- - - + + + + + Formation électronique appliquée | L'électron rare + + + + + +
+ + + +
+ + -
- -
-
- -

Formation électronique appliquée

- +
+

Formation électronique appliquée

+

Des formations pratiques, ancrées dans le terrain. Chaque programme est construit sur mesure à partir de votre contexte réel : vos projets, vos équipes, vos contraintes.

+
+ 🏅 +
+

Formations finançables OPCO / Afdas

+

Intervention possible via un organisme partenaire certifié Qualiopi.

+
-

- Des formations pratiques, ancrées dans le terrain, pour monter en compétences sur l'électronique, l'automatisme, l'énergie, le stockage, la conception PCB, l'instrumentation, l'IoT et l'optimisation d'usage. -

-

- Chaque formation est construite sur mesure à partir de votre contexte réel : vos projets, vos équipes, vos contraintes. Pas de slides génériques — du bench, du schéma et du livrable. -

-
+
+ Entreprises industrielles + Écoles et universités + Centres de formation + Studios créatifs +
-
-
- -

Publics cibles

- +

Formats

+
+
+

1 jour

+

Initiation

+

Fondamentaux, démos live, TP sur matériel réel, support PDF

- -
-
    -
  • - Entreprises industrielles — équipes R&D, bureaux d'études, techniciens de maintenance qui veulent concevoir, diagnostiquer ou fiabiliser des systèmes électroniques dans leurs produits ou process. -
  • -
  • - Écoles et universités — BTS, IUT, grandes écoles : TP encadrés, projets tutorés, modules spécialisés en systèmes électroniques, PCB et prototypage. -
  • -
  • - Centres de formation — organismes souhaitant intégrer des modules électronique, instrumentation, énergie, stockage, IoT ou automatisme à leur catalogue. -
  • -
  • - Studios créatifs et compagnies — équipes techniques du spectacle vivant, de l'événementiel ou des arts numériques qui veulent gagner en autonomie. -
  • -
+
+

3 jours

+

Approfondissement

+

Projet fil rouge, prototype fonctionnel, documentation technique

-
- -
-
-
-

Formats

-

Trois formats, un principe : le concret d'abord

-

- Chaque format inclut un support de cours, des exercices pratiques sur matériel réel et un livrable que les participants repartent avec. -

-
- -
-
-
-

Initiation

-

1 jour (7h)

-
-

Découverte

-

- Comprendre les fondamentaux d'un domaine technique. Idéal pour une première prise en main ou une mise à niveau rapide. -

-
- -
-
    -
  • Théorie appliquée + démos live
  • -
  • TP sur matériel réel
  • -
  • Support de cours PDF
  • -
-
- -
-
-

Approfondissement

-

3 jours (21h)

-
-

Pratique

-

- Maîtriser un sujet de bout en bout avec un projet fil rouge. Les participants repartent avec un prototype fonctionnel. -

-
- -
-
    -
  • Projet fil rouge réalisé
  • -
  • Prototype fonctionnel
  • -
  • Documentation technique complète
  • -
-
- -
-
-

Sur mesure

-

Durée adaptée

-
-

Programme dédié

-

- Formation construite intégralement autour de votre projet, vos équipements et vos objectifs. Accompagnement post-formation inclus. -

-
- -
-
    -
  • Programme sur mesure
  • -
  • Matériel adapté à votre contexte
  • -
  • Suivi post-formation
  • -
-
-
+
+

Sur mesure

+

Programme dédié

+

Autour de votre projet, vos équipements, suivi post-formation

-
+ -
-
- -

Sujets de formation

- -
+

Sujets

+
+

Systèmes électroniques et embarqués

Architecture matérielle, µC (STM32, ESP32, Teensy), alimentation, debug bench, IPC-A-610, CEM.

+

Conception PCB

KiCad, routage, stackup, DRC/ERC, Gerber, BOM, pick & place.

+

IoT et protocoles

Capteurs, MQTT, WiFi, BLE, LoRa, TinyML, du capteur au dashboard.

+

Énergie et stockage

Batterie, BMS, télémétrie, supervision, optimisation d'exploitation.

+

Automatisme industriel

Siemens S7, Schneider, câblage armoires, variateurs, IEC 60204-1.

+

Contrôle LED et scénographie

DMX, Art-Net, DANTE, pilotage LED, intégration spectacle.

+

Firmware et programmation

C/C++ embarqué, Python, debug série, interruptions, RTOS.

+

Workflows agentiques et IA

Agents IA, génération de code, revue automatisée, documentation assistée.

+
-
-
-

Systèmes électroniques et embarqués

-

- Architecture matérielle, microcontrôleurs (STM32, ESP32, Teensy), alimentation, lecture de datasheet, debug sur bench. Conformité IPC-A-610 et CEM. -

-
+

Catalogue

-
-

Conception PCB

-

- De la schématique au routage avec KiCad. Règles de design, stackup, vérification DRC/ERC, préparation des fichiers de fabrication (Gerber, BOM, pick & place). -

-
+

Initiations — Tous publics

+
+

Mes Premiers Circuits

Breadboard, soudure, badge lumineux. Zéro prérequis.

1 jour · 420€ · 6-8 pers.

+

Arduino Pour Tous

Capteurs, LED, moteurs. Kit Arduino à emporter.

1 jour · 450€ · 6-8 pers.

+

Lumière et Son

LED NeoPixel, DMX512, audio interactif. Kit inclus.

1 jour · 480€ · 6-8 pers.

+

KiCad Découverte

Du breadboard au PCB. Commandez votre carte chez JLCPCB.

1 jour · 420€ · 6-8 pers.

+
-
-

IoT et protocoles

-

- Capteurs, télémétrie, protocoles terrain (MQTT, HTTP, VE.Direct), connectivité (WiFi, BLE, LoRa), TinyML et IA embarquée. Du capteur au dashboard. -

-
+

Créatif & Artistes

+
+

Créer son Contrôleur MIDI

Teensy, potentiomètres, capteurs → instrument MIDI USB.

1 jour · 450€ · Variante 3h : 220€

+

Programmation Embarquée pour Artistes

ESP32, capteurs, DMX, Art-Net, OSC → installations interactives.

1 jour · 480€ · Variante 3h : 240€

+

IA pour la Création Artistique

Stable Diffusion, ComfyUI, Ollama. Image, son, vidéo, performance live.

1 jour · 500€ · Variante 3h : 250€

+
-
-

Énergie, stockage et optimisation

-

- Batterie, BMS, télémétrie, supervision, pilotage de charges, logique d'optimisation et amélioration d'exploitation. De la mesure au système mieux réglé. -

-
+

Pro — Avancé

+
+

KiCad — Conception PCB

KiCad 9/10, routage avancé, impédance, DFM, fabrication JLCPCB.

2 jours · 1 550€ · 6 pers. max

+

Dispositifs Culturels

LED, DMX512, Art-Net, autonomie batteries, intégration terrain spectacle.

1 jour · 775€ · Finançable Afdas/OPCO

+
-
-

Automatisme industriel

-

- Automates Siemens (S7) et Schneider, câblage d'armoires, variateurs, mise en service. Conformité IEC 60204-1, IEC 61439, NFC 13-200. Lecture de schémas électriques industriels. -

-
+

Enfants & Jeunes

+
+

Premiers Circuits (8-10 ans)

micro:bit, programmation par blocs, LED, capteurs.

3 demi-journées · 50€/demi-journée

+

Maker Junior (10-13 ans)

Arduino, première soudure, capteurs, projet personnel.

5 demi-journées · 50€/demi-journée

+

Electro Lab (13-16 ans)

ESP32, KiCad, conception et fabrication de vrai PCB.

8 sessions · 299€ hors frais de fabrication

+

Atelier Parent-Enfant

3h de découverte en duo. Montage, soudure, LED.

3h · 59€/binôme · À partir de 8 ans

+
-
-

Contrôle LED et scénographie

-

- Protocoles DMX, Art-Net, DANTE. Pilotage LED, topologies multi-cartes, intégration dans des dispositifs scéniques et événementiels. -

-
- -
-

Firmware et programmation

-

- C/C++ embarqué, Python pour l'instrumentation, debug série, gestion mémoire, interruptions, RTOS. Du code qui tourne sur le terrain. -

-
- -
-

Workflows agentiques et IA appliquée

-

- Intégrer l'IA et les agents autonomes dans vos flux de travail : génération de code embarqué, revue automatisée, documentation technique, analyse de datasheets et tests assistés par IA. Productivité et qualité augmentées. -

-
-
-
- -
-
- -

Catalogue des formations

- -
- -

14 modules prêts à l'emploi. Chaque formation est disponible en inter-entreprises (Lyon) ou intra/sur site (toute la France).

- -

Initiations — Tous publics

-
-
-

Mes Premiers Circuits

-

Breadboard, soudure, badge lumineux. Zéro prérequis.

-

1 jour · 420€ · 6-8 pers.

-
-
-

Arduino Pour Tous

-

Capteurs, LED, moteurs. Kit Arduino à emporter.

-

1 jour · 450€ · 6-8 pers.

-
-
-

Lumière et Son

-

LED NeoPixel, DMX512, audio interactif. Kit LED+son inclus.

-

1 jour · 480€ · 6-8 pers.

-
-
-

KiCad Découverte

-

Du breadboard au PCB. Commandez votre carte chez JLCPCB.

-

1 jour · 420€ · 6-8 pers.

-
-
- -

Créatif & Artistes

-
-
-

Créer son Contrôleur MIDI

-

Teensy, potentiomètres, capteurs → instrument MIDI USB personnalisé.

-

1 jour · 450€ · Variante 3h : 220€

-
-
-

Programmation Embarquée pour Artistes

-

ESP32, capteurs, DMX, Art-Net, OSC → installations interactives.

-

1 jour · 480€ · Variante 3h : 240€

-
-
-

IA pour la Création Artistique

-

Stable Diffusion, ComfyUI, Ollama. Image, son, vidéo, performance live.

-

1 jour · 500€ · Variante 3h : 250€

-
-
- -

Pro — Avancé

-
-
-

KiCad — Conception PCB

-

KiCad 9/10, routage avancé, impedance, DFM, fabrication JLCPCB.

-

2 jours · 1 550€ · 6 pers. max

-
-
-

Dispositifs Culturels

-

LED, DMX512, Art-Net, autonomie batteries, intégration terrain spectacle.

-

1 jour · 775€ · Finançable Afdas/OPCO

-
-
- -

Enfants & Jeunes

-
-
-

Premiers Circuits (8-10 ans)

-

micro:bit, programmation par blocs, LED, capteurs.

-

3 demi-journées · 89€

-
-
-

Maker Junior (10-13 ans)

-

Arduino, première soudure, capteurs, projet personnel.

-

5 demi-journées · 149€

-
-
-

Electro Lab (13-16 ans)

-

ESP32, KiCad, conception et fabrication de vrai PCB.

-

8 sessions · 299€

-
-
-

Atelier Parent-Enfant

-

3h de découverte en duo. Montage, soudure, LED.

-

3h · 59€/binôme · À partir de 8 ans

-
-
-
- -
-
- -

Demander un programme

- -
- -

- Décrivez votre besoin en quelques lignes. Je vous propose un programme adapté sous 48h. -

-

- Précisez le public, le niveau, le sujet visé et le format souhaité. Même une idée vague suffit pour démarrer. -

- - - -
    -
  • Intervention en présentiel (France entière) ou en distanciel
  • -
  • Matériel de TP fourni ou adapté à votre parc existant
  • -
  • Convention de formation et programme détaillé sur demande
  • -
  • Intervention possible via un organisme certifié Qualiopi pour le financement OPCO
  • -
  • Assuré RC Pro · NDA sur demande
  • -
-
+ +

+ Intervention en présentiel (France entière) ou en distanciel · Matériel de TP fourni · Convention de formation sur demande · Financement OPCO possible via organisme Qualiopi · Assuré RC Pro · NDA sur demande +

- -
+ + + diff --git a/src/pages/index-classic.astro b/src/pages/index-classic.astro new file mode 100644 index 0000000..8c17f59 --- /dev/null +++ b/src/pages/index-classic.astro @@ -0,0 +1,508 @@ +--- +import BaseLayout from '@/layouts/BaseLayout.astro'; +import '@/styles/global.css'; +import '@/styles/home-workbench.css'; +import SiteHeader from '@/components/SiteHeader.astro'; +import { Hero } from '@/components/sections/Hero'; +import { About } from '@/components/sections/About'; +import { CaseStudies } from '@/components/sections/CaseStudies'; +import { GraphicSprints } from '@/components/sections/GraphicSprints'; +import Contact from '@/components/sections/Contact.astro'; +import Faq from '@/components/sections/Faq.astro'; +import { withSiteBase } from '@/lib/site'; +import { TRACK_EVENTS, trackAttrs } from '@/lib/tracking'; + +const LAUNCH_DATE = new Date('2026-05-01T00:00:00+02:00'); +const isLaunched = Date.now() >= LAUNCH_DATE.getTime(); + +const formationHref = withSiteBase('/formation/'); +const mentionsHref = withSiteBase('/mentions-legales/'); +const headerItems = [ + { href: '#a-propos', label: 'Approche' }, + { href: '#graphic-sprints-title', label: 'Missions' }, + { href: formationHref, label: 'Formation' }, + { href: '#faq', label: 'FAQ' }, + { href: '#contact', label: 'Contact', desktopOnly: true } +]; + +const title = isLaunched + ? "Systèmes électroniques spécifiques | L'électron rare" + : "L'Electron Rare — Bientôt"; +const description = isLaunched + ? "Conception, mise au point et fiabilisation de systèmes électroniques spécifiques." + : "L'Electron Rare arrive. Systèmes électroniques spécifiques, formations, prototypage."; +--- + + +{isLaunched ? ( + + +
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+) : ( + +
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ MCU +
+
+
+
+ FPGA +
+
+
+
+ PWR +
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+ +
+
+
+ L'ELECTRON RARE +
+ +
+
+

+ Systèmes + électroniques + spécifiques +

+
+

Conception · Mise au point · Fiabilisation

+
+
+ > + +
+
+ +
+ electronique · automatisme · energie + mai 2026 +
+ +
+

L'électron rare

+

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

+

Contact : LinkedIn · contact@lelectronrare.fr

+
+
+
+)} +
+ +{!isLaunched && ( + +)} + + diff --git a/src/pages/index.astro b/src/pages/index.astro index ee6af17..7a03f40 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,188 +1,381 @@ --- -import BaseLayout from '@/layouts/BaseLayout.astro'; -import '@/styles/global.css'; -import '@/styles/home-workbench.css'; -import SiteHeader from '@/components/SiteHeader.astro'; -import { Hero } from '@/components/sections/Hero'; -import { About } from '@/components/sections/About'; -import { CaseStudies } from '@/components/sections/CaseStudies'; -import { GraphicSprints } from '@/components/sections/GraphicSprints'; -import Contact from '@/components/sections/Contact.astro'; -import Faq from '@/components/sections/Faq.astro'; -import { withSiteBase } from '@/lib/site'; -import { TRACK_EVENTS, trackAttrs } from '@/lib/tracking'; - -const LAUNCH_DATE = new Date('2026-05-01T00:00:00+02:00'); -const isLaunched = Date.now() >= LAUNCH_DATE.getTime(); - -const formationHref = withSiteBase('/formation/'); -const mentionsHref = withSiteBase('/mentions-legales/'); -const headerItems = [ - { href: '#a-propos', label: 'Approche' }, - { href: '#graphic-sprints-title', label: 'Missions' }, - { href: formationHref, label: 'Formation' }, - { href: '#faq', label: 'FAQ' }, - { href: '#contact', label: 'Contact', desktopOnly: true } -]; - -const title = isLaunched - ? "Systèmes électroniques spécifiques | L'électron rare" - : "L'Electron Rare — Bientôt"; -const description = isLaunched - ? "Conception, mise au point et fiabilisation de systèmes électroniques spécifiques." - : "L'Electron Rare arrive. Systèmes électroniques spécifiques, formations, prototypage."; +import { BmuViewer } from '@/components/BmuViewer'; --- - -{isLaunched ? ( - - -
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
- -
-) : ( - -
- -
- -
-
-
- L'ELECTRON RARE -
- -
-
- > - -
-
- -
- electronique · automatisme · energie - mai 2026 -
- -
-

L'électron rare

-

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

-

Contact : LinkedIn · contact@lelectronrare.fr

-
-
-
-)} -
- -{!isLaunched && ( - + + /* Hero */ + .ov-hero { inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; } + .hero-bottom { display: flex; flex-direction: column; align-items: center; padding-bottom: clamp(60px, 10vh, 120px); } + .hero-t { text-align: center; margin: 0; line-height: 1.1; } + .hl1,.hl3 { display: block; font-size: clamp(28px, 4.5vw, 56px); font-weight: 800; color: rgba(255,255,255,0.85); letter-spacing: -0.02em; } + .hl2 { display: block; font-size: clamp(32px, 5.5vw, 64px); font-weight: 800; color: #5bd1d8; letter-spacing: -0.02em; } + .hero-s { font-size: clamp(11px, 1.2vw, 15px); color: rgba(255,255,255,0.3); letter-spacing: 0.15em; text-transform: uppercase; margin: 16px 0 0; } + .hero-s2 { font-size: clamp(10px, 1vw, 13px); color: rgba(255,255,255,0.2); letter-spacing: 0.12em; text-transform: uppercase; margin: 8px 0 0; } + + /* Header */ + .site-hdr { position: fixed; top: 0; left: 0; right: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; padding: 16px clamp(20px, 3vw, 40px); pointer-events: none; } + .hdr-left { display: flex; align-items: center; gap: 10px; pointer-events: auto; } + .hdr-logo { width: 36px; height: auto; filter: drop-shadow(0 0 12px rgba(91,209,216,0.3)); } + .hdr-name { font-size: 16px; font-weight: 800; color: rgba(255,255,255,0.8); letter-spacing: 0.04em; } + .hdr-nav { display: flex; align-items: center; gap: 8px; pointer-events: auto; } + .hdr-link { padding: 8px 16px; color: rgba(255,255,255,0.6); font-size: 12px; font-weight: 600; text-decoration: none; letter-spacing: 0.04em; transition: color 0.2s; } + .hdr-link:hover { color: #5bd1d8; } + .hdr-social { font-weight: 800; font-size: 11px; text-transform: uppercase; padding: 8px 10px; opacity: 0.5; } + .hdr-social:hover { opacity: 1; } + .hdr-cta { padding: 8px 20px; border-radius: 6px; background: rgba(91,209,216,0.15); border: 1px solid rgba(91,209,216,0.3); color: #5bd1d8; font-size: 12px; font-weight: 700; text-decoration: none; letter-spacing: 0.05em; transition: background 0.2s; } + .hdr-cta:hover { background: rgba(91,209,216,0.25); } + .burger { display: none; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 8px; pointer-events: auto; } + .mobile-menu { display: none; position: fixed; inset: 0; z-index: 100; background: rgba(6,8,11,0.95); backdrop-filter: blur(16px); flex-direction: column; align-items: center; justify-content: center; gap: 24px; } + .mobile-menu.open { display: flex; } + .mobile-menu a { font-size: 20px; font-weight: 700; color: rgba(255,255,255,0.8); text-decoration: none; letter-spacing: 0.05em; padding: 12px 24px; } + .mobile-menu .close-btn { position: absolute; top: 20px; right: 20px; background: none; border: none; color: #fff; font-size: 28px; cursor: pointer; } + @media (max-width: 768px) { .hdr-nav { display: none; } .burger { display: block; } } + + /* Formats */ + .ov-formats { bottom: clamp(30px, 5vh, 60px); left: 50%; transform: translateX(-50%); display: flex; gap: 12px; max-width: 820px; width: 92vw; } + .ov-formats.on { transform: translateX(-50%); } + .fmt { flex: 1; padding: 14px 16px; border-radius: 8px; background: rgba(6,8,11,0.7); backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.05); text-align: center; } + .fmt-t { font-size: 13px; font-weight: 700; color: #fff; letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: 4px; } + .fmt-d { font-size: 10px; color: rgba(255,255,255,0.35); } + + /* Progress */ + .prog { position: fixed; top: 0; left: 0; height: 2px; z-index: 5; background: linear-gradient(90deg, #5bd1d8, #a78bfa, #f59e0b); width: 0%; } + .inf { position: fixed; bottom: 10px; right: 14px; z-index: 4; font-size: 8px; color: rgba(255,255,255,0.1); letter-spacing: 0.06em; } + + /* Dots */ + .dots { position: fixed; right: 20px; top: 50%; transform: translateY(-50%); z-index: 4; display: flex; flex-direction: column; gap: 14px; } + .dot { width: 12px; height: 12px; border-radius: 50%; background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.1); transition: background 0.3s, transform 0.3s, border-color 0.3s; cursor: pointer; pointer-events: auto; position: relative; } + .dot.on { background: #5bd1d8; border-color: #5bd1d8; transform: scale(1.3); box-shadow: 0 0 10px rgba(91,209,216,0.4); } + .dot:hover { background: rgba(91,209,216,0.5); border-color: rgba(91,209,216,0.5); } + .dot[data-l]:not([data-l=""])::after { content: attr(data-l); position: absolute; right: 20px; top: 50%; transform: translateY(-50%); font-size: 9px; color: rgba(255,255,255,0.5); letter-spacing: 0.05em; white-space: nowrap; opacity: 0; transition: opacity 0.2s; pointer-events: none; } + .dot[data-l]:not([data-l=""]):hover::after { opacity: 1; } + + /* Trust */ + .trust { position: fixed; bottom: 12px; left: clamp(16px, 3vw, 40px); z-index: 3; pointer-events: none; } + .trust-k { font-size: 9px; color: rgba(255,255,255,0.2); letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: 4px; } + .trust-names { font-size: 11px; color: rgba(255,255,255,0.3); letter-spacing: 0.04em; } + + @media (max-width: 768px) { + .ov-formats { flex-direction: column; gap: 6px; } + .trust { display: none; } + } + + + + +
+ +

L'électron rare

+

Systèmes électroniques spécifiques

+
+

Chargement des modèles 3D...

+ +

Version accessible : Conception · Formation · Consulting · Contact

+
+ + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ + L'électron rare +
+ + +
+ + + +
+
+

+ Systèmes + électroniques + spécifiques +

+

électronique · automatisme · énergie

+

Conception · Mise au point · Fiabilisation · Consulting · Formation

+
+
+ + +
+
+

Conception

+

Schéma · PCB · Firmware · Validation

+
+
+

Formation

+

PCB · KiCad · µC · Soudure

+
+
+

Consulting

+

Audit · Diagnostic · Transfert

+
+
+ + +
+

Ils m'ont fait confiance

+

KXKM · Spectacle vivant · Secteur ferroviaire · Industrie automatisée · Écoles d'art numérique

+
+ +
+ + +
+

L'électron rare — Systèmes électroniques spécifiques

+

Conception, mise au point et fiabilisation de systèmes électroniques spécifiques, automatismes et ensembles liés à l'énergie, au stockage et à l'optimisation terrain.

+

Domaines d'expertise

+
    +
  • Électronique spécifique : cartes, interfaces, capteurs, alimentation
  • +
  • Instrumentation et automatisme : bancs, automates, variateurs, protocoles terrain
  • +
  • Énergie et stockage : batterie, conversion, supervision, télémétrie
  • +
  • Dispositifs pour le réel : audio, LED, scène, robustesse terrain
  • +
  • Consulting et formation : audit, transfert, pédagogie, projet
  • +
+

Cas concrets

+
    +
  • Industries créatives : système audio embarqué sur batterie pour le spectacle (KXKM)
  • +
  • Industrie : diagnostic et remise en service d'une ligne automatisée
  • +
  • Formation : formation PCB et embarqué pour une école d'art numérique
  • +
+

Services

+
    +
  • Conception : schéma, PCB, firmware, validation
  • +
  • Formation : PCB, KiCad, microcontrôleurs, soudure
  • +
  • Consulting : audit, diagnostic, transfert
  • +
+

Ils m'ont fait confiance

+

KXKM (spectacle vivant), secteur ferroviaire, industrie automatisée, écoles d'art numérique.

+

À propos

+

Clément Saillant — 10 ans d'expérience en systèmes électroniques. Contributeur open-source KiCad et KXKM. Ambassadeur IA appliquée à l'électronique.

+

Contact : Parlons de votre projet — contact@lelectronrare.fr · Blog technique

+
+ + + +
L'électron rare · Clément Saillant
+ + + + + + + diff --git a/src/pages/preview.astro b/src/pages/preview.astro index 655041b..d688fab 100644 --- a/src/pages/preview.astro +++ b/src/pages/preview.astro @@ -1,177 +1,304 @@ --- -import BaseLayout from '@/layouts/BaseLayout.astro'; -import '@/styles/global.css'; -import '@/styles/home-workbench.css'; -import SiteHeader from '@/components/SiteHeader.astro'; -import { About } from '@/components/sections/About'; -import { CaseStudies } from '@/components/sections/CaseStudies'; -import { GraphicSprints } from '@/components/sections/GraphicSprints'; -import Contact from '@/components/sections/Contact.astro'; -import Faq from '@/components/sections/Faq.astro'; -import { withSiteBase } from '@/lib/site'; -import { TRACK_EVENTS, trackAttrs } from '@/lib/tracking'; - -const formationHref = withSiteBase('/formation/'); -const mentionsHref = withSiteBase('/mentions-legales/'); -const headerItems = [ - { href: '#a-propos', label: 'Approche' }, - { href: '#graphic-sprints-title', label: 'Missions' }, - { href: formationHref, label: 'Formation' }, - { href: '#faq', label: 'FAQ' }, - { href: '#contact', label: 'Contact', desktopOnly: true } -]; +import { BmuViewer } from '@/components/BmuViewer'; --- - - -
-
- BMU v2 Assembly -

L'électron rare

-
-
-
-

Chargement...

- Version sans 3D → -

électronique · automatisme · énergie

+ + + + + L'électron rare — Systèmes électroniques spécifiques + + + + + + + + + + + + + + + + + +
+ +

L'électron rare

+

Systèmes électroniques spécifiques

+
+

Chargement des modèles 3D...

+ +

Version accessible : Conception · Formation · Consulting · Contact

+
+ + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ + L'électron rare +
+ +
+ + +
+
+

+ Systèmes + électroniques + spécifiques +

+

électronique · automatisme · énergie

+

Conception · Mise au point · Fiabilisation · Consulting · Formation

- -
- - - -
- - -
-
- -
-
- - - -
-
- -
-
- - - -
-
- PCB custom Teensy LED - Câblage automate industriel - Banc de test BMS batteries - Automate Siemens S7 - Soudure PCB - Oscilloscope -
-
- - - -
-
- -
-
- - - -
-
- -
-
- - - -
-
- -
-
-
- - - +
+

Formation

+

PCB · KiCad · µC · Soudure

+
+
+

Consulting

+

Audit · Diagnostic · Transfert

+
+
- + - + // Dots cliquables + dots.forEach(d => { + d.addEventListener('click', () => { + const p = parseFloat(d.getAttribute('data-p') || '0'); + const max = document.documentElement.scrollHeight - window.innerHeight; + window.scrollTo({ top: Math.round(max * p), behavior: 'smooth' }); + }); + }); + + // GA4 tracking + document.querySelectorAll('a[href]').forEach(a => { + a.addEventListener('click', () => { + const label = a.textContent?.trim() || a.getAttribute('href') || ''; + window.dataLayer = window.dataLayer || []; + window.dataLayer.push({ event: 'click', event_category: 'navigation', event_label: label, da_variant: 'preview', page_path: '/preview/' }); + }); + }); + + // Loader + const loader = document.getElementById('loader'); + const loaderFill = document.getElementById('loader-fill'); + const loaderStatus = document.getElementById('loader-status'); + const msgs = ['Chargement des modèles 3D...', 'Compilation shaders...', 'Préparation du rendu...']; + let msgIdx = 0; + const msgInterval = setInterval(() => { + msgIdx = (msgIdx + 1) % msgs.length; + if (loaderStatus) loaderStatus.textContent = msgs[msgIdx]; + if (loaderFill) loaderFill.style.width = Math.min(90, parseFloat(loaderFill.style.width || '0') + 20) + '%'; + }, 1200); + + function hideLoader() { + if (loaderFill) loaderFill.style.width = '100%'; + if (loaderStatus) loaderStatus.textContent = 'Prêt'; + clearInterval(msgInterval); + setTimeout(() => { if (loader) loader.classList.add('done'); }, 400); + } + + const checkReady = setInterval(() => { + const cvs = document.querySelector('canvas'); + if (cvs && cvs.width > 0) { clearInterval(checkReady); setTimeout(hideLoader, 800); } + }, 200); + setTimeout(hideLoader, 10000); + + + diff --git a/src/pages/sitemap.xml.ts b/src/pages/sitemap.xml.ts index f341cca..7be3bf9 100644 --- a/src/pages/sitemap.xml.ts +++ b/src/pages/sitemap.xml.ts @@ -1,11 +1,14 @@ import type { APIRoute } from 'astro'; import { CANONICAL_URL, PUBLIC_SITE_ROOT_URL, SITE_IS_SUBPATH_BUILD } from '../lib/site'; +export const prerender = false; + export const GET: APIRoute = () => { - const sitemapRootUrl = SITE_IS_SUBPATH_BUILD ? PUBLIC_SITE_ROOT_URL : CANONICAL_URL; + const sitemapRootUrl = process.env.PUBLIC_SITE_URL || CANONICAL_URL; const pages = [ { path: '', priority: '1.0', changefreq: 'weekly' }, { path: 'formation/', priority: '0.9', changefreq: 'monthly' }, + { path: 'preview/', priority: '0.8', changefreq: 'weekly' }, { path: 'mentions-legales/', priority: '0.3', changefreq: 'yearly' } ];