diff --git a/squelettes/content/auteur.html b/squelettes/content/auteur.html
index 2a20784c9d1e521385dd6a7b84f0f8d845842364..378b666cd884fb88b8616aa76c9edba29613a10b 100644
--- a/squelettes/content/auteur.html
+++ b/squelettes/content/auteur.html
@@ -59,109 +59,15 @@
 						<INCLURE{fond=noisettes/listes/versions,objet=tradlang,url_modif=#GET{url_modif},id_auteur,ajax,nb=15,sinon=<:revisions:info_aucune_revision:>} />
 			    </div>
 				  <div class="tab-pane fade[ (#GET{active}|=={stats}|?{active show})]" id="stats" role="tabpanel" aria-labelledby="stats-tab">
-					  <INCLURE{fond=inclure/stats-trads-jours,options=#ARRAY{id_auteur,#ID_AUTEUR},ajax,env} />
-						<INCLURE{fond=inclure/stats-trads-mois,options=#ARRAY{id_auteur,#ID_AUTEUR},ajax,env} />
-
-						<INCLURE{fond=prive/stats/visites} />
+					<INCLURE{fond=css/vars_spip.css} />
+					<INCLURE{fond=prive/stats/visites} />
+					<INCLURE{fond=inclure/stats-trads-data, id_auteur=#ID_AUTEUR, ajax, env} />
 				  </div>
 				</div>
 			</div>
 		</div>
-
-		<script type="text/javascript">
-				function trace_trads_table(table, classes, options) {
-					$table = $(table);
-					if ($table.is(':hidden')) {
-						return true; // pas a faire ou deja fait.
-					}
-				
-					// copier le titre des tableaux
-					titre = $table.find("caption").text();
-					$table
-						.before("<h2>" + titre + "</h2>")
-						.wrap("<div class='" + classes + "'></div>");
-				
-					// mettre les visites avec un fond colore pour le graphique
-					$table.find("thead th:eq(1)").data({fill: true, serie: 'bar', color: '#FFD845'});
-					$table.find("thead th:eq(2)").data({serie: 'line', color: '#7FC4FF'});
-				
-					params = {
-						legendeExterne:true,
-						legendeActions:true,
-						width:$table.parent().width()+'px',
-						height:'250px',
-						modeDate:true,
-						zoom:true,
-						parse:{
-							axeOnTitle:true,
-							defaultSerie:{
-								bars:{show:true},
-								lines:{show:true},
-								points:{show:false}
-							}
-						},
-						flot:{
-							xaxis:{
-								labelWidth:45,
-								monthNames: [
-									'[(#VAL{2000-01-01}|nom_mois)]',
-									'[(#VAL{2000-02-01}|nom_mois)]',
-									'[(#VAL{2000-03-01}|nom_mois)]',
-									'[(#VAL{2000-04-01}|nom_mois)]',
-									'[(#VAL{2000-05-01}|nom_mois)]',
-									'[(#VAL{2000-06-01}|nom_mois)]',
-									'[(#VAL{2000-07-01}|nom_mois)]',
-									'[(#VAL{2000-08-01}|nom_mois)]',
-									'[(#VAL{2000-09-01}|nom_mois)]',
-									'[(#VAL{2000-10-01}|nom_mois)]',
-									'[(#VAL{2000-11-01}|nom_mois)]',
-									'[(#VAL{2000-12-01}|nom_mois)]'
-								]
-							}
-						},
-						infobulle:{show:true}
-					}
-				
-					$table.tFlot($.extend(true, {}, params, options));	
-				}
-				
-				function trace_trads(){
-					trace_trads_table(
-						"#trads_quotidiennes",
-						"statistiques_trads_quotidiennes statistiques_trads",
-						{
-							grille:{weekend:true},
-							flot:{
-								xaxis:{
-									minTickSize: [1, "day"]
-								},
-								bars:{barWidth:24 * 60 * 60 * 1000}
-							}
-						});
-						
-					trace_trads_table(
-						"#trads_mensuelles",
-						"statistiques_trads_mensuelles statistiques_trads", {
-							grille:{years:true},
-							flot:{
-								xaxis:{
-									timeformat:"%b %y",
-									minTickSize: [1, "month"]
-								},
-								bars:{barWidth:30 * 24 * 60 * 60 * 1000 /* nb de jours... approximatif */}
-							}
-					});
-					
-				}
-				(function($){
-					$(document).ready(function() {
-						trace_trads();
-						onAjaxLoad(trace_trads);
-					});
-				})(jQuery);
-				</script>
 	</BOUCLE_si_pas_profil>
 </section>
 [(#ENV{vue}|=={profil}|non|et{#SESSION{id_auteur}|!={#ENV{id_auteur}}|oui})
 #FORMULAIRE_ECRIRE_AUTEUR]
-</BOUCLE_content>
\ No newline at end of file
+</BOUCLE_content>
diff --git a/squelettes/content/tradlang_stats.html b/squelettes/content/tradlang_stats.html
index e818852c837d9190adeb01e83ba4b7094901969b..d630755bf27d90fc9502799fe9518469e91ae320 100644
--- a/squelettes/content/tradlang_stats.html
+++ b/squelettes/content/tradlang_stats.html
@@ -63,7 +63,7 @@ function trace_trads_table(table, classes, options) {
 		infobulle:{show:true}
 	}
 
-	$table.tFlot($.extend(true, {}, params, options));	
+	$table.tFlot($.extend(true, {}, params, options));
 }
 
 function trace_trads(){
@@ -79,7 +79,7 @@ function trace_trads(){
 				bars:{barWidth:24 * 60 * 60 * 1000}
 			}
 		});
-		
+
 	trace_trads_table(
 		"#trads_mensuelles",
 		"statistiques_trads_mensuelles statistiques_trads", {
@@ -92,7 +92,7 @@ function trace_trads(){
 				bars:{barWidth:30 * 24 * 60 * 60 * 1000 /* nb de jours... approximatif */}
 			}
 	});
-	
+
 }
 (function($){
 	$(document).ready(function(){
@@ -100,4 +100,4 @@ function trace_trads(){
 		onAjaxLoad(trace_trads);
 	});
 })(jQuery);
-</script>
\ No newline at end of file
+</script>
diff --git a/squelettes/css/vars_spip.css.html b/squelettes/css/vars_spip.css.html
new file mode 100644
index 0000000000000000000000000000000000000000..1a75499e9e0614e28327da8eaa262139cb7319bf
--- /dev/null
+++ b/squelettes/css/vars_spip.css.html
@@ -0,0 +1,6 @@
+<style>
+:root {
+	[(#VAL{3874b0}|spip_generer_variables_css_couleurs_theme)]
+	[(#VAL|spip_generer_variables_css_couleurs)]
+}
+</style>
diff --git a/squelettes/css/vars_spip.css_fonctions.php b/squelettes/css/vars_spip.css_fonctions.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e4ddcf5963755138128862fb897547a13a41ba5
--- /dev/null
+++ b/squelettes/css/vars_spip.css_fonctions.php
@@ -0,0 +1,3 @@
+<?php
+
+include_spip('prive/themes/spip/vars.css_fonctions');
diff --git a/squelettes/inclure/stats-trads-data.html b/squelettes/inclure/stats-trads-data.html
new file mode 100644
index 0000000000000000000000000000000000000000..3ae609d2916b35e2a08a40bd021f0666ba653397
--- /dev/null
+++ b/squelettes/inclure/stats-trads-data.html
@@ -0,0 +1,3 @@
+
+<INCLURE{fond=inclure/stats-trads-jours,ajax,env} />
+<INCLURE{fond=inclure/stats-trads-jours-resume, id_auteur} />
diff --git a/squelettes/inclure/stats-trads-data_fonctions.php b/squelettes/inclure/stats-trads-data_fonctions.php
new file mode 100644
index 0000000000000000000000000000000000000000..8c1ede5d40c13f85fe02db8ae9068d0c52430cbc
--- /dev/null
+++ b/squelettes/inclure/stats-trads-data_fonctions.php
@@ -0,0 +1,22 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+	return;
+}
+
+include_spip('inc/acces');
+include_spip('inc/statistiques');
+include_spip('prive/squelettes/inclure/stats-visites-jours_fonctions');
+
+function stats_tradlang_total(?int $id_auteur = null, $serveur = ''): int {
+	$where = [
+		'objet = ' . sql_quote('tradlang'),
+		'id_version > 0'
+	];
+	if ($id_auteur) {
+		$where[] = 'id_auteur = ' . $id_auteur;
+	}
+	$total = sql_getfetsel('count(*) AS total_absolu', 'spip_versions', $where, '', '', '', '', $serveur);
+
+	return $total ?: 0;
+}
diff --git a/squelettes/inclure/stats-trads-jours-resume.html b/squelettes/inclure/stats-trads-jours-resume.html
new file mode 100644
index 0000000000000000000000000000000000000000..1f5c1fc7fb972c6d8b17f1e2831529a6e4547917
--- /dev/null
+++ b/squelettes/inclure/stats-trads-jours-resume.html
@@ -0,0 +1,21 @@
+
+<table class='spip spip_table--responsive spip_table--statistiques spip_table--statistiques-resume'>
+	<caption><:statistiques:resume:></caption>
+	<thead>
+		<tr>
+			<th scope="col"><:info_maximum|label_nettoyer:></th>
+			<th scope="col"><:info_aujourdhui|label_nettoyer:></th>
+			<th scope="col"><:info_hier|label_nettoyer:></th>
+			<th scope="col"><:info_total|label_nettoyer:></th>
+		</tr>
+	</thead>
+	<tbody>
+		<tr>
+			#SET{stats,#NULL|statistiques_tradlang_stats_generales}
+			<td class="num" data-label="<:info_maximum|label_nettoyer|attribut_html:>">[(#GET{stats/max}|sinon{0}|number_format{0,"","&nbsp;"})]</td>
+			<td class="num" data-label="<:info_aujourdhui|label_nettoyer|attribut_html:>">[(#GET{stats/today}|sinon{0}|number_format{0,"","&nbsp;"})]</td>
+			<td class="num" data-label="<:info_hier|label_nettoyer|attribut_html:>">[(#GET{stats/yesterday}|sinon{0}|number_format{0,"","&nbsp;"})]</td>
+			<td class="num" data-label="<:info_total|label_nettoyer|attribut_html:>">[(#ENV{id_auteur,#NULL}|stats_tradlang_total|sinon{0}|number_format{0,"","&nbsp;"})]</td>
+		</tr>
+	</tbody>
+</table>
diff --git a/squelettes/inclure/stats-trads-jours-resume_fonctions.php b/squelettes/inclure/stats-trads-jours-resume_fonctions.php
new file mode 100644
index 0000000000000000000000000000000000000000..9f09f896cc2665b682ddac3eedf5e41f90189079
--- /dev/null
+++ b/squelettes/inclure/stats-trads-jours-resume_fonctions.php
@@ -0,0 +1,35 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+	return;
+}
+
+/**
+ * Calcule visites totales, aujourd'hui, hier pour le site ou id_auteur
+ */
+function statistiques_tradlang_stats_generales(array $Pile): array {
+
+	$id_auteur = ($Pile[0]['id_auteur'] ?? null) ?: null;
+	$table = 'spip_versions';
+	$where = [
+		'objet = ' . sql_quote('tradlang'),
+		'id_version > 0'
+	];
+	if (!$id_auteur) {
+		$where[] = 'id_auteur > 0';
+	} else {
+		$where[] = 'id_auteur = ' . $id_auteur;
+	}
+
+	$res = [];
+	$aggregate = sql_get_select('COUNT(*) as max', $table, $where, 'DATE_FORMAT(date, "%Y-%m-%d")');
+	$res['max'] = sql_getfetsel('MAX(max)', "($aggregate) AS source");
+	$stats_visites_to_array = charger_fonction('stats_trads_to_array', 'inc');
+	// on demande 2 jours de stats, à partir d'aujourd'hui
+	$stats = $stats_visites_to_array('day', 2, $id_auteur);
+	$data = array_column($stats['data'], 'revisions', 'date');
+	// les lignes ne sortent que s'il y a des entrées...
+	$res['today'] = $data[$stats['meta']['end_date']] ?? 0;
+	$res['yesterday'] = $data[$stats['meta']['start_date']] ?? 0;
+	return $res;
+}
diff --git a/squelettes/inclure/stats-trads-jours.html b/squelettes/inclure/stats-trads-jours.html
index c3f703090d8d7cfaa0be1a8d2c8949bd7532fd77..6812b0de56bc750d1d11d9f44076fb66408e3825 100644
--- a/squelettes/inclure/stats-trads-jours.html
+++ b/squelettes/inclure/stats-trads-jours.html
@@ -1,57 +1,57 @@
-[(#SET{max,0})][(#SET{moy,0})][(#SET{last,0})][(#SET{lastlast,0})]
-<B_statsj>
-	<table class='table table-striped center trads'>
-		<caption><:statistiques:resume:></caption>
-		<thead>
-			<tr>
-				<th><:info_maximum|trim{':'}|trim|ucfirst:></th>
-				<th><:info_moyenne|trim{':'}|trim|ucfirst:></th>
-				<th><:info_aujourdhui|trim{':'}|trim|ucfirst:></th>
-				<th><:info_hier|trim{':'}|trim|ucfirst:></th>
-				<th><:info_total|trim{':'}|trim|ucfirst:></th>
-			</tr>
-		</thead>
-		<tbody>
-			<tr>
-				<td class='num'>#GET{max}</td>
-				<td class='num'>[(#GET{moy}|round)]</td>
-				<td class='num'>#GET{last}</td>
-				<td class='num'>#GET{lastlast}</td>
-				<td class='num'>[(#REM|stats_tradlang_total{#ENV{options,#ARRAY}})]</td>
-			</tr>
-		</tbody>
-	</table>
+[(#NULL|setenv{objet})]
+[(#ENV{id_auteur,#NULL}|setenv{id_objet})]
+<div class="spip_d3_nav spip_d3_nav--statistiques-visites">
+	<div class="groupe-btns groupe-btns_menu groupe-btns--stats-graph">
+		<a class="btn[ (#ENV{graph}|non|ou{#ENV{graph}|=={90-days}})btn_on]" href="#"
+			data-graph="90-days"
+			data-title="<:statistiques:visites_journalieres|attribut_html:>"
+			data-json="[(#VAL{json}|statistiques_url_data{trads,jour,90})]"
+			onclick="spip_d3_statistiques_load_json(this, '#statistiques_visites'); return false;">
+			<:statistiques:duree_mois_nb{nb=3}:>
+		</a>
+		<a class="btn[ (#ENV{graph}|=={104-weeks}|oui)btn_on]" href="#"
+			data-graph="104-weeks"
+			data-title="<:statistiques:visites_hebdomadaires|attribut_html:>"
+			data-json="[(#VAL{json}|statistiques_url_data{trads,semaine,104})]"
+			onclick="spip_d3_statistiques_load_json(this, '#statistiques_visites'); return false;">
+			<:statistiques:duree_annee_nb{nb=2}:>
+		</a>
+		<a class="btn[ (#ENV{graph}|=={60-months}|oui)btn_on]" href="#"
+			data-graph="60-months"
+			data-title="<:statistiques:visites_mensuelles|attribut_html:>"
+			data-json="[(#VAL{json}|statistiques_url_data{trads,mois,60})]"
+			onclick="spip_d3_statistiques_load_json(this, '#statistiques_visites'); return false;">
+			<:statistiques:duree_annee_nb{nb=5}:>
+		</a>
+		<a class="btn[ (#ENV{graph}|=={years}|oui)btn_on]" href="#"
+			data-graph="years"
+			data-title="<:statistiques:visites_annuelles|attribut_html:>"
+			data-json="[(#VAL{json}|statistiques_url_data{trads,annee})]"
+			onclick="spip_d3_statistiques_load_json(this, '#statistiques_visites'); return false;">
+			∞
+		</a>
+	</div>
+
+	<h3 class="spip_d3_nav_caption">
+		[(#ENV{graph}|in_any{#LISTE{104-weeks,60-months,years}}|non|?{<:statistiques:visites_journalieres:>})]
+		[(#ENV{graph}|=={104-weeks}|?{<:statistiques:visites_hebdomadaires:>})]
+		[(#ENV{graph}|=={60-months}|?{<:statistiques:visites_mensuelles:>})]
+		[(#ENV{graph}|=={years}|?{<:statistiques:visites_annuelles:>})]
+	</h3>
 
-	<div class="clear">
-		<div class="pagination float-right">
-			[<span class="duree">(#ENV{duree,90}) <:info_jours:></span>] |
-			<a href="[(#SELF|parametre_url{duree,#ENV{duree,90}|duree_zoom{moins}})]"
-				 class="ajax">[(#CHEMIN_IMAGE{zoomout-24.png}|balise_img{<:statistiques:info_zoom:> -})]</a>
-			<a href="[(#SELF|parametre_url{duree,#ENV{duree,90}|duree_zoom{plus}})]"
-				 class="ajax">[(#CHEMIN_IMAGE{zoomin-24.png}|balise_img{<:statistiques:info_zoom:> +})]</a>
-		</div>
-		<table class='trads' id='trads_quotidiennes'>
-			<caption><:tradlang:titre_stats_trads_journalieres:></caption>
-		  <thead>
-				<tr class='row_first'>
-					<th><:date|trim{':'}|trim:></th>
-					<th class='valeur'><:tradlang:info_revisions_stats|trim{':'}|trim:></th>
-					<th class='moyenne'><:info_moyenne|trim{':'}|trim|ucfirst:></th>
-				</tr>
-			</thead>
-		  <tbody>
-			<BOUCLE_statsj(DATA){source stats_trads,jour,#ENV{duree,90},#ENV{id_tradlang_module},#ENV{options,#ARRAY}}>
-				<tr class="c_[(#CLE|affdate{l}|substr{0,3})][(#COMPTEUR_BOUCLE|=={#TOTAL_BOUCLE}|oui)c_today]">
-					<th title="[(#CLE|affdate{'Y/m/d'})]">[(#COMPTEUR_BOUCLE|=={#TOTAL_BOUCLE}|?{<:info_aujourdhui:>,#CLE|affdate_jourcourt})]</th>
-					<td class="val">#VALEUR{versions}</td>
-					<td class="mean">#VALEUR{moyenne}</td>
-				</tr>
-				#SET{max,#GET{max}|max{#VALEUR{versions}}}
-				#SET{moy,#VALEUR{moyenne}}
-				#SET{lastlast,#GET{last}}
-				#SET{last,#VALEUR{versions}}
-			</BOUCLE_statsj>
-			</tbody>
-		</table>
+	<div class="groupe-btns groupe-btns_menu groupe-btns--stats-vue">
+		<a class="btn[ (#ENV{vue}|non|ou{#ENV{vue}|=={svg}})btn_on] noajax btn--stats-to-svg" href="#" onclick="spip_d3_statistiques_toggle_svg_table(this, '#statistiques_visites', 'svg'); return false;"><:statistiques:info_graphiques:></a>
+		<a class="btn[ (#ENV{vue}|=={table}|oui)btn_on] noajax btn--stats-to-table" href="#"  onclick="spip_d3_statistiques_toggle_svg_table(this, '#statistiques_visites', 'table'); return false;"><:statistiques:info_tableaux:></a>
 	</div>
-</B_statsj>
\ No newline at end of file
+</div>
+
+<div
+	id="statistiques_visites"
+	class="spip_d3_graph spip_d3_statistiques spip_d3_statistiques--visites spip_d3_graph--loading"
+	data-json="[(#VAL{json}|statistiques_url_data{jour,90})]"
+	data-title="<:statistiques:visites_journalieres|attribut_html:>"
+>
+	<svg class="spip_d3_graph_ratio" viewBox="0 0 2 1"></svg>
+	[(#CHEMIN_IMAGE{loader.svg}|balise_svg{Chargement, spip_d3_graph_loader})]
+	<div class="spip_d3_graph_inner"></div>
+</div>
diff --git a/squelettes/inclure/stats-trads-jours_fonctions.php b/squelettes/inclure/stats-trads-jours_fonctions.php
index bad5c9bcbc61f848572d54804fc786484f4b81df..7aa147582ef21f20f92cf7a1c977e08c2df1c3e1 100644
--- a/squelettes/inclure/stats-trads-jours_fonctions.php
+++ b/squelettes/inclure/stats-trads-jours_fonctions.php
@@ -4,22 +4,4 @@ if (!defined('_ECRIRE_INC_VERSION')) {
 	return;
 }
 
-include_spip('prive/squelettes/inclure/stats-visites-data_fonctions');
-
-function stats_tradlang_total($serveur = '', $options = array()) {
-	$where = array(
-		'objet = "tradlang"',
-		'id_version > 0'
-	);
-
-	if (!isset($options['id_auteur']) or !is_numeric($options['id_auteur'])) {
-		$where[] = 'id_auteur > 0';
-	} else {
-		$where[] = 'id_auteur = '.intval($options['id_auteur']);
-	}
-
-	$where = implode(' AND ', $where);
-	$format = ($unite == 'jour' ? '%Y-%m-%d' : '%Y-%m-01');
-	$row = sql_fetsel('COUNT(*) AS total_absolu', 'spip_versions', $where, '', '', '', '', $serveur);
-	return $row ? $row['total_absolu'] : 0;
-}
+include_spip('inclure/stats-trads-data_fonctions');
diff --git a/squelettes/inclure/stats-trads-mois.html b/squelettes/inclure/stats-trads-mois.html
deleted file mode 100644
index 806d2c578a66771e0886fb90ed4e58d9ee24b967..0000000000000000000000000000000000000000
--- a/squelettes/inclure/stats-trads-mois.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<div class="pagination float-right">
-	[<span class="duree">(#ENV{duree_mois,365}|div{30}|intval) <:date_mois:></span>] |
-	<a href="[(#SELF|parametre_url{duree_mois,#ENV{duree_mois,365}|duree_zoom{moins}})]"
-		 class="ajax">[(#CHEMIN_IMAGE{zoomout-24.png}|balise_img{<:statistiques:info_zoom:> -})]</a>
-	<a href="[(#SELF|parametre_url{duree_mois,#ENV{duree_mois,365}|duree_zoom{plus}})]"
-		 class="ajax">[(#CHEMIN_IMAGE{zoomin-24.png}|balise_img{<:statistiques:info_zoom:> +})]</a>
-</div>
-<B_statsm>
-	<table class='trads' id='trads_mensuelles'>
-		<caption><:tradlang:titre_stats_trads_mensuelles:></caption>
-	  <thead>
-			<tr class='row_first'>
-				<th><:date|trim{':'}|trim:></th>
-				<th class='valeur'><:tradlang:info_revisions_stats|trim{':'}|trim:></th>
-				<th class='moyenne'><:info_moyenne|trim{':'}|trim|ucfirst:></th>
-			</tr>
-		</thead>
-	  <tbody>
-		<BOUCLE_statsm(DATA){source stats_trads,mois,#ENV{duree_mois,365},#ENV{id_tradlang_module},#ENV{options,#ARRAY}}>
-			<tr class="c_[(#CLE|affdate{l}|substr{0,3})][(#COMPTEUR_BOUCLE|=={#TOTAL_BOUCLE}|oui)c_today]">
-				<th title="[(#CLE|affdate{'Y/m/01'})]">[(#CLE|affdate_mois_annee)]</th>
-				<td class="val">#VALEUR{versions}</td>
-				<td class="mean">[(#VALEUR{moyenne}|round)]</td>
-			</tr>
-		</BOUCLE_statsm>
-		</tbody>
-	</table>
-</B_statsm>
\ No newline at end of file
diff --git a/squelettes/inclure/stats-trads-mois_fonctions.php b/squelettes/inclure/stats-trads-mois_fonctions.php
deleted file mode 100644
index c94f525dcc8cc7c9994a1b69cb800fe1d8bd4dca..0000000000000000000000000000000000000000
--- a/squelettes/inclure/stats-trads-mois_fonctions.php
+++ /dev/null
@@ -1,7 +0,0 @@
-<?php
-
-if (!defined('_ECRIRE_INC_VERSION')) {
-	return;
-}
-
-include_spip('prive/squelettes/inclure/stats-visites-data_fonctions');