@jjlmoya/utils-science 1.20.0 → 1.22.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.
- package/package.json +2 -1
- package/src/category/i18n/de.ts +1 -1
- package/src/category/i18n/fr.ts +6 -6
- package/src/category/i18n/ru.ts +1 -1
- package/src/category/index.ts +4 -1
- package/src/category/seo.astro +2 -2
- package/src/entries.ts +7 -1
- package/src/index.ts +3 -0
- package/src/pages/[locale]/[slug].astro +5 -4
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/seo_length.test.ts +5 -3
- package/src/tests/title_quality.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/asteroid-impact/bibliography.astro +2 -2
- package/src/tool/asteroid-impact/component.astro +16 -9
- package/src/tool/asteroid-impact/i18n/fr.ts +6 -6
- package/src/tool/asteroid-impact/i18n/ru.ts +4 -4
- package/src/tool/asteroid-impact/index.ts +1 -0
- package/src/tool/asteroid-impact/script.ts +13 -7
- package/src/tool/cellular-renewal/bibliography.astro +2 -2
- package/src/tool/cellular-renewal/i18n/fr.ts +13 -13
- package/src/tool/cellular-renewal/i18n/ru.ts +17 -17
- package/src/tool/cellular-renewal/i18n/zh.ts +9 -9
- package/src/tool/cellular-renewal/index.ts +1 -0
- package/src/tool/colony-counter/bibliography.astro +2 -2
- package/src/tool/colony-counter/i18n/ru.ts +5 -5
- package/src/tool/colony-counter/i18n/zh.ts +2 -2
- package/src/tool/colony-counter/index.ts +1 -0
- package/src/tool/cosmic-inflation/bibliography.astro +14 -0
- package/src/tool/cosmic-inflation/bibliography.ts +12 -0
- package/src/tool/cosmic-inflation/component.astro +270 -0
- package/src/tool/cosmic-inflation/cosmic-inflation-calculator.css +277 -0
- package/src/tool/cosmic-inflation/entry.ts +26 -0
- package/src/tool/cosmic-inflation/i18n/de.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/en.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/es.ts +168 -0
- package/src/tool/cosmic-inflation/i18n/fr.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/id.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/it.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/ja.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/ko.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/nl.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/pl.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/pt.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/ru.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/sv.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/tr.ts +188 -0
- package/src/tool/cosmic-inflation/i18n/zh.ts +188 -0
- package/src/tool/cosmic-inflation/index.ts +11 -0
- package/src/tool/cosmic-inflation/logic/CosmicInflationEngine.ts +21 -0
- package/src/tool/cosmic-inflation/seo.astro +15 -0
- package/src/tool/lorenz-attractor/bibliography.astro +14 -0
- package/src/tool/lorenz-attractor/bibliography.ts +12 -0
- package/src/tool/lorenz-attractor/component.astro +146 -0
- package/src/tool/lorenz-attractor/entry.ts +27 -0
- package/src/tool/lorenz-attractor/i18n/de.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/en.ts +185 -0
- package/src/tool/lorenz-attractor/i18n/es.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/fr.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/id.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/it.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/ja.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/ko.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/nl.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/pl.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/pt.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/ru.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/sv.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/tr.ts +113 -0
- package/src/tool/lorenz-attractor/i18n/zh.ts +113 -0
- package/src/tool/lorenz-attractor/index.ts +9 -0
- package/src/tool/lorenz-attractor/logic/LorenzEngine.ts +32 -0
- package/src/tool/lorenz-attractor/lorenz-attractor.css +335 -0
- package/src/tool/lorenz-attractor/renderer.ts +136 -0
- package/src/tool/lorenz-attractor/script.ts +282 -0
- package/src/tool/lorenz-attractor/seo.astro +15 -0
- package/src/tool/microwave-detector/bibliography.astro +2 -2
- package/src/tool/microwave-detector/component.astro +9 -7
- package/src/tool/microwave-detector/i18n/fr.ts +4 -4
- package/src/tool/microwave-detector/i18n/ru.ts +18 -18
- package/src/tool/microwave-detector/i18n/zh.ts +10 -10
- package/src/tool/microwave-detector/index.ts +1 -0
- package/src/tool/microwave-detector/logic/MicrowaveEngine.ts +5 -1
- package/src/tool/simulation-probability/bibliography.astro +2 -2
- package/src/tool/simulation-probability/i18n/fr.ts +5 -5
- package/src/tool/simulation-probability/i18n/ru.ts +7 -7
- package/src/tool/simulation-probability/i18n/zh.ts +4 -4
- package/src/tool/simulation-probability/index.ts +1 -0
- package/src/tool/temperature-timeline/bibliography.astro +14 -0
- package/src/tool/temperature-timeline/bibliography.ts +12 -0
- package/src/tool/temperature-timeline/component.astro +289 -0
- package/src/tool/temperature-timeline/entry.ts +26 -0
- package/src/tool/temperature-timeline/i18n/de.ts +213 -0
- package/src/tool/temperature-timeline/i18n/en.ts +213 -0
- package/src/tool/temperature-timeline/i18n/es.ts +178 -0
- package/src/tool/temperature-timeline/i18n/fr.ts +213 -0
- package/src/tool/temperature-timeline/i18n/id.ts +213 -0
- package/src/tool/temperature-timeline/i18n/it.ts +213 -0
- package/src/tool/temperature-timeline/i18n/ja.ts +213 -0
- package/src/tool/temperature-timeline/i18n/ko.ts +213 -0
- package/src/tool/temperature-timeline/i18n/nl.ts +213 -0
- package/src/tool/temperature-timeline/i18n/pl.ts +213 -0
- package/src/tool/temperature-timeline/i18n/pt.ts +213 -0
- package/src/tool/temperature-timeline/i18n/ru.ts +213 -0
- package/src/tool/temperature-timeline/i18n/sv.ts +213 -0
- package/src/tool/temperature-timeline/i18n/tr.ts +213 -0
- package/src/tool/temperature-timeline/i18n/zh.ts +213 -0
- package/src/tool/temperature-timeline/index.ts +11 -0
- package/src/tool/temperature-timeline/logic/TemperatureTimelineEngine.ts +58 -0
- package/src/tool/temperature-timeline/planet-temperature-timeline.css +158 -0
- package/src/tool/temperature-timeline/seo.astro +15 -0
- package/src/tools.ts +6 -0
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jjlmoya/utils-science",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@astrojs/check": "^0.9.8",
|
|
53
|
+
"@types/leaflet": "^1.9.21",
|
|
53
54
|
"eslint": "^9.39.4",
|
|
54
55
|
"eslint-plugin-astro": "^1.6.0",
|
|
55
56
|
"eslint-plugin-no-comments": "^1.1.10",
|
package/src/category/i18n/de.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const content: CategoryLocaleContent = {
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
type: 'paragraph',
|
|
15
|
-
html: '<p>Wissenschaft ist nicht nur ein statisches Wissensgebiet, sondern ein dynamischer Prozess der Erkundung und des Experimentierens. In diesem Bereich bieten wir kostenlose Online-Tools an, die komplexe wissenschaftliche Konzepte durch Simulation und Datenanalyse jedem Nutzer näherbringen. Von der Mikrobiologie bis zur Astrophysik wenden unsere Hilfsprogramme mathematische Modelle und physikalische Theorien an, um Ihnen eine interaktive Perspektive auf das Universum zu bieten.</p><p>Ob Sie nun koloniebildende Einheiten in einem Labor zählen müssen oder die Wahrscheinlichkeit schätzen möchten, dass wir in einer virtuellen Umgebung leben
|
|
15
|
+
html: '<p>Wissenschaft ist nicht nur ein statisches Wissensgebiet, sondern ein dynamischer Prozess der Erkundung und des Experimentierens. In diesem Bereich bieten wir kostenlose Online-Tools an, die komplexe wissenschaftliche Konzepte durch Simulation und Datenanalyse jedem Nutzer näherbringen. Von der Mikrobiologie bis zur Astrophysik wenden unsere Hilfsprogramme mathematische Modelle und physikalische Theorien an, um Ihnen eine interaktive Perspektive auf das Universum zu bieten.</p><p>Ob Sie nun koloniebildende Einheiten in einem Labor zählen müssen oder die Wahrscheinlichkeit schätzen möchten, dass wir in einer virtuellen Umgebung leben - unsere Rechner bieten technische Präzision mit einer auf Neugier ausgerichteten Benutzeroberfläche.</p>',
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
type: 'title',
|
package/src/category/i18n/fr.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const content: CategoryLocaleContent = {
|
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
|
10
|
-
text: 'Exploration Scientifique et Simulation
|
|
10
|
+
text: 'Exploration Scientifique et Simulation: La Science Entre vos Mains',
|
|
11
11
|
level: 2,
|
|
12
12
|
},
|
|
13
13
|
{
|
|
@@ -16,7 +16,7 @@ export const content: CategoryLocaleContent = {
|
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
type: 'title',
|
|
19
|
-
text: 'Astrophysique et Risque Cosmique
|
|
19
|
+
text: 'Astrophysique et Risque Cosmique: L\'Impact des Astéroïdes',
|
|
20
20
|
level: 2,
|
|
21
21
|
},
|
|
22
22
|
{
|
|
@@ -68,10 +68,10 @@ export const content: CategoryLocaleContent = {
|
|
|
68
68
|
type: 'summary',
|
|
69
69
|
title: 'Ce qui nous définit :',
|
|
70
70
|
items: [
|
|
71
|
-
'Modélisation Mathématique
|
|
72
|
-
'Éducation Interactif
|
|
73
|
-
'Rigueur Technique
|
|
74
|
-
'Confidentialité des Données Scientifiques
|
|
71
|
+
'Modélisation Mathématique: Simulations basées sur des lois physiques réelles (gravitation, thermodynamique, balistique).',
|
|
72
|
+
'Éducation Interactif: Outils conçus pour que les étudiants et les passionnés expérimentent avec des variables scientifiques.',
|
|
73
|
+
'Rigueur Technique: Données et formules extraites de publications scientifiques et de bases de données académiques.',
|
|
74
|
+
'Confidentialité des Données Scientifiques: Tous les processus de calcul et de simulation s\'exécutent localement pour garantir la sécurité de votre recherche.',
|
|
75
75
|
],
|
|
76
76
|
},
|
|
77
77
|
{
|
package/src/category/i18n/ru.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const content: CategoryLocaleContent = {
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
type: 'paragraph',
|
|
15
|
-
html: '<p>Наука
|
|
15
|
+
html: '<p>Наука - это не просто статический набор знаний, а динамичный процесс исследования и экспериментирования. В этом разделе мы предлагаем бесплатные онлайн-инструменты, разработанные для того, чтобы донести сложные научные концепции до любого пользователя посредством моделирования и анализа данных. От микробиологии до астрофизики наши утилиты применяют математические модели и физические теории, чтобы предложить вам интерактивный взгляд на Вселенную.</p><p>Если вам нужно подсчитать количество колониеобразующих единиц в лаборатории или вы хотите оценить вероятность того, что мы живем в виртуальной среде, наши калькуляторы обеспечивают техническую точность и интерфейс, созданный для любознательных умов.</p>',
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
type: 'title',
|
package/src/category/index.ts
CHANGED
|
@@ -4,10 +4,13 @@ import { asteroidImpact } from '../tool/asteroid-impact/index';
|
|
|
4
4
|
import { microwaveDetector } from '../tool/microwave-detector/index';
|
|
5
5
|
import { simulationProbability } from '../tool/simulation-probability/index';
|
|
6
6
|
import { cellularRenewal } from '../tool/cellular-renewal/index';
|
|
7
|
+
import { cosmicInflation } from '../tool/cosmic-inflation/index';
|
|
8
|
+
import { temperatureTimeline } from '../tool/temperature-timeline/index';
|
|
9
|
+
import { lorenzAttractor } from '../tool/lorenz-attractor/index';
|
|
7
10
|
|
|
8
11
|
export const scienceCategory: ScienceCategoryEntry = {
|
|
9
12
|
icon: 'mdi:flask',
|
|
10
|
-
tools: [colonyCounter, asteroidImpact, microwaveDetector, simulationProbability, cellularRenewal],
|
|
13
|
+
tools: [colonyCounter, asteroidImpact, microwaveDetector, simulationProbability, cellularRenewal, cosmicInflation, temperatureTimeline, lorenzAttractor],
|
|
11
14
|
i18n: {
|
|
12
15
|
es: () => import('./i18n/es').then((m) => m.content),
|
|
13
16
|
en: () => import('./i18n/en').then((m) => m.content),
|
package/src/category/seo.astro
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
3
|
import { scienceCategory } from './index';
|
|
4
|
-
import type { KnownLocale } from '
|
|
4
|
+
import type { KnownLocale } from '../types';
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
locale?: KnownLocale;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const { locale = 'es' } = Astro.props;
|
|
11
|
-
const content = await scienceCategory.i18n[locale]?.();
|
|
11
|
+
const content = await scienceCategory.i18n[locale as KnownLocale]?.();
|
|
12
12
|
if (!content) return null;
|
|
13
13
|
---
|
|
14
14
|
|
package/src/entries.ts
CHANGED
|
@@ -4,10 +4,16 @@ export { colonyCounter } from './tool/colony-counter/entry';
|
|
|
4
4
|
export type { ColonyCounterUI, ColonyCounterLocaleContent } from './tool/colony-counter/entry';
|
|
5
5
|
export { microwaveDetector } from './tool/microwave-detector/entry';
|
|
6
6
|
export { simulationProbability } from './tool/simulation-probability/entry';
|
|
7
|
+
export { cosmicInflation } from './tool/cosmic-inflation/entry';
|
|
8
|
+
export { temperatureTimeline } from './tool/temperature-timeline/entry';
|
|
9
|
+
export { lorenzAttractor } from './tool/lorenz-attractor/entry';
|
|
7
10
|
export { scienceCategory } from './category';
|
|
8
11
|
import { asteroidImpact } from './tool/asteroid-impact/entry';
|
|
9
12
|
import { cellularRenewal } from './tool/cellular-renewal/entry';
|
|
10
13
|
import { colonyCounter } from './tool/colony-counter/entry';
|
|
11
14
|
import { microwaveDetector } from './tool/microwave-detector/entry';
|
|
12
15
|
import { simulationProbability } from './tool/simulation-probability/entry';
|
|
13
|
-
|
|
16
|
+
import { cosmicInflation } from './tool/cosmic-inflation/entry';
|
|
17
|
+
import { temperatureTimeline } from './tool/temperature-timeline/entry';
|
|
18
|
+
import { lorenzAttractor } from './tool/lorenz-attractor/entry';
|
|
19
|
+
export const ALL_ENTRIES = [asteroidImpact, cellularRenewal, colonyCounter, microwaveDetector, simulationProbability, cosmicInflation, temperatureTimeline, lorenzAttractor];
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,9 @@ export { ASTEROID_IMPACT_TOOL } from './tool/asteroid-impact/index';
|
|
|
5
5
|
export { MICROWAVE_DETECTOR_TOOL } from './tool/microwave-detector/index';
|
|
6
6
|
export { SIMULATION_PROBABILITY_TOOL } from './tool/simulation-probability/index';
|
|
7
7
|
export { CELLULAR_RENEWAL_TOOL } from './tool/cellular-renewal/index';
|
|
8
|
+
export { COSMIC_INFLATION_TOOL } from './tool/cosmic-inflation/index';
|
|
9
|
+
export { TEMPERATURE_TIMELINE_TOOL } from './tool/temperature-timeline/index';
|
|
10
|
+
export { LORENZ_ATTRACTOR_TOOL } from './tool/lorenz-attractor/index';
|
|
8
11
|
|
|
9
12
|
export type {
|
|
10
13
|
KnownLocale,
|
|
@@ -15,7 +15,7 @@ export async function getStaticPaths() {
|
|
|
15
15
|
const paths = [];
|
|
16
16
|
|
|
17
17
|
for (const { entry, Component: lazyComp } of ALL_TOOLS) {
|
|
18
|
-
const { default: Component } = await lazyComp();
|
|
18
|
+
const { default: Component } = await (lazyComp as () => Promise<{ default: unknown }>)();
|
|
19
19
|
const localeEntries = Object.entries(entry.i18n) as [
|
|
20
20
|
KnownLocale,
|
|
21
21
|
() => Promise<ToolLocaleContent>,
|
|
@@ -79,11 +79,12 @@ interface Props {
|
|
|
79
79
|
englishSlug: string;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props;
|
|
82
|
+
const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props as Props;
|
|
83
83
|
|
|
84
84
|
const cssFiles = import.meta.glob("../../tool/*/*.css", { query: "?raw", import: "default" });
|
|
85
85
|
const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
|
|
86
|
-
const
|
|
86
|
+
const cssLoader = cssKey ? cssFiles[cssKey] : null;
|
|
87
|
+
const toolCss = cssLoader ? await cssLoader() as string : "";
|
|
87
88
|
|
|
88
89
|
const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
|
|
89
90
|
|
|
@@ -132,7 +133,7 @@ const titleBase = words.slice(1).join(" ") || "";
|
|
|
132
133
|
</section>
|
|
133
134
|
|
|
134
135
|
<section class="section-faq">
|
|
135
|
-
<FAQSection items={content.faq}
|
|
136
|
+
<FAQSection items={content.faq} />
|
|
136
137
|
</section>
|
|
137
138
|
|
|
138
139
|
<section class="section-bibliography">
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function getFiles(dir: string): string[] {
|
|
6
|
+
const results: string[] = [];
|
|
7
|
+
if (!fs.existsSync(dir)) {
|
|
8
|
+
return results;
|
|
9
|
+
}
|
|
10
|
+
const list = fs.readdirSync(dir);
|
|
11
|
+
for (const file of list) {
|
|
12
|
+
const fullPath = path.join(dir, file);
|
|
13
|
+
const stat = fs.statSync(fullPath);
|
|
14
|
+
if (stat && stat.isDirectory()) {
|
|
15
|
+
if (file !== 'tests' && file !== 'node_modules' && file !== '.astro') {
|
|
16
|
+
results.push(...getFiles(fullPath));
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
results.push(fullPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return results;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isContentFile(filePath: string): boolean {
|
|
26
|
+
return /\\i18n\\/.test(filePath) || filePath.endsWith('bibliography.ts');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const srcDir = path.join(process.cwd(), 'src');
|
|
30
|
+
const scriptsDir = path.join(process.cwd(), 'scripts');
|
|
31
|
+
const filesToTest = [
|
|
32
|
+
...getFiles(srcDir).filter(isContentFile),
|
|
33
|
+
...getFiles(scriptsDir).filter(isContentFile),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const aiTypographyGarbage = [
|
|
37
|
+
'\u2013',
|
|
38
|
+
'\u2014',
|
|
39
|
+
'\u2026',
|
|
40
|
+
'\u201C',
|
|
41
|
+
'\u201D',
|
|
42
|
+
'\u2018',
|
|
43
|
+
'\u2019',
|
|
44
|
+
'\u00AB',
|
|
45
|
+
'\u00BB',
|
|
46
|
+
'\u200B',
|
|
47
|
+
'\u201E',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
describe('Typography Garbage Character Validation', () => {
|
|
51
|
+
filesToTest.forEach((filePath) => {
|
|
52
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
53
|
+
it(`should not contain typography garbage characters in ${relativePath}`, () => {
|
|
54
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
55
|
+
const hasAiPatterns = aiTypographyGarbage.some(char => content.includes(char));
|
|
56
|
+
expect(hasAiPatterns).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`should not contain space before colon in ${relativePath}`, () => {
|
|
60
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
61
|
+
const spaceBeforeColon = / : /.test(content);
|
|
62
|
+
expect(spaceBeforeColon).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it(`should not contain double hyphen in ${relativePath}`, () => {
|
|
66
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
67
|
+
expect(content).not.toContain('--');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -11,9 +11,11 @@ describe('SEO Content Length Validation', () => {
|
|
|
11
11
|
Object.keys(entry.i18n).forEach((locale) => {
|
|
12
12
|
it(`${locale}: SEO section should exist`, async () => {
|
|
13
13
|
const loader = (entry.i18n as Record<string, () => Promise<{ seo?: unknown[] }>>)[locale];
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if (loader) {
|
|
15
|
+
const content = await loader();
|
|
16
|
+
if (!content.seo) return;
|
|
17
|
+
expect(Array.isArray(content.seo)).toBe(true);
|
|
18
|
+
}
|
|
17
19
|
});
|
|
18
20
|
});
|
|
19
21
|
});
|
|
@@ -41,7 +41,7 @@ describe('Project Titles - Separator Validation', () => {
|
|
|
41
41
|
let match;
|
|
42
42
|
while ((match = pattern.exec(content)) !== null) {
|
|
43
43
|
const title = match[1];
|
|
44
|
-
if (title.includes('|') || title.includes('-')) {
|
|
44
|
+
if (title && (title.includes('|') || title.includes('-'))) {
|
|
45
45
|
findings.push(title);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -4,8 +4,8 @@ import { scienceCategory } from '../data';
|
|
|
4
4
|
|
|
5
5
|
describe('Tool Validation Suite', () => {
|
|
6
6
|
describe('Library Registration', () => {
|
|
7
|
-
it('should have
|
|
8
|
-
expect(ALL_TOOLS.length).toBe(
|
|
7
|
+
it('should have 8 tools in ALL_TOOLS', () => {
|
|
8
|
+
expect(ALL_TOOLS.length).toBe(8);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it('scienceCategory should be defined', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { Bibliography } from '@jjlmoya/utils-shared';
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
3
|
import { asteroidImpact } from './index';
|
|
4
4
|
import type { KnownLocale } from '../../types';
|
|
5
5
|
|
|
@@ -11,4 +11,4 @@ const { locale = 'es' } = Astro.props;
|
|
|
11
11
|
const content = await asteroidImpact.i18n[locale]?.();
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
{content && <
|
|
14
|
+
{content && <SharedBibliography links={content.bibliography} />}
|
|
@@ -255,6 +255,13 @@ const { ui } = Astro.props;
|
|
|
255
255
|
clearBtn?.addEventListener("click", clearAll);
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
function getEventCoordinates(e: MouseEvent | TouchEvent) {
|
|
259
|
+
const touch = (e as TouchEvent).changedTouches?.[0];
|
|
260
|
+
const clientX = (e as MouseEvent).clientX || touch?.clientX || 0;
|
|
261
|
+
const clientY = (e as MouseEvent).clientY || touch?.clientY || 0;
|
|
262
|
+
return { clientX, clientY };
|
|
263
|
+
}
|
|
264
|
+
|
|
258
265
|
function setupGhostDrag() {
|
|
259
266
|
const source = document.getElementById("drag-source-desktop");
|
|
260
267
|
if (!source) return;
|
|
@@ -271,10 +278,8 @@ const { ui } = Astro.props;
|
|
|
271
278
|
const onEnd = (endEvent: MouseEvent | TouchEvent) => {
|
|
272
279
|
if (targetOverlay) targetOverlay.classList.remove("active");
|
|
273
280
|
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
const point = map.mouseEventToContainerPoint({ clientX, clientY } as MouseEvent);
|
|
281
|
+
const coords = getEventCoordinates(endEvent);
|
|
282
|
+
const point = map.mouseEventToContainerPoint(coords as MouseEvent);
|
|
278
283
|
const latlng = map.containerPointToLatLng(point);
|
|
279
284
|
spawnImpact(latlng);
|
|
280
285
|
|
|
@@ -404,12 +409,14 @@ const { ui } = Astro.props;
|
|
|
404
409
|
const container = document.getElementById("asteroid-verdict-container");
|
|
405
410
|
|
|
406
411
|
const c = VERDICT_CONFIGS[verdict];
|
|
407
|
-
if (
|
|
408
|
-
|
|
409
|
-
|
|
412
|
+
if (c) {
|
|
413
|
+
if (value) value.textContent = c.text;
|
|
414
|
+
if (label) label.textContent = c.label;
|
|
415
|
+
if (container) container.className = `asteroid-verdict-container ${c.class}`;
|
|
410
416
|
|
|
411
|
-
|
|
412
|
-
|
|
417
|
+
const iconSource = document.getElementById(c.iconId);
|
|
418
|
+
if (icon && iconSource) icon.innerHTML = iconSource.innerHTML;
|
|
419
|
+
}
|
|
413
420
|
}
|
|
414
421
|
|
|
415
422
|
function updateVerdict() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const description = 'Simulez l\'impact d\'astéroïdes avec la physique réelle. Calculez l\'énergie, le cratère, les radiations thermiques et l\'onde de choc. Survivriez-vous à Chicxulub ?';
|
|
2
|
-
const title = 'Simulateur d\'Impact d\'Astéroïde
|
|
2
|
+
const title = 'Simulateur d\'Impact d\'Astéroïde: Calculateur d\'Apocalypse';
|
|
3
3
|
const slug = 'simulateur-impact-asteroide';
|
|
4
4
|
const howTo = [
|
|
5
5
|
{
|
|
@@ -22,7 +22,7 @@ const howTo = [
|
|
|
22
22
|
const faq = [
|
|
23
23
|
{
|
|
24
24
|
question: 'Comment l\'énergie d\'un impact est-elle calculée ?',
|
|
25
|
-
answer: 'L\'énergie principale est cinétique
|
|
25
|
+
answer: 'L\'énergie principale est cinétique: (1/2) * masse * vitesse². Nous utilisons des densités réalistes (ex. 3000 kg/m³ pour les astéroïdes rocheux) et des vitesses d\'entrée atmosphérique typiques (11 à 72 km/s). L\'énergie résultante est mesurée en mégatonnes de TNT.',
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
question: 'Qu\'est-ce que l\'onde de choc thermique ?',
|
|
@@ -80,7 +80,7 @@ export const content: ToolLocaleContent = {
|
|
|
80
80
|
seo: [
|
|
81
81
|
{
|
|
82
82
|
type: 'title',
|
|
83
|
-
text: 'Quand le Ciel Tombe
|
|
83
|
+
text: 'Quand le Ciel Tombe: La Physique de l\'Apocalypse Cosmique',
|
|
84
84
|
level: 2,
|
|
85
85
|
},
|
|
86
86
|
{
|
|
@@ -94,7 +94,7 @@ export const content: ToolLocaleContent = {
|
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
type: 'paragraph',
|
|
97
|
-
html: 'Tout commence par l\'énergie cinétique
|
|
97
|
+
html: 'Tout commence par l\'énergie cinétique: <strong>E = ½mv²</strong>. Un astéroïde de 100 mètres voyageant à 20 km/s libère environ 0,5 mégatonne de TNT. Pour comparaison, la bombe d\'Hiroshima était de 0,015 mégatonne.',
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
type: 'paragraph',
|
|
@@ -106,7 +106,7 @@ export const content: ToolLocaleContent = {
|
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
108
|
type: 'title',
|
|
109
|
-
text: 'Anatomie de la Destruction
|
|
109
|
+
text: 'Anatomie de la Destruction: Les Couches Concentriques de l\'Apocalypse',
|
|
110
110
|
level: 3,
|
|
111
111
|
},
|
|
112
112
|
{
|
|
@@ -120,7 +120,7 @@ export const content: ToolLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'title',
|
|
123
|
-
text: 'Impacts Historiques
|
|
123
|
+
text: 'Impacts Historiques: Les Leçons du Passé',
|
|
124
124
|
level: 3,
|
|
125
125
|
},
|
|
126
126
|
{
|
|
@@ -4,11 +4,11 @@ const title = 'Симулятор столкновения с астероидо
|
|
|
4
4
|
const howTo = [
|
|
5
5
|
{
|
|
6
6
|
name: 'Выберите размер объекта',
|
|
7
|
-
text: 'Введите диаметр астероида: от небольшого 10-метрового метеорита до 10-километрового
|
|
7
|
+
text: 'Введите диаметр астероида: от небольшого 10-метрового метеорита до 10-километрового "убийцы планет".',
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
name: 'Настройте скорость и угол',
|
|
11
|
-
text: 'Отрегулируйте скорость сближения и угол входа (45°
|
|
11
|
+
text: 'Отрегулируйте скорость сближения и угол входа (45° - статистически наиболее вероятное значение).',
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
name: 'Определите тип астероида',
|
|
@@ -34,7 +34,7 @@ const faq = [
|
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
question: 'Какова реальная вероятность столкновения?',
|
|
37
|
-
answer: 'Небольшие столкновения (как в России в 2013 году) происходят каждое десятилетие. Катастрофические столкновения (типа Тунгусского)
|
|
37
|
+
answer: 'Небольшие столкновения (как в России в 2013 году) происходят каждое десятилетие. Катастрофические столкновения (типа Тунгусского) - каждые несколько столетий. Глобальное вымирание, подобное Чикшулубу, случается примерно раз в 100 миллионов лет.',
|
|
38
38
|
},
|
|
39
39
|
];
|
|
40
40
|
import { bibliography } from '../bibliography';
|
|
@@ -85,7 +85,7 @@ export const content: ToolLocaleContent = {
|
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
type: 'paragraph',
|
|
88
|
-
html: 'Астероиды
|
|
88
|
+
html: 'Астероиды - это не просто космические булыжники. Это космические пули, летящие со скоростью 20 км/с и способные высвободить больше энергии, чем все ядерное оружие планеты вместе взятое. Этот симулятор переводит абстрактную физику в осязаемые человеческие последствия.',
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
type: 'title',
|
|
@@ -2,7 +2,7 @@ import L from "leaflet";
|
|
|
2
2
|
import { ImpactPhysics, type Composition, type ImpactResults } from "./logic/impactPhysics";
|
|
3
3
|
import { getCompositionColor } from "./helpers";
|
|
4
4
|
import { getConfig, updateUI } from "./ui";
|
|
5
|
-
import { VERDICT_CONFIGS, DEFAULT_LABELS, type Labels } from "./constants";
|
|
5
|
+
import { VERDICT_CONFIGS, DEFAULT_LABELS, type Labels, type VerdictConfig } from "./constants";
|
|
6
6
|
|
|
7
7
|
let map: L.Map;
|
|
8
8
|
|
|
@@ -53,6 +53,13 @@ function setupControls() {
|
|
|
53
53
|
clearBtn?.addEventListener("click", clearAll);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function getEventCoordinates(e: MouseEvent | TouchEvent) {
|
|
57
|
+
const touch = (e as TouchEvent).changedTouches?.[0];
|
|
58
|
+
const clientX = (e as MouseEvent).clientX || touch?.clientX || 0;
|
|
59
|
+
const clientY = (e as MouseEvent).clientY || touch?.clientY || 0;
|
|
60
|
+
return { clientX, clientY };
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
function setupGhostDrag() {
|
|
57
64
|
const source = document.getElementById("drag-source-desktop");
|
|
58
65
|
if (!source) return;
|
|
@@ -65,10 +72,8 @@ function setupGhostDrag() {
|
|
|
65
72
|
const onEnd = (endEvent: MouseEvent | TouchEvent) => {
|
|
66
73
|
if (targetOverlay) targetOverlay.classList.remove("active");
|
|
67
74
|
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const point = map.mouseEventToContainerPoint({ clientX, clientY } as MouseEvent);
|
|
75
|
+
const coords = getEventCoordinates(endEvent);
|
|
76
|
+
const point = map.mouseEventToContainerPoint(coords as MouseEvent);
|
|
72
77
|
const latlng = map.containerPointToLatLng(point);
|
|
73
78
|
spawnImpact(latlng);
|
|
74
79
|
|
|
@@ -202,8 +207,9 @@ function updateVerdict() {
|
|
|
202
207
|
if (impacts.length === 0) {
|
|
203
208
|
pill?.classList.remove("active");
|
|
204
209
|
} else {
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
if (VERDICT_CONFIGS.safe) {
|
|
211
|
+
updateVerdictUI(VERDICT_CONFIGS.safe);
|
|
212
|
+
}
|
|
207
213
|
}
|
|
208
214
|
}
|
|
209
215
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { Bibliography } from '@jjlmoya/utils-shared';
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
3
|
import { cellularRenewal } from './index';
|
|
4
4
|
import type { KnownLocale } from '../../types';
|
|
5
5
|
|
|
@@ -11,4 +11,4 @@ const { locale = 'es' } = Astro.props;
|
|
|
11
11
|
const content = await cellularRenewal.i18n[locale]?.();
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
{content && <
|
|
14
|
+
{content && <SharedBibliography links={content.bibliography} />}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const description = 'Calculez le pourcentage de votre corps qui s\'est renouvelé depuis votre naissance. Estimation basée sur le taux de division cellulaire des organes, des os et des tissus. Le Paradoxe de Thésée rendu tangible.';
|
|
2
2
|
const slug = 'calculateur-renouvellement-cellulaire';
|
|
3
|
-
const title = 'Calculateur de Renouvellement Cellulaire
|
|
3
|
+
const title = 'Calculateur de Renouvellement Cellulaire: Que reste t il du "vous" original ?';
|
|
4
4
|
const howTo = [
|
|
5
5
|
{
|
|
6
6
|
name: 'Réglez votre âge',
|
|
@@ -12,7 +12,7 @@ const howTo = [
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
name: 'Analysez les barres de progression',
|
|
15
|
-
text: 'Chaque barre représente un tissu différent
|
|
15
|
+
text: 'Chaque barre représente un tissu différent: peau/sang, os, organes et cerveau. Notez que le cerveau change à peine tandis que la peau se renouvelle constamment.',
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
name: 'Réfléchissez sur votre identité',
|
|
@@ -26,7 +26,7 @@ const faq = [
|
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
question: 'Pourquoi le cerveau se renouvelle-t-il si lentement ?',
|
|
29
|
-
answer: 'Les neurones du cortex cérébral se génèrent avant la naissance et généralement ne se divisent plus. Cela assure une stabilité neurologique
|
|
29
|
+
answer: 'Les neurones du cortex cérébral se génèrent avant la naissance et généralement ne se divisent plus. Cela assure une stabilité neurologique: vos "câbles" fondamentaux restent constants. Cependant, les cellules gliales (de soutien) se renouvellent. La mémoire est stockée dans les connexions, pas dans les atomes.',
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
question: 'Est-il vrai que nous sommes de "nouvelles personnes" tous les 7 ans ?',
|
|
@@ -34,11 +34,11 @@ const faq = [
|
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
question: 'Si mon corps est neuf à 99%, suis-je toujours la même personne ?',
|
|
37
|
-
answer: 'Oui. L\'identité est une continuité d\'information, de conscience et de mémoire, pas d\'atomes. Vous êtes comme un fleuve
|
|
37
|
+
answer: 'Oui. L\'identité est une continuité d\'information, de conscience et de mémoire, pas d\'atomes. Vous êtes comme un fleuve: l\'eau change constamment, mais le fleuve persiste. Le Paradoxe de Thésée suggère que l\'identité réside dans le motif, pas dans la matière.',
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
question: 'Quels sont les tissus qui se renouvellent le plus rapidement ?',
|
|
41
|
-
answer: 'Le sang et la peau sont en tête. Votre moelle osseuse produit 200 milliards de cellules sanguines par jour. Vous perdez ~30 000 cellules de peau par minute. C\'est pourquoi ils cicatrisent si bien et vieillissent si visiblement
|
|
41
|
+
answer: 'Le sang et la peau sont en tête. Votre moelle osseuse produit 200 milliards de cellules sanguines par jour. Vous perdez ~30 000 cellules de peau par minute. C\'est pourquoi ils cicatrisent si bien et vieillissent si visiblement: ils sont jeunes en termes d\'âge cellulaire.',
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
question: 'Le cristallin de l\'œil ne se renouvelle vraiment pas ?',
|
|
@@ -79,11 +79,11 @@ export const content: ToolLocaleContent = {
|
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
type: 'paragraph',
|
|
82
|
-
html: 'Ce paradoxe, connu sous le nom de <strong>Paradoxe du Navire de Thésée</strong>, pose une question ancienne
|
|
82
|
+
html: 'Ce paradoxe, connu sous le nom de <strong>Paradoxe du Navire de Thésée</strong>, pose une question ancienne: si vous remplacez toutes les parties de quelque chose, est-ce toujours la même chose ? Dans votre cas, c\'est une question littérale. Les atomes qui constituent votre corps aujourd\'hui ne sont pas les mêmes que ceux d\'il y a 10 ans, mais <em>vous</em> restez vous.',
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
type: 'title',
|
|
86
|
-
text: 'Le renouvellement cellulaire
|
|
86
|
+
text: 'Le renouvellement cellulaire: un mapa de votre corps dynamique',
|
|
87
87
|
level: 3,
|
|
88
88
|
},
|
|
89
89
|
{
|
|
@@ -103,16 +103,16 @@ export const content: ToolLocaleContent = {
|
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
type: 'title',
|
|
106
|
-
text: 'Le cristallin de l\'œil
|
|
106
|
+
text: 'Le cristallin de l\'œil: la partie la plus ancienne de vous',
|
|
107
107
|
level: 3,
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
type: 'paragraph',
|
|
111
|
-
html: 'Il existe une structure dans votre corps qui est spéciale
|
|
111
|
+
html: 'Il existe une structure dans votre corps qui est spéciale: le <strong>cristallin de l\'œil</strong>. Les cellules qui composent le cristallin se déposent pendant le développement fœtal et ne sont jamais remplacées. Si vous parvenez à vivre jusqu\'à 100 ans, les cellules centrales de votre cristallin seront toujours les mêmes que celles que vous aviez dans l\'utérus de votre mère. Elles sont, littéralement, la partie la plus ancienne de vous.',
|
|
112
112
|
},
|
|
113
113
|
{
|
|
114
114
|
type: 'title',
|
|
115
|
-
text: 'Calcul du renouvellement total
|
|
115
|
+
text: 'Calcul du renouvellement total: le paradoxe des poids',
|
|
116
116
|
level: 3,
|
|
117
117
|
},
|
|
118
118
|
{
|
|
@@ -134,16 +134,16 @@ export const content: ToolLocaleContent = {
|
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
type: 'title',
|
|
137
|
-
text: 'Implications philosophiques
|
|
137
|
+
text: 'Implications philosophiques: l\'identité est information, pas matière',
|
|
138
138
|
level: 3,
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
141
|
type: 'paragraph',
|
|
142
|
-
html: 'Si votre corps est complètement nouveau chaque décennie, pourquoi êtes-vous "vous" ? La réponse réside dans le fait que l\'identité ne réside pas dans les atomes spécifiques, mais dans le <strong>modèle d\'information</strong> que ces atomes soutiennent. Vous êtes comme une chanson
|
|
142
|
+
html: 'Si votre corps est complètement nouveau chaque décennie, pourquoi êtes-vous "vous" ? La réponse réside dans le fait que l\'identité ne réside pas dans les atomes spécifiques, mais dans le <strong>modèle d\'information</strong> que ces atomes soutiennent. Vous êtes comme une chanson: ce n\'est pas le même air qui vibre, mais le modèle persiste.',
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
type: 'paragraph',
|
|
146
|
-
html: 'Ceci a des implications profondes
|
|
146
|
+
html: 'Ceci a des implications profondes: votre corps est un processus, pas une chose. Vous êtes un modèle auto-organisé qui persiste à travers le changement. Vous ne possédez pas d\'atomes ; vous êtes une structure qui les canalise temporairement.',
|
|
147
147
|
},
|
|
148
148
|
],
|
|
149
149
|
faq,
|