@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
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@jjlmoya/utils-sports",
3
+ "version": "1.1.0",
4
+ "type": "module",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./data": "./src/data.ts"
10
+ },
11
+ "files": [
12
+ "src"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "dev": "astro dev",
19
+ "start": "astro dev",
20
+ "build": "astro build",
21
+ "preview": "astro preview",
22
+ "astro": "astro",
23
+ "lint": "eslint src/ --max-warnings 0 && stylelint \"src/**/*.{css,astro}\"",
24
+ "check": "astro check",
25
+ "type-check": "astro check",
26
+ "test": "vitest run",
27
+ "preversion": "npm run lint && npm run test",
28
+ "postversion": "git push && git push --tags",
29
+ "patch": "npm version patch",
30
+ "minor": "npm version minor",
31
+ "major": "npm version major"
32
+ },
33
+ "lint-staged": {
34
+ "*.{ts,tsx,astro}": [
35
+ "eslint --fix"
36
+ ]
37
+ },
38
+ "dependencies": {
39
+ "@iconify-json/mdi": "^1.2.3",
40
+ "@jjlmoya/utils-shared": "1.2.0",
41
+ "astro": "^6.1.2",
42
+ "astro-icon": "^1.1.0",
43
+ "lz-string": "^1.5.0"
44
+ },
45
+ "devDependencies": {
46
+ "@astrojs/check": "^0.9.8",
47
+ "@types/lz-string": "^1.3.34",
48
+ "eslint": "^9.39.4",
49
+ "eslint-plugin-astro": "^1.6.0",
50
+ "eslint-plugin-no-comments": "^1.1.10",
51
+ "husky": "^9.1.7",
52
+ "lint-staged": "^16.4.0",
53
+ "postcss-html": "^1.8.1",
54
+ "schema-dts": "^1.1.2",
55
+ "stylelint": "^17.6.0",
56
+ "stylelint-config-standard": "^40.0.0",
57
+ "stylelint-declaration-strict-value": "^1.11.1",
58
+ "typescript": "^5.4.0",
59
+ "typescript-eslint": "^8.58.0",
60
+ "vitest": "^4.1.2"
61
+ }
62
+ }
@@ -0,0 +1,108 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'sports',
5
+ title: 'Sports & Performance Tools and Calculators',
6
+ description:
7
+ 'Optimize your training with free online tools. Giant scoreboards, tournament organizers, reflex tests and gym progress tracking.',
8
+ seo: [
9
+ {
10
+ type: 'title',
11
+ text: 'Sports Performance Science: Data to Win',
12
+ level: 2,
13
+ },
14
+ {
15
+ type: 'paragraph',
16
+ html: 'High-performance sports, whether amateur or professional, is no longer just about intuition. In today\'s era, <strong>biometrics and data analysis</strong> define who crosses the finish line first or who lifts the most weight. In this section, we offer <strong>free online tools</strong> designed to digitize your performance and manage competition logistics with technical precision.',
17
+ },
18
+ {
19
+ type: 'paragraph',
20
+ html: 'From measuring neuromuscular reaction speed to managing tournament elimination brackets, our calculators apply algorithmic and statistical logic so you can focus on what matters most: pushing your limits.',
21
+ },
22
+ {
23
+ type: 'title',
24
+ text: 'Competitive Logistics: Scoreboards and Organizers',
25
+ level: 2,
26
+ },
27
+ {
28
+ type: 'paragraph',
29
+ html: 'Organizing a competition, whether a local padel tournament or an eSports event, requires a clear structure. Our <strong>tournament organizer</strong> automates the creation of matchup brackets, eliminating manual errors and ensuring fair management of eliminations.',
30
+ },
31
+ {
32
+ type: 'paragraph',
33
+ html: 'For match day, the <strong>giant sports scoreboard</strong> turns any screen into a professional control center, letting players and spectators follow the padel, ping-pong or any set-based sport score with total visibility.',
34
+ },
35
+ {
36
+ type: 'title',
37
+ text: 'Strength Training: Progression-Based Tracking',
38
+ level: 2,
39
+ },
40
+ {
41
+ type: 'paragraph',
42
+ html: 'At the gym, what isn\'t measured doesn\'t improve. The <strong>training tracker</strong> lets you log your workloads and visualize real progress graphs. Understanding the supercompensation curve and accumulated training volume is vital to avoid stagnation and optimize strength and muscle gains.',
43
+ },
44
+ {
45
+ type: 'title',
46
+ text: 'Neurology & Reflexes: Reaction Speed',
47
+ level: 2,
48
+ },
49
+ {
50
+ type: 'paragraph',
51
+ html: 'Physical speed has limits, but <strong>reaction speed</strong> can be trained. Our reflex test measures neuromuscular response time in milliseconds, letting you compare your performance against professional athlete ranges. It\'s an essential tool for combat sports, racket sports, and competitive gaming.',
52
+ },
53
+ {
54
+ type: 'list',
55
+ items: [
56
+ '<strong>Progress Analysis:</strong> Visualize your historical evolution in key exercises through dynamic performance graphs.',
57
+ '<strong>Automated Management:</strong> Create complex tournament structures in seconds without administrative hassle.',
58
+ '<strong>Cognitive Training:</strong> Improve visual processing and motor response speed with precision tests.',
59
+ '<strong>Real-Time Control:</strong> Stay focused on the game with high-visibility digital scoreboards.',
60
+ ],
61
+ },
62
+ {
63
+ type: 'tip',
64
+ title: 'Peak Performance Tip',
65
+ html: '<p><strong>Applied Physiology:</strong> Make sure to run the reflex test before and after your training session to assess central nervous system fatigue. A 10% drop from your usual speed is a key indicator to reduce training volume.</p>',
66
+ },
67
+ {
68
+ type: 'title',
69
+ text: 'Training Physiology: Heart Rate Zones',
70
+ level: 2,
71
+ },
72
+ {
73
+ type: 'paragraph',
74
+ html: 'Your heart is the most precise indicator of effort. Maximum heart rate (MHR) varies by age, genetics, and cardiovascular capacity. Calculating it correctly allows training in specific zones: aerobic zone (60-70% MHR) for base endurance, anaerobic zone (80-90% MHR) for explosiveness, VO2 max zone (95-100%) for cardiorespiratory capacity.',
75
+ },
76
+ {
77
+ type: 'paragraph',
78
+ html: 'A common mistake is training too hard too often. Most training should occur in the aerobic zone (low intensity, long duration) building a solid base. Only 1-2 weekly sessions in high-intensity zones are optimal to avoid overtraining. Our tools let you calculate your zones based on age and fitness level, ensuring every session has a defined physiological purpose.',
79
+ },
80
+ {
81
+ type: 'title',
82
+ text: 'Periodization: Planning Macrocycles and Mesocycles',
83
+ level: 2,
84
+ },
85
+ {
86
+ type: 'paragraph',
87
+ html: 'Unstructured training produces unstructured results. Periodization (long-term planning) divides your year into phases: preparation, competition, and recovery. Each phase has goals: the preparation phase maximizes raw strength; the competitive phase converts that strength into power and speed; the recovery phase allows hormonal adaptations and tissue repair.',
88
+ },
89
+ {
90
+ type: 'title',
91
+ text: 'The Future of Connected Sport in 2026',
92
+ level: 2,
93
+ },
94
+ {
95
+ type: 'paragraph',
96
+ html: 'The 2026 trend is <strong>Velocity-Based Training (VBT)</strong>. Wearable devices and tracking apps are democratizing access to analytics that were once exclusive to elite sports. These tools put you at the cutting edge, allowing you to train smarter, not necessarily harder.',
97
+ },
98
+ {
99
+ type: 'grid',
100
+ columns: [
101
+ { type: 'card', title: 'Reflexes', html: '<p>Millisecond precision</p>' },
102
+ { type: 'card', title: 'Progress', html: '<p>Historical visualization</p>' },
103
+ { type: 'card', title: 'Competition', html: '<p>Structured management</p>' },
104
+ { type: 'card', title: 'Precision', html: '<p>Absolute data</p>' },
105
+ ],
106
+ },
107
+ ],
108
+ };
@@ -0,0 +1,108 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'deporte',
5
+ title: 'Herramientas y Calculadoras Deportivas y de Rendimiento',
6
+ description:
7
+ 'Optimiza tu entrenamiento con herramientas gratuitas online. Marcadores gigantes, organizadores de torneos, test de reflejos y seguimiento de progreso en el gimnasio.',
8
+ seo: [
9
+ {
10
+ type: 'title',
11
+ text: 'Ciencia del Rendimiento Deportivo: Datos para Ganar',
12
+ level: 2,
13
+ },
14
+ {
15
+ type: 'paragraph',
16
+ html: 'El deporte de alto rendimiento, ya sea amateur o profesional, ha dejado de ser una cuestión de simple intuición. En la era actual, la <strong>biometría y el análisis de datos</strong> definen quién cruza la meta primero o quién levanta más peso. En esta sección, ofrecemos <strong>herramientas gratuitas online</strong> diseñadas para digitalizar tu rendimiento y gestionar la logística de tus competiciones con precisión técnica.',
17
+ },
18
+ {
19
+ type: 'paragraph',
20
+ html: 'Desde la medición de la velocidad de reacción neuromuscular hasta la gestión de cuadros de eliminatorias para torneos, nuestras calculadoras aplican lógica algorítmica y estadística para que te centres en lo más importante: superar tus límites.',
21
+ },
22
+ {
23
+ type: 'title',
24
+ text: 'Logística Competitiva: Marcadores y Organizadores',
25
+ level: 2,
26
+ },
27
+ {
28
+ type: 'paragraph',
29
+ html: 'Organizar una competición, ya sea un torneo de pádel local o un evento de eSports, requiere una estructura clara. Nuestro <strong>organizador de torneos</strong> automatiza la creación de cuadros de enfrentamientos, eliminando errores manuales y garantizando una gestión justa de las eliminatorias.',
30
+ },
31
+ {
32
+ type: 'paragraph',
33
+ html: 'Para el día del partido, el <strong>marcador deportivo gigante</strong> transforma cualquier pantalla en un centro de control profesional, permitiendo que jugadores y público sigan el marcador de pádel, ping-pong o cualquier deporte de set con visibilidad total.',
34
+ },
35
+ {
36
+ type: 'title',
37
+ text: 'Entrenamiento de Fuerza: Seguimiento Basado en Progresión',
38
+ level: 2,
39
+ },
40
+ {
41
+ type: 'paragraph',
42
+ html: 'En el gimnasio, lo que no se mide no mejora. El <strong>seguimiento de entrenamiento</strong> permite registrar tus cargas de trabajo y visualizar gráficas de progreso real. Entender la curva de supercompensación y el volumen de entrenamiento acumulado es vital para evitar el estancamiento y optimizar las ganancias de fuerza y masa muscular.',
43
+ },
44
+ {
45
+ type: 'title',
46
+ text: 'Neuroclínica y Reflejos: Velocidad de Reacción',
47
+ level: 2,
48
+ },
49
+ {
50
+ type: 'paragraph',
51
+ html: 'La velocidad física es limitada, pero la <strong>velocidad de reacción</strong> se puede entrenar. Nuestro test de reflejos mide el tiempo de respuesta neuromuscular en milisegundos, permitiéndote comparar tu rendimiento con rangos de atletas profesionales. Es una herramienta esencial para deportes de combate, deportes de raqueta y gaming competitivo.',
52
+ },
53
+ {
54
+ type: 'list',
55
+ items: [
56
+ '<strong>Análisis de Progreso:</strong> Visualiza tu evolución histórica en ejercicios clave mediante gráficas de rendimiento dinámicas.',
57
+ '<strong>Gestión Automatizada:</strong> Crea estructuras de torneos complejas en segundos sin complicaciones administrativas.',
58
+ '<strong>Entrenamiento Cognitivo:</strong> Mejora el procesamiento visual y la velocidad de respuesta motora con tests de precisión.',
59
+ '<strong>Control en Tiempo Real:</strong> Mantén el foco en el juego con marcadores digitales de alta visibilidad.',
60
+ ],
61
+ },
62
+ {
63
+ type: 'tip',
64
+ title: 'Tip de Rendimiento Máximo',
65
+ html: '<p><strong>Fisiología Aplicada:</strong> Asegúrate de realizar el test de reflejos antes y después de tu sesión de entrenamiento para evaluar la fatiga de tu sistema nervioso central. Un descenso del 10% en tu velocidad habitual es un indicador clave para reducir el volumen de trabajo.</p>',
66
+ },
67
+ {
68
+ type: 'title',
69
+ text: 'Fisiología del Entrenamiento: Zonas de Frecuencia Cardíaca',
70
+ level: 2,
71
+ },
72
+ {
73
+ type: 'paragraph',
74
+ html: 'Tu corazón es el indicador más preciso del esfuerzo. La máxima frecuencia cardíaca (MFC) varía según edad, genética y capacidad cardiovascular. Calcularla correctamente permite entrenar en zonas específicas: zona aeróbica (60-70% MFC) para resistencia base, zona anaeróbica (80-90% MFC) para explosividad, zona VO2 máximo (95-100%) para capacidad cardiorespiratoria.',
75
+ },
76
+ {
77
+ type: 'paragraph',
78
+ html: 'Un error común es entrenar demasiado duro demasiado frecuente. La mayoría del entrenamiento debe ocurrir en zona aeróbica (baja intensidad, larga duración) construyendo una base sólida. Solo 1-2 sesiones semanales en zonas de alta intensidad son óptimas para evitar sobreentreno. Nuestras herramientas te permiten calcular tus zonas según tu edad y condición física, asegurando que cada sesión tiene un propósito fisiológico definido.',
79
+ },
80
+ {
81
+ type: 'title',
82
+ text: 'Periodización: Planificación de Macrociclos y Mesociclos',
83
+ level: 2,
84
+ },
85
+ {
86
+ type: 'paragraph',
87
+ html: 'El entrenamiento desestructurado produce resultados desestructurados. La periodización (planificación a largo plazo) divide tu año en fases: preparación, competencia y recuperación. Cada fase tiene objetivos: la fase de preparación maximiza fuerza bruta; la fase competitiva convierte esa fuerza en potencia y velocidad; la fase de recuperación permite adaptaciones hormonales y reparación tisular.',
88
+ },
89
+ {
90
+ type: 'title',
91
+ text: 'El futuro del deporte conectado 2026',
92
+ level: 2,
93
+ },
94
+ {
95
+ type: 'paragraph',
96
+ html: 'La tendencia de 2026 es el <strong>Entrenamiento Basado en Datos (VBT)</strong>. Los dispositivos vestibles y las aplicaciones de seguimiento están democratizando el acceso a la analítica que antes era exclusiva del deporte de élite. Estas herramientas te sitúan en la vanguardia, permitiéndote entrenar de forma más inteligente, no necesariamente más dura.',
97
+ },
98
+ {
99
+ type: 'grid',
100
+ columns: [
101
+ { type: 'card', title: 'Reflejos', html: '<p>Milisegundos de precisión</p>' },
102
+ { type: 'card', title: 'Progreso', html: '<p>Visualización histórica</p>' },
103
+ { type: 'card', title: 'Competencia', html: '<p>Gestión estructurada</p>' },
104
+ { type: 'card', title: 'Precisión', html: '<p>Datos absolutos</p>' },
105
+ ],
106
+ },
107
+ ],
108
+ };
@@ -0,0 +1,95 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'sport',
5
+ title: 'Outils et Calculateurs Sportifs et de Performance',
6
+ description:
7
+ 'Optimisez votre entraînement avec des outils gratuits en ligne. Tableaux de score géants, organisateurs de tournois, tests de réflexes et suivi de progression en salle.',
8
+ seo: [
9
+ {
10
+ type: 'title',
11
+ text: 'Science de la Performance Sportive : Les Données pour Gagner',
12
+ level: 2,
13
+ },
14
+ {
15
+ type: 'paragraph',
16
+ html: 'Le sport de haute performance, amateur ou professionnel, ne repose plus sur la simple intuition. À l\'ère actuelle, la <strong>biométrie et l\'analyse de données</strong> définissent qui franchit la ligne d\'arrivée en premier ou qui soulève le plus. Dans cette section, nous proposons des <strong>outils gratuits en ligne</strong> conçus pour digitaliser votre performance et gérer la logistique de vos compétitions avec précision technique.',
17
+ },
18
+ {
19
+ type: 'paragraph',
20
+ html: 'De la mesure de la vitesse de réaction neuromusculaire à la gestion des tableaux d\'élimination de tournois, nos calculateurs appliquent une logique algorithmique et statistique pour que vous vous concentriez sur l\'essentiel : dépasser vos limites.',
21
+ },
22
+ {
23
+ type: 'title',
24
+ text: 'Logistique Compétitive : Tableaux de Score et Organisateurs',
25
+ level: 2,
26
+ },
27
+ {
28
+ type: 'paragraph',
29
+ html: 'Organiser une compétition, qu\'il s\'agisse d\'un tournoi de padel local ou d\'un événement eSports, nécessite une structure claire. Notre <strong>organisateur de tournois</strong> automatise la création de tableaux de confrontations, éliminant les erreurs manuelles et garantissant une gestion équitable des éliminations.',
30
+ },
31
+ {
32
+ type: 'paragraph',
33
+ html: 'Pour le jour du match, le <strong>tableau de score sportif géant</strong> transforme n\'importe quel écran en centre de contrôle professionnel, permettant aux joueurs et au public de suivre le score de padel, ping-pong ou tout sport à sets avec une visibilité totale.',
34
+ },
35
+ {
36
+ type: 'title',
37
+ text: 'Entraînement en Force : Suivi Basé sur la Progression',
38
+ level: 2,
39
+ },
40
+ {
41
+ type: 'paragraph',
42
+ html: 'En salle de sport, ce qui ne se mesure pas ne s\'améliore pas. Le <strong>suivi de l\'entraînement</strong> permet d\'enregistrer vos charges de travail et de visualiser des graphiques de progression réels. Comprendre la courbe de surcompensation et le volume d\'entraînement accumulé est vital pour éviter la stagnation et optimiser les gains de force et de masse musculaire.',
43
+ },
44
+ {
45
+ type: 'title',
46
+ text: 'Neurologie et Réflexes : Vitesse de Réaction',
47
+ level: 2,
48
+ },
49
+ {
50
+ type: 'paragraph',
51
+ html: 'La vitesse physique est limitée, mais la <strong>vitesse de réaction</strong> peut être entraînée. Notre test de réflexes mesure le temps de réponse neuromusculaire en millisecondes, vous permettant de comparer votre performance avec les plages des athlètes professionnels. C\'est un outil essentiel pour les sports de combat, les sports de raquette et le gaming compétitif.',
52
+ },
53
+ {
54
+ type: 'list',
55
+ items: [
56
+ '<strong>Analyse de Progression :</strong> Visualisez votre évolution historique sur les exercices clés grâce à des graphiques de performance dynamiques.',
57
+ '<strong>Gestion Automatisée :</strong> Créez des structures de tournois complexes en quelques secondes sans complications administratives.',
58
+ '<strong>Entraînement Cognitif :</strong> Améliorez le traitement visuel et la vitesse de réponse motrice avec des tests de précision.',
59
+ '<strong>Contrôle en Temps Réel :</strong> Restez concentré sur le jeu avec des tableaux de score numériques haute visibilité.',
60
+ ],
61
+ },
62
+ {
63
+ type: 'tip',
64
+ title: 'Conseil de Performance Maximale',
65
+ html: '<p><strong>Physiologie Appliquée :</strong> Assurez-vous d\'effectuer le test de réflexes avant et après votre séance d\'entraînement pour évaluer la fatigue de votre système nerveux central. Une baisse de 10% par rapport à votre vitesse habituelle est un indicateur clé pour réduire le volume de travail.</p>',
66
+ },
67
+ {
68
+ type: 'title',
69
+ text: 'Physiologie de l\'Entraînement : Zones de Fréquence Cardiaque',
70
+ level: 2,
71
+ },
72
+ {
73
+ type: 'paragraph',
74
+ html: 'Votre cœur est l\'indicateur le plus précis de l\'effort. La fréquence cardiaque maximale (FCM) varie selon l\'âge, la génétique et la capacité cardiovasculaire. La calculer correctement permet de s\'entraîner dans des zones spécifiques : zone aérobie (60-70% FCM) pour l\'endurance de base, zone anaérobie (80-90% FCM) pour l\'explosivité, zone VO2 max (95-100%) pour la capacité cardio-respiratoire.',
75
+ },
76
+ {
77
+ type: 'title',
78
+ text: 'Le Futur du Sport Connecté en 2026',
79
+ level: 2,
80
+ },
81
+ {
82
+ type: 'paragraph',
83
+ html: 'La tendance de 2026 est l\'<strong>Entraînement Basé sur la Vélocité (EBV)</strong>. Les appareils connectés et les applications de suivi démocratisent l\'accès à l\'analytique autrefois réservée au sport d\'élite. Ces outils vous placent à l\'avant-garde, vous permettant de vous entraîner plus intelligemment, pas nécessairement plus durement.',
84
+ },
85
+ {
86
+ type: 'grid',
87
+ columns: [
88
+ { type: 'card', title: 'Réflexes', html: '<p>Précision en millisecondes</p>' },
89
+ { type: 'card', title: 'Progression', html: '<p>Visualisation historique</p>' },
90
+ { type: 'card', title: 'Compétition', html: '<p>Gestion structurée</p>' },
91
+ { type: 'card', title: 'Précision', html: '<p>Données absolues</p>' },
92
+ ],
93
+ },
94
+ ],
95
+ };
@@ -0,0 +1,21 @@
1
+ import type { SportsCategoryEntry, SportsToolEntry } from '../types';
2
+ import { scoreKeeper } from '../tool/scoreKeeper/index';
3
+ import { tournamentBracket } from '../tool/tournamentBracket/index';
4
+ import { gymTracker } from '../tool/gymTracker/index';
5
+ import { reactionTester } from '../tool/reactionTester/index';
6
+
7
+ export const sportsCategory: SportsCategoryEntry = {
8
+ icon: 'mdi:basketball',
9
+ tools: [
10
+ scoreKeeper,
11
+ tournamentBracket,
12
+ gymTracker,
13
+ reactionTester,
14
+ ] as unknown as SportsToolEntry<Record<string, string>>[],
15
+ i18n: {
16
+ es: () => import('./i18n/es').then((m) => m.content),
17
+ en: () => import('./i18n/en').then((m) => m.content),
18
+ fr: () => import('./i18n/fr').then((m) => m.content),
19
+ },
20
+ };
21
+
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { sportsCategory } 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 sportsCategory.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SEORenderer content={{ locale, sections: content.seo }} />}
15
+
@@ -0,0 +1,116 @@
1
+ ---
2
+ interface NavItem {
3
+ id: string;
4
+ title: string;
5
+ href: string;
6
+ isActive?: boolean;
7
+ }
8
+
9
+ interface Props {
10
+ categoryTitle: string;
11
+ tools?: NavItem[];
12
+ }
13
+
14
+ const { categoryTitle, tools = [] } = Astro.props;
15
+ ---
16
+
17
+ <nav class="preview-nav-sidebar">
18
+ <div class="sidebar-header">
19
+ <h3>{categoryTitle}</h3>
20
+ </div>
21
+
22
+ <ul class="tools-list">
23
+ {tools.map((tool) => (
24
+ <li>
25
+ <a
26
+ href={tool.href}
27
+ class:list={['tool-link', { active: tool.isActive }]}
28
+ >
29
+ <span class="tool-title">{tool.title}</span>
30
+ </a>
31
+ </li>
32
+ ))}
33
+ </ul>
34
+ </nav>
35
+
36
+ <style>
37
+ .preview-nav-sidebar {
38
+ display: flex;
39
+ flex-direction: column;
40
+ height: 100%;
41
+ background: var(--bg-surface, #0f172a);
42
+ }
43
+
44
+ .sidebar-header {
45
+ padding: 2rem 1.5rem 1.5rem;
46
+ border-bottom: 1px solid var(--border-color, #1e293b);
47
+ }
48
+
49
+ .sidebar-header h3 {
50
+ margin: 0;
51
+ font-size: 0.75rem;
52
+ font-weight: 900;
53
+ text-transform: uppercase;
54
+ letter-spacing: 0.1em;
55
+ color: var(--text-muted, #94a3b8);
56
+ }
57
+
58
+ .tools-list {
59
+ list-style: none;
60
+ margin: 0;
61
+ padding: 0.5rem 0;
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 0.25rem;
65
+ }
66
+
67
+ .tools-list li {
68
+ margin: 0;
69
+ }
70
+
71
+ .tool-link {
72
+ display: flex;
73
+ align-items: center;
74
+ padding: 0.75rem 1.5rem;
75
+ color: var(--text-muted, #94a3b8);
76
+ text-decoration: none;
77
+ font-size: 0.9375rem;
78
+ font-weight: 500;
79
+ transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
80
+ border-left: 3px solid transparent;
81
+ position: relative;
82
+ }
83
+
84
+ .tool-link:hover {
85
+ color: var(--text-base, #f1f5f9);
86
+ background: rgba(255, 255, 255, 0.08);
87
+ padding-left: 1.75rem;
88
+ }
89
+
90
+ .tool-link.active {
91
+ color: var(--accent, #f43f5e);
92
+ background: rgba(244, 63, 94, 0.15);
93
+ border-left-color: var(--accent, #f43f5e);
94
+ font-weight: 600;
95
+ }
96
+
97
+ .tool-link.active::before {
98
+ content: '';
99
+ position: absolute;
100
+ left: 0;
101
+ top: 50%;
102
+ transform: translateY(-50%);
103
+ width: 3px;
104
+ height: 24px;
105
+ background: var(--accent, #f43f5e);
106
+ border-radius: 0 2px 2px 0;
107
+ }
108
+
109
+ .tool-title {
110
+ display: block;
111
+ overflow: hidden;
112
+ text-overflow: ellipsis;
113
+ white-space: nowrap;
114
+ }
115
+ </style>
116
+
@@ -0,0 +1,143 @@
1
+ ---
2
+ import type { KnownLocale } from '../types';
3
+
4
+ interface Props {
5
+ currentLocale: KnownLocale;
6
+ localeUrls?: Partial<Record<KnownLocale, string>>;
7
+ }
8
+
9
+ const { currentLocale, localeUrls = {} } = Astro.props;
10
+
11
+ const otherLocales = Object.entries(localeUrls).filter(([l]) => l !== currentLocale) as [
12
+ KnownLocale,
13
+ string,
14
+ ][];
15
+ ---
16
+
17
+ <nav class="preview-toolbar">
18
+ {
19
+ otherLocales.length > 0 && (
20
+ <div class="locale-group">
21
+ <span class="locale-current">{currentLocale.toUpperCase()}</span>
22
+ {otherLocales.map(([locale, url]) => (
23
+ <a href={url} class="btn-locale">
24
+ {locale.toUpperCase()}
25
+ </a>
26
+ ))}
27
+ </div>
28
+ )
29
+ }
30
+
31
+ <button id="theme-toggle" class="btn-theme" aria-label="Toggle theme" title="Toggle theme">
32
+ <span class="theme-icon"></span>
33
+ </button>
34
+ </nav>
35
+
36
+ <script>
37
+ function initToolbar() {
38
+ const btn = document.getElementById('theme-toggle');
39
+ if (!btn) return;
40
+
41
+ const applyTheme = (theme: string) => {
42
+ document.documentElement.classList.remove('theme-light', 'theme-dark');
43
+ document.documentElement.classList.add(theme);
44
+ localStorage.setItem('theme', theme);
45
+ };
46
+
47
+ btn.addEventListener('click', () => {
48
+ const next = document.documentElement.classList.contains('theme-dark')
49
+ ? 'theme-light'
50
+ : 'theme-dark';
51
+ applyTheme(next);
52
+ });
53
+ }
54
+
55
+ initToolbar();
56
+ document.addEventListener('astro:after-swap', initToolbar);
57
+ </script>
58
+
59
+ <style>
60
+ .preview-toolbar {
61
+ position: fixed;
62
+ top: 1rem;
63
+ right: 1.5rem;
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.5rem;
67
+ z-index: 1000;
68
+ background: var(--bg-surface);
69
+ padding: 0.4rem;
70
+ border-radius: 1rem;
71
+ border: 1px solid var(--border-color);
72
+ backdrop-filter: blur(12px);
73
+ }
74
+
75
+ .locale-group {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 0.25rem;
79
+ padding-right: 0.5rem;
80
+ border-right: 1px solid var(--border-color);
81
+ }
82
+
83
+ .locale-current {
84
+ font-size: 0.7rem;
85
+ font-weight: 900;
86
+ color: var(--text-muted);
87
+ padding: 0.4rem 0.6rem;
88
+ letter-spacing: 0.08em;
89
+ }
90
+
91
+ .btn-locale {
92
+ background: transparent;
93
+ border: 1px solid var(--border-color);
94
+ color: var(--text-main);
95
+ cursor: pointer;
96
+ padding: 0.4rem 0.7rem;
97
+ border-radius: 0.5rem;
98
+ font-weight: 700;
99
+ font-size: 0.7rem;
100
+ text-decoration: none;
101
+ letter-spacing: 0.08em;
102
+ transition: all 0.2s ease;
103
+ }
104
+
105
+ .btn-locale:hover {
106
+ border-color: var(--accent);
107
+ color: var(--accent);
108
+ }
109
+
110
+ .btn-theme {
111
+ background: transparent;
112
+ border: none;
113
+ cursor: pointer;
114
+ padding: 0.5rem;
115
+ border-radius: 0.6rem;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ transition: background 0.2s ease;
120
+ }
121
+
122
+ .btn-theme:hover {
123
+ background: rgba(255, 255, 255, 0.08);
124
+ }
125
+
126
+ .theme-icon {
127
+ display: block;
128
+ width: 16px;
129
+ height: 16px;
130
+ border-radius: 50%;
131
+ }
132
+
133
+ :global(.theme-light) .theme-icon {
134
+ background: #f59e0b;
135
+ box-shadow: 0 0 8px #fbbf24;
136
+ }
137
+
138
+ :global(.theme-dark) .theme-icon {
139
+ background: #94a3b8;
140
+ box-shadow: inset -4px -2px 0 #1e293b;
141
+ }
142
+ </style>
143
+