wip: rescue source from photon

14 files (6 modified + 8 new) recovered from
photon /home/electron/electron-rare.github.io
during decom forensics.

New:
- src/components/BmuViewer.tsx (3D viewer)
- public/assets/models3d/{bmu_v2,bmu-assembly,
  i2c_repeater,mosfet_switch}.glb
- src/pages/{conception,consulting,contact,
  index-classic}.astro
- infra/Dockerfile.prebuild
- CLAUDE.md

Modified:
- public/sitemap.xml
- src/layouts/BaseLayout.astro
- src/pages/{formation,index,preview}.astro
- src/pages/sitemap.xml.ts (+5/-)

Snapshot mtime 2026-04-02 (photon last edits).
Recovery 2026-04-28 02:30 CEST.
This commit is contained in:
L'électron rare
2026-04-28 08:37:59 +02:00
parent e49fdea8f0
commit 913df2aee8
17 changed files with 2432 additions and 703 deletions
+73
View File
@@ -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 (01) 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/`.
+9
View File
@@ -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"]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+12 -6
View File
@@ -1,20 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url> <url>
<loc>https://electron-rare.github.io/</loc> <loc>https://www.lelectronrare.fr/</loc>
<lastmod>2026-03-02</lastmod> <lastmod>2026-04-01</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
<url> <url>
<loc>https://electron-rare.github.io/formation/</loc> <loc>https://www.lelectronrare.fr/formation/</loc>
<lastmod>2026-03-14</lastmod> <lastmod>2026-04-01</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
</url> </url>
<url> <url>
<loc>https://electron-rare.github.io/mentions-legales/</loc> <loc>https://www.lelectronrare.fr/preview/</loc>
<lastmod>2026-03-14</lastmod> <lastmod>2026-04-01</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.lelectronrare.fr/mentions-legales/</loc>
<lastmod>2026-04-01</lastmod>
<changefreq>yearly</changefreq> <changefreq>yearly</changefreq>
<priority>0.3</priority> <priority>0.3</priority>
</url> </url>
+586
View File
@@ -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 = (
<div style={{
minWidth: isBig ? '340px' : '260px',
padding: isBig ? '24px 36px' : '14px 24px',
textAlign: 'center',
background: isClickable ? 'rgba(0,0,0,0.7)' : 'rgba(0,0,0,0.55)',
border: isClickable ? '2px solid #5bd1d8' : '1.5px solid rgba(255,255,255,0.4)',
borderRadius: isClickable ? '12px' : '8px',
backdropFilter: 'blur(4px)',
cursor: isClickable ? 'pointer' : 'default',
}}>
<div style={{
fontSize: isBig ? '24px' : '18px',
fontWeight: 800,
color: isClickable ? '#5bd1d8' : '#fff',
letterSpacing: '0.05em', textTransform: 'uppercase',
fontFamily: "'Courier New', monospace",
}}>{title}</div>
{sub && <div style={{
fontSize: isBig ? '14px' : '12px',
color: 'rgba(255,255,255,0.55)',
marginTop: isBig ? '10px' : '6px',
fontFamily: "'Courier New', monospace", letterSpacing: '0.03em',
}}>{sub}</div>}
</div>
);
return (
<Html position={offset} center distanceFactor={isBig ? 0.4 : 0.8}
style={{
opacity: visible ? 1 : 0,
transition: 'opacity 0.5s',
pointerEvents: isClickable && visible ? 'auto' : 'none',
whiteSpace: 'nowrap',
}}>
{isContact ? (
<div onClick={() => window.dispatchEvent(new CustomEvent('open-contact-drawer', { detail: { source: 'pcb_contact' } }))}
style={{ textDecoration: 'none', display: 'block' }}>{content}</div>
) : linkHref ? (
<a href={linkHref} style={{ textDecoration: 'none', display: 'block' }}>{content}</a>
) : content}
</Html>
);
}
/* ── 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 <primitive object={cloned} />;
}
/* ── 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<THREE.Mesh>(null);
const videoRef = useRef<HTMLVideoElement | null>(null);
const texRef = useRef<THREE.VideoTexture | null>(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 (
<mesh ref={meshRef} position={offset} rotation={[0, Math.PI, 0]} scale={[-1, -1, 1]}>
<planeGeometry args={[planeW, planeH]} />
<meshStandardMaterial
map={texRef.current}
transparent
opacity={0}
emissive="#ffffff"
emissiveIntensity={0.3}
emissiveMap={texRef.current}
side={THREE.DoubleSide}
/>
</mesh>
);
}
/* ── 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<THREE.Mesh>(null);
const textures = useRef<THREE.Texture[]>([]);
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 (
<mesh ref={meshRef} position={offset} rotation={[0, Math.PI, 0]} scale={[-1, -1, 1]}>
<planeGeometry args={[planeW, planeH]} />
<meshStandardMaterial
transparent
opacity={0}
emissive="#ffffff"
emissiveIntensity={0.3}
side={THREE.DoubleSide}
/>
</mesh>
);
}
/* ── 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<number>; onSection: (id: string) => void;
}) {
const assemblyGroup = useRef<THREE.Group>(null);
const cardsRef = useRef<THREE.Group[]>([]);
const fitted = useRef(false);
const { camera } = useThree();
const smoothScroll = useRef(0);
const origPositions = useRef<THREE.Vector3[]>([]);
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 (
<group ref={assemblyGroup} rotation={[-0.25, 0, 0]}>
{CARDS_DEF.map((card, i) => (
<group key={i}
ref={(el) => { if (el) cardsRef.current[i] = el; }}
position={card.position} rotation={card.rotation}>
<CardModel file={card.file} />
{/* Tuiles 3D plaquées sur les faces */}
{PCB_LABELS.filter(l => l.cardIndex === i).map((label, li) => (
<PcbTile key={`t${li}`} offset={label.localOffset}
title={label.title} sub={label.sub} color={label.color}
visible={currentSection === label.section || (label.section === 'cas' && currentSection === 'media')} section={label.section}
href={label.href} />
))}
{/* Videos on mosfet bottoms — real 3D mesh planes */}
{MOSFET_VIDEOS.filter(v => v.cardIndex === i).map((v, vi) => (
<PcbVideoMesh key={`v${vi}`} offset={[0, 0, -0.1]} src={v.src}
visible={currentSection === 'media' || currentSection === 'cas'} />
))}
{/* Carousel on mosfet #4 bottom — real 3D mesh plane */}
{i === MOSFET_CAROUSEL_CARD && (
<PcbCarouselMesh offset={[0, 0, -0.1]} visible={currentSection === 'media' || currentSection === 'cas'} />
)}
</group>
))}
</group>
);
}
/* ── Pulsed Bloom — machine breathing ── */
function PulsedBloom() {
const bloomRef = useRef<any>(null);
useFrame(({ clock }) => {
if (!bloomRef.current) return;
const t = clock.getElapsedTime();
bloomRef.current.intensity = 0.25 + Math.sin(t * 0.8) * 0.1;
});
return <Bloom ref={bloomRef} intensity={0.25} luminanceThreshold={0.8} luminanceSmoothing={0.9} mipmapBlur />;
}
/* ── 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 (
<div style={{ position: 'fixed', inset: 0, width: '100vw', height: '100vh', zIndex: 0, pointerEvents: 'none' }}>
<Canvas gl={{ antialias: true, alpha: true, powerPreference: 'high-performance' }}
dpr={[1, 1.5]} camera={{ fov: 35, near: 0.01, far: 50 }}
style={{ background: 'transparent', pointerEvents: 'none' }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 8, 5]} intensity={2.2} color="#ffffff" />
<directionalLight position={[-3, 4, -2]} intensity={0.9} color="#5bd1d8" />
<directionalLight position={[0, -4, 2]} intensity={0.5} color="#f1c27a" />
<Suspense fallback={null}>
<Assembly scrollRef={scrollRef} onSection={setSectionId} />
<ContactShadows position={[0, -1.5, 0]} opacity={0.15} scale={10} blur={2.5} far={5} color="#000000" />
<Environment preset="city" />
</Suspense>
</Canvas>
</div>
);
}
+2 -2
View File
@@ -321,7 +321,7 @@ const structuredDataJsonLd = JSON.stringify({
var uiAllowed = { a: 1, b: 1 }; var uiAllowed = { a: 1, b: 1 };
var uiSprint = uiRaw.toLowerCase().trim() in uiAllowed ? uiRaw.toLowerCase().trim() : 'b'; 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); root.setAttribute('data-ui-sprint', uiSprint);
})(); })();
</script> </script>
@@ -344,7 +344,7 @@ const structuredDataJsonLd = JSON.stringify({
<script is:inline> <script is:inline>
(function () { (function () {
var daVariant = document.documentElement.getAttribute('data-da-variant') || 'v12'; var daVariant = document.documentElement.getAttribute('data-da-variant') || 'classic';
var pagePath = window.location.pathname || '/'; var pagePath = window.location.pathname || '/';
var pageTitle = document.title || ''; var pageTitle = document.title || '';
+135
View File
@@ -0,0 +1,135 @@
---
---
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Conception électronique | L'électron rare</title>
<meta name="description" content="Conception de systèmes électroniques spécifiques : schéma, PCB, firmware, prototypage et validation. Du diagnostic à la mission complète." />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html { background: #06080b; color: #fff; }
body { background: #06080b; min-height: 100vh; font-family: 'Courier New', monospace; }
.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); background: rgba(6,8,11,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.05); }
.hdr-left { display: flex; align-items: center; gap: 10px; }
.hdr-left a { text-decoration: none; display: flex; align-items: center; gap: 10px; }
.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; }
.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-link.active { color: #5bd1d8; }
.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; }
.burger { display: none; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 8px; }
.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 a:hover { color: #5bd1d8; }
.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; }
}
.content { max-width: 800px; margin: 0 auto; padding: 120px 24px 80px; }
h1 { font-size: clamp(28px, 4vw, 48px); font-weight: 800; color: #5bd1d8; margin-bottom: 24px; letter-spacing: -0.02em; }
h2 { font-size: 18px; font-weight: 700; color: #fff; margin: 40px 0 16px; text-transform: uppercase; letter-spacing: 0.06em; }
p { font-size: 14px; color: rgba(255,255,255,0.6); line-height: 1.8; margin-bottom: 16px; }
.card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 24px; margin-bottom: 16px; }
.card-t { font-size: 16px; font-weight: 700; color: #fff; margin-bottom: 8px; }
.card-pct { font-size: 28px; font-weight: 800; margin-bottom: 4px; }
.card-time { font-size: 11px; color: rgba(255,255,255,0.35); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 12px; }
.card-desc { font-size: 13px; color: rgba(255,255,255,0.5); line-height: 1.6; margin-bottom: 12px; }
.card-del { font-size: 12px; color: rgba(255,255,255,0.4); }
.cta-box { margin-top: 48px; text-align: center; }
.cta-btn { display: inline-block; padding: 14px 32px; border-radius: 8px; background: #5bd1d8; color: #06080b; font-weight: 700; font-size: 14px; text-decoration: none; letter-spacing: 0.04em; }
.cta-btn:hover { background: #7de0e6; }
.footer { text-align: center; padding: 40px 24px; font-size: 10px; color: rgba(255,255,255,0.15); }
</style>
</head>
<body>
<header class="site-hdr">
<div class="hdr-left"><a href="/"><img src="/assets/brand/logo-mark.svg" alt="" class="hdr-logo" /><span class="hdr-name">L'électron rare</span></a></div>
<nav class="hdr-nav">
<a href="/conception/" class="hdr-link active">Conception</a>
<a href="/formation/" class="hdr-link">Formation</a>
<a href="/consulting/" class="hdr-link">Consulting</a>
<a href="/contact/" class="hdr-cta">Contact</a>
</nav>
<button class="burger" id="burger-btn" aria-label="Menu">☰</button>
</header>
<div class="mobile-menu" id="mobile-menu">
<button class="close-btn" id="close-menu">✕</button>
<a href="/">Accueil</a>
<a href="/conception/">Conception</a>
<a href="/formation/">Formation</a>
<a href="/consulting/">Consulting</a>
<a href="/contact/">Contact</a>
</div>
<script>
document.getElementById('burger-btn')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.add('open'));
document.getElementById('close-menu')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.remove('open'));
</script>
<main class="content">
<h1>Conception électronique</h1>
<p>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.</p>
<h2>Formats de mission</h2>
<div class="card">
<p class="card-pct" style="color:#5bd1d8">28%</p>
<p class="card-t">Diagnostic</p>
<p class="card-time">1 à 2 semaines</p>
<p class="card-desc">Analyser votre besoin, identifier les sous-systèmes critiques et livrer une carte de faisabilité.</p>
<p class="card-del">Livrables : analyse de faisabilité · risques identifiés · plan d'action</p>
</div>
<div class="card">
<p class="card-pct" style="color:#a78bfa">55%</p>
<p class="card-t">Prototype</p>
<p class="card-time">4 à 6 semaines</p>
<p class="card-desc">Concevoir et tester un prototype fonctionnel : schéma, PCB, firmware, mesures et validation.</p>
<p class="card-del">Livrables : prototype testé · mesures documentées · go/no-go argumenté</p>
</div>
<div class="card">
<p class="card-pct" style="color:#f59e0b">100%</p>
<p class="card-t">Mission complète</p>
<p class="card-time">2 à 6 mois</p>
<p class="card-desc">Développement complet jusqu'au produit industrialisable : itérations, tests terrain, documentation et transfert.</p>
<p class="card-del">Livrables : livrables par lot · dossier de production · transfert clé en main</p>
</div>
<h2>Cas concrets</h2>
<div class="card">
<p class="card-t" style="color:#5bd1d8">Battery Parallelator — KXKM</p>
<p class="card-desc">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.</p>
<p class="card-del" style="color:#5bd1d8;">→ 8h autonomie · OTA · 0 panne sur 2 saisons</p>
</div>
<div class="card">
<p class="card-t" style="color:#a78bfa">HypnoLED — Contrôle LED embarqué</p>
<p class="card-desc">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.</p>
<p class="card-del" style="color:#a78bfa;">→ 512 canaux DMX · Latence &lt;1ms · Déployé en tournée</p>
</div>
<div class="card">
<p class="card-t" style="color:#34d399">Télémétrie ESP32 — Monitoring énergie</p>
<p class="card-desc">Système de supervision terrain avec ESP32 : mesure courant/tension, transmission MQTT, dashboard temps réel. Capteurs INA226, connectivité WiFi, boîtier IP65.</p>
<p class="card-del" style="color:#34d399;">→ Autonomie 6 mois sur batterie · Dashboard Grafana</p>
</div>
<h2>Domaines d'intervention</h2>
<p>Électronique spécifique · Instrumentation et automatisme · Énergie et stockage · Dispositifs pour le réel · Montages multi-techniques avec partenaires.</p>
<div class="cta-box">
<a href="/contact/?from=conception" class="cta-btn">Discuter de votre projet</a>
</div>
</main>
<div class="footer">© 2026 Clément Saillant — L'électron rare · <a href="https://blog.saillant.cc" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">Blog</a> · <a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">LinkedIn</a> · <a href="https://github.com/electron-rare/" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">GitHub</a></div>
</body>
</html>
+118
View File
@@ -0,0 +1,118 @@
---
---
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Consulting technique | L'électron rare</title>
<meta name="description" content="Consulting en systèmes électroniques : audit, diagnostic, transfert de compétences, accompagnement technique terrain." />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html { background: #06080b; color: #fff; }
body { background: #06080b; min-height: 100vh; font-family: 'Courier New', monospace; }
.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); background: rgba(6,8,11,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.05); }
.hdr-left { display: flex; align-items: center; gap: 10px; }
.hdr-left a { text-decoration: none; display: flex; align-items: center; gap: 10px; }
.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; }
.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-link.active { color: #5bd1d8; }
.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; }
.burger { display: none; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 8px; }
.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 a:hover { color: #5bd1d8; }
.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; } }
.content { max-width: 800px; margin: 0 auto; padding: 120px 24px 80px; }
h1 { font-size: clamp(28px, 4vw, 48px); font-weight: 800; color: #a78bfa; margin-bottom: 24px; letter-spacing: -0.02em; }
h2 { font-size: 18px; font-weight: 700; color: #fff; margin: 40px 0 16px; text-transform: uppercase; letter-spacing: 0.06em; }
p { font-size: 14px; color: rgba(255,255,255,0.6); line-height: 1.8; margin-bottom: 16px; }
.card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 24px; margin-bottom: 16px; }
.card-t { font-size: 16px; font-weight: 700; color: #fff; margin-bottom: 8px; }
.card-desc { font-size: 13px; color: rgba(255,255,255,0.5); line-height: 1.6; }
.chip { display: inline-block; font-size: 11px; padding: 4px 12px; border-radius: 20px; border: 1px solid; margin: 4px 4px 4px 0; letter-spacing: 0.04em; }
.families { margin: 20px 0 32px; }
.cta-box { margin-top: 48px; text-align: center; }
.cta-btn { display: inline-block; padding: 14px 32px; border-radius: 8px; background: #a78bfa; color: #06080b; font-weight: 700; font-size: 14px; text-decoration: none; letter-spacing: 0.04em; }
.cta-btn:hover { background: #c4b5fd; }
.footer { text-align: center; padding: 40px 24px; font-size: 10px; color: rgba(255,255,255,0.15); }
</style>
</head>
<body>
<header class="site-hdr">
<div class="hdr-left"><a href="/"><img src="/assets/brand/logo-mark.svg" alt="" class="hdr-logo" /><span class="hdr-name">L'électron rare</span></a></div>
<nav class="hdr-nav">
<a href="/conception/" class="hdr-link">Conception</a>
<a href="/formation/" class="hdr-link">Formation</a>
<a href="/consulting/" class="hdr-link active">Consulting</a>
<a href="/contact/" class="hdr-cta">Contact</a>
</nav>
<button class="burger" id="burger-btn" aria-label="Menu">☰</button>
</header>
<div class="mobile-menu" id="mobile-menu">
<button class="close-btn" id="close-menu">✕</button>
<a href="/">Accueil</a><a href="/conception/">Conception</a><a href="/formation/">Formation</a><a href="/consulting/">Consulting</a><a href="/contact/">Contact</a>
</div>
<script>document.getElementById('burger-btn')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.add('open'));document.getElementById('close-menu')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.remove('open'));</script>
<main class="content">
<h1>Consulting technique</h1>
<p>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.</p>
<h2>Familles d'intervention</h2>
<div class="families">
<span class="chip" style="color:#5bd1d8;border-color:#5bd1d833">Électronique spécifique</span>
<span class="chip" style="color:#a78bfa;border-color:#a78bfa33">Instrumentation & Automatisme</span>
<span class="chip" style="color:#f472b6;border-color:#f472b633">Dispositifs pour le réel</span>
<span class="chip" style="color:#34d399;border-color:#34d39933">Énergie & Stockage</span>
<span class="chip" style="color:#fbbf24;border-color:#fbbf2433">Multi-techniques & Partenaires</span>
</div>
<div class="card">
<p class="card-t">Audit & Diagnostic</p>
<p class="card-desc">Analyse de l'existant, identification des points critiques, recommandations chiffrées. Diagnostic terrain sur site industriel, automate, variateur, instrumentation.</p>
</div>
<div class="card">
<p class="card-t">Accompagnement technique</p>
<p class="card-desc">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.</p>
</div>
<div class="card">
<p class="card-t">Transfert & Documentation</p>
<p class="card-desc">Rédaction de dossiers techniques, procédures de maintenance, documentation de production. Transfert de compétences vers l'équipe interne.</p>
</div>
<h2>Cas concret</h2>
<div class="card">
<p class="card-t">Mise en production et correction V3.2 KXKM</p>
<p class="card-desc">Mise en production du hardware ESP32, correction des bugs V3.2, passage STM32F030. Tests terrain, validation embarquée, documentation technique.</p>
<p class="card-desc" style="color:#a78bfa;margin-top:8px;">→ Production V3.2 livrée · Migration STM32F030 validée</p>
</div>
<h2>À propos</h2>
<div class="card">
<p class="card-t">Clément Saillant — 10 ans d'expérience</p>
<p class="card-desc">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.</p>
<p class="card-desc">Secteurs : industries créatives, ferroviaire, automatisme industriel, éducation, énergie/stockage.</p>
</div>
<div class="cta-box">
<a href="/contact/?from=consulting" class="cta-btn">Discuter de votre besoin</a>
</div>
</main>
<div class="footer">
© 2026 Clément Saillant — L'électron rare ·
<a href="https://blog.saillant.cc" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">Blog</a> ·
<a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">LinkedIn</a> ·
<a href="https://github.com/electron-rare/" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">GitHub</a>
</div>
</body>
</html>
+170
View File
@@ -0,0 +1,170 @@
---
---
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Contact | L'électron rare</title>
<meta name="description" content="Parlons de votre projet électronique. Premier échange sans engagement." />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html { background: #06080b; color: #fff; }
body { background: #06080b; min-height: 100vh; font-family: 'Courier New', monospace; }
.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); background: rgba(6,8,11,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.05); }
.hdr-left { display: flex; align-items: center; gap: 10px; }
.hdr-left a { text-decoration: none; display: flex; align-items: center; gap: 10px; }
.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; }
.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-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; }
.hdr-cta.active { background: rgba(91,209,216,0.25); }
.burger { display: none; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 8px; }
.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; } }
.content { max-width: 600px; margin: 0 auto; padding: 120px 24px 80px; }
h1 { font-size: clamp(28px, 4vw, 48px); font-weight: 800; color: #5bd1d8; margin-bottom: 12px; letter-spacing: -0.02em; }
.sub { font-size: 14px; color: rgba(255,255,255,0.4); margin-bottom: 40px; }
label { display: block; font-size: 11px; color: rgba(255,255,255,0.5); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 6px; margin-top: 20px; }
input, select, textarea { width: 100%; padding: 12px 16px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; color: #fff; font-family: 'Courier New', monospace; font-size: 14px; outline: none; transition: border-color 0.2s; }
input:focus, select:focus, textarea:focus { border-color: #5bd1d8; }
select { appearance: none; cursor: pointer; }
select option { background: #06080b; color: #fff; }
textarea { resize: vertical; min-height: 160px; }
.consent { display: flex; align-items: flex-start; gap: 10px; margin-top: 20px; font-size: 11px; color: rgba(255,255,255,0.4); line-height: 1.5; }
.consent input { width: auto; }
.submit { display: block; width: 100%; margin-top: 28px; padding: 14px; border: none; border-radius: 8px; background: #5bd1d8; color: #06080b; font-family: 'Courier New', monospace; font-size: 14px; font-weight: 700; cursor: pointer; letter-spacing: 0.04em; transition: background 0.2s; }
.submit:hover { background: #7de0e6; }
.submit:disabled { opacity: 0.5; cursor: not-allowed; }
.success { display: none; text-align: center; padding: 40px 0; }
.success h2 { font-size: 24px; color: #5bd1d8; margin-bottom: 12px; }
.success p { font-size: 14px; color: rgba(255,255,255,0.5); }
.error { display: none; color: #f66; font-size: 12px; margin-top: 12px; }
.alt { margin-top: 40px; text-align: center; font-size: 12px; color: rgba(255,255,255,0.3); }
.alt a { color: #5bd1d8; text-decoration: none; }
.footer { text-align: center; padding: 40px 24px; font-size: 10px; color: rgba(255,255,255,0.15); }
</style>
</head>
<body>
<header class="site-hdr">
<div class="hdr-left"><a href="/"><img src="/assets/brand/logo-mark.svg" alt="" class="hdr-logo" /><span class="hdr-name">L'électron rare</span></a></div>
<nav class="hdr-nav">
<a href="/conception/" class="hdr-link">Conception</a>
<a href="/formation/" class="hdr-link">Formation</a>
<a href="/consulting/" class="hdr-link">Consulting</a>
<a href="/contact/" class="hdr-cta active">Contact</a>
</nav>
<button class="burger" id="burger-btn" aria-label="Menu">☰</button>
</header>
<div class="mobile-menu" id="mobile-menu">
<button class="close-btn" id="close-menu">✕</button>
<a href="/">Accueil</a><a href="/conception/">Conception</a><a href="/formation/">Formation</a><a href="/consulting/">Consulting</a><a href="/contact/">Contact</a>
</div>
<script>document.getElementById('burger-btn')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.add('open'));document.getElementById('close-menu')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.remove('open'));</script>
<main class="content">
<h1>Parlons de votre projet</h1>
<p class="sub">Décrivez votre besoin en quelques lignes — premier échange sans engagement.</p>
<form id="contact-form">
<label for="name">Nom *</label>
<input type="text" id="name" name="name" required placeholder="Votre nom" />
<label for="email">Email *</label>
<input type="email" id="email" name="email" required placeholder="votre@email.com" />
<label for="phone">Téléphone</label>
<input type="tel" id="phone" name="phone" placeholder="06 00 00 00 00" />
<label for="org">Organisation</label>
<input type="text" id="org" name="organization" placeholder="Entreprise / École / Association" />
<label for="need">Type de besoin *</label>
<select id="need" name="needType" required>
<option value="">Choisir...</option>
<option value="conception">Projet technique / Conception</option>
<option value="formation">Formation</option>
<option value="consulting">Consulting / Audit</option>
<option value="mixte">Projet mixte</option>
</select>
<script>
// Préremplir selon la page d'origine (?from=conception|formation|consulting)
const params = new URLSearchParams(window.location.search);
const from = params.get('from');
if (from) {
const sel = document.getElementById('need');
if (sel) sel.value = from;
}
</script>
<label for="message">Message *</label>
<textarea id="message" name="message" required placeholder="Bonjour Clément,
J'ai un projet / besoin autour d'un système électronique spécifique.
Contexte :
Ce que j'attends :
Contraintes (délai, budget) :"></textarea>
<div class="consent">
<input type="checkbox" id="consent" required />
<label for="consent" style="margin:0;text-transform:none;font-size:11px;">J'accepte que mes données soient utilisées pour répondre à ma demande. Aucune donnée n'est transmise à des tiers.</label>
</div>
<button type="submit" class="submit" id="submit-btn">Envoyer le message</button>
<p class="error" id="error-msg"></p>
</form>
<div class="success" id="success-msg">
<h2>Merci !</h2>
<p>Je reviens vers vous sous 48h.</p>
</div>
<div class="alt">
<p>Ou directement : <a href="mailto:contact@lelectronrare.fr">contact@lelectronrare.fr</a></p>
<p style="margin-top:8px;"><a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener">LinkedIn</a> · <a href="https://github.com/electron-rare/" target="_blank" rel="noopener">GitHub</a></p>
</div>
</main>
<div class="footer">© 2026 Clément Saillant — L'électron rare · <a href="https://blog.saillant.cc" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">Blog</a> · <a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">LinkedIn</a> · <a href="https://github.com/electron-rare/" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">GitHub</a></div>
<script>
const form = document.getElementById('contact-form');
const btn = document.getElementById('submit-btn');
const errEl = document.getElementById('error-msg');
const successEl = document.getElementById('success-msg');
form?.addEventListener('submit', async (e) => {
e.preventDefault();
btn.disabled = true;
btn.textContent = 'Envoi...';
errEl.style.display = 'none';
const data = Object.fromEntries(new FormData(form));
try {
const res = await fetch('https://api.lelectronrare.fr/api/submit-lead', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Erreur serveur');
form.style.display = 'none';
successEl.style.display = 'block';
if (typeof gtag === 'function') gtag('event', 'contact_form_submit', { need_type: data.needType });
} catch (err) {
errEl.textContent = 'Erreur lors de l\'envoi. Essayez par email : contact@lelectronrare.fr';
errEl.style.display = 'block';
btn.disabled = false;
btn.textContent = 'Envoyer le message';
}
});
</script>
</body>
</html>
+151 -350
View File
@@ -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 }
];
--- ---
<BaseLayout title={title} description={description} ogDescription="Formations sur mesure en électronique, automatisme, énergie, stockage, PCB, instrumentation et IoT. 1 jour, 3 jours ou programme dédié. Écoles, entreprises, centres de formation."> <html lang="fr">
<SiteHeader brandHref={homeHref} items={headerItems} quickHref={contactHref} /> <head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Formation électronique appliquée | L'électron rare</title>
<meta name="description" content="Formations sur mesure : électronique, automatisme, énergie, PCB, instrumentation, IoT. 1 jour, 3 jours ou programme dédié. Écoles, entreprises." />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html { background: #06080b; color: #fff; }
body { background: #06080b; min-height: 100vh; font-family: 'Courier New', monospace; }
.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); background: rgba(6,8,11,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.05); }
.hdr-left { display: flex; align-items: center; gap: 10px; }
.hdr-left a { text-decoration: none; display: flex; align-items: center; gap: 10px; }
.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; }
.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-link.active { color: #f59e0b; }
.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; }
.burger { display: none; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 8px; }
.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 a:hover { color: #5bd1d8; }
.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; } }
.content { max-width: 800px; margin: 0 auto; padding: 120px 24px 80px; }
h1 { font-size: clamp(28px, 4vw, 48px); font-weight: 800; color: #f59e0b; margin-bottom: 16px; letter-spacing: -0.02em; }
h2 { font-size: 18px; font-weight: 700; color: #fff; margin: 48px 0 16px; text-transform: uppercase; letter-spacing: 0.06em; border-bottom: 1px solid rgba(255,255,255,0.08); padding-bottom: 8px; }
h3 { font-size: 14px; font-weight: 700; color: #f59e0b; margin: 28px 0 8px; text-transform: uppercase; letter-spacing: 0.08em; }
p { font-size: 14px; color: rgba(255,255,255,0.6); line-height: 1.8; margin-bottom: 12px; }
.lead { font-size: 15px; color: rgba(255,255,255,0.5); margin-bottom: 32px; }
.card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 20px; margin-bottom: 12px; }
.card-t { font-size: 15px; font-weight: 700; color: #fff; margin-bottom: 6px; }
.card-desc { font-size: 12px; color: rgba(255,255,255,0.45); line-height: 1.6; margin-bottom: 8px; }
.card-meta { font-size: 11px; color: rgba(255,255,255,0.3); letter-spacing: 0.04em; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 12px; }
.subjects { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 700px) { .subjects, .grid { grid-template-columns: 1fr; } }
.subject { background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; padding: 16px; }
.subject-t { font-size: 13px; font-weight: 700; color: #fff; margin-bottom: 6px; }
.subject-d { font-size: 11px; color: rgba(255,255,255,0.4); line-height: 1.5; }
.audience { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 32px; }
.audience-chip { font-size: 11px; padding: 6px 14px; border-radius: 20px; border: 1px solid rgba(255,255,255,0.12); color: rgba(255,255,255,0.55); }
.format-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
@media (max-width: 700px) { .format-grid { grid-template-columns: 1fr; } }
.format { text-align: center; padding: 24px 16px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; }
.format-dur { font-size: 24px; font-weight: 800; color: #f59e0b; margin-bottom: 4px; }
.format-name { font-size: 13px; font-weight: 700; color: #fff; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.06em; }
.format-desc { font-size: 11px; color: rgba(255,255,255,0.4); line-height: 1.5; }
.cta-box { margin-top: 48px; text-align: center; }
.cta-btn { display: inline-block; padding: 14px 32px; border-radius: 8px; background: #f59e0b; color: #06080b; font-weight: 700; font-size: 14px; text-decoration: none; letter-spacing: 0.04em; }
.cta-btn:hover { background: #fbbf24; }
.info { margin-top: 40px; font-size: 11px; color: rgba(255,255,255,0.25); line-height: 1.8; }
.footer { text-align: center; padding: 40px 24px; font-size: 10px; color: rgba(255,255,255,0.15); }
</style>
</head>
<body>
<header class="site-hdr">
<div class="hdr-left"><a href="/"><img src="/assets/brand/logo-mark.svg" alt="" class="hdr-logo" /><span class="hdr-name">L'électron rare</span></a></div>
<nav class="hdr-nav">
<a href="/conception/" class="hdr-link">Conception</a>
<a href="/formation/" class="hdr-link active">Formation</a>
<a href="/consulting/" class="hdr-link">Consulting</a>
<a href="/contact/" class="hdr-cta">Contact</a>
</nav>
<button class="burger" id="burger-btn" aria-label="Menu">☰</button>
</header>
<div class="mobile-menu" id="mobile-menu">
<button class="close-btn" id="close-menu">✕</button>
<a href="/">Accueil</a><a href="/conception/">Conception</a><a href="/formation/">Formation</a><a href="/consulting/">Consulting</a><a href="/contact/">Contact</a>
</div>
<script>document.getElementById('burger-btn')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.add('open'));document.getElementById('close-menu')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.remove('open'));</script>
<main id="main-content" class="site-shell studio-structure pb-12 pt-3"> <main class="content">
<h1>Formation électronique appliquée</h1>
<section class="circuit-board rounded-2xl p-5 md:p-6 mt-4" data-reveal> <p class="lead">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.</p>
<div class="circuit-title-row"> <div style="display:flex;align-items:center;gap:12px;padding:14px 18px;background:rgba(245,158,11,0.08);border:1px solid rgba(245,158,11,0.2);border-radius:10px;margin-bottom:32px;">
<span class="circuit-node" aria-hidden="true"></span> <span style="font-size:24px;">🏅</span>
<h1 class="m-0 text-3xl md:text-4xl">Formation électronique appliquée</h1> <div>
<span class="circuit-pinline" aria-hidden="true"></span> <p style="font-size:13px;color:#f59e0b;font-weight:700;margin:0;">Formations finançables OPCO / Afdas</p>
<p style="font-size:11px;color:rgba(255,255,255,0.4);margin:4px 0 0;">Intervention possible via un organisme partenaire certifié Qualiopi.</p>
</div> </div>
</div>
<p class="section-lead mb-0 mt-3"> <div class="audience">
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. <span class="audience-chip">Entreprises industrielles</span>
</p> <span class="audience-chip">Écoles et universités</span>
<p class="mb-0 mt-3 studio-muted"> <span class="audience-chip">Centres de formation</span>
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. <span class="audience-chip">Studios créatifs</span>
</p> </div>
</section>
<section class="circuit-board rounded-2xl p-5 md:p-6 mt-5" data-reveal> <h2>Formats</h2>
<div class="circuit-title-row"> <div class="format-grid">
<span class="circuit-node circuit-node--magenta" aria-hidden="true"></span> <div class="format">
<h2 class="m-0 text-2xl md:text-3xl">Publics cibles</h2> <p class="format-dur">1 jour</p>
<span class="circuit-pinline" aria-hidden="true"></span> <p class="format-name">Initiation</p>
<p class="format-desc">Fondamentaux, démos live, TP sur matériel réel, support PDF</p>
</div> </div>
<div class="format">
<div class="mt-4 rounded-xl border border-[color:var(--line)] p-3"> <p class="format-dur">3 jours</p>
<ul class="m-0 grid grid-cols-1 gap-2 p-0"> <p class="format-name">Approfondissement</p>
<li class="list-none studio-chip studio-chip--cyan"> <p class="format-desc">Projet fil rouge, prototype fonctionnel, documentation technique</p>
<strong>Entreprises industrielles</strong> — équipes R&D, bureaux d'études, techniciens de maintenance qui veulent concevoir, diagnostiquer ou fiabiliser des systèmes électroniques dans leurs produits ou process.
</li>
<li class="list-none studio-chip studio-chip--vio">
<strong>Écoles et universités</strong> — BTS, IUT, grandes écoles : TP encadrés, projets tutorés, modules spécialisés en systèmes électroniques, PCB et prototypage.
</li>
<li class="list-none studio-chip studio-chip--pink">
<strong>Centres de formation</strong> — organismes souhaitant intégrer des modules électronique, instrumentation, énergie, stockage, IoT ou automatisme à leur catalogue.
</li>
<li class="list-none studio-chip studio-chip--emerald">
<strong>Studios créatifs et compagnies</strong> — équipes techniques du spectacle vivant, de l'événementiel ou des arts numériques qui veulent gagner en autonomie.
</li>
</ul>
</div> </div>
</section> <div class="format">
<p class="format-dur">Sur mesure</p>
<section class="mt-5" data-reveal> <p class="format-name">Programme dédié</p>
<div class="graphic-sprints-panel graphic-sprints-panel--routing"> <p class="format-desc">Autour de votre projet, vos équipements, suivi post-formation</p>
<div class="graphic-sprints-head">
<p class="graphic-sprints-kicker">Formats</p>
<h2 class="graphic-sprints-title">Trois formats, un principe : le concret d'abord</h2>
<p class="graphic-sprints-copy">
Chaque format inclut un support de cours, des exercices pratiques sur matériel réel et un livrable que les participants repartent avec.
</p>
</div>
<div class="graphic-sprints-grid">
<article class="graphic-sprint-card" style="--sprint-progress-target: 33%">
<header class="graphic-sprint-card-head">
<p class="graphic-sprint-status graphic-sprint-status--trace">Initiation</p>
<p class="graphic-sprint-window">1 jour (7h)</p>
</header>
<h3 class="graphic-sprint-title">Découverte</h3>
<p class="graphic-sprint-objective">
Comprendre les fondamentaux d'un domaine technique. Idéal pour une première prise en main ou une mise à niveau rapide.
</p>
<div class="graphic-sprint-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="33">
<span class="graphic-sprint-progress-fill"></span>
</div>
<ul class="graphic-sprint-deliverables">
<li>Théorie appliquée + démos live</li>
<li>TP sur matériel réel</li>
<li>Support de cours PDF</li>
</ul>
</article>
<article class="graphic-sprint-card" style="--sprint-progress-target: 66%">
<header class="graphic-sprint-card-head">
<p class="graphic-sprint-status graphic-sprint-status--bench">Approfondissement</p>
<p class="graphic-sprint-window">3 jours (21h)</p>
</header>
<h3 class="graphic-sprint-title">Pratique</h3>
<p class="graphic-sprint-objective">
Maîtriser un sujet de bout en bout avec un projet fil rouge. Les participants repartent avec un prototype fonctionnel.
</p>
<div class="graphic-sprint-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="66">
<span class="graphic-sprint-progress-fill"></span>
</div>
<ul class="graphic-sprint-deliverables">
<li>Projet fil rouge réalisé</li>
<li>Prototype fonctionnel</li>
<li>Documentation technique complète</li>
</ul>
</article>
<article class="graphic-sprint-card" style="--sprint-progress-target: 100%">
<header class="graphic-sprint-card-head">
<p class="graphic-sprint-status graphic-sprint-status--run">Sur mesure</p>
<p class="graphic-sprint-window">Durée adaptée</p>
</header>
<h3 class="graphic-sprint-title">Programme dédié</h3>
<p class="graphic-sprint-objective">
Formation construite intégralement autour de votre projet, vos équipements et vos objectifs. Accompagnement post-formation inclus.
</p>
<div class="graphic-sprint-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100">
<span class="graphic-sprint-progress-fill"></span>
</div>
<ul class="graphic-sprint-deliverables">
<li>Programme sur mesure</li>
<li>Matériel adapté à votre contexte</li>
<li>Suivi post-formation</li>
</ul>
</article>
</div>
</div> </div>
</section> </div>
<section class="circuit-board rounded-2xl p-5 md:p-6 mt-5" data-reveal> <h2>Sujets</h2>
<div class="circuit-title-row"> <div class="subjects">
<span class="circuit-node" aria-hidden="true"></span> <div class="subject"><p class="subject-t">Systèmes électroniques et embarqués</p><p class="subject-d">Architecture matérielle, µC (STM32, ESP32, Teensy), alimentation, debug bench, IPC-A-610, CEM.</p></div>
<h2 class="m-0 text-2xl md:text-3xl">Sujets de formation</h2> <div class="subject"><p class="subject-t">Conception PCB</p><p class="subject-d">KiCad, routage, stackup, DRC/ERC, Gerber, BOM, pick & place.</p></div>
<span class="circuit-pinline" aria-hidden="true"></span> <div class="subject"><p class="subject-t">IoT et protocoles</p><p class="subject-d">Capteurs, MQTT, WiFi, BLE, LoRa, TinyML, du capteur au dashboard.</p></div>
</div> <div class="subject"><p class="subject-t">Énergie et stockage</p><p class="subject-d">Batterie, BMS, télémétrie, supervision, optimisation d'exploitation.</p></div>
<div class="subject"><p class="subject-t">Automatisme industriel</p><p class="subject-d">Siemens S7, Schneider, câblage armoires, variateurs, IEC 60204-1.</p></div>
<div class="subject"><p class="subject-t">Contrôle LED et scénographie</p><p class="subject-d">DMX, Art-Net, DANTE, pilotage LED, intégration spectacle.</p></div>
<div class="subject"><p class="subject-t">Firmware et programmation</p><p class="subject-d">C/C++ embarqué, Python, debug série, interruptions, RTOS.</p></div>
<div class="subject"><p class="subject-t">Workflows agentiques et IA</p><p class="subject-d">Agents IA, génération de code, revue automatisée, documentation assistée.</p></div>
</div>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3"> <h2>Catalogue</h2>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h3 class="m-0 text-lg font-semibold">Systèmes électroniques et embarqués</h3>
<p class="mt-2 mb-0 studio-muted">
Architecture matérielle, microcontrôleurs (STM32, ESP32, Teensy), alimentation, lecture de datasheet, debug sur bench. Conformité IPC-A-610 et CEM.
</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4"> <h3>Initiations — Tous publics</h3>
<h3 class="m-0 text-lg font-semibold">Conception PCB</h3> <div class="grid">
<p class="mt-2 mb-0 studio-muted"> <div class="card"><p class="card-t">Mes Premiers Circuits</p><p class="card-desc">Breadboard, soudure, badge lumineux. Zéro prérequis.</p><p class="card-meta">1 jour · 420€ · 6-8 pers.</p></div>
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). <div class="card"><p class="card-t">Arduino Pour Tous</p><p class="card-desc">Capteurs, LED, moteurs. Kit Arduino à emporter.</p><p class="card-meta">1 jour · 450€ · 6-8 pers.</p></div>
</p> <div class="card"><p class="card-t">Lumière et Son</p><p class="card-desc">LED NeoPixel, DMX512, audio interactif. Kit inclus.</p><p class="card-meta">1 jour · 480€ · 6-8 pers.</p></div>
</article> <div class="card"><p class="card-t">KiCad Découverte</p><p class="card-desc">Du breadboard au PCB. Commandez votre carte chez JLCPCB.</p><p class="card-meta">1 jour · 420€ · 6-8 pers.</p></div>
</div>
<article class="rounded-xl border border-[color:var(--line)] p-4"> <h3>Créatif & Artistes</h3>
<h3 class="m-0 text-lg font-semibold">IoT et protocoles</h3> <div class="grid">
<p class="mt-2 mb-0 studio-muted"> <div class="card"><p class="card-t">Créer son Contrôleur MIDI</p><p class="card-desc">Teensy, potentiomètres, capteurs → instrument MIDI USB.</p><p class="card-meta">1 jour · 450€ · Variante 3h : 220€</p></div>
Capteurs, télémétrie, protocoles terrain (MQTT, HTTP, VE.Direct), connectivité (WiFi, BLE, LoRa), TinyML et IA embarquée. Du capteur au dashboard. <div class="card"><p class="card-t">Programmation Embarquée pour Artistes</p><p class="card-desc">ESP32, capteurs, DMX, Art-Net, OSC → installations interactives.</p><p class="card-meta">1 jour · 480€ · Variante 3h : 240€</p></div>
</p> <div class="card"><p class="card-t">IA pour la Création Artistique</p><p class="card-desc">Stable Diffusion, ComfyUI, Ollama. Image, son, vidéo, performance live.</p><p class="card-meta">1 jour · 500€ · Variante 3h : 250€</p></div>
</article> </div>
<article class="rounded-xl border border-[color:var(--line)] p-4"> <h3>Pro — Avancé</h3>
<h3 class="m-0 text-lg font-semibold">Énergie, stockage et optimisation</h3> <div class="grid">
<p class="mt-2 mb-0 studio-muted"> <div class="card"><p class="card-t">KiCad — Conception PCB</p><p class="card-desc">KiCad 9/10, routage avancé, impédance, DFM, fabrication JLCPCB.</p><p class="card-meta">2 jours · 1 550€ · 6 pers. max</p></div>
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é. <div class="card"><p class="card-t">Dispositifs Culturels</p><p class="card-desc">LED, DMX512, Art-Net, autonomie batteries, intégration terrain spectacle.</p><p class="card-meta">1 jour · 775€ · Finançable Afdas/OPCO</p></div>
</p> </div>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4"> <h3>Enfants & Jeunes</h3>
<h3 class="m-0 text-lg font-semibold">Automatisme industriel</h3> <div class="grid">
<p class="mt-2 mb-0 studio-muted"> <div class="card"><p class="card-t">Premiers Circuits (8-10 ans)</p><p class="card-desc">micro:bit, programmation par blocs, LED, capteurs.</p><p class="card-meta">3 demi-journées · 50€/demi-journée</p></div>
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. <div class="card"><p class="card-t">Maker Junior (10-13 ans)</p><p class="card-desc">Arduino, première soudure, capteurs, projet personnel.</p><p class="card-meta">5 demi-journées · 50€/demi-journée</p></div>
</p> <div class="card"><p class="card-t">Electro Lab (13-16 ans)</p><p class="card-desc">ESP32, KiCad, conception et fabrication de vrai PCB.</p><p class="card-meta">8 sessions · 299€ hors frais de fabrication</p></div>
</article> <div class="card"><p class="card-t">Atelier Parent-Enfant</p><p class="card-desc">3h de découverte en duo. Montage, soudure, LED.</p><p class="card-meta">3h · 59€/binôme · À partir de 8 ans</p></div>
</div>
<article class="rounded-xl border border-[color:var(--line)] p-4"> <div class="cta-box">
<h3 class="m-0 text-lg font-semibold">Contrôle LED et scénographie</h3> <a href="/contact/?from=formation" class="cta-btn">Demander un programme</a>
<p class="mt-2 mb-0 studio-muted"> </div>
Protocoles DMX, Art-Net, DANTE. Pilotage LED, topologies multi-cartes, intégration dans des dispositifs scéniques et événementiels.
</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h3 class="m-0 text-lg font-semibold">Firmware et programmation</h3>
<p class="mt-2 mb-0 studio-muted">
C/C++ embarqué, Python pour l'instrumentation, debug série, gestion mémoire, interruptions, RTOS. Du code qui tourne sur le terrain.
</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h3 class="m-0 text-lg font-semibold">Workflows agentiques et IA appliquée</h3>
<p class="mt-2 mb-0 studio-muted">
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.
</p>
</article>
</div>
</section>
<section class="circuit-board rounded-2xl p-5 md:p-6 mt-5" data-reveal>
<div class="circuit-title-row">
<span class="circuit-node circuit-node--magenta" aria-hidden="true"></span>
<h2 class="m-0 text-2xl md:text-3xl">Catalogue des formations</h2>
<span class="circuit-pinline" aria-hidden="true"></span>
</div>
<p class="section-lead mb-0 mt-3">14 modules prêts à l'emploi. Chaque formation est disponible en inter-entreprises (Lyon) ou intra/sur site (toute la France).</p>
<h3 class="mt-4 mb-2 text-lg font-semibold" style="color:var(--electric)">Initiations — Tous publics</h3>
<div class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-3">
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Mes Premiers Circuits</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">Breadboard, soudure, badge lumineux. Zéro prérequis.</p>
<p class="m-0" style="font-size:0.8rem"><strong>1 jour · 420€</strong> · 6-8 pers.</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Arduino Pour Tous</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">Capteurs, LED, moteurs. Kit Arduino à emporter.</p>
<p class="m-0" style="font-size:0.8rem"><strong>1 jour · 450€</strong> · 6-8 pers.</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Lumière et Son</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">LED NeoPixel, DMX512, audio interactif. Kit LED+son inclus.</p>
<p class="m-0" style="font-size:0.8rem"><strong>1 jour · 480€</strong> · 6-8 pers.</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">KiCad Découverte</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">Du breadboard au PCB. Commandez votre carte chez JLCPCB.</p>
<p class="m-0" style="font-size:0.8rem"><strong>1 jour · 420€</strong> · 6-8 pers.</p>
</article>
</div>
<h3 class="mt-4 mb-2 text-lg font-semibold" style="color:var(--trace-magenta)">Créatif & Artistes</h3>
<div class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-3">
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Créer son Contrôleur MIDI</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">Teensy, potentiomètres, capteurs → instrument MIDI USB personnalisé.</p>
<p class="m-0" style="font-size:0.8rem"><strong>1 jour · 450€</strong> · Variante 3h : 220€</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Programmation Embarquée pour Artistes</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">ESP32, capteurs, DMX, Art-Net, OSC → installations interactives.</p>
<p class="m-0" style="font-size:0.8rem"><strong>1 jour · 480€</strong> · Variante 3h : 240€</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">IA pour la Création Artistique</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">Stable Diffusion, ComfyUI, Ollama. Image, son, vidéo, performance live.</p>
<p class="m-0" style="font-size:0.8rem"><strong>1 jour · 500€</strong> · Variante 3h : 250€</p>
</article>
</div>
<h3 class="mt-4 mb-2 text-lg font-semibold" style="color:var(--accent)">Pro — Avancé</h3>
<div class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-3">
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">KiCad — Conception PCB</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">KiCad 9/10, routage avancé, impedance, DFM, fabrication JLCPCB.</p>
<p class="m-0" style="font-size:0.8rem"><strong>2 jours · 1 550€</strong> · 6 pers. max</p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Dispositifs Culturels</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">LED, DMX512, Art-Net, autonomie batteries, intégration terrain spectacle.</p>
<p class="m-0" style="font-size:0.8rem"><strong>1 jour · 775€</strong> · Finançable Afdas/OPCO</p>
</article>
</div>
<h3 class="mt-4 mb-2 text-lg font-semibold" style="color:var(--trace-green)">Enfants & Jeunes</h3>
<div class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-3">
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Premiers Circuits (8-10 ans)</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">micro:bit, programmation par blocs, LED, capteurs.</p>
<p class="m-0" style="font-size:0.8rem"><strong>3 demi-journées · 89€</strong></p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Maker Junior (10-13 ans)</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">Arduino, première soudure, capteurs, projet personnel.</p>
<p class="m-0" style="font-size:0.8rem"><strong>5 demi-journées · 149€</strong></p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Electro Lab (13-16 ans)</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">ESP32, KiCad, conception et fabrication de vrai PCB.</p>
<p class="m-0" style="font-size:0.8rem"><strong>8 sessions · 299€</strong></p>
</article>
<article class="rounded-xl border border-[color:var(--line)] p-4">
<h4 class="m-0 text-base font-semibold">Atelier Parent-Enfant</h4>
<p class="mt-1 mb-1 studio-muted" style="font-size:0.85rem">3h de découverte en duo. Montage, soudure, LED.</p>
<p class="m-0" style="font-size:0.8rem"><strong>3h · 59€/binôme</strong> · À partir de 8 ans</p>
</article>
</div>
</section>
<section class="circuit-board rounded-2xl p-5 md:p-6 mt-5" data-reveal>
<div class="circuit-title-row">
<span class="circuit-node circuit-node--magenta" aria-hidden="true"></span>
<h2 class="m-0 text-2xl md:text-3xl">Demander un programme</h2>
<span class="circuit-pinline" aria-hidden="true"></span>
</div>
<p class="section-lead mb-0 mt-3">
Décrivez votre besoin en quelques lignes. Je vous propose un programme adapté sous 48h.
</p>
<p class="mb-0 mt-3 studio-muted">
Précisez le public, le niveau, le sujet visé et le format souhaité. Même une idée vague suffit pour démarrer.
</p>
<div class="mt-4 flex flex-wrap gap-3">
<a
href="mailto:contact@lelectronrare.fr?subject=Demande%20de%20formation&body=Bonjour%20Cl%C3%A9ment%2C%0A%0AJe%20suis%20int%C3%A9ress%C3%A9%20par%20une%20formation.%0A%0AContexte%20(entreprise%2C%20%C3%A9cole%2C%20centre%20de%20formation)%20%3A%0ASujet%20vis%C3%A9%20%3A%0APublic%20concern%C3%A9%20%3A%0AFormat%20souhait%C3%A9%20(dur%C3%A9e%2C%20pr%C3%A9sentiel%2Fdistanciel)%20%3A"
class="figma-lab-pill figma-lab-pill--primary"
{...trackAttrs(TRACK_EVENTS.outboundEmailContact, 'mailto:contact@lelectronrare.fr')}
>
Envoyer par mail
</a>
<a
href="https://rdv.saillant.cc/clement/formation"
target="_blank"
rel="noopener noreferrer"
class="figma-lab-pill figma-lab-pill--secondary"
>
Réserver un échange 30 min
</a>
<a
href="https://fr.linkedin.com/in/electron-rare"
target="_blank"
rel="noopener noreferrer"
class="figma-lab-pill figma-lab-pill--secondary"
{...trackAttrs(TRACK_EVENTS.outboundLinkedinContact, 'linkedin.com')}
>
Contacter sur LinkedIn
</a>
</div>
<ul aria-label="Repères" class="mb-0 mt-4 pl-5 studio-muted">
<li>Intervention en présentiel (France entière) ou en distanciel</li>
<li>Matériel de TP fourni ou adapté à votre parc existant</li>
<li>Convention de formation et programme détaillé sur demande</li>
<li>Intervention possible via un organisme certifié Qualiopi pour le financement OPCO</li>
<li>Assuré RC Pro · NDA sur demande</li>
</ul>
</section>
<p class="info">
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
</p>
</main> </main>
<footer class="site-footer border-t border-[var(--line)] py-6 text-sm text-[var(--text-muted)]"> <div class="footer">© 2026 Clément Saillant — L'électron rare · <a href="https://blog.saillant.cc" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">Blog</a> · <a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">LinkedIn</a> · <a href="https://github.com/electron-rare/" target="_blank" rel="noopener" style="color:rgba(255,255,255,0.3);">GitHub</a></div>
<div class="site-shell footer-grid footer-grid--enhanced"> </body>
<div class="footer-col footer-col--brand"> </html>
<p class="m-0 footer-title">L'électron rare</p>
<div class="footer-brand-meta">
<p class="mb-0 footer-copy">
© 2026 Clément Saillant - systèmes électroniques spécifiques, conseil, formation — industrie, culture et projets multi-techniques.
</p>
</div>
</div>
<nav aria-label="Ressources" class="footer-links footer-links--single-rail">
<a href={homeHref} class="footer-link">Accueil</a>
<a href={formationHref} class="footer-link">Formation</a>
<a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener noreferrer me" class="footer-link">LinkedIn</a>
<a href="mailto:contact@lelectronrare.fr" class="footer-link">contact@lelectronrare.fr</a>
<a href="https://github.com/electron-rare/" target="_blank" rel="noopener noreferrer me" class="footer-link">GitHub</a>
<a href={mentionsHref} class="footer-link">Mentions légales</a>
</nav>
</div>
</footer>
</BaseLayout>
+508
View File
@@ -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.";
---
<BaseLayout title={title} description={description}>
{isLaunched ? (
<Fragment>
<SiteHeader brandHref="#top" items={headerItems} quickHref="#contact" />
<main id="main-content" class="site-shell studio-structure pb-12 pt-3">
<section class="structure-grid structure-grid--hero" data-reveal>
<div class="structure-cell structure-cell--hero">
<Hero />
</div>
</section>
<section class="structure-grid structure-grid--systems" data-reveal>
<div class="structure-cell structure-cell--about">
<About />
</div>
</section>
<section class="structure-grid structure-grid--cases" data-reveal>
<div class="structure-cell structure-cell--cases">
<CaseStudies />
</div>
</section>
<section class="structure-grid structure-grid--sprints" data-reveal>
<div class="structure-cell structure-cell--sprints">
<GraphicSprints />
</div>
</section>
<section class="structure-grid structure-grid--faq" data-reveal>
<div class="structure-cell structure-cell--faq">
<Faq />
</div>
</section>
<section class="structure-grid structure-grid--conversion" data-reveal>
<div class="structure-cell structure-cell--contact">
<Contact />
</div>
</section>
</main>
<footer class="site-footer border-t border-[var(--line)] py-6 text-sm text-[var(--text-muted)]">
<div class="site-shell footer-grid footer-grid--enhanced">
<div class="footer-col footer-col--brand">
<p class="m-0 footer-title">L'électron rare</p>
<p class="mb-0 footer-copy">© 2026 Clément Saillant</p>
</div>
<nav aria-label="Ressources" class="footer-links footer-links--single-rail">
<a href="#a-propos" class="footer-link">Approche</a>
<a href="#graphic-sprints-title" class="footer-link">Missions</a>
<a href={formationHref} class="footer-link">Formation</a>
<a href="#faq" class="footer-link">FAQ</a>
<a href="#contact" class="footer-link">Contact</a>
<a href={mentionsHref} class="footer-link">Mentions légales</a>
</nav>
</div>
</footer>
</Fragment>
) : (
<!-- COUNTDOWN — random WebGL or CSS -->
<main class="countdown-page">
<!-- BG A: WebGL shader (PCB traces) -->
<div class="countdown-bg" id="bg-webgl">
<div class="webgl-bg" id="webgl-countdown"></div>
</div>
<!-- BG B: Pure CSS circuit board -->
<div class="countdown-bg" id="bg-css">
<div class="css-circuit">
<div class="circuit-grid">
<!-- Horizontal traces -->
<div class="trace trace-h trace-h1"><span class="pulse"></span></div>
<div class="trace trace-h trace-h2"><span class="pulse"></span></div>
<div class="trace trace-h trace-h3"><span class="pulse"></span></div>
<div class="trace trace-h trace-h4"><span class="pulse"></span></div>
<div class="trace trace-h trace-h5"><span class="pulse"></span></div>
<div class="trace trace-h trace-h6"><span class="pulse"></span></div>
<!-- Vertical traces -->
<div class="trace trace-v trace-v1"><span class="pulse"></span></div>
<div class="trace trace-v trace-v2"><span class="pulse"></span></div>
<div class="trace trace-v trace-v3"><span class="pulse"></span></div>
<div class="trace trace-v trace-v4"><span class="pulse"></span></div>
<div class="trace trace-v trace-v5"><span class="pulse"></span></div>
<div class="trace trace-v trace-v6"><span class="pulse"></span></div>
<!-- Nodes (intersections) -->
<div class="node node-1"></div>
<div class="node node-2"></div>
<div class="node node-3"></div>
<div class="node node-4"></div>
<div class="node node-5"></div>
<div class="node node-6"></div>
<div class="node node-7"></div>
<div class="node node-8"></div>
<div class="node node-9"></div>
<div class="node node-10"></div>
<div class="node node-11"></div>
<div class="node node-12"></div>
<!-- IC chips -->
<div class="ic ic-1">
<span class="ic-label">MCU</span>
<div class="ic-pins ic-pins-l"></div>
<div class="ic-pins ic-pins-r"></div>
</div>
<div class="ic ic-2">
<span class="ic-label">FPGA</span>
<div class="ic-pins ic-pins-l"></div>
<div class="ic-pins ic-pins-r"></div>
</div>
<div class="ic ic-3">
<span class="ic-label">PWR</span>
<div class="ic-pins ic-pins-l"></div>
<div class="ic-pins ic-pins-r"></div>
</div>
<!-- SMD components -->
<div class="smd smd-1"></div>
<div class="smd smd-2"></div>
<div class="smd smd-3"></div>
<div class="smd smd-4"></div>
<div class="smd smd-5"></div>
<div class="smd smd-6"></div>
<div class="smd smd-7"></div>
<div class="smd smd-8"></div>
<!-- Vias -->
<div class="via via-1"></div>
<div class="via via-2"></div>
<div class="via via-3"></div>
<div class="via via-4"></div>
<div class="via via-5"></div>
<div class="via via-6"></div>
</div>
<!-- Scanline sweep -->
<div class="scanline"></div>
<!-- Vignette -->
<div class="css-vignette"></div>
</div>
</div>
<div class="overlay">
<div class="top-bar">
<div class="logo-sprite" id="logo-sprite" aria-label="L'Electron Rare"></div>
<span class="brand-mark">L'ELECTRON RARE</span>
</div>
<div class="center-spacer">
<div class="countdown-hero-text" id="hero-text">
<h1 class="hero-title">
<span class="hero-line hero-line-1">Systèmes</span>
<span class="hero-line hero-line-2">électroniques</span>
<span class="hero-line hero-line-3">spécifiques</span>
</h1>
<div class="hero-divider"></div>
<p class="hero-sub">Conception · Mise au point · Fiabilisation</p>
</div>
<div class="tagline-area" id="tagline-bubble" aria-live="polite">
<span class="tagline-cursor">></span>
<span class="tagline-text" id="tagline"></span>
</div>
</div>
<div class="bottom-bar">
<span class="bottom-left">electronique · automatisme · energie</span>
<span class="bottom-right">mai 2026</span>
</div>
<div class="cartouche">
<p class="cartouche-title">L'électron rare</p>
<p class="cartouche-copy">© 2026 Clément Saillant — systèmes électroniques spécifiques, conseil, formation — industrie, culture et projets multi-techniques.</p>
<p class="cartouche-contact">Contact : <a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener">LinkedIn</a> · <a href="mailto:contact@lelectronrare.fr">contact@lelectronrare.fr</a></p>
</div>
</div>
</main>
)}
</BaseLayout>
{!isLaunched && (
<script>
// ── Random mode pick ──
const useWebGL = Math.random() < 0.5;
const bgWebGL = document.getElementById('bg-webgl');
const bgCSS = document.getElementById('bg-css');
if (useWebGL) { bgCSS?.remove(); }
else { bgWebGL?.remove(); }
document.body.dataset.mode = useWebGL ? 'webgl' : 'css';
// ── Hero text reveal ──
const heroText = document.getElementById('hero-text');
if (heroText) {
setTimeout(() => heroText.classList.add('revealed'), 300);
}
// ── Tagline rotation ──
const messages = [
"Charge des condensateurs en cours...",
"Calibration de l'oscilloscope...",
"Soudure des derniers composants...",
"Vérification de la BOM...",
"Flash du firmware en cours...",
"Mesure de continuité... bip.",
"Routage des dernières pistes...",
"Test de la chaîne d'alimentation...",
"Programmation du bootloader...",
"Revue de schéma en cours...",
"Placement des composants CMS...",
"Passage au four de refusion...",
"Debug JTAG en cours...",
"Vérification du plan de masse...",
"Analyse du spectre RF..."
];
let msgIndex = Math.floor(Math.random() * messages.length);
function rotateMessage() {
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(() => {
el.textContent = messages[msgIndex];
bubble.style.opacity = '1';
bubble.style.transform = 'translateY(0)';
msgIndex = (msgIndex + 1) % messages.length;
}, 400);
}
const sprite = document.getElementById('logo-sprite');
if (sprite) sprite.style.backgroundImage = 'url(/assets/brand/logo-mark.png)';
rotateMessage();
setInterval(rotateMessage, 8000);
// ── WebGL shader (only if picked) ──
if (useWebGL) {
(function () {
var c = document.getElementById('webgl-countdown');
if (!c) return;
var cvs = document.createElement('canvas');
cvs.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;display:block;';
c.appendChild(cvs);
var gl = cvs.getContext('webgl') || cvs.getContext('experimental-webgl');
if (!gl) { cvs.remove(); return; }
function mk(type, src) {
var s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.warn('WebGL shader error:', gl.getShaderInfoLog(s));
return null;
}
return s;
}
var vs = mk(gl.VERTEX_SHADER, 'attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}');
var fs = mk(gl.FRAGMENT_SHADER, [
'precision mediump float;',
'uniform float T;uniform vec2 R;',
'float h(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5);}',
'float h1(float n){return fract(sin(n)*43758.5);}',
'void main(){',
' vec2 uv=gl_FragCoord.xy/R;',
' float t=T*.22;',
' vec3 col=vec3(0.),cy=vec3(.357,.82,.847),wh=vec3(.7,.97,1.);',
' float S=18.;',
' vec2 g=fract(uv*S),cell=floor(uv*S);',
' float lw=.03;',
' float hH=h(cell),hV=h(cell.yx+7.3);',
' float tH=smoothstep(lw,0.,abs(g.y-.5))*step(.38,hH);',
' float tV=smoothstep(lw,0.,abs(g.x-.5))*step(.38,hV);',
' float tr=max(tH,tV);',
' col+=cy*tr*.2;',
' float nd=smoothstep(.11,.01,length(g-.5));',
' col+=cy*nd*step(.38,hH)*step(.38,hV)*.8;',
' for(int i=0;i<8;i++){',
' float fi=float(i),sp=.3+h1(fi*7.31)*.5,ph=h1(fi*3.17+9.);',
' float rw=floor(h1(fi*5.71+1.)*S);',
' float rm=step(0.,.499-abs(uv.y*S-rw-.5));',
' if(rm>.0){',
' float px=fract(t*sp+ph);',
' float pi=smoothstep(.06,.0,abs(uv.x-px));',
' float pl=smoothstep(lw*.5,.0,abs(g.y-.5));',
' col+=wh*pi*pl*step(.38,h(vec2(floor(uv.x*S),rw)))*rm*2.5;',
' }',
' float cl=floor(h1(fi*4.13+11.)*S);',
' float cm=step(0.,.499-abs(uv.x*S-cl-.5));',
' if(cm>.0){',
' float py=fract(t*sp*.75+ph+.5);',
' float pi2=smoothstep(.06,.0,abs(uv.y-py));',
' float pl2=smoothstep(lw*.5,.0,abs(g.x-.5));',
' col+=wh*pi2*pl2*step(.38,h(vec2(cl,floor(uv.y*S))+7.3))*cm*2.5;',
' }',
' }',
' col+=cy*tr*(sin(t*6.28)*.5+.5)*.05;',
' col=1.-exp(-col*1.8);',
' float v=length((uv-.5)*vec2(1.4,1.));',
' col*=1.-smoothstep(.35,1.1,v);',
' col*=.94+.06*sin(gl_FragCoord.y*3.14159);',
' gl_FragColor=vec4(col,1.);',
'}'
].join('\n'));
if (!vs || !fs) return;
var prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
console.warn('WebGL link error:', gl.getProgramInfoLog(prog));
return;
}
gl.useProgram(prog);
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,1,1]), gl.STATIC_DRAW);
var ap = gl.getAttribLocation(prog, 'p');
gl.enableVertexAttribArray(ap);
gl.vertexAttribPointer(ap, 2, gl.FLOAT, false, 0, 0);
var uT = gl.getUniformLocation(prog, 'T');
var uR = gl.getUniformLocation(prog, 'R');
function resize() {
cvs.width = Math.round(cvs.offsetWidth * (window.devicePixelRatio || 1));
cvs.height = Math.round(cvs.offsetHeight * (window.devicePixelRatio || 1));
gl.viewport(0, 0, cvs.width, cvs.height);
}
resize();
window.addEventListener('resize', resize, { passive: true });
var t0 = performance.now();
function frame() {
gl.uniform1f(uT, (performance.now() - t0) * .001);
gl.uniform2f(uR, cvs.width, cvs.height);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(frame);
}
frame();
})();
}
// ── CSS mode: animate random node blinks ──
if (!useWebGL) {
const nodes = document.querySelectorAll('.node');
function blinkNodes() {
nodes.forEach(n => {
if (Math.random() < 0.3) {
n.classList.add('node-active');
setTimeout(() => n.classList.remove('node-active'), 600 + Math.random() * 1200);
}
});
}
blinkNodes();
setInterval(blinkNodes, 2000);
}
</script>
)}
<style>
/* ── Base countdown ── */
.countdown-page { position:relative; min-height:100vh; overflow:hidden; background:#000; color:#fff; font-family:'Manrope',-apple-system,sans-serif; }
.countdown-bg { position:fixed; inset:0; z-index:0; width:100vw; height:100vh; }
.webgl-bg { position:absolute; inset:0; width:100%; height:100%; }
.overlay { position:relative; z-index:10; min-height:100vh; display:flex; flex-direction:column; justify-content:space-between; padding:clamp(20px,3vw,40px) clamp(24px,5vw,60px); pointer-events:none; }
.overlay > * { pointer-events:none; }
.overlay a { pointer-events:auto; }
/* ── Top bar ── */
.top-bar { display:flex; align-items:center; gap:16px; }
.logo-sprite { width:48px; height:48px; background-size:contain; background-repeat:no-repeat; filter:brightness(1.2) drop-shadow(0 0 12px rgba(91,209,216,0.4)); }
.brand-mark { font-size:11px; font-weight:600; letter-spacing:0.25em; color:rgba(255,255,255,0.5); text-transform:uppercase; }
/* ── Hero text ── */
.center-spacer { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:24px; }
.countdown-hero-text { text-align:center; opacity:0; transform:translateY(20px); transition:opacity 1.2s ease, transform 1.2s ease; }
.countdown-hero-text.revealed { opacity:1; transform:translateY(0); }
.hero-title { margin:0; line-height:1.1; }
.hero-line { display:block; font-weight:800; letter-spacing:-0.03em; color:rgba(255,255,255,0.85); }
.hero-line-1 { font-size:clamp(32px,5vw,64px); opacity:0; animation:fadeUp 0.8s ease 0.4s forwards; }
.hero-line-2 { font-size:clamp(36px,6vw,72px); color:#5bd1d8; opacity:0; animation:fadeUp 0.8s ease 0.7s forwards; }
.hero-line-3 { font-size:clamp(32px,5vw,64px); opacity:0; animation:fadeUp 0.8s ease 1.0s forwards; }
.hero-divider { width:60px; height:2px; margin:16px auto; background:linear-gradient(90deg,transparent,#5bd1d8,transparent); opacity:0; animation:fadeUp 0.6s ease 1.3s forwards; }
.hero-sub { font-size:clamp(12px,1.4vw,16px); color:rgba(255,255,255,0.35); letter-spacing:0.15em; text-transform:uppercase; margin:0; opacity:0; animation:fadeUp 0.6s ease 1.5s forwards; }
@keyframes fadeUp { to { opacity:1; transform:translateY(0); } from { opacity:0; transform:translateY(12px); } }
/* ── Tagline ── */
.tagline-area { display:flex; align-items:center; gap:8px; padding:10px 20px; background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.06); border-radius:8px; transition:opacity 0.4s ease, transform 0.4s ease; pointer-events:auto; }
.tagline-cursor { font-family:monospace; font-size:14px; color:#5bd1d8; animation:blink 1s step-end infinite; }
@keyframes blink { 50% { opacity:0; } }
.tagline-text { font-family:monospace; font-size:clamp(11px,1.3vw,14px); color:rgba(255,255,255,0.4); }
/* ── Bottom bar ── */
.bottom-bar { display:flex; justify-content:space-between; align-items:flex-end; }
.bottom-left, .bottom-right { font-size:clamp(10px,1.1vw,12px); color:rgba(255,255,255,0.15); letter-spacing:0.08em; text-transform:uppercase; }
/* ── Cartouche ── */
.cartouche { position:fixed; bottom:clamp(16px,2vw,24px); right:clamp(16px,2vw,24px); max-width:320px; padding:14px 18px; background:rgba(0,0,0,0.5); backdrop-filter:blur(12px); border:1px solid rgba(255,255,255,0.06); border-radius:12px; pointer-events:auto; }
.cartouche-title { font-size:13px; font-weight:700; color:rgba(255,255,255,0.6); margin:0 0 6px; }
.cartouche-copy { font-size:9px; color:rgba(255,255,255,0.25); margin:0 0 4px; line-height:1.4; }
.cartouche-contact { font-size:9px; color:rgba(255,255,255,0.25); margin:0; }
.cartouche-contact a { color:#5bd1d8; text-decoration:none; }
/* ── CSS Circuit Board Background ── */
.css-circuit { position:absolute; inset:0; background:#020a0d; overflow:hidden; }
.circuit-grid { position:absolute; inset:5%; }
/* Traces */
.trace { position:absolute; background:rgba(91,209,216,0.08); }
.trace-h { height:1px; left:0; right:0; }
.trace-v { width:1px; top:0; bottom:0; }
.trace-h1 { top:8%; } .trace-h2 { top:22%; } .trace-h3 { top:38%; }
.trace-h4 { top:55%; } .trace-h5 { top:72%; } .trace-h6 { top:88%; }
.trace-v1 { left:10%; } .trace-v2 { left:25%; } .trace-v3 { left:42%; }
.trace-v4 { left:58%; } .trace-v5 { left:75%; } .trace-v6 { left:90%; }
/* Pulse on traces */
.trace .pulse { position:absolute; border-radius:50%; }
.trace-h .pulse { width:40px; height:3px; top:-1px; background:linear-gradient(90deg,transparent,#5bd1d8,transparent); animation:pulseH 4s linear infinite; }
.trace-v .pulse { width:3px; height:40px; left:-1px; background:linear-gradient(180deg,transparent,#5bd1d8,transparent); animation:pulseV 5s linear infinite; }
.trace-h1 .pulse { animation-delay:0s; } .trace-h2 .pulse { animation-delay:0.7s; } .trace-h3 .pulse { animation-delay:1.4s; }
.trace-h4 .pulse { animation-delay:2.1s; } .trace-h5 .pulse { animation-delay:0.3s; } .trace-h6 .pulse { animation-delay:1.8s; }
.trace-v1 .pulse { animation-delay:0.5s; } .trace-v2 .pulse { animation-delay:1.2s; } .trace-v3 .pulse { animation-delay:2.5s; }
.trace-v4 .pulse { animation-delay:0.9s; } .trace-v5 .pulse { animation-delay:1.7s; } .trace-v6 .pulse { animation-delay:3.1s; }
@keyframes pulseH { 0% { left:-40px; } 100% { left:100%; } }
@keyframes pulseV { 0% { top:-40px; } 100% { top:100%; } }
/* Nodes */
.node { position:absolute; width:6px; height:6px; border-radius:50%; background:rgba(91,209,216,0.15); border:1px solid rgba(91,209,216,0.2); transition:all 0.6s ease; }
.node-active { background:#5bd1d8; box-shadow:0 0 12px rgba(91,209,216,0.6), 0 0 24px rgba(91,209,216,0.3); }
.node-1 { top:8%; left:10%; } .node-2 { top:8%; left:42%; } .node-3 { top:8%; left:75%; }
.node-4 { top:22%; left:25%; } .node-5 { top:38%; left:58%; } .node-6 { top:38%; left:90%; }
.node-7 { top:55%; left:10%; } .node-8 { top:55%; left:42%; } .node-9 { top:72%; left:25%; }
.node-10 { top:72%; left:75%; } .node-11 { top:88%; left:42%; } .node-12 { top:88%; left:58%; }
/* IC chips */
.ic { position:absolute; background:rgba(91,209,216,0.04); border:1px solid rgba(91,209,216,0.12); border-radius:2px; display:flex; align-items:center; justify-content:center; }
.ic-label { font-family:monospace; font-size:8px; color:rgba(91,209,216,0.4); letter-spacing:0.1em; }
.ic-pins { position:absolute; }
.ic-pins-l { left:-4px; top:20%; bottom:20%; width:4px; border-right:1px solid rgba(91,209,216,0.15); background:repeating-linear-gradient(180deg,transparent,transparent 3px,rgba(91,209,216,0.1) 3px,rgba(91,209,216,0.1) 5px); }
.ic-pins-r { right:-4px; top:20%; bottom:20%; width:4px; border-left:1px solid rgba(91,209,216,0.15); background:repeating-linear-gradient(180deg,transparent,transparent 3px,rgba(91,209,216,0.1) 3px,rgba(91,209,216,0.1) 5px); }
.ic-1 { width:60px; height:36px; top:18%; left:55%; }
.ic-2 { width:50px; height:30px; top:50%; left:20%; }
.ic-3 { width:44px; height:24px; top:78%; left:68%; }
/* SMD resistors/caps */
.smd { position:absolute; width:12px; height:5px; background:rgba(241,194,122,0.08); border:1px solid rgba(241,194,122,0.15); border-radius:1px; }
.smd-1 { top:12%; left:32%; } .smd-2 { top:15%; left:65%; transform:rotate(90deg); }
.smd-3 { top:30%; left:15%; } .smd-4 { top:45%; left:80%; transform:rotate(90deg); }
.smd-5 { top:60%; left:48%; } .smd-6 { top:65%; left:35%; transform:rotate(90deg); }
.smd-7 { top:82%; left:52%; } .smd-8 { top:35%; left:72%; }
/* Vias */
.via { position:absolute; width:8px; height:8px; border-radius:50%; border:1px solid rgba(91,209,216,0.2); background:radial-gradient(circle,rgba(91,209,216,0.15) 30%,transparent 70%); }
.via::after { content:''; position:absolute; inset:2px; border-radius:50%; background:rgba(91,209,216,0.1); }
.via-1 { top:14%; left:48%; } .via-2 { top:28%; left:82%; } .via-3 { top:42%; left:18%; }
.via-4 { top:62%; left:62%; } .via-5 { top:76%; left:38%; } .via-6 { top:85%; left:85%; }
/* Scanline sweep */
.scanline { position:absolute; inset:0; background:linear-gradient(180deg,transparent 0%,transparent 48%,rgba(91,209,216,0.03) 50%,transparent 52%,transparent 100%); background-size:100% 200%; animation:scanline 8s linear infinite; pointer-events:none; }
@keyframes scanline { 0% { background-position:0 0; } 100% { background-position:0 100%; } }
/* CSS vignette */
.css-vignette { position:absolute; inset:0; background:radial-gradient(ellipse at center,transparent 30%,rgba(0,0,0,0.7) 100%); pointer-events:none; }
/* ── Mobile ── */
@media (max-width:640px) {
.bottom-bar { flex-direction:column; align-items:center; gap:4px; }
.cartouche { position:relative; bottom:auto; right:auto; max-width:100%; margin-top:12px; text-align:center; }
.hero-line-1, .hero-line-3 { font-size:28px; }
.hero-line-2 { font-size:32px; }
}
/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
.trace .pulse, .scanline { animation:none !important; }
.hero-line-1, .hero-line-2, .hero-line-3, .hero-divider, .hero-sub { animation:none !important; opacity:1 !important; }
.countdown-hero-text { opacity:1 !important; transform:none !important; transition:none !important; }
}
</style>
+374 -181
View File
@@ -1,188 +1,381 @@
--- ---
import BaseLayout from '@/layouts/BaseLayout.astro'; import { BmuViewer } from '@/components/BmuViewer';
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.";
--- ---
<BaseLayout title={title} description={description}> <html lang="fr">
{isLaunched ? ( <head>
<Fragment> <meta charset="UTF-8" />
<SiteHeader brandHref="#top" items={headerItems} quickHref="#contact" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<main id="main-content" class="site-shell studio-structure pb-12 pt-3"> <title>L'électron rare — Systèmes électroniques spécifiques</title>
<section class="structure-grid structure-grid--hero" data-reveal> <meta name="description" content="Conception, mise au point et fiabilisation de systèmes électroniques spécifiques, automatismes et ensembles liés à l'énergie. Consulting et formation PCB, KiCad, µC." />
<div class="structure-cell structure-cell--hero"> <meta property="og:title" content="L'électron rare — Systèmes électroniques spécifiques" />
<Hero /> <meta property="og:description" content="Conception, mise au point et fiabilisation de systèmes électroniques spécifiques. Consulting et formation." />
</div> <meta property="og:image" content="https://www.lelectronrare.fr/assets/og-cover.jpg" />
</section> <meta property="og:url" content="https://www.lelectronrare.fr/" />
<section class="structure-grid structure-grid--systems" data-reveal> <meta property="og:type" content="website" />
<div class="structure-cell structure-cell--about"> <meta property="og:site_name" content="L'électron rare" />
<About /> <meta name="twitter:card" content="summary_large_image" />
</div> <meta name="twitter:title" content="L'électron rare — Systèmes électroniques spécifiques" />
</section> <meta name="twitter:description" content="Conception, mise au point et fiabilisation de systèmes électroniques spécifiques." />
<section class="structure-grid structure-grid--cases" data-reveal> <meta name="twitter:image" content="https://www.lelectronrare.fr/assets/og-cover.jpg" />
<div class="structure-cell structure-cell--cases"> <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<CaseStudies /> <link rel="preload" href="/assets/models3d/bmu_v2.glb" as="fetch" crossorigin />
</div> <link rel="preload" href="/assets/models3d/i2c_repeater.glb" as="fetch" crossorigin />
</section> <link rel="preload" href="/assets/models3d/mosfet_switch.glb" as="fetch" crossorigin />
<section class="structure-grid structure-grid--sprints" data-reveal> <script type="application/ld+json">
<div class="structure-cell structure-cell--sprints"> {
<GraphicSprints /> "@context": "https://schema.org",
</div> "@type": "ProfessionalService",
</section> "name": "L'électron rare",
<section class="structure-grid structure-grid--faq" data-reveal> "description": "Conception, mise au point et fiabilisation de systèmes électroniques spécifiques, automatismes et ensembles liés à l'énergie.",
<div class="structure-cell structure-cell--faq"> "url": "https://www.lelectronrare.fr",
<Faq /> "email": "contact@lelectronrare.fr",
</div> "founder": { "@type": "Person", "name": "Clément Saillant" },
</section> "areaServed": "France",
<section class="structure-grid structure-grid--conversion" data-reveal> "serviceType": ["Conception électronique", "Formation PCB et embarqué", "Consulting technique", "Automatisme industriel"],
<div class="structure-cell structure-cell--contact"> "sameAs": ["https://fr.linkedin.com/in/electron-rare", "https://github.com/electron-rare/"]
<Contact />
</div>
</section>
</main>
<footer class="site-footer border-t border-[var(--line)] py-6 text-sm text-[var(--text-muted)]">
<div class="site-shell footer-grid footer-grid--enhanced">
<div class="footer-col footer-col--brand">
<p class="m-0 footer-title">L'électron rare</p>
<p class="mb-0 footer-copy">© 2026 Clément Saillant</p>
</div>
<nav aria-label="Ressources" class="footer-links footer-links--single-rail">
<a href="#a-propos" class="footer-link">Approche</a>
<a href="#graphic-sprints-title" class="footer-link">Missions</a>
<a href={formationHref} class="footer-link">Formation</a>
<a href="#faq" class="footer-link">FAQ</a>
<a href="#contact" class="footer-link">Contact</a>
<a href={mentionsHref} class="footer-link">Mentions légales</a>
</nav>
</div>
</footer>
</Fragment>
) : (
<!-- COUNTDOWN — WebGL placeholder -->
<main class="countdown-page">
<!-- WebGL goes here -->
<div class="webgl-bg" id="webgl-countdown"></div>
<div class="overlay">
<div class="top-bar">
<div class="logo-sprite" id="logo-sprite" aria-label="L'Electron Rare"></div>
<span class="brand-mark">L'ELECTRON RARE</span>
</div>
<div class="center-spacer">
<div class="tagline-area" id="tagline-bubble" aria-live="polite">
<span class="tagline-cursor">></span>
<span class="tagline-text" id="tagline"></span>
</div>
</div>
<div class="bottom-bar">
<span class="bottom-left">electronique · automatisme · energie</span>
<span class="bottom-right">mai 2026</span>
</div>
<div class="cartouche">
<p class="cartouche-title">L'électron rare</p>
<p class="cartouche-copy">© 2026 Clément Saillant — systèmes électroniques spécifiques, conseil, formation — industrie, culture et projets multi-techniques.</p>
<p class="cartouche-contact">Contact : <a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener">LinkedIn</a> · <a href="mailto:contact@lelectronrare.fr">contact@lelectronrare.fr</a></p>
</div>
</div>
</main>
)}
</BaseLayout>
{!isLaunched && (
<script>
const messages = [
"Charge des condensateurs en cours...",
"Calibration de l'oscilloscope...",
"Soudure des derniers composants...",
"Vérification de la BOM...",
"Flash du firmware en cours...",
"Mesure de continuité... bip.",
"Routage des dernières pistes...",
"Test de la chaîne d'alimentation...",
"Programmation du bootloader...",
"Revue de schéma en cours...",
"Placement des composants CMS...",
"Passage au four de refusion...",
"Debug JTAG en cours...",
"Vérification du plan de masse...",
"Analyse du spectre RF..."
];
let msgIndex = Math.floor(Math.random() * messages.length);
function rotateMessage() {
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(() => {
el.textContent = messages[msgIndex];
bubble.style.opacity = '1';
bubble.style.transform = 'translateY(0)';
msgIndex = (msgIndex + 1) % messages.length;
}, 400);
} }
</script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html { background: #06080b; color: #fff; overflow-x: hidden; }
body { background: #06080b; min-height: 100vh; font-family: 'Courier New', monospace; }
.scroll-track { height: 1000vh; position: relative; z-index: 1; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; }
const sprite = document.getElementById('logo-sprite'); /* Loader */
if (sprite) sprite.style.backgroundImage = 'url(/assets/brand/logo-mark.png)'; .loader { position: fixed; inset: 0; z-index: 9999; background: #06080b; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; transition: opacity 0.8s ease, visibility 0.8s ease; }
.loader.done { opacity: 0; visibility: hidden; pointer-events: none; }
.loader-logo { width: clamp(60px, 8vw, 100px); height: auto; filter: drop-shadow(0 0 20px rgba(91,209,216,0.3)); }
.loader-name { font-size: clamp(20px, 2.5vw, 32px); font-weight: 800; color: rgba(255,255,255,0.8); letter-spacing: 0.06em; }
.loader-bar { width: clamp(180px, 30vw, 280px); height: 2px; background: rgba(255,255,255,0.06); border-radius: 1px; overflow: hidden; }
.loader-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #5bd1d8, #a78bfa); transition: width 0.3s ease; }
.loader-status { font-size: 10px; color: rgba(255,255,255,0.25); letter-spacing: 0.12em; text-transform: uppercase; }
.loader-tagline { font-size: 12px; color: rgba(255,255,255,0.3); letter-spacing: 0.08em; margin-bottom: 20px; }
.loader-actions { margin-top: 24px; }
.loader-skip { display: inline-block; padding: 10px 24px; border: 1px solid rgba(91,209,216,0.3); border-radius: 8px; color: rgba(91,209,216,0.7); font-size: 12px; text-decoration: none; letter-spacing: 0.04em; transition: all 0.2s; }
.loader-skip:hover { border-color: #5bd1d8; color: #5bd1d8; background: rgba(91,209,216,0.08); }
.loader-a11y { margin-top: 20px; font-size: 10px; color: rgba(255,255,255,0.15); }
.loader-a11y a { color: rgba(255,255,255,0.25); text-decoration: none; }
.loader-a11y a:hover { color: #5bd1d8; }
rotateMessage(); .ov { position: fixed; z-index: 3; pointer-events: none; opacity: 0; transform: translateY(6px); transition: opacity 0.5s, transform 0.5s; }
setInterval(rotateMessage, 8000); .ov.on { opacity: 1; transform: translateY(0); }
</script>
)}
<style> /* Hero */
.countdown-page { position:relative; min-height:100vh; overflow:hidden; background:#000; color:#fff; font-family:'Manrope',-apple-system,sans-serif; } .ov-hero { inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; }
.webgl-bg { position:fixed; inset:0; z-index:0; width:100vw; height:100vh; pointer-events:auto; } .hero-bottom { display: flex; flex-direction: column; align-items: center; padding-bottom: clamp(60px, 10vh, 120px); }
.overlay { position:relative; z-index:10; min-height:100vh; display:flex; flex-direction:column; justify-content:space-between; padding:clamp(20px,3vw,40px) clamp(24px,5vw,60px); pointer-events:none; } .hero-t { text-align: center; margin: 0; line-height: 1.1; }
.overlay > * { pointer-events:none; } .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; }
.overlay a { pointer-events:auto; } .hl2 { display: block; font-size: clamp(32px, 5.5vw, 64px); font-weight: 800; color: #5bd1d8; letter-spacing: -0.02em; }
.top-bar { display:flex; align-items:center; gap:16px; } .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; }
.logo-sprite { width:48px; height:48px; background-size:contain; background-repeat:no-repeat; filter:brightness(1.2) drop-shadow(0 0 12px rgba(91,209,216,0.4)); } .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; }
.brand-mark { font-size:11px; font-weight:600; letter-spacing:0.25em; color:rgba(255,255,255,0.5); text-transform:uppercase; }
.center-spacer { flex:1; display:flex; align-items:center; justify-content:center; } /* Header */
.tagline-area { display:flex; align-items:center; gap:8px; padding:10px 20px; background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.06); border-radius:8px; transition:opacity 0.4s ease, transform 0.4s ease; pointer-events:auto; } .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; }
.tagline-cursor { font-family:monospace; font-size:14px; color:#5bd1d8; animation:blink 1s step-end infinite; } .hdr-left { display: flex; align-items: center; gap: 10px; pointer-events: auto; }
@keyframes blink { 50% { opacity:0; } } .hdr-logo { width: 36px; height: auto; filter: drop-shadow(0 0 12px rgba(91,209,216,0.3)); }
.tagline-text { font-family:monospace; font-size:clamp(11px,1.3vw,14px); color:rgba(255,255,255,0.4); } .hdr-name { font-size: 16px; font-weight: 800; color: rgba(255,255,255,0.8); letter-spacing: 0.04em; }
.bottom-bar { display:flex; justify-content:space-between; align-items:flex-end; } .hdr-nav { display: flex; align-items: center; gap: 8px; pointer-events: auto; }
.bottom-left, .bottom-right { font-size:clamp(10px,1.1vw,12px); color:rgba(255,255,255,0.15); letter-spacing:0.08em; text-transform:uppercase; } .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; }
.cartouche { position:fixed; bottom:clamp(16px,2vw,24px); right:clamp(16px,2vw,24px); max-width:320px; padding:14px 18px; background:rgba(0,0,0,0.5); backdrop-filter:blur(12px); border:1px solid rgba(255,255,255,0.06); border-radius:12px; pointer-events:auto; } .hdr-link:hover { color: #5bd1d8; }
.cartouche-title { font-size:13px; font-weight:700; color:rgba(255,255,255,0.6); margin:0 0 6px; } .hdr-social { font-weight: 800; font-size: 11px; text-transform: uppercase; padding: 8px 10px; opacity: 0.5; }
.cartouche-copy { font-size:9px; color:rgba(255,255,255,0.25); margin:0 0 4px; line-height:1.4; } .hdr-social:hover { opacity: 1; }
.cartouche-contact { font-size:9px; color:rgba(255,255,255,0.25); margin:0; } .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; }
.cartouche-contact a { color:#5bd1d8; text-decoration:none; } .hdr-cta:hover { background: rgba(91,209,216,0.25); }
@media (max-width:640px) { .bottom-bar{flex-direction:column;align-items:center;gap:4px;} .cartouche{position:relative;bottom:auto;right:auto;max-width:100%;margin-top:12px;text-align:center;} } .burger { display: none; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 8px; pointer-events: auto; }
</style> .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; }
}
</style>
</head>
<body data-da-variant="preview">
<!-- Loader -->
<div class="loader" id="loader">
<img src="/assets/brand/logo-mark.svg" alt="L'électron rare" class="loader-logo" />
<p class="loader-name">L'électron rare</p>
<p class="loader-tagline">Systèmes électroniques spécifiques</p>
<div class="loader-bar"><div class="loader-fill" id="loader-fill"></div></div>
<p class="loader-status" id="loader-status">Chargement des modèles 3D...</p>
<div class="loader-actions">
<a href="/conception/" class="loader-skip">Accéder au site sans 3D →</a>
</div>
<p class="loader-a11y">Version accessible : <a href="/conception/">Conception</a> · <a href="/formation/">Formation</a> · <a href="/consulting/">Consulting</a> · <a href="/contact/">Contact</a></p>
</div>
<BmuViewer client:load />
<div class="prog" id="prog"></div>
<div class="dots" id="dots">
<div class="dot on" data-s="hero" data-p="0.03" data-l="Hero"></div>
<div class="dot" data-s="hero-to-exp" data-p="0.09" data-l=""></div>
<div class="dot" data-s="exp-elec" data-p="0.14" data-l="Élec"></div>
<div class="dot" data-s="exp-auto" data-p="0.18" data-l="Auto"></div>
<div class="dot" data-s="exp-energie" data-p="0.22" data-l="Énergie"></div>
<div class="dot" data-s="exp-dispositifs" data-p="0.255" data-l="Dispositifs"></div>
<div class="dot" data-s="exp-consulting" data-p="0.285" data-l="Consulting"></div>
<div class="dot" data-s="eclate-recul" data-p="0.31" data-l=""></div>
<div class="dot" data-s="eclate-sep" data-p="0.34" data-l=""></div>
<div class="dot" data-s="eclate-plat" data-p="0.36" data-l=""></div>
<div class="dot" data-s="cas" data-p="0.53" data-l="Cas concrets"></div>
<div class="dot" data-s="media" data-p="0.70" data-l="Médias"></div>
<div class="dot" data-s="formats" data-p="0.85" data-l="Services"></div>
<div class="dot" data-s="contact" data-p="0.95" data-l="Contact"></div>
</div>
<!-- HEADER -->
<header class="site-hdr">
<div class="hdr-left">
<img src="/assets/brand/logo-mark.svg" alt="" class="hdr-logo" />
<span class="hdr-name">L'électron rare</span>
</div>
<nav class="hdr-nav">
<a href="/conception/" class="hdr-link">Conception</a>
<a href="/formation/" class="hdr-link">Formation</a>
<a href="/consulting/" class="hdr-link">Consulting</a>
<a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener" class="hdr-link hdr-social" title="LinkedIn">in</a>
<a href="https://github.com/electron-rare/" target="_blank" rel="noopener" class="hdr-link hdr-social" title="GitHub">gh</a>
<a href="/contact/" class="hdr-cta">Contact</a>
</nav>
<button class="burger" id="burger-btn" aria-label="Menu">☰</button>
</header>
<div class="mobile-menu" id="mobile-menu">
<button class="close-btn" id="close-menu">✕</button>
<a href="/">Accueil</a><a href="/conception/">Conception</a><a href="/formation/">Formation</a><a href="/consulting/">Consulting</a><a href="/contact/">Contact</a>
</div>
<!-- HERO -->
<div class="ov ov-hero on" data-sec="hero">
<div class="hero-bottom">
<h1 class="hero-t">
<span class="hl1">Systèmes</span>
<span class="hl2">électroniques</span>
<span class="hl3">spécifiques</span>
</h1>
<p class="hero-s">électronique · automatisme · énergie</p>
<p class="hero-s2">Conception · Mise au point · Fiabilisation · Consulting · Formation</p>
</div>
</div>
<!-- FORMATS -->
<div class="ov ov-formats" data-sec="formats">
<div class="fmt">
<p class="fmt-t" style="color:#5bd1d8">Conception</p>
<p class="fmt-d">Schéma · PCB · Firmware · Validation</p>
</div>
<div class="fmt">
<p class="fmt-t" style="color:#a78bfa">Formation</p>
<p class="fmt-d">PCB · KiCad · µC · Soudure</p>
</div>
<div class="fmt">
<p class="fmt-t" style="color:#f59e0b">Consulting</p>
<p class="fmt-d">Audit · Diagnostic · Transfert</p>
</div>
</div>
<!-- CONFIANCE -->
<div class="trust" id="trust">
<p class="trust-k">Ils m'ont fait confiance</p>
<p class="trust-names">KXKM · Spectacle vivant · Secteur ferroviaire · Industrie automatisée · Écoles d'art numérique</p>
</div>
<div class="scroll-track"></div>
<!-- SEO: texte indexable caché -->
<div class="sr-only" aria-hidden="false">
<h1>L'électron rare — Systèmes électroniques spécifiques</h1>
<p>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.</p>
<h2>Domaines d'expertise</h2>
<ul>
<li>Électronique spécifique : cartes, interfaces, capteurs, alimentation</li>
<li>Instrumentation et automatisme : bancs, automates, variateurs, protocoles terrain</li>
<li>Énergie et stockage : batterie, conversion, supervision, télémétrie</li>
<li>Dispositifs pour le réel : audio, LED, scène, robustesse terrain</li>
<li>Consulting et formation : audit, transfert, pédagogie, projet</li>
</ul>
<h2>Cas concrets</h2>
<ul>
<li>Industries créatives : système audio embarqué sur batterie pour le spectacle (KXKM)</li>
<li>Industrie : diagnostic et remise en service d'une ligne automatisée</li>
<li>Formation : formation PCB et embarqué pour une école d'art numérique</li>
</ul>
<h2>Services</h2>
<ul>
<li>Conception : schéma, PCB, firmware, validation</li>
<li>Formation : PCB, KiCad, microcontrôleurs, soudure</li>
<li>Consulting : audit, diagnostic, transfert</li>
</ul>
<h2>Ils m'ont fait confiance</h2>
<p>KXKM (spectacle vivant), secteur ferroviaire, industrie automatisée, écoles d'art numérique.</p>
<h2>À propos</h2>
<p>Clément Saillant — 10 ans d'expérience en systèmes électroniques. Contributeur open-source KiCad et KXKM. Ambassadeur IA appliquée à l'électronique.</p>
<p>Contact : <a href="/contact/">Parlons de votre projet</a> — contact@lelectronrare.fr · <a href="https://blog.saillant.cc">Blog technique</a></p>
</div>
<noscript>
<div style="position:fixed;inset:0;z-index:9999;background:#06080b;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;font-family:monospace;text-align:center;padding:40px;">
<img src="/assets/photos/bmu-assembly.jpg" alt="BMU v2 Assembly" style="width:clamp(200px,50vw,500px);border-radius:12px;margin-bottom:20px;" />
<h1 style="font-size:24px;">L'électron rare</h1>
<p style="color:rgba(255,255,255,0.5);margin:12px 0;">Systèmes électroniques spécifiques</p>
<a href="https://www.lelectronrare.fr/" style="color:#5bd1d8;margin-top:16px;">Accéder au site →</a>
</div>
</noscript>
<div class="inf">L'électron rare · Clément Saillant</div>
<!-- GTM for A/B tracking -->
<script>
window.dataLayer = window.dataLayer || [];
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!=='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-5SLM67QF');
</script>
<script>
const prog = document.getElementById('prog');
const dots = [...document.querySelectorAll('.dot')];
const overlays = [...document.querySelectorAll('.ov')];
const RANGES = [
['hero',0,0.10],['hero-to-exp',0.10,0.16],
['exp-elec',0.16,0.20],['exp-auto',0.20,0.24],['exp-energie',0.24,0.28],
['exp-dispositifs',0.28,0.31],['exp-consulting',0.31,0.34],
['eclate-recul',0.34,0.36],['eclate-sep',0.36,0.39],['eclate-plat',0.39,0.41],
['cas',0.41,0.65],
['media',0.65,0.81],['formats',0.81,0.91],['contact',0.91,1.0],
];
function update() {
const max = document.documentElement.scrollHeight - window.innerHeight;
const t = max > 0 ? window.scrollY / max : 0;
if (prog) prog.style.width = (t*100)+'%';
let active = 'hero';
for (const [id,s,e] of RANGES) { if (t>=s&&t<=e){active=id;break;} }
overlays.forEach(el => el.classList.toggle('on', el.getAttribute('data-sec')===active));
dots.forEach(d => d.classList.toggle('on', d.getAttribute('data-s')===active));
}
window.addEventListener('scroll', update, {passive:true});
update();
// 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/' });
});
});
// Burger menu
document.getElementById('burger-btn')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.add('open'));
document.getElementById('close-menu')?.addEventListener('click',()=>document.getElementById('mobile-menu')?.classList.remove('open'));
// Preloader — télécharge les assets en arrière-plan avec progression réelle
const loader = document.getElementById('loader');
const loaderFill = document.getElementById('loader-fill');
const loaderStatus = document.getElementById('loader-status');
const ASSETS = [
{ url: '/assets/models3d/bmu_v2.glb', label: 'Carte BMU principale', size: 5500000 },
{ url: '/assets/models3d/i2c_repeater.glb', label: 'Carte I2C Repeater', size: 1800000 },
{ url: '/assets/models3d/mosfet_switch.glb', label: 'Cartes Mosfet Switch', size: 1500000 },
];
const TOTAL_SIZE = ASSETS.reduce((s, a) => s + a.size, 0);
let loadedBytes = 0;
let allDone = false;
function updateProgress(bytes, label) {
loadedBytes += bytes;
const pct = Math.min(85, Math.round(loadedBytes / TOTAL_SIZE * 85));
if (loaderFill) loaderFill.style.width = pct + '%';
if (loaderStatus) loaderStatus.textContent = label;
}
async function preloadAsset(asset) {
try {
const res = await fetch(asset.url);
const reader = res.body?.getReader();
if (!reader) { updateProgress(asset.size, asset.label); return; }
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
received += value.length;
updateProgress(value.length, asset.label);
}
} catch (e) {
updateProgress(asset.size, asset.label + ' (cache)');
}
}
function hideLoader() {
if (allDone) return;
allDone = true;
if (loaderFill) loaderFill.style.width = '100%';
if (loaderStatus) loaderStatus.textContent = 'Prêt';
setTimeout(() => { if (loader) loader.classList.add('done'); }, 400);
}
// Phase 1 : preload les GLB en streaming
(async () => {
if (loaderStatus) loaderStatus.textContent = 'Téléchargement des modèles 3D...';
for (const asset of ASSETS) {
await preloadAsset(asset);
}
if (loaderStatus) loaderStatus.textContent = 'Initialisation WebGL...';
if (loaderFill) loaderFill.style.width = '90%';
// Phase 2 : attendre le canvas Three.js
const checkCanvas = setInterval(() => {
const cvs = document.querySelector('canvas');
if (cvs && cvs.width > 0) {
clearInterval(checkCanvas);
if (loaderFill) loaderFill.style.width = '95%';
if (loaderStatus) loaderStatus.textContent = 'Compilation shaders...';
setTimeout(hideLoader, 600);
}
}, 150);
})();
// Fallback 15s max
setTimeout(hideLoader, 15000);
</script>
</body>
</html>
+290 -163
View File
@@ -1,177 +1,304 @@
--- ---
import BaseLayout from '@/layouts/BaseLayout.astro'; import { BmuViewer } from '@/components/BmuViewer';
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 }
];
--- ---
<BaseLayout> <html lang="fr">
<!-- Loading screen — BMU photo while WebGL loads --> <head>
<div class="loading-screen" id="loading-screen"> <meta charset="UTF-8" />
<div class="loading-content"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<img src="/assets/photos/bmu-assembly.jpg" alt="BMU v2 Assembly" class="loading-bmu-img" /> <title>L'électron rare — Systèmes électroniques spécifiques</title>
<h1 class="loading-title">L'<span class="loading-electric">é</span>lectron rare</h1> <meta name="description" content="Conception, mise au point et fiabilisation de systèmes électroniques spécifiques, automatismes et ensembles liés à l'énergie. Consulting et formation PCB, KiCad, µC." />
<div class="loading-bar"> <meta property="og:title" content="L'électron rare — Systèmes électroniques spécifiques" />
<div class="loading-bar-fill" id="loading-bar-fill"></div> <meta property="og:description" content="Conception, mise au point et fiabilisation de systèmes électroniques spécifiques. Consulting et formation." />
</div> <meta property="og:image" content="https://www.lelectronrare.fr/assets/og-cover.jpg" />
<p class="loading-status" id="loading-status">Chargement...</p> <meta property="og:url" content="https://www.lelectronrare.fr/preview/" />
<a href="/static/" class="loading-static-btn">Version sans 3D →</a> <meta property="og:type" content="website" />
<p class="loading-tagline">électronique · automatisme · énergie</p> <meta property="og:site_name" content="L'électron rare" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="L'électron rare — Systèmes électroniques spécifiques" />
<meta name="twitter:description" content="Conception, mise au point et fiabilisation de systèmes électroniques spécifiques." />
<meta name="twitter:image" content="https://www.lelectronrare.fr/assets/og-cover.jpg" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ProfessionalService",
"name": "L'électron rare",
"description": "Conception, mise au point et fiabilisation de systèmes électroniques spécifiques, automatismes et ensembles liés à l'énergie.",
"url": "https://www.lelectronrare.fr",
"email": "contact@lelectronrare.fr",
"founder": { "@type": "Person", "name": "Clément Saillant" },
"areaServed": "France",
"serviceType": ["Conception électronique", "Formation PCB et embarqué", "Consulting technique", "Automatisme industriel"],
"sameAs": ["https://fr.linkedin.com/in/electron-rare", "https://github.com/electron-rare/"]
}
</script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html { background: #06080b; color: #fff; overflow-x: hidden; }
body { background: #06080b; min-height: 100vh; font-family: 'Courier New', monospace; }
.scroll-track { height: 1000vh; position: relative; z-index: 1; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; }
/* Loader */
.loader { position: fixed; inset: 0; z-index: 9999; background: #06080b; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; transition: opacity 0.8s ease, visibility 0.8s ease; }
.loader.done { opacity: 0; visibility: hidden; pointer-events: none; }
.loader-logo { width: clamp(60px, 8vw, 100px); height: auto; filter: drop-shadow(0 0 20px rgba(91,209,216,0.3)); }
.loader-name { font-size: clamp(20px, 2.5vw, 32px); font-weight: 800; color: rgba(255,255,255,0.8); letter-spacing: 0.06em; }
.loader-bar { width: clamp(180px, 30vw, 280px); height: 2px; background: rgba(255,255,255,0.06); border-radius: 1px; overflow: hidden; }
.loader-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #5bd1d8, #a78bfa); transition: width 0.3s ease; }
.loader-status { font-size: 10px; color: rgba(255,255,255,0.25); letter-spacing: 0.12em; text-transform: uppercase; }
.loader-tagline { font-size: 12px; color: rgba(255,255,255,0.3); letter-spacing: 0.08em; margin-bottom: 20px; }
.loader-actions { margin-top: 24px; }
.loader-skip { display: inline-block; padding: 10px 24px; border: 1px solid rgba(91,209,216,0.3); border-radius: 8px; color: rgba(91,209,216,0.7); font-size: 12px; text-decoration: none; letter-spacing: 0.04em; transition: all 0.2s; }
.loader-skip:hover { border-color: #5bd1d8; color: #5bd1d8; background: rgba(91,209,216,0.08); }
.loader-a11y { margin-top: 20px; font-size: 10px; color: rgba(255,255,255,0.15); }
.loader-a11y a { color: rgba(255,255,255,0.25); text-decoration: none; }
.loader-a11y a:hover { color: #5bd1d8; }
.ov { position: fixed; z-index: 3; pointer-events: none; opacity: 0; transform: translateY(6px); transition: opacity 0.5s, transform 0.5s; }
.ov.on { opacity: 1; transform: translateY(0); }
/* 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); }
/* 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; }
@media (max-width: 768px) {
.ov-formats { flex-direction: column; gap: 6px; }
}
</style>
</head>
<body data-da-variant="preview">
<!-- Loader -->
<div class="loader" id="loader">
<img src="/assets/brand/logo-mark.svg" alt="L'électron rare" class="loader-logo" />
<p class="loader-name">L'électron rare</p>
<p class="loader-tagline">Systèmes électroniques spécifiques</p>
<div class="loader-bar"><div class="loader-fill" id="loader-fill"></div></div>
<p class="loader-status" id="loader-status">Chargement des modèles 3D...</p>
<div class="loader-actions">
<a href="/conception/" class="loader-skip">Accéder au site sans 3D →</a>
</div>
<p class="loader-a11y">Version accessible : <a href="/conception/">Conception</a> · <a href="/formation/">Formation</a> · <a href="/consulting/">Consulting</a> · <a href="/contact/">Contact</a></p>
</div>
<BmuViewer client:load />
<div class="prog" id="prog"></div>
<div class="dots" id="dots">
<div class="dot on" data-s="hero" data-p="0.03" data-l="Hero"></div>
<div class="dot" data-s="hero-to-exp" data-p="0.09" data-l=""></div>
<div class="dot" data-s="exp-elec" data-p="0.14" data-l="Élec"></div>
<div class="dot" data-s="exp-auto" data-p="0.18" data-l="Auto"></div>
<div class="dot" data-s="exp-energie" data-p="0.22" data-l="Énergie"></div>
<div class="dot" data-s="exp-dispositifs" data-p="0.255" data-l="Dispositifs"></div>
<div class="dot" data-s="exp-consulting" data-p="0.285" data-l="Consulting"></div>
<div class="dot" data-s="eclate-recul" data-p="0.31" data-l=""></div>
<div class="dot" data-s="eclate-sep" data-p="0.34" data-l=""></div>
<div class="dot" data-s="eclate-plat" data-p="0.36" data-l=""></div>
<div class="dot" data-s="cas-kxkm" data-p="0.42" data-l="KXKM"></div>
<div class="dot" data-s="cas-industrie" data-p="0.52" data-l="Industrie"></div>
<div class="dot" data-s="cas-formation" data-p="0.60" data-l="Formation"></div>
<div class="dot" data-s="media" data-p="0.70" data-l="Médias"></div>
<div class="dot" data-s="formats" data-p="0.85" data-l="Services"></div>
<div class="dot" data-s="contact" data-p="0.95" data-l="Contact"></div>
</div>
<!-- HEADER -->
<header class="site-hdr">
<div class="hdr-left">
<img src="/assets/brand/logo-mark.svg" alt="" class="hdr-logo" />
<span class="hdr-name">L'électron rare</span>
</div>
<nav class="hdr-nav">
<a href="https://www.lelectronrare.fr/#graphic-sprints-title" class="hdr-link">Conception</a>
<a href="https://www.lelectronrare.fr/formation/" class="hdr-link">Formation</a>
<a href="https://www.lelectronrare.fr/#a-propos" class="hdr-link">Consulting</a>
<a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener" class="hdr-link hdr-social" title="LinkedIn">in</a>
<a href="https://github.com/electron-rare/" target="_blank" rel="noopener" class="hdr-link hdr-social" title="GitHub">gh</a>
<a href="https://www.lelectronrare.fr/" class="hdr-link" style="opacity:0.4">Site →</a>
<a href="https://www.lelectronrare.fr/#contact" class="hdr-cta">Contact</a>
</nav>
</header>
<!-- HERO -->
<div class="ov ov-hero on" data-sec="hero">
<div class="hero-bottom">
<h1 class="hero-t">
<span class="hl1">Systèmes</span>
<span class="hl2">électroniques</span>
<span class="hl3">spécifiques</span>
</h1>
<p class="hero-s">électronique · automatisme · énergie</p>
<p class="hero-s2">Conception · Mise au point · Fiabilisation · Consulting · Formation</p>
</div> </div>
</div> </div>
<!-- WebGL background placeholder — NEW WEBGL GOES HERE --> <!-- FORMATS -->
<div id="webgl-container"></div> <div class="ov ov-formats" data-sec="formats">
<div class="fmt">
<SiteHeader brandHref="#top" items={headerItems} quickHref="#contact" /> <p class="fmt-t" style="color:#5bd1d8">Conception</p>
<p class="fmt-d">Schéma · PCB · Firmware · Validation</p>
<main id="main-content" class="dark-site">
<div class="pcb-spacer pcb-spacer--hero" id="top" aria-hidden="true"></div>
<section class="section-dark" id="a-propos">
<div class="section-content">
<About />
</div>
</section>
<div class="pcb-spacer" aria-hidden="true"></div>
<section class="section-dark">
<div class="section-content">
<CaseStudies />
</div>
</section>
<div class="pcb-spacer" aria-hidden="true"></div>
<section class="photo-strip section-dark" aria-label="Photos de terrain">
<div class="photo-strip-track">
<img src={withSiteBase('/assets/photos/pcb-teensy-led-kxkm.webp')} alt="PCB custom Teensy LED" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
<img src={withSiteBase('/assets/photos/platine-automate-cablage.webp')} alt="Câblage automate industriel" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
<img src={withSiteBase('/assets/photos/bench-bms-batteries-xt60.webp')} alt="Banc de test BMS batteries" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
<img src={withSiteBase('/assets/photos/automate-siemens-s7.webp')} alt="Automate Siemens S7" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
<img src={withSiteBase('/assets/photos/soudure-pcb-composants.webp')} alt="Soudure PCB" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
<img src={withSiteBase('/assets/photos/oscilloscope-philips-vintage.webp')} alt="Oscilloscope" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
</div>
</section>
<div class="pcb-spacer" aria-hidden="true"></div>
<section class="section-dark" id="graphic-sprints-title">
<div class="section-content">
<GraphicSprints />
</div>
</section>
<div class="pcb-spacer" aria-hidden="true"></div>
<section class="section-dark" id="faq">
<div class="section-content">
<Faq />
</div>
</section>
<div class="pcb-spacer" aria-hidden="true"></div>
<section class="section-dark" id="contact">
<div class="section-content">
<Contact />
</div>
</section>
</main>
<footer class="dark-footer">
<div class="site-shell footer-grid footer-grid--enhanced">
<div class="footer-col footer-col--brand">
<p class="m-0 footer-title">L'électron rare</p>
<div class="footer-brand-meta">
<p class="mb-0 footer-copy">© 2026 Clément Saillant — systèmes électroniques spécifiques, conseil, formation.</p>
<p class="mb-0 footer-copy">Contact : <a href="mailto:contact@lelectronrare.fr">contact@lelectronrare.fr</a></p>
</div>
</div>
<nav aria-label="Ressources" class="footer-links footer-links--single-rail">
<a href="#a-propos" class="footer-link">Approche</a>
<a href="#graphic-sprints-title" class="footer-link">Missions</a>
<a href={formationHref} class="footer-link">Formation</a>
<a href="#faq" class="footer-link">FAQ</a>
<a href="#contact" class="footer-link">Contact</a>
<a href="https://fr.linkedin.com/in/electron-rare" target="_blank" rel="noopener" class="footer-link">LinkedIn</a>
<a href="https://github.com/electron-rare/" target="_blank" rel="noopener" class="footer-link">GitHub</a>
<a href={mentionsHref} class="footer-link">Mentions légales</a>
</nav>
</div> </div>
</footer> <div class="fmt">
</BaseLayout> <p class="fmt-t" style="color:#a78bfa">Formation</p>
<p class="fmt-d">PCB · KiCad · µC · Soudure</p>
</div>
<div class="fmt">
<p class="fmt-t" style="color:#f59e0b">Consulting</p>
<p class="fmt-d">Audit · Diagnostic · Transfert</p>
</div>
</div>
<script> <div class="scroll-track"></div>
// Loading screen — auto-hide after 10s or when ready
const loadScreen = document.getElementById('loading-screen');
const loadBar = document.getElementById('loading-bar-fill');
const loadStatus = document.getElementById('loading-status');
const msgs = ["Chargement...", "Initialisation...", "Préparation du rendu..."]; <!-- SEO: texte indexable caché -->
let idx = 0; <div class="sr-only" aria-hidden="false">
const interval = setInterval(() => { <h1>L'électron rare — Systèmes électroniques spécifiques</h1>
idx = (idx + 1) % msgs.length; <p>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.</p>
if (loadStatus) loadStatus.textContent = msgs[idx]; <h2>Domaines d'expertise</h2>
if (loadBar) loadBar.style.width = Math.min(90, parseFloat(loadBar.style.width || '0') + 20) + '%'; <ul>
}, 1500); <li>Électronique spécifique : cartes, interfaces, capteurs, alimentation</li>
<li>Instrumentation et automatisme : bancs, automates, variateurs, protocoles terrain</li>
<li>Énergie et stockage : batterie, conversion, supervision, télémétrie</li>
<li>Dispositifs pour le réel : audio, LED, scène, robustesse terrain</li>
<li>Consulting et formation : audit, transfert, pédagogie, projet</li>
</ul>
<h2>Cas concrets</h2>
<ul>
<li>Industries créatives : système audio embarqué sur batterie pour le spectacle (KXKM)</li>
<li>Industrie : diagnostic et remise en service d'une ligne automatisée</li>
<li>Formation : formation PCB et embarqué pour une école d'art numérique</li>
</ul>
<h2>Services</h2>
<ul>
<li>Conception : schéma, PCB, firmware, validation</li>
<li>Formation : PCB, KiCad, microcontrôleurs, soudure</li>
<li>Consulting : audit, diagnostic, transfert</li>
</ul>
<p>Contact : <a href="https://www.lelectronrare.fr/#contact">Parlons de votre projet</a> — contact@lelectronrare.fr</p>
</div>
setTimeout(() => { <noscript>
if (loadBar) loadBar.style.width = '100%'; <div style="position:fixed;inset:0;z-index:9999;background:#06080b;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;font-family:monospace;text-align:center;padding:40px;">
if (loadStatus) loadStatus.textContent = 'Prêt.'; <img src="/assets/photos/bmu-assembly.jpg" alt="BMU v2 Assembly" style="width:clamp(200px,50vw,500px);border-radius:12px;margin-bottom:20px;" />
clearInterval(interval); <h1 style="font-size:24px;">L'électron rare</h1>
setTimeout(() => loadScreen?.classList.add('loading-done'), 500); <p style="color:rgba(255,255,255,0.5);margin:12px 0;">Systèmes électroniques spécifiques</p>
}, 10000); <a href="https://www.lelectronrare.fr/" style="color:#5bd1d8;margin-top:16px;">Accéder au site →</a>
</script> </div>
</noscript>
<style> <div class="inf">L'électron rare · Clément Saillant</div>
/* Loading screen */
.loading-screen { position:fixed; inset:0; z-index:9999; background:#000; display:flex; align-items:center; justify-content:center; transition:opacity 1s ease, visibility 1s ease; }
.loading-screen.loading-done { opacity:0; visibility:hidden; pointer-events:none; }
.loading-content { display:flex; flex-direction:column; align-items:center; text-align:center; gap:20px; padding:24px; }
.loading-bmu-img { width:clamp(200px,50vw,400px); height:auto; border-radius:16px; border:1px solid rgba(91,209,216,0.15); box-shadow:0 0 40px rgba(91,209,216,0.08); }
.loading-title { font-size:clamp(28px,5vw,48px); font-weight:800; color:#fff; margin:0; }
.loading-electric { color:#5bd1d8; }
.loading-bar { width:clamp(200px,40vw,300px); height:2px; background:rgba(255,255,255,0.06); border-radius:1px; overflow:hidden; }
.loading-bar-fill { height:100%; width:0%; background:linear-gradient(90deg,#5bd1d8,#f1c27a); transition:width 0.3s ease; }
.loading-status { font-family:monospace; font-size:12px; color:rgba(255,255,255,0.3); margin:0; }
.loading-static-btn { font-size:11px; color:rgba(91,209,216,0.6); text-decoration:none; padding:8px 16px; border:1px solid rgba(91,209,216,0.15); border-radius:8px; }
.loading-static-btn:hover { color:#5bd1d8; border-color:rgba(91,209,216,0.4); }
.loading-tagline { font-size:11px; color:rgba(255,255,255,0.12); letter-spacing:0.1em; text-transform:uppercase; margin:0; }
/* Dark site base */ <!-- GTM for A/B tracking -->
.dark-site { position:relative; z-index:1; background:transparent; color:#e0e0e0; } <script>
.section-dark { padding:clamp(40px,8vw,100px) 0; } window.dataLayer = window.dataLayer || [];
.section-content { max-width:1200px; margin:0 auto; padding:0 clamp(20px,4vw,48px); } (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!=='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-5SLM67QF');
.pcb-spacer { height:60vh; } </script>
.pcb-spacer--hero { height:100vh; }
/* Dark overrides */ <script>
.section-dark :global(h1), .section-dark :global(h2), .section-dark :global(h3) { color:#fff !important; } const prog = document.getElementById('prog');
.section-dark :global(p), .section-dark :global(li) { color:rgba(255,255,255,0.7) !important; } const dots = [...document.querySelectorAll('.dot')];
.section-dark :global(a) { color:#5bd1d8 !important; } const overlays = [...document.querySelectorAll('.ov')];
const RANGES = [
['hero',0,0.10],['hero-to-exp',0.10,0.16],
['exp-elec',0.16,0.20],['exp-auto',0.20,0.24],['exp-energie',0.24,0.28],
['exp-dispositifs',0.28,0.31],['exp-consulting',0.31,0.34],
['eclate-recul',0.34,0.36],['eclate-sep',0.36,0.39],['eclate-plat',0.39,0.41],
['cas',0.41,0.65],
['media',0.65,0.81],['formats',0.81,0.91],['contact',0.91,1.0],
];
function update() {
const max = document.documentElement.scrollHeight - window.innerHeight;
const t = max > 0 ? window.scrollY / max : 0;
if (prog) prog.style.width = (t*100)+'%';
let active = 'hero';
for (const [id,s,e] of RANGES) { if (t>=s&&t<=e){active=id;break;} }
overlays.forEach(el => el.classList.toggle('on', el.getAttribute('data-sec')===active));
dots.forEach(d => d.classList.toggle('on', d.getAttribute('data-s')===active));
}
window.addEventListener('scroll', update, {passive:true});
update();
/* Dark footer */ // Dots cliquables
.dark-footer { position:relative; z-index:2; border-top:1px solid rgba(255,255,255,0.06); padding:24px 0; background:rgba(0,0,0,0.4); } dots.forEach(d => {
.dark-footer :global(.footer-title) { color:#fff; } d.addEventListener('click', () => {
.dark-footer :global(.footer-copy) { color:rgba(255,255,255,0.4); } const p = parseFloat(d.getAttribute('data-p') || '0');
.dark-footer :global(.footer-link) { color:rgba(255,255,255,0.5) !important; } const max = document.documentElement.scrollHeight - window.innerHeight;
</style> 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);
</script>
</body>
</html>
+4 -1
View File
@@ -1,11 +1,14 @@
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
import { CANONICAL_URL, PUBLIC_SITE_ROOT_URL, SITE_IS_SUBPATH_BUILD } from '../lib/site'; import { CANONICAL_URL, PUBLIC_SITE_ROOT_URL, SITE_IS_SUBPATH_BUILD } from '../lib/site';
export const prerender = false;
export const GET: APIRoute = () => { 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 = [ const pages = [
{ path: '', priority: '1.0', changefreq: 'weekly' }, { path: '', priority: '1.0', changefreq: 'weekly' },
{ path: 'formation/', priority: '0.9', changefreq: 'monthly' }, { path: 'formation/', priority: '0.9', changefreq: 'monthly' },
{ path: 'preview/', priority: '0.8', changefreq: 'weekly' },
{ path: 'mentions-legales/', priority: '0.3', changefreq: 'yearly' } { path: 'mentions-legales/', priority: '0.3', changefreq: 'yearly' }
]; ];