@jjlmoya/utils-sports 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/package.json +62 -0
  2. package/src/category/i18n/en.ts +108 -0
  3. package/src/category/i18n/es.ts +108 -0
  4. package/src/category/i18n/fr.ts +95 -0
  5. package/src/category/index.ts +21 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +55 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/gymTracker/bibliography.astro +15 -0
  25. package/src/tool/gymTracker/component.astro +835 -0
  26. package/src/tool/gymTracker/exercises.ts +28 -0
  27. package/src/tool/gymTracker/i18n/en.ts +225 -0
  28. package/src/tool/gymTracker/i18n/es.ts +225 -0
  29. package/src/tool/gymTracker/i18n/fr.ts +225 -0
  30. package/src/tool/gymTracker/index.ts +34 -0
  31. package/src/tool/gymTracker/logic.ts +169 -0
  32. package/src/tool/gymTracker/seo.astro +15 -0
  33. package/src/tool/gymTracker/storage.ts +43 -0
  34. package/src/tool/gymTracker/timer.ts +126 -0
  35. package/src/tool/gymTracker/types.ts +11 -0
  36. package/src/tool/gymTracker/ui-utils.ts +59 -0
  37. package/src/tool/gymTracker/ui.ts +27 -0
  38. package/src/tool/reactionTester/bibliography.astro +2 -0
  39. package/src/tool/reactionTester/component.astro +1074 -0
  40. package/src/tool/reactionTester/i18n/en.ts +144 -0
  41. package/src/tool/reactionTester/i18n/es.ts +144 -0
  42. package/src/tool/reactionTester/i18n/fr.ts +144 -0
  43. package/src/tool/reactionTester/index.ts +34 -0
  44. package/src/tool/reactionTester/seo.astro +12 -0
  45. package/src/tool/reactionTester/ui.ts +43 -0
  46. package/src/tool/scoreKeeper/bibliography.astro +14 -0
  47. package/src/tool/scoreKeeper/component.astro +858 -0
  48. package/src/tool/scoreKeeper/i18n/en.ts +207 -0
  49. package/src/tool/scoreKeeper/i18n/es.ts +207 -0
  50. package/src/tool/scoreKeeper/i18n/fr.ts +207 -0
  51. package/src/tool/scoreKeeper/index.ts +35 -0
  52. package/src/tool/scoreKeeper/logic.ts +275 -0
  53. package/src/tool/scoreKeeper/seo.astro +15 -0
  54. package/src/tool/scoreKeeper/sports.ts +70 -0
  55. package/src/tool/scoreKeeper/ui.ts +19 -0
  56. package/src/tool/tournamentBracket/bibliography.astro +10 -0
  57. package/src/tool/tournamentBracket/component.astro +1092 -0
  58. package/src/tool/tournamentBracket/i18n/en.ts +160 -0
  59. package/src/tool/tournamentBracket/i18n/es.ts +178 -0
  60. package/src/tool/tournamentBracket/i18n/fr.ts +160 -0
  61. package/src/tool/tournamentBracket/index.ts +34 -0
  62. package/src/tool/tournamentBracket/logic/active.controller.ts +106 -0
  63. package/src/tool/tournamentBracket/logic/generator.ts +71 -0
  64. package/src/tool/tournamentBracket/logic/manager.ts +165 -0
  65. package/src/tool/tournamentBracket/logic/setup.controller.ts +84 -0
  66. package/src/tool/tournamentBracket/logic/sharing.ts +81 -0
  67. package/src/tool/tournamentBracket/logic/storage.ts +56 -0
  68. package/src/tool/tournamentBracket/models.ts +34 -0
  69. package/src/tool/tournamentBracket/seo.astro +12 -0
  70. package/src/tool/tournamentBracket/tournament.controller.ts +65 -0
  71. package/src/tool/tournamentBracket/tournament.renderer.ts +45 -0
  72. package/src/tool/tournamentBracket/ui/bracket-desktop.ts +143 -0
  73. package/src/tool/tournamentBracket/ui/bracket-mobile.ts +82 -0
  74. package/src/tool/tournamentBracket/ui/mediator.ts +96 -0
  75. package/src/tool/tournamentBracket/ui/navigator.ts +84 -0
  76. package/src/tool/tournamentBracket/ui/setup.ts +120 -0
  77. package/src/tool/tournamentBracket/ui.ts +42 -0
  78. package/src/tools.ts +13 -0
  79. package/src/types.ts +72 -0
@@ -0,0 +1,144 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { ReactionTesterUI } from '../ui';
4
+
5
+ const slug = 'reaction-test';
6
+ const title = 'Reaction Speed Test (Reflexes): Online Benchmark';
7
+ const description = 'Measure your reflexes in milliseconds with our professional reaction test. Compete for ranks from \'Turtle\' to \'Cyberathlete\' and analyze your performance.';
8
+
9
+ const ui: ReactionTesterUI = {
10
+ title: 'Reaction Test',
11
+ subtitle: 'Measure your reaction speed in milliseconds. Average of 5 attempts.',
12
+ startBtn: 'Click to start',
13
+ goTitle: 'GO!',
14
+ goSubtitle: 'HIT IT NOW!',
15
+ earlyTitle: 'Too soon!',
16
+ earlySubtitle: 'Wait for the green.',
17
+ earlyRetry: 'Click to retry',
18
+ promptNext: 'Click for attempt',
19
+ promptFinal: 'See Final Results',
20
+ avgLabel: 'Final average',
21
+ copyBtn: 'Copy',
22
+ tweetBtn: 'Tweet',
23
+ retryBtn: 'Try again',
24
+ inputMouse: 'Mouse',
25
+ inputTouch: 'Touch',
26
+ inputSpace: 'Space',
27
+ historyTitle: 'Local History',
28
+ historyBest: 'Top 5 Best',
29
+ historyWorst: 'Top 5 Worst',
30
+ historyEmpty: 'Play to record your first score',
31
+ copySuccess: 'Copied!',
32
+ shareText: 'I scored {ms}ms on the Reaction Test!\nRank: {rank}\n\nCan you beat me?\n{url}',
33
+ rankCiberatleta: 'Cyberathlete',
34
+ rankTigre: 'Tiger',
35
+ rankHumano: 'Human',
36
+ rankTortuga: 'Turtle',
37
+ rankDormido: 'Asleep',
38
+ f150: 'ARE YOU A ROBOT?!',
39
+ f180: 'INCREDIBLE!',
40
+ f210: 'Very fast!',
41
+ f250: 'Good reaction',
42
+ f300: 'Not bad...',
43
+ f400: 'A bit slow...',
44
+ f600: 'You fell asleep',
45
+ fSlow: 'Wake up...',
46
+ wp0t: 'WAIT...', wp0s: 'Hold your breath...',
47
+ wp1t: 'STILL...', wp1s: 'Don\'t blink now',
48
+ wp2t: 'READY...', wp2s: 'Fingers ready...',
49
+ wp3t: 'HOLD...', wp3s: 'Patience, grasshopper',
50
+ wp4t: 'FOCUS...', wp4s: 'It\'s coming...',
51
+ };
52
+
53
+ const faqData = [
54
+ { question: 'What is the average human reaction time?', answer: 'The average human reaction time to a visual stimulus is approximately 250 to 270 milliseconds. High-performance athletes and pro-gamers are usually below 200ms.' },
55
+ { question: 'How can I improve my reflexes?', answer: 'The key is consistent practice and rest. Playing action video games (FPS), practicing fast sports like ping-pong and staying well hydrated helps keep the nervous system alert.' },
56
+ { question: 'Does the screen affect my result?', answer: 'Yes. There is something called \'input lag\'. Screens with a low refresh rate (60Hz) or old wireless mice can add 15 to 50ms to your actual reaction time.' },
57
+ { question: 'At what age do we start losing reflexes?', answer: 'Physiologically, human reflexes peak between ages 20 and 24. After that, there is a very slow decline (approx. 2-6ms per decade) if not trained.' },
58
+ ];
59
+
60
+ const howTo = [
61
+ { name: 'Wait for red', text: 'Click the start area and wait patiently for the screen to change color.' },
62
+ { name: 'React to green', text: 'As soon as you see green, click or tap the screen as fast as you can.' },
63
+ { name: 'See your result', text: 'The system will calculate the exact difference in milliseconds between the color change and your tap.' },
64
+ { name: 'Analyze your rank', text: 'Repeat the test 5 times to get a reliable average and find out if you are a \'Turtle\' or a \'Cyberathlete\'.' },
65
+ ];
66
+
67
+ const seo = [
68
+ { type: 'title' as const, text: 'How Does This Reaction Test Work?', level: 2 as const },
69
+ {
70
+ type: 'paragraph' as const,
71
+ html: 'This tool measures your <strong>visual reaction time</strong> with surgical precision. We calculate the exact interval from when the screen lights up until the signal travels from your eyes to your brain, and then to your fingers. The test requires <strong>5 consecutive attempts</strong> to calculate your real average, eliminating the luck factor.',
72
+ },
73
+ {
74
+ type: 'list' as const,
75
+ items: [
76
+ '<strong>Average of 5 attempts:</strong> We eliminate the "luck" factor by requiring consistency. A single lucky click won\'t make you a Cyberathlete.',
77
+ '<strong>Anti-Cheat System:</strong> Random wait times (1.5s - 4.5s) and early-click detection to prevent prediction.',
78
+ '<strong>Real science:</strong> Brain processing ~180-200ms + motor response +20-40ms = your total reaction time.',
79
+ ],
80
+ },
81
+ { type: 'title' as const, text: 'World Ranking Table', level: 2 as const },
82
+ {
83
+ type: 'table' as const,
84
+ headers: ['Time', 'Rank', 'Description'],
85
+ rows: [
86
+ ['&lt; 180 ms', 'Cyberathlete', 'Superhuman level typical of eSports pros or fighter pilots.'],
87
+ ['180 - 230 ms', 'Tiger', 'Sharp reflexes. You\'re probably great at shooters.'],
88
+ ['230 - 280 ms', 'Human', 'The healthy average. You\'re awake and functioning properly.'],
89
+ ['280 - 350 ms', 'Turtle', 'Below average. Rest or train more.'],
90
+ ['&gt; 350 ms', 'Asleep', 'Possible fatigue, sleepiness or slow hardware (input lag).'],
91
+ ],
92
+ },
93
+ { type: 'title' as const, text: 'Why Are You Slow? Blaming the Hardware', level: 2 as const },
94
+ {
95
+ type: 'comparative' as const,
96
+ columns: 3 as const,
97
+ items: [
98
+ {
99
+ title: 'Monitor (Hz)',
100
+ description: 'A 60Hz monitor takes ~16ms to paint a frame. A 144Hz one takes only 7ms. That 10ms difference counts in your final score.',
101
+ icon: 'mdi:monitor',
102
+ points: ['60Hz = ~16ms per frame', '144Hz = ~7ms per frame', 'Visible difference in test'],
103
+ },
104
+ {
105
+ title: 'Wireless Mouse',
106
+ description: 'Cheap office Bluetooth mice have high latency. For this test, use a cable or low-latency 2.4GHz wireless technology.',
107
+ icon: 'mdi:mouse-variant',
108
+ points: ['Cheap Bluetooth: +15-50ms', '2.4GHz gaming: <1ms extra', 'USB cable: reference'],
109
+ },
110
+ {
111
+ title: 'Browser',
112
+ description: 'Extensions that block ads or heavy scripts can cause stuttering. Try incognito mode for your real record.',
113
+ icon: 'mdi:web',
114
+ points: ['Close heavy extensions', 'Incognito = cleaner', 'Chrome/Firefox recommended'],
115
+ },
116
+ ],
117
+ },
118
+ ];
119
+
120
+ const schemas: [WithContext<FAQPage>, WithContext<HowTo>, WithContext<SoftwareApplication>] = [
121
+ {
122
+ '@context': 'https://schema.org',
123
+ '@type': 'FAQPage',
124
+ mainEntity: faqData.map((f) => ({ '@type': 'Question', name: f.question, acceptedAnswer: { '@type': 'Answer', text: f.answer } })),
125
+ },
126
+ {
127
+ '@context': 'https://schema.org',
128
+ '@type': 'HowTo',
129
+ name: title,
130
+ description,
131
+ step: howTo.map((s) => ({ '@type': 'HowToStep', name: s.name, text: s.text })),
132
+ },
133
+ {
134
+ '@context': 'https://schema.org',
135
+ '@type': 'SoftwareApplication',
136
+ name: title,
137
+ description,
138
+ applicationCategory: 'SportsApplication',
139
+ operatingSystem: 'Web',
140
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
141
+ },
142
+ ];
143
+
144
+ export const content: ToolLocaleContent<ReactionTesterUI> = { slug, title, description, ui, seo, faqTitle: 'Frequently Asked Questions', faq: faqData, bibliography: [], howTo, schemas };
@@ -0,0 +1,144 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { ReactionTesterUI } from '../ui';
4
+
5
+ const slug = 'test-reflejos';
6
+ const title = 'Test de Velocidad de Reacción (Reflejos): Benchmark Online';
7
+ const description = 'Mide tus reflejos en milisegundos con nuestro test de reacción profesional. Compite por rangos desde \'Tortuga\' hasta \'Ciberatleta\' y analiza tu rendimiento.';
8
+
9
+ const ui: ReactionTesterUI = {
10
+ title: 'Test de Reflejos',
11
+ subtitle: 'Mide tu velocidad de reacción en milisegundos. Promedio de 5 intentos.',
12
+ startBtn: 'Haz click para comenzar',
13
+ goTitle: '¡YA!',
14
+ goSubtitle: '¡DALE AHORA!',
15
+ earlyTitle: '¡Demasiado pronto!',
16
+ earlySubtitle: 'Espera a que se ponga verde.',
17
+ earlyRetry: 'Click para reintentar',
18
+ promptNext: 'Click para intento',
19
+ promptFinal: 'Ver Resultados Finales',
20
+ avgLabel: 'Promedio final',
21
+ copyBtn: 'Copiar',
22
+ tweetBtn: 'Twittear',
23
+ retryBtn: 'Intentar de nuevo',
24
+ inputMouse: 'Ratón',
25
+ inputTouch: 'Táctil',
26
+ inputSpace: 'Espacio',
27
+ historyTitle: 'Historial Local',
28
+ historyBest: 'Top 5 Mejores',
29
+ historyWorst: 'Top 5 Peores',
30
+ historyEmpty: 'Juega para registrar tu primer récord',
31
+ copySuccess: '¡Copiado!',
32
+ shareText: '¡He sacado {ms}ms en el Test de Reflejos!\nRango: {rank}\n\n¿Puedes superarme?\n{url}',
33
+ rankCiberatleta: 'Ciberatleta',
34
+ rankTigre: 'Tigre',
35
+ rankHumano: 'Humano',
36
+ rankTortuga: 'Tortuga',
37
+ rankDormido: 'Dormido',
38
+ f150: '¡¿ERES UN ROBOT?!',
39
+ f180: '¡INCREÍBLE!',
40
+ f210: '¡Muy rápido!',
41
+ f250: 'Buena reacción',
42
+ f300: 'No está mal...',
43
+ f400: 'Un poco lento...',
44
+ f600: 'Te has dormido',
45
+ fSlow: 'Despierta...',
46
+ wp0t: 'ESPERA...', wp0s: 'Aguanta la respiración...',
47
+ wp1t: 'QUIETO...', wp1s: 'No parpadees ahora',
48
+ wp2t: 'ATENTO...', wp2s: 'Dedos preparados...',
49
+ wp3t: 'HOLD...', wp3s: 'Paciencia, saltamontes',
50
+ wp4t: 'OJITO...', wp4s: 'Va a salir ya...',
51
+ };
52
+
53
+ const faqData = [
54
+ { question: '¿Cuál es el tiempo de reacción medio de un humano?', answer: 'El tiempo medio de reacción humana ante un estímulo visual es de aproximadamente 250 a 270 milisegundos. Los atletas de alto rendimiento y pro-gamers suelen estar por debajo de los 200ms.' },
55
+ { question: '¿Cómo puedo mejorar mis reflejos?', answer: 'La clave es la práctica constante y el descanso. Jugar a videojuegos de acción (FPS), practicar deportes rápidos como el ping-pong y mantener una buena hidratación ayuda a mantener el sistema nervioso alerta.' },
56
+ { question: '¿Afecta la pantalla a mi resultado?', answer: 'Sí. Existe algo llamado \'input lag\'. Las pantallas con baja tasa de refresco (60Hz) o los ratones inalámbricos antiguos pueden añadir entre 15 y 50ms extras a tu tiempo real de reacción.' },
57
+ { question: '¿A qué edad empezamos a perder reflejos?', answer: 'Fisiológicamente, los reflejos humanos alcanzan su pico máximo entre los 20 y 24 años. A partir de ahí, se produce un declive muy lento (aprox. 2-6ms por década) si no se entrenan.' },
58
+ ];
59
+
60
+ const howTo = [
61
+ { name: 'Esperar al color rojo', text: 'Haz clic en la zona de inicio y espera pacientemente a que la pantalla cambie de color.' },
62
+ { name: 'Reaccionar al verde', text: 'En cuanto veas el color verde, haz clic o toca la pantalla lo más rápido que puedas.' },
63
+ { name: 'Ver el resultado', text: 'El sistema calculará la diferencia exacta en milisegundos entre el cambio de color y tu pulsación.' },
64
+ { name: 'Analizar tu rango', text: 'Repite el test 5 veces para obtener una media fiable y descubrir si eres una \'Tortuga\' o un \'Ciberatleta\'.' },
65
+ ];
66
+
67
+ const seo = [
68
+ { type: 'title' as const, text: '¿Cómo funciona este Test de Reflejos?', level: 2 as const },
69
+ {
70
+ type: 'paragraph' as const,
71
+ html: 'Esta herramienta mide tu <strong>tiempo de reacción visual</strong> con precisión quirúrgica. Calculamos el intervalo exacto desde que la pantalla se ilumina hasta que la señal viaja de tus ojos al cerebro, y de ahí a tus dedos. El test exige <strong>5 intentos consecutivos</strong> para calcular tu promedio real, eliminando el factor suerte.',
72
+ },
73
+ {
74
+ type: 'list' as const,
75
+ items: [
76
+ '<strong>Promedio de 5 intentos:</strong> Eliminamos el factor "suerte" obligándote a ser consistente. Un solo clic afortunado no te hará un Ciberatleta.',
77
+ '<strong>Sistema Anti-Trampas:</strong> Tiempos de espera aleatorios (1.5s - 4.5s) y detección de clics anticipados para evitar predicciones.',
78
+ '<strong>Ciencia real:</strong> Procesamiento cerebral ~180-200ms + respuesta motora +20-40ms = tu tiempo total de reacción.',
79
+ ],
80
+ },
81
+ { type: 'title' as const, text: 'Tabla de Clasificación Mundial', level: 2 as const },
82
+ {
83
+ type: 'table' as const,
84
+ headers: ['Tiempo', 'Rango', 'Descripción'],
85
+ rows: [
86
+ ['&lt; 180 ms', 'Ciberatleta', 'Nivel sobrehumano propio de pros de eSports o pilotos de caza.'],
87
+ ['180 - 230 ms', 'Tigre', 'Reflejos afilados. Probablemente seas muy bueno en shooters.'],
88
+ ['230 - 280 ms', 'Humano', 'El promedio saludable. Estás despierto y funcionando correctamente.'],
89
+ ['280 - 350 ms', 'Tortuga', 'Por debajo de la media. Descansa o entrena más.'],
90
+ ['&gt; 350 ms', 'Dormido', 'Posible fatiga, sueño o hardware lento (input lag).'],
91
+ ],
92
+ },
93
+ { type: 'title' as const, text: '¿Por qué eres lento? Culpando al Hardware', level: 2 as const },
94
+ {
95
+ type: 'comparative' as const,
96
+ columns: 3 as const,
97
+ items: [
98
+ {
99
+ title: 'Monitor (Hz)',
100
+ description: 'Un monitor de 60Hz tarda ~16ms en pintar un cuadro. Uno de 144Hz tarda solo 7ms. Esa diferencia de 10ms cuenta en tu puntuación final.',
101
+ icon: 'mdi:monitor',
102
+ points: ['60Hz = ~16ms por frame', '144Hz = ~7ms por frame', 'Diferencia visible en test'],
103
+ },
104
+ {
105
+ title: 'Ratón Inalámbrico',
106
+ description: 'Los ratones bluetooth de oficina baratos tienen latencias altas. Para este test, usa cable o tecnología wireless 2.4GHz de baja latencia.',
107
+ icon: 'mdi:mouse-variant',
108
+ points: ['Bluetooth barato: +15-50ms', '2.4GHz gamer: <1ms extra', 'Cable USB: referencia'],
109
+ },
110
+ {
111
+ title: 'Navegador',
112
+ description: 'Las extensiones que bloquean anuncios o scripts pesados pueden causar "stuttering". Prueba en modo incógnito para tu récord real.',
113
+ icon: 'mdi:web',
114
+ points: ['Cierra extensiones pesadas', 'Modo incógnito = más limpio', 'Chrome/Firefox recomendado'],
115
+ },
116
+ ],
117
+ },
118
+ ];
119
+
120
+ const schemas: [WithContext<FAQPage>, WithContext<HowTo>, WithContext<SoftwareApplication>] = [
121
+ {
122
+ '@context': 'https://schema.org',
123
+ '@type': 'FAQPage',
124
+ mainEntity: faqData.map((f) => ({ '@type': 'Question', name: f.question, acceptedAnswer: { '@type': 'Answer', text: f.answer } })),
125
+ },
126
+ {
127
+ '@context': 'https://schema.org',
128
+ '@type': 'HowTo',
129
+ name: title,
130
+ description,
131
+ step: howTo.map((s) => ({ '@type': 'HowToStep', name: s.name, text: s.text })),
132
+ },
133
+ {
134
+ '@context': 'https://schema.org',
135
+ '@type': 'SoftwareApplication',
136
+ name: title,
137
+ description,
138
+ applicationCategory: 'SportsApplication',
139
+ operatingSystem: 'Web',
140
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
141
+ },
142
+ ];
143
+
144
+ export const content: ToolLocaleContent<ReactionTesterUI> = { slug, title, description, ui, seo, faqTitle: 'Preguntas frecuentes', faq: faqData, bibliography: [], howTo, schemas };
@@ -0,0 +1,144 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { ReactionTesterUI } from '../ui';
4
+
5
+ const slug = 'test-reflexes';
6
+ const title = 'Test de Vitesse de Réaction (Réflexes): Benchmark en Ligne';
7
+ const description = 'Mesurez vos réflexes en millisecondes avec notre test de réaction professionnel. Compétez pour des rangs de \'Tortue\' à \'Cyberathlète\' et analysez vos performances.';
8
+
9
+ const ui: ReactionTesterUI = {
10
+ title: 'Test de Réflexes',
11
+ subtitle: 'Mesurez votre vitesse de réaction en millisecondes. Moyenne de 5 tentatives.',
12
+ startBtn: 'Cliquez pour commencer',
13
+ goTitle: 'ALLEZ!',
14
+ goSubtitle: 'CLIQUEZ MAINTENANT!',
15
+ earlyTitle: 'Trop tôt !',
16
+ earlySubtitle: 'Attendez le vert.',
17
+ earlyRetry: 'Cliquez pour réessayer',
18
+ promptNext: 'Clic pour tentative',
19
+ promptFinal: 'Voir les résultats finaux',
20
+ avgLabel: 'Moyenne finale',
21
+ copyBtn: 'Copier',
22
+ tweetBtn: 'Tweeter',
23
+ retryBtn: 'Réessayer',
24
+ inputMouse: 'Souris',
25
+ inputTouch: 'Tactile',
26
+ inputSpace: 'Espace',
27
+ historyTitle: 'Historique local',
28
+ historyBest: 'Top 5 Meilleurs',
29
+ historyWorst: 'Top 5 Pires',
30
+ historyEmpty: 'Jouez pour enregistrer votre premier score',
31
+ copySuccess: 'Copié !',
32
+ shareText: 'J\'ai obtenu {ms}ms au Test de Réflexes !\nRang : {rank}\n\nPeux-tu me battre ?\n{url}',
33
+ rankCiberatleta: 'Cyberathlète',
34
+ rankTigre: 'Tigre',
35
+ rankHumano: 'Humain',
36
+ rankTortuga: 'Tortue',
37
+ rankDormido: 'Endormi',
38
+ f150: 'ES-TU UN ROBOT ?!',
39
+ f180: 'INCROYABLE !',
40
+ f210: 'Très rapide !',
41
+ f250: 'Bonne réaction',
42
+ f300: 'Pas mal...',
43
+ f400: 'Un peu lent...',
44
+ f600: 'Tu t\'es endormi',
45
+ fSlow: 'Réveille-toi...',
46
+ wp0t: 'ATTENDS...', wp0s: 'Retiens ta respiration...',
47
+ wp1t: 'IMMOBILE...', wp1s: 'Ne cligne pas des yeux',
48
+ wp2t: 'PRÊT...', wp2s: 'Doigts prêts...',
49
+ wp3t: 'HOLD...', wp3s: 'Patience, sauterelle',
50
+ wp4t: 'FOCUS...', wp4s: 'Ça arrive...',
51
+ };
52
+
53
+ const faqData = [
54
+ { question: 'Quel est le temps de réaction moyen d\'un humain ?', answer: 'Le temps de réaction moyen d\'un humain à un stimulus visuel est d\'environ 250 à 270 millisecondes. Les athlètes de haut niveau et les pro-gamers sont généralement en dessous de 200ms.' },
55
+ { question: 'Comment puis-je améliorer mes réflexes ?', answer: 'La clé est la pratique constante et le repos. Jouer à des jeux vidéo d\'action (FPS), pratiquer des sports rapides comme le ping-pong et rester bien hydraté aide à maintenir le système nerveux alerte.' },
56
+ { question: 'L\'écran affecte-t-il mon résultat ?', answer: 'Oui. Il y a quelque chose appelé \'input lag\'. Les écrans à faible taux de rafraîchissement (60Hz) ou les vieilles souris sans fil peuvent ajouter 15 à 50ms à votre temps de réaction réel.' },
57
+ { question: 'À quel âge commence-t-on à perdre des réflexes ?', answer: 'Physiologiquement, les réflexes humains atteignent leur pic entre 20 et 24 ans. Ensuite, il y a un déclin très lent (environ 2-6ms par décennie) si on ne s\'entraîne pas.' },
58
+ ];
59
+
60
+ const howTo = [
61
+ { name: 'Attendre le rouge', text: 'Cliquez sur la zone de départ et attendez patiemment que l\'écran change de couleur.' },
62
+ { name: 'Réagir au vert', text: 'Dès que vous voyez le vert, cliquez ou touchez l\'écran le plus vite possible.' },
63
+ { name: 'Voir le résultat', text: 'Le système calculera la différence exacte en millisecondes entre le changement de couleur et votre pression.' },
64
+ { name: 'Analyser votre rang', text: 'Répétez le test 5 fois pour obtenir une moyenne fiable et découvrir si vous êtes une \'Tortue\' ou un \'Cyberathlète\'.' },
65
+ ];
66
+
67
+ const seo = [
68
+ { type: 'title' as const, text: 'Comment fonctionne ce Test de Réflexes ?', level: 2 as const },
69
+ {
70
+ type: 'paragraph' as const,
71
+ html: 'Cet outil mesure votre <strong>temps de réaction visuelle</strong> avec une précision chirurgicale. Nous calculons l\'intervalle exact depuis que l\'écran s\'illumine jusqu\'à ce que le signal voyage de vos yeux au cerveau, puis à vos doigts. Le test exige <strong>5 tentatives consécutives</strong> pour calculer votre vraie moyenne, éliminant le facteur chance.',
72
+ },
73
+ {
74
+ type: 'list' as const,
75
+ items: [
76
+ '<strong>Moyenne de 5 tentatives :</strong> Nous éliminons le facteur "chance" en exigeant la consistance. Un seul clic chanceux ne fera pas de vous un Cyberathlète.',
77
+ '<strong>Système anti-triche :</strong> Temps d\'attente aléatoires (1,5s - 4,5s) et détection des clics anticipés pour éviter les prédictions.',
78
+ '<strong>Science réelle :</strong> Traitement cérébral ~180-200ms + réponse motrice +20-40ms = votre temps de réaction total.',
79
+ ],
80
+ },
81
+ { type: 'title' as const, text: 'Tableau de Classement Mondial', level: 2 as const },
82
+ {
83
+ type: 'table' as const,
84
+ headers: ['Temps', 'Rang', 'Description'],
85
+ rows: [
86
+ ['&lt; 180 ms', 'Cyberathlète', 'Niveau surhumain typique des pros d\'eSports ou pilotes de chasse.'],
87
+ ['180 - 230 ms', 'Tigre', 'Réflexes aiguisés. Vous êtes probablement très bon aux shooters.'],
88
+ ['230 - 280 ms', 'Humain', 'La moyenne saine. Vous êtes éveillé et fonctionnez correctement.'],
89
+ ['280 - 350 ms', 'Tortue', 'En dessous de la moyenne. Reposez-vous ou entraînez-vous plus.'],
90
+ ['&gt; 350 ms', 'Endormi', 'Fatigue possible, sommeil ou matériel lent (input lag).'],
91
+ ],
92
+ },
93
+ { type: 'title' as const, text: 'Pourquoi êtes-vous lent ? La faute au matériel', level: 2 as const },
94
+ {
95
+ type: 'comparative' as const,
96
+ columns: 3 as const,
97
+ items: [
98
+ {
99
+ title: 'Moniteur (Hz)',
100
+ description: 'Un moniteur 60Hz met ~16ms à afficher une image. Un 144Hz seulement 7ms. Cette différence de 10ms compte dans votre score final.',
101
+ icon: 'mdi:monitor',
102
+ points: ['60Hz = ~16ms par image', '144Hz = ~7ms par image', 'Différence visible au test'],
103
+ },
104
+ {
105
+ title: 'Souris sans fil',
106
+ description: 'Les souris Bluetooth bon marché ont des latences élevées. Pour ce test, utilisez un câble ou la technologie sans fil 2,4GHz à faible latence.',
107
+ icon: 'mdi:mouse-variant',
108
+ points: ['Bluetooth bas de gamme: +15-50ms', '2,4GHz gaming: <1ms extra', 'Câble USB: référence'],
109
+ },
110
+ {
111
+ title: 'Navigateur',
112
+ description: 'Les extensions qui bloquent les publicités ou les scripts lourds peuvent causer du "stuttering". Essayez en mode incognito pour votre vrai record.',
113
+ icon: 'mdi:web',
114
+ points: ['Fermez les extensions lourdes', 'Incognito = plus propre', 'Chrome/Firefox recommandé'],
115
+ },
116
+ ],
117
+ },
118
+ ];
119
+
120
+ const schemas: [WithContext<FAQPage>, WithContext<HowTo>, WithContext<SoftwareApplication>] = [
121
+ {
122
+ '@context': 'https://schema.org',
123
+ '@type': 'FAQPage',
124
+ mainEntity: faqData.map((f) => ({ '@type': 'Question', name: f.question, acceptedAnswer: { '@type': 'Answer', text: f.answer } })),
125
+ },
126
+ {
127
+ '@context': 'https://schema.org',
128
+ '@type': 'HowTo',
129
+ name: title,
130
+ description,
131
+ step: howTo.map((s) => ({ '@type': 'HowToStep', name: s.name, text: s.text })),
132
+ },
133
+ {
134
+ '@context': 'https://schema.org',
135
+ '@type': 'SoftwareApplication',
136
+ name: title,
137
+ description,
138
+ applicationCategory: 'SportsApplication',
139
+ operatingSystem: 'Web',
140
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
141
+ },
142
+ ];
143
+
144
+ export const content: ToolLocaleContent<ReactionTesterUI> = { slug, title, description, ui, seo, faqTitle: 'Questions fréquentes', faq: faqData, bibliography: [], howTo, schemas };
@@ -0,0 +1,34 @@
1
+ import type { SportsToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
2
+ import ReactionTesterComponent from './component.astro';
3
+ import ReactionTesterSEO from './seo.astro';
4
+ import ReactionTesterBibliography from './bibliography.astro';
5
+ import type { ReactionTesterUI } from './ui';
6
+
7
+ export type { ReactionTesterUI };
8
+ export type ReactionTesterLocaleContent = ToolLocaleContent<ReactionTesterUI>;
9
+
10
+ import { content as es } from './i18n/es';
11
+ import { content as en } from './i18n/en';
12
+ import { content as fr } from './i18n/fr';
13
+
14
+ export const reactionTester: SportsToolEntry<ReactionTesterUI> = {
15
+ id: 'reaction-tester',
16
+ icons: {
17
+ bg: 'mdi:lightning-bolt',
18
+ fg: 'mdi:timer-outline',
19
+ },
20
+ i18n: {
21
+ es: async () => es,
22
+ en: async () => en,
23
+ fr: async () => fr,
24
+ },
25
+ };
26
+
27
+ export { ReactionTesterComponent, ReactionTesterSEO, ReactionTesterBibliography };
28
+
29
+ export const REACTION_TESTER_TOOL: ToolDefinition = {
30
+ entry: reactionTester,
31
+ Component: ReactionTesterComponent,
32
+ SEOComponent: ReactionTesterSEO,
33
+ BibliographyComponent: ReactionTesterBibliography,
34
+ };
@@ -0,0 +1,12 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { reactionTester } from './index';
4
+ import type { KnownLocale, ToolLocaleContent } from '../../types';
5
+ import type { ReactionTesterUI } from './ui';
6
+
7
+ interface Props { locale: KnownLocale }
8
+ const { locale } = Astro.props;
9
+ const content = await reactionTester.i18n[locale]?.() as ToolLocaleContent<ReactionTesterUI> | undefined;
10
+ ---
11
+
12
+ {content && <SEORenderer content={{ locale: locale as string, sections: content.seo }} />}
@@ -0,0 +1,43 @@
1
+ export interface ReactionTesterUI {
2
+ title: string;
3
+ subtitle: string;
4
+ startBtn: string;
5
+ goTitle: string;
6
+ goSubtitle: string;
7
+ earlyTitle: string;
8
+ earlySubtitle: string;
9
+ earlyRetry: string;
10
+ promptNext: string;
11
+ promptFinal: string;
12
+ avgLabel: string;
13
+ copyBtn: string;
14
+ tweetBtn: string;
15
+ retryBtn: string;
16
+ inputMouse: string;
17
+ inputTouch: string;
18
+ inputSpace: string;
19
+ historyTitle: string;
20
+ historyBest: string;
21
+ historyWorst: string;
22
+ historyEmpty: string;
23
+ copySuccess: string;
24
+ shareText: string;
25
+ rankCiberatleta: string;
26
+ rankTigre: string;
27
+ rankHumano: string;
28
+ rankTortuga: string;
29
+ rankDormido: string;
30
+ f150: string;
31
+ f180: string;
32
+ f210: string;
33
+ f250: string;
34
+ f300: string;
35
+ f400: string;
36
+ f600: string;
37
+ fSlow: string;
38
+ wp0t: string; wp0s: string;
39
+ wp1t: string; wp1s: string;
40
+ wp2t: string; wp2s: string;
41
+ wp3t: string; wp3s: string;
42
+ wp4t: string; wp4s: string;
43
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography } from '@jjlmoya/utils-shared';
3
+ import { scoreKeeper } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await scoreKeeper.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <Bibliography links={content.bibliography} />}