Skip to content

Commit a00d113

Browse files
committed
perf(landing): optimiser le SEO et extraire les scripts de la landing page
- index.html : * Ajout des balises meta SEO fondamentales (description, keywords, author, robots, canonical). * Ajout des metadonnées Open Graph (og:type, og:title, og:description, og:url, og:image, og:site_name, og:locale) pour l'affichage sur les réseaux sociaux. * Intégration des balises Twitter Card (summary_large_image, title, description, image). * Configuration de la couleur de thème (theme-color), du schéma de couleur (light) et liaison du favicon via le logo SVG. * Extraction et déportation de l'ensemble de la logique JavaScript inline présente en fin de fichier vers un module externe propre : /src/landing-main.ts.
1 parent a8023cb commit a00d113

2 files changed

Lines changed: 178 additions & 127 deletions

File tree

index.html

Lines changed: 31 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,36 @@
44
<meta charset="UTF-8"/>
55
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
66
<title>Synapse.js — Zero-DOM WebGPU Rendering</title>
7+
8+
<!-- SEO -->
9+
<meta name="description" content="Synapse.js est un moteur de rendu Zero-DOM pour le web. Il transforme n'importe quel framework en cible WebGPU avec un scene graph en RAM et un pipeline GPU pur. 61x plus rapide que le DOM."/>
10+
<meta name="keywords" content="synapse, webgpu, zero-dom, rendering engine, gpu, javascript, typescript, scene graph, instancing, react, svelte, angular"/>
11+
<meta name="author" content="Klaynight"/>
12+
<meta name="robots" content="index, follow"/>
13+
<link rel="canonical" href="https://klaynight-dev.github.io/SynapseJS/"/>
14+
15+
<!-- Open Graph -->
16+
<meta property="og:type" content="website"/>
17+
<meta property="og:title" content="Synapse.js — Zero-DOM WebGPU Rendering Engine"/>
18+
<meta property="og:description" content="Moteur de rendu Zero-DOM pour le web. Scene graph en RAM, pipeline GPU pur, 1 draw call pour des milliers d'elements. 61x plus rapide que le DOM."/>
19+
<meta property="og:url" content="https://klaynight-dev.github.io/SynapseJS/"/>
20+
<meta property="og:image" content="https://klaynight-dev.github.io/SynapseJS/assets/synapse-logo.svg"/>
21+
<meta property="og:site_name" content="Synapse.js"/>
22+
<meta property="og:locale" content="fr_FR"/>
23+
24+
<!-- Twitter Card -->
25+
<meta name="twitter:card" content="summary_large_image"/>
26+
<meta name="twitter:title" content="Synapse.js — Zero-DOM WebGPU Rendering"/>
27+
<meta name="twitter:description" content="Moteur de rendu Zero-DOM WebGPU. 857 FPS pour 10k elements, 1 draw call, 61x plus rapide que le DOM."/>
28+
<meta name="twitter:image" content="https://klaynight-dev.github.io/SynapseJS/assets/synapse-logo.svg"/>
29+
30+
<!-- Theme -->
31+
<meta name="theme-color" content="#f5f0e8"/>
32+
<meta name="color-scheme" content="light"/>
33+
34+
<!-- Favicon -->
35+
<link rel="icon" type="image/svg+xml" href="/assets/synapse-logo.svg"/>
36+
737
<link rel="preconnect" href="https://fonts.googleapis.com"/>
838
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
939
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600;700&family=Patrick+Hand&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
@@ -277,132 +307,6 @@ <h2>Pret a <span class="strike">utiliser le DOM</span> passer au GPU ?</h2>
277307
<div class="footer">&copy; 2026 <a href="https://github.com/Klaynight-dev/SynapseJS" target="_blank">Synapse.js</a> &mdash; Zero-DOM WebGPU rendering core.</div>
278308
</div>
279309

280-
<script>
281-
// ═══════════════════════════════════════
282-
// 1. SCROLL PROGRESS BAR
283-
// ═══════════════════════════════════════
284-
const progressBar=document.getElementById('scroll-progress');
285-
function updateProgress(){
286-
const h=document.documentElement.scrollHeight-window.innerHeight;
287-
progressBar.style.width=(h>0?(window.scrollY/h)*100:0)+'%';
288-
}
289-
window.addEventListener('scroll',updateProgress,{passive:true});
290-
updateProgress();
291-
292-
// ═══════════════════════════════════════
293-
// 2. SCROLL REVEAL (with stagger)
294-
// ═══════════════════════════════════════
295-
const revealObs=new IntersectionObserver(entries=>{
296-
entries.forEach(e=>{if(e.isIntersecting){e.target.classList.add('vis');revealObs.unobserve(e.target)}});
297-
},{threshold:.12,rootMargin:'0px 0px -40px 0px'});
298-
document.querySelectorAll('.rv,.rv-left,.rv-right,.rv-scale').forEach(e=>revealObs.observe(e));
299-
300-
// ═══════════════════════════════════════
301-
// 3. COUNTER ANIMATION (on scroll)
302-
// ═══════════════════════════════════════
303-
const countEls=document.querySelectorAll('[data-count]');
304-
const countObs=new IntersectionObserver(entries=>{
305-
entries.forEach(e=>{
306-
if(!e.isIntersecting)return;
307-
countObs.unobserve(e.target);
308-
const el=e.target;
309-
const target=parseFloat(el.dataset.count);
310-
const suffix=el.dataset.suffix||'';
311-
const decimals=parseInt(el.dataset.decimals||'0');
312-
const duration=1200;
313-
const start=performance.now();
314-
function tick(now){
315-
const t=Math.min((now-start)/duration,1);
316-
const ease=1-Math.pow(1-t,3);
317-
const val=ease*target;
318-
el.textContent=(decimals>0?val.toFixed(decimals):Math.round(val))+suffix;
319-
if(t<1)requestAnimationFrame(tick);
320-
}
321-
requestAnimationFrame(tick);
322-
});
323-
},{threshold:.5});
324-
countEls.forEach(e=>countObs.observe(e));
325-
326-
// ═══════════════════════════════════════
327-
// 4. ANIMATED BAR FILLS (on scroll)
328-
// ═══════════════════════════════════════
329-
const barEls=document.querySelectorAll('.bar-anim');
330-
const barObs=new IntersectionObserver(entries=>{
331-
entries.forEach(e=>{
332-
if(!e.isIntersecting)return;
333-
barObs.unobserve(e.target);
334-
const el=e.target;
335-
requestAnimationFrame(()=>{
336-
el.classList.remove('bar-anim');
337-
el.style.width=el.dataset.bar;
338-
});
339-
});
340-
},{threshold:.3});
341-
barEls.forEach(e=>barObs.observe(e));
342-
343-
// ═══════════════════════════════════════
344-
// 5. PARALLAX
345-
// ═══════════════════════════════════════
346-
const parallaxEls=document.querySelectorAll('.parallax');
347-
let ticking=false;
348-
function updateParallax(){
349-
const scrollTop=window.scrollY;
350-
parallaxEls.forEach(el=>{
351-
const speed=parseFloat(el.dataset.speed||'.2');
352-
const rect=el.getBoundingClientRect();
353-
const offset=(rect.top+scrollTop)*speed;
354-
el.style.transform='translateY('+(-offset*.15).toFixed(1)+'px)';
355-
});
356-
ticking=false;
357-
}
358-
window.addEventListener('scroll',()=>{if(!ticking){ticking=true;requestAnimationFrame(updateParallax)}},{passive:true});
359-
360-
// ═══════════════════════════════════════
361-
// 6. NAV SHRINK ON SCROLL
362-
// ═══════════════════════════════════════
363-
const nav=document.querySelector('.nav');
364-
window.addEventListener('scroll',()=>{
365-
nav.classList.toggle('scrolled',window.scrollY>50);
366-
},{passive:true});
367-
368-
// ═══════════════════════════════════════
369-
// 7. PM TOGGLE
370-
// ═══════════════════════════════════════
371-
const cmds={
372-
bun:['bun install','bun run dev','bun run bench','bun run bench:report'],
373-
pnpm:['pnpm install','pnpm dev','pnpm bench','pnpm bench:report'],
374-
npm:['npm install','npm run dev','npm run bench','npm run bench:report'],
375-
yarn:['yarn','yarn dev','yarn bench','yarn bench:report'],
376-
};
377-
document.getElementById('pm-toggle').addEventListener('click',e=>{
378-
const btn=e.target.closest('.pm-btn');if(!btn)return;
379-
const pm=btn.dataset.pm;
380-
document.querySelectorAll('.pm-btn').forEach(b=>b.classList.remove('active'));
381-
btn.classList.add('active');
382-
const c=cmds[pm];
383-
document.getElementById('cmd-install').textContent=c[0];
384-
document.getElementById('cmd-dev').textContent=c[1];
385-
document.getElementById('cmd-bench').textContent=c[2];
386-
document.getElementById('cmd-report').textContent=c[3];
387-
});
388-
389-
// ═══════════════════════════════════════
390-
// 8. SYNAPSE DEMO
391-
// ═══════════════════════════════════════
392-
(async()=>{
393-
const canvas=document.getElementById('demo-canvas');
394-
if(!navigator.gpu){
395-
canvas.parentElement.innerHTML='<div class="demo-nope">WebGPU non disponible dans ce navigateur.<br>Utilisez Chrome 113+, Edge 113+ ou Firefox Nightly.</div>';
396-
return;
397-
}
398-
try{
399-
const{mountDemo}=await import('/src/landing.ts');
400-
await mountDemo(canvas);
401-
}catch(err){
402-
console.error('Demo mount failed:',err);
403-
document.getElementById('demo-fps').textContent='Erreur: '+err.message;
404-
}
405-
})();
406-
</script>
310+
<script type="module" src="/src/landing-main.ts"></script>
407311
</body>
408312
</html>

src/landing-main.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { mountDemo } from "./landing";
2+
3+
// ── Scroll progress bar ──
4+
const progressBar = document.getElementById("scroll-progress")!;
5+
function updateProgress() {
6+
const h = document.documentElement.scrollHeight - window.innerHeight;
7+
progressBar.style.width = (h > 0 ? (window.scrollY / h) * 100 : 0) + "%";
8+
}
9+
window.addEventListener("scroll", updateProgress, { passive: true });
10+
updateProgress();
11+
12+
// ── Scroll reveal (with stagger) ──
13+
const revealObs = new IntersectionObserver(
14+
(entries) => {
15+
entries.forEach((e) => {
16+
if (e.isIntersecting) {
17+
e.target.classList.add("vis");
18+
revealObs.unobserve(e.target);
19+
}
20+
});
21+
},
22+
{ threshold: 0.12, rootMargin: "0px 0px -40px 0px" }
23+
);
24+
document
25+
.querySelectorAll(".rv,.rv-left,.rv-right,.rv-scale")
26+
.forEach((e) => revealObs.observe(e));
27+
28+
// ── Counter animation ──
29+
const countObs = new IntersectionObserver(
30+
(entries) => {
31+
entries.forEach((e) => {
32+
if (!e.isIntersecting) return;
33+
countObs.unobserve(e.target);
34+
const el = e.target as HTMLElement;
35+
const target = parseFloat(el.dataset.count!);
36+
const suffix = el.dataset.suffix || "";
37+
const decimals = parseInt(el.dataset.decimals || "0");
38+
const duration = 1200;
39+
const start = performance.now();
40+
function tick(now: number) {
41+
const t = Math.min((now - start) / duration, 1);
42+
const ease = 1 - Math.pow(1 - t, 3);
43+
const val = ease * target;
44+
el.textContent =
45+
(decimals > 0 ? val.toFixed(decimals) : String(Math.round(val))) +
46+
suffix;
47+
if (t < 1) requestAnimationFrame(tick);
48+
}
49+
requestAnimationFrame(tick);
50+
});
51+
},
52+
{ threshold: 0.5 }
53+
);
54+
document
55+
.querySelectorAll<HTMLElement>("[data-count]")
56+
.forEach((e) => countObs.observe(e));
57+
58+
// ── Animated bar fills ──
59+
const barObs = new IntersectionObserver(
60+
(entries) => {
61+
entries.forEach((e) => {
62+
if (!e.isIntersecting) return;
63+
barObs.unobserve(e.target);
64+
const el = e.target as HTMLElement;
65+
requestAnimationFrame(() => {
66+
el.classList.remove("bar-anim");
67+
el.style.width = el.dataset.bar!;
68+
});
69+
});
70+
},
71+
{ threshold: 0.3 }
72+
);
73+
document.querySelectorAll(".bar-anim").forEach((e) => barObs.observe(e));
74+
75+
// ── Parallax ──
76+
const parallaxEls = document.querySelectorAll<HTMLElement>(".parallax");
77+
let ticking = false;
78+
function updateParallax() {
79+
const scrollTop = window.scrollY;
80+
parallaxEls.forEach((el) => {
81+
const speed = parseFloat(el.dataset.speed || ".2");
82+
const rect = el.getBoundingClientRect();
83+
const offset = (rect.top + scrollTop) * speed;
84+
el.style.transform = "translateY(" + (-offset * 0.15).toFixed(1) + "px)";
85+
});
86+
ticking = false;
87+
}
88+
window.addEventListener(
89+
"scroll",
90+
() => {
91+
if (!ticking) {
92+
ticking = true;
93+
requestAnimationFrame(updateParallax);
94+
}
95+
},
96+
{ passive: true }
97+
);
98+
99+
// ── Nav shrink on scroll ──
100+
const nav = document.querySelector(".nav")!;
101+
window.addEventListener(
102+
"scroll",
103+
() => {
104+
nav.classList.toggle("scrolled", window.scrollY > 50);
105+
},
106+
{ passive: true }
107+
);
108+
109+
// ── PM Toggle ──
110+
const cmds: Record<string, string[]> = {
111+
bun: ["bun install", "bun run dev", "bun run bench", "bun run bench:report"],
112+
pnpm: ["pnpm install", "pnpm dev", "pnpm bench", "pnpm bench:report"],
113+
npm: [
114+
"npm install",
115+
"npm run dev",
116+
"npm run bench",
117+
"npm run bench:report",
118+
],
119+
yarn: ["yarn", "yarn dev", "yarn bench", "yarn bench:report"],
120+
};
121+
document.getElementById("pm-toggle")!.addEventListener("click", (e) => {
122+
const btn = (e.target as HTMLElement).closest<HTMLElement>(".pm-btn");
123+
if (!btn) return;
124+
const pm = btn.dataset.pm!;
125+
document
126+
.querySelectorAll(".pm-btn")
127+
.forEach((b) => b.classList.remove("active"));
128+
btn.classList.add("active");
129+
const c = cmds[pm];
130+
document.getElementById("cmd-install")!.textContent = c[0];
131+
document.getElementById("cmd-dev")!.textContent = c[1];
132+
document.getElementById("cmd-bench")!.textContent = c[2];
133+
document.getElementById("cmd-report")!.textContent = c[3];
134+
});
135+
136+
// ── Synapse Demo ──
137+
const canvas = document.getElementById("demo-canvas") as HTMLCanvasElement;
138+
if (!navigator.gpu) {
139+
canvas.parentElement!.innerHTML =
140+
'<div class="demo-nope">WebGPU non disponible dans ce navigateur.<br>Utilisez Chrome 113+, Edge 113+ ou Firefox Nightly.</div>';
141+
} else {
142+
mountDemo(canvas).catch((err) => {
143+
console.error("Demo mount failed:", err);
144+
document.getElementById("demo-fps")!.textContent =
145+
"Erreur: " + err.message;
146+
});
147+
}

0 commit comments

Comments
 (0)