@jjlmoya/utils-science 1.20.0 → 1.21.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 (89) hide show
  1. package/package.json +2 -1
  2. package/src/category/i18n/de.ts +1 -1
  3. package/src/category/i18n/fr.ts +6 -6
  4. package/src/category/i18n/ru.ts +1 -1
  5. package/src/category/index.ts +3 -1
  6. package/src/category/seo.astro +2 -2
  7. package/src/entries.ts +5 -1
  8. package/src/index.ts +2 -0
  9. package/src/pages/[locale]/[slug].astro +5 -4
  10. package/src/tests/locale_completeness.test.ts +2 -2
  11. package/src/tests/no_en_dash.test.ts +70 -0
  12. package/src/tests/seo_length.test.ts +5 -3
  13. package/src/tests/title_quality.test.ts +1 -1
  14. package/src/tests/tool_validation.test.ts +2 -2
  15. package/src/tool/asteroid-impact/bibliography.astro +2 -2
  16. package/src/tool/asteroid-impact/component.astro +16 -9
  17. package/src/tool/asteroid-impact/i18n/fr.ts +6 -6
  18. package/src/tool/asteroid-impact/i18n/ru.ts +4 -4
  19. package/src/tool/asteroid-impact/index.ts +1 -0
  20. package/src/tool/asteroid-impact/script.ts +13 -7
  21. package/src/tool/cellular-renewal/bibliography.astro +2 -2
  22. package/src/tool/cellular-renewal/i18n/fr.ts +13 -13
  23. package/src/tool/cellular-renewal/i18n/ru.ts +17 -17
  24. package/src/tool/cellular-renewal/i18n/zh.ts +9 -9
  25. package/src/tool/cellular-renewal/index.ts +1 -0
  26. package/src/tool/colony-counter/bibliography.astro +2 -2
  27. package/src/tool/colony-counter/i18n/ru.ts +5 -5
  28. package/src/tool/colony-counter/i18n/zh.ts +2 -2
  29. package/src/tool/colony-counter/index.ts +1 -0
  30. package/src/tool/cosmic-inflation/bibliography.astro +14 -0
  31. package/src/tool/cosmic-inflation/bibliography.ts +12 -0
  32. package/src/tool/cosmic-inflation/component.astro +270 -0
  33. package/src/tool/cosmic-inflation/cosmic-inflation-calculator.css +277 -0
  34. package/src/tool/cosmic-inflation/entry.ts +26 -0
  35. package/src/tool/cosmic-inflation/i18n/de.ts +188 -0
  36. package/src/tool/cosmic-inflation/i18n/en.ts +188 -0
  37. package/src/tool/cosmic-inflation/i18n/es.ts +168 -0
  38. package/src/tool/cosmic-inflation/i18n/fr.ts +188 -0
  39. package/src/tool/cosmic-inflation/i18n/id.ts +188 -0
  40. package/src/tool/cosmic-inflation/i18n/it.ts +188 -0
  41. package/src/tool/cosmic-inflation/i18n/ja.ts +188 -0
  42. package/src/tool/cosmic-inflation/i18n/ko.ts +188 -0
  43. package/src/tool/cosmic-inflation/i18n/nl.ts +188 -0
  44. package/src/tool/cosmic-inflation/i18n/pl.ts +188 -0
  45. package/src/tool/cosmic-inflation/i18n/pt.ts +188 -0
  46. package/src/tool/cosmic-inflation/i18n/ru.ts +188 -0
  47. package/src/tool/cosmic-inflation/i18n/sv.ts +188 -0
  48. package/src/tool/cosmic-inflation/i18n/tr.ts +188 -0
  49. package/src/tool/cosmic-inflation/i18n/zh.ts +188 -0
  50. package/src/tool/cosmic-inflation/index.ts +11 -0
  51. package/src/tool/cosmic-inflation/logic/CosmicInflationEngine.ts +21 -0
  52. package/src/tool/cosmic-inflation/seo.astro +15 -0
  53. package/src/tool/microwave-detector/bibliography.astro +2 -2
  54. package/src/tool/microwave-detector/component.astro +9 -7
  55. package/src/tool/microwave-detector/i18n/fr.ts +4 -4
  56. package/src/tool/microwave-detector/i18n/ru.ts +18 -18
  57. package/src/tool/microwave-detector/i18n/zh.ts +10 -10
  58. package/src/tool/microwave-detector/index.ts +1 -0
  59. package/src/tool/microwave-detector/logic/MicrowaveEngine.ts +5 -1
  60. package/src/tool/simulation-probability/bibliography.astro +2 -2
  61. package/src/tool/simulation-probability/i18n/fr.ts +5 -5
  62. package/src/tool/simulation-probability/i18n/ru.ts +7 -7
  63. package/src/tool/simulation-probability/i18n/zh.ts +4 -4
  64. package/src/tool/simulation-probability/index.ts +1 -0
  65. package/src/tool/temperature-timeline/bibliography.astro +14 -0
  66. package/src/tool/temperature-timeline/bibliography.ts +12 -0
  67. package/src/tool/temperature-timeline/component.astro +289 -0
  68. package/src/tool/temperature-timeline/entry.ts +26 -0
  69. package/src/tool/temperature-timeline/i18n/de.ts +213 -0
  70. package/src/tool/temperature-timeline/i18n/en.ts +213 -0
  71. package/src/tool/temperature-timeline/i18n/es.ts +178 -0
  72. package/src/tool/temperature-timeline/i18n/fr.ts +213 -0
  73. package/src/tool/temperature-timeline/i18n/id.ts +213 -0
  74. package/src/tool/temperature-timeline/i18n/it.ts +213 -0
  75. package/src/tool/temperature-timeline/i18n/ja.ts +213 -0
  76. package/src/tool/temperature-timeline/i18n/ko.ts +213 -0
  77. package/src/tool/temperature-timeline/i18n/nl.ts +213 -0
  78. package/src/tool/temperature-timeline/i18n/pl.ts +213 -0
  79. package/src/tool/temperature-timeline/i18n/pt.ts +213 -0
  80. package/src/tool/temperature-timeline/i18n/ru.ts +213 -0
  81. package/src/tool/temperature-timeline/i18n/sv.ts +213 -0
  82. package/src/tool/temperature-timeline/i18n/tr.ts +213 -0
  83. package/src/tool/temperature-timeline/i18n/zh.ts +213 -0
  84. package/src/tool/temperature-timeline/index.ts +11 -0
  85. package/src/tool/temperature-timeline/logic/TemperatureTimelineEngine.ts +58 -0
  86. package/src/tool/temperature-timeline/planet-temperature-timeline.css +158 -0
  87. package/src/tool/temperature-timeline/seo.astro +15 -0
  88. package/src/tools.ts +4 -0
  89. package/src/types.ts +1 -1
@@ -1,6 +1,6 @@
1
1
  const slug = 'kalkulyator-obnovleniya-kletok';
2
2
  const description = 'Рассчитайте процент вашего тела, который обновился с момента рождения. Оценки основаны на скорости деления клеток органов, костей и тканей. Парадокс Тесея, ставший осязаемым.';
3
- const title = 'Калькулятор обновления клеток: сколько осталось от оригинального «вас»?';
3
+ const title = 'Калькулятор обновления клеток: сколько осталось от оригинального "вас"?';
4
4
  const howTo = [
5
5
  {
6
6
  name: 'Укажите свой возраст',
@@ -8,7 +8,7 @@ const howTo = [
8
8
  },
9
9
  {
10
10
  name: 'Посмотрите на основной процент',
11
- text: 'Большое число в центре показывает, какой процент вашей текущей материи является «новым» (обновленным с момента рождения).',
11
+ text: 'Большое число в центре показывает, какой процент вашей текущей материи является "новым" (обновленным с момента рождения).',
12
12
  },
13
13
  {
14
14
  name: 'Изучите графики прогресса',
@@ -16,25 +16,25 @@ const howTo = [
16
16
  },
17
17
  {
18
18
  name: 'Задумайтесь о своей идентичности',
19
- text: 'Если 99% вас это новая материя, то кто вы на самом деле? Используйте этот инструмент как отправную точку для размышлений о преемственности личности и парадоксе Тесея.',
19
+ text: 'Если 99% вас - это новая материя, то кто вы на самом деле? Используйте этот инструмент как отправную точку для размышлений о преемственности личности и парадоксе Тесея.',
20
20
  },
21
21
  ];
22
22
  const faq = [
23
23
  {
24
- question: 'Что именно представляет собой «обновление клеток»?',
24
+ question: 'Что именно представляет собой "обновление клеток"?',
25
25
  answer: 'Это естественный процесс, при котором состарившиеся или поврежденные клетки умирают (апоптоз) и заменяются новыми клетками, образующимися в результате митотического деления. Этот цикл необходим для поддержания функциональности тканей и заживления повреждений.',
26
26
  },
27
27
  {
28
28
  question: 'Почему мозг обновляется так медленно?',
29
- answer: 'Нейроны в коре головного мозга образуются до рождения и, как правило, больше не делятся. Это обеспечивает неврологическую стабильность: ваша фундаментальная «проводка» остается неизменной. Однако глиальные (вспомогательные) клетки обновляются. Память хранится в связях, а не в атомах.',
29
+ answer: 'Нейроны в коре головного мозга образуются до рождения и, как правило, больше не делятся. Это обеспечивает неврологическую стабильность: ваша фундаментальная "проводка" остается неизменной. Однако глиальные (вспомогательные) клетки обновляются. Память хранится в связях, а не в атомах.',
30
30
  },
31
31
  {
32
- question: 'Правда ли, что каждые 7 лет мы становимся «новыми людьми»?',
33
- answer: 'Это упрощение, которое не совсем точно. Ваша кровь обновляется за 4 месяца, кожа за месяц, скелет за 10 лет. Ваш мозг остается практически неизменным. Число 7 лет является скорее историческим мифом (об этом упоминал еще Аристотель), но биологически это неточное среднее значение.',
32
+ question: 'Правда ли, что каждые 7 лет мы становимся "новыми людьми"?',
33
+ answer: 'Это упрощение, которое не совсем точно. Ваша кровь обновляется за 4 месяца, кожа - за месяц, скелет - за 10 лет. Ваш мозг остается практически неизменным. Число 7 лет является скорее историческим мифом (об этом упоминал еще Аристотель), но биологически это неточное среднее значение.',
34
34
  },
35
35
  {
36
36
  question: 'Если мое тело на 99% новое, я все еще тот же человек?',
37
- answer: 'Да. Идентичность это преемственность информации, сознания и памяти, а не атомов. Вы подобны реке: вода постоянно меняется, но река остается прежней. Парадокс Тесея предполагает, что идентичность заключается в паттерне, а не в материи.',
37
+ answer: 'Да. Идентичность - это преемственность информации, сознания и памяти, а не атомов. Вы подобны реке: вода постоянно меняется, но река остается прежней. Парадокс Тесея предполагает, что идентичность заключается в паттерне, а не в материи.',
38
38
  },
39
39
  {
40
40
  question: 'Какие ткани обновляются быстрее всего?',
@@ -65,21 +65,21 @@ export const content: ToolLocaleContent = {
65
65
  boneRemodeling: 'Перестройка костей',
66
66
  organicMatrix: 'Органическая матрица',
67
67
  perennialCells: 'Вечные клетки',
68
- disclaimerText: 'Расчеты основаны на средней продолжительности жизни клеток согласно изотопным исследованиям. В то время как кровь и кожа обновляются за недели, белки хрусталика глаза и большая часть коры головного мозга остаются с вами со времен эмбрионального развития. Физически вы динамическая структура в постоянном потоке.',
68
+ disclaimerText: 'Расчеты основаны на средней продолжительности жизни клеток согласно изотопным исследованиям. В то время как кровь и кожа обновляются за недели, белки хрусталика глаза и большая часть коры головного мозга остаются с вами со времен эмбрионального развития. Физически вы - динамическая структура в постоянном потоке.',
69
69
  },
70
70
  seo: [
71
71
  {
72
72
  type: 'title',
73
- text: 'Сколько в вас действительно «вашего»? Наука об обновлении клеток',
73
+ text: 'Сколько в вас действительно "вашего"? Наука об обновлении клеток',
74
74
  level: 2,
75
75
  },
76
76
  {
77
77
  type: 'paragraph',
78
- html: 'Ваше тело это река в постоянном движении. Каждую секунду миллионы клеток умирают и заменяются новыми. За семь лет практически каждый атом в вашем теле будет заменен. Однако эта статистика глубоко вводит в заблуждение, потому что разные части вашего организма обновляются с радикально разной скоростью.',
78
+ html: 'Ваше тело - это река в постоянном движении. Каждую секунду миллионы клеток умирают и заменяются новыми. За семь лет практически каждый атом в вашем теле будет заменен. Однако эта статистика глубоко вводит в заблуждение, потому что разные части вашего организма обновляются с радикально разной скоростью.',
79
79
  },
80
80
  {
81
81
  type: 'paragraph',
82
- html: 'Этот парадокс, известный как <strong>Парадокс корабля Тесея</strong>, ставит древний вопрос: если заменить все части чего-либо, останется ли оно прежним? В вашем случае это буквальный вопрос. Атомы, из которых сегодня состоит ваше тело, не те же самые, что были там 10 лет назад, но <em>вы</em> все еще вы.',
82
+ html: 'Этот парадокс, известный как <strong>Парадокс корабля Тесея</strong>, ставит древний вопрос: если заменить все части чего-либо, останется ли оно прежним? В вашем случае это буквальный вопрос. Атомы, из которых сегодня состоит ваше тело, - не те же самые, что были там 10 лет назад, но <em>вы</em> все еще вы.',
83
83
  },
84
84
  {
85
85
  type: 'title',
@@ -94,10 +94,10 @@ export const content: ToolLocaleContent = {
94
94
  type: 'table',
95
95
  headers: ['Ткань', 'Средняя жизнь клеток', 'Полное обновление', 'Описание'],
96
96
  rows: [
97
- ['<strong>Кровь</strong>', '120 дней', '4 месяца', 'Эритроциты рекордсмены скорости. Ваш костный мозг производит 200 миллиардов клеток ежедневно.'],
97
+ ['<strong>Кровь</strong>', '120 дней', '4 месяца', 'Эритроциты - рекордсмены скорости. Ваш костный мозг производит 200 миллиардов клеток ежедневно.'],
98
98
  ['<strong>Кожа</strong>', '2-4 недели', '1 месяц', 'Чрезвычайно быстрое обновление. Вы теряете около 30 000 клеток кожи в минуту.'],
99
99
  ['<strong>Кости</strong>', '10 лет', 'Десятилетие', 'Скелет более консервативен. Тем не менее, через 10 лет вы практически полностью обновите свой скелет.'],
100
- ['<strong>Органы</strong>', '15 лет', '1,5 десятилетия', 'Печень обновляется за месяцы. Сердце за годы. Мозаика ритмов.'],
100
+ ['<strong>Органы</strong>', '15 лет', '1,5 десятилетия', 'Печень обновляется за месяцы. Сердце - за годы. Мозаика ритмов.'],
101
101
  ['<strong>Мозг</strong>', '80+ лет (нейроны)', 'Почти никогда', 'Ваши корковые нейроны с вами с рождения. Но глия (вспомогательные клетки) обновляется.'],
102
102
  ],
103
103
  },
@@ -134,16 +134,16 @@ export const content: ToolLocaleContent = {
134
134
  },
135
135
  {
136
136
  type: 'title',
137
- text: 'Философские выводы: идентичность это информация, а не материя',
137
+ text: 'Философские выводы: идентичность - это информация, а не материя',
138
138
  level: 3,
139
139
  },
140
140
  {
141
141
  type: 'paragraph',
142
- html: 'Если ваше тело полностью обновляется каждое десятилетие, почему это все еще «вы»? Ответ заключается в том, что идентичность заключается не в конкретных атомах, а в <strong>информационном паттерне</strong>, который эти атомы удерживают. Вы подобны песне: вибрирует не тот же самый воздух, но мелодия сохраняется.',
142
+ html: 'Если ваше тело полностью обновляется каждое десятилетие, почему это все еще "вы"? Ответ заключается в том, что идентичность заключается не в конкретных атомах, а в <strong>информационном паттерне</strong>, который эти атомы удерживают. Вы подобны песне: вибрирует не тот же самый воздух, но мелодия сохраняется.',
143
143
  },
144
144
  {
145
145
  type: 'paragraph',
146
- html: 'Это имеет глубокие последствия: ваше тело это процесс, а не вещь. Вы самоорганизующийся паттерн, который сохраняется в изменениях. Вы не владеете атомами; вы структура, которая временно направляет их.',
146
+ html: 'Это имеет глубокие последствия: ваше тело - это процесс, а не вещь. Вы - самоорганизующийся паттерн, который сохраняется в изменениях. Вы не владеете атомами; вы - структура, которая временно направляет их.',
147
147
  },
148
148
  ],
149
149
  faq,
@@ -1,6 +1,6 @@
1
1
  const slug = 'cellular-renewal-calculator';
2
- const description = '计算自出生以来你身体更新的百分比。基于器官、骨骼和组织的细胞分裂率进行估算。让“忒修斯之船”悖论变得触手可及。';
3
- const title = '细胞更新计算器:最初的“你”还剩下多少?';
2
+ const description = '计算自出生以来你身体更新的百分比。基于器官、骨骼和组织的细胞分裂率进行估算。让"忒修斯之船"悖论变得触手可及。';
3
+ const title = '细胞更新计算器:最初的"你"还剩下多少?';
4
4
  const howTo = [
5
5
  {
6
6
  name: '调整你的年龄',
@@ -8,7 +8,7 @@ const howTo = [
8
8
  },
9
9
  {
10
10
  name: '观察核心百分比',
11
- text: '中间的大数字显示了你当前物质中有多少百分比是“新的”(自出生以来更新的部分)。',
11
+ text: '中间的大数字显示了你当前物质中有多少百分比是"新的"(自出生以来更新的部分)。',
12
12
  },
13
13
  {
14
14
  name: '分析进度条',
@@ -21,20 +21,20 @@ const howTo = [
21
21
  ];
22
22
  const faq = [
23
23
  {
24
- question: '到底什么是“细胞更新”?',
24
+ question: '到底什么是"细胞更新"?',
25
25
  answer: '这是一种自然过程,衰老或受损的细胞通过凋亡死亡,并由通过有丝分裂产生的新细胞取代。这个过程对于维持组织功能和修复损伤至关重要。',
26
26
  },
27
27
  {
28
28
  question: '为什么大脑更新如此缓慢?',
29
- answer: '大脑皮层的神经元产生于出生前,通常不会再分裂。这提供了神经系统的稳定性:你的基本“布线”保持不变。然而,神经胶质(支持)细胞确实会更新。记忆存储在连接中,而非原子中。',
29
+ answer: '大脑皮层的神经元产生于出生前,通常不会再分裂。这提供了神经系统的稳定性:你的基本"布线"保持不变。然而,神经胶质(支持)细胞确实会更新。记忆存储在连接中,而非原子中。',
30
30
  },
31
31
  {
32
- question: '每 7 年我们就变成“全新的人”是真的吗?',
32
+ question: '每 7 年我们就变成"全新的人"是真的吗?',
33
33
  answer: '这是一种简化,并不准确。你的血液在 4 个月内更新,皮肤在一个月内,骨架在 10 年内。你的大脑基本保持不变。7 年这个数字具有历史意义(亚里士多德提到过),但在生物学上是一个不精确的平均值。',
34
34
  },
35
35
  {
36
36
  question: '如果我的身体 99% 是新的,我依然是同一个人吗?',
37
- answer: '是的。身份是信息、意识和记忆的连续性,而非原子的连续性。你如同一条河:水流不断更替,但河依然是那条河。“忒修斯之船”悖论表明,身份存在于模式中,而非物质中。',
37
+ answer: '是的。身份是信息、意识和记忆的连续性,而非原子的连续性。你如同一条河:水流不断更替,但河依然是那条河。"忒修斯之船"悖论表明,身份存在于模式中,而非物质中。',
38
38
  },
39
39
  {
40
40
  question: '哪些组织更新最快?',
@@ -70,7 +70,7 @@ export const content: ToolLocaleContent = {
70
70
  seo: [
71
71
  {
72
72
  type: 'title',
73
- text: '你身体里有多少东西真正属于“你”?细胞更新的科学',
73
+ text: '你身体里有多少东西真正属于"你"?细胞更新的科学',
74
74
  level: 2,
75
75
  },
76
76
  {
@@ -139,7 +139,7 @@ export const content: ToolLocaleContent = {
139
139
  },
140
140
  {
141
141
  type: 'paragraph',
142
- html: '如果你的身体每十年都是全新的,为什么它还是“你”?答案是身份不在于特定的原子,而在于这些原子所承载的<strong>信息模式</strong>。你就如同一首歌:振动的空气已不相同,但旋律依然存在。',
142
+ html: '如果你的身体每十年都是全新的,为什么它还是"你"?答案是身份不在于特定的原子,而在于这些原子所承载的<strong>信息模式</strong>。你就如同一首歌:振动的空气已不相同,但旋律依然存在。',
143
143
  },
144
144
  {
145
145
  type: 'paragraph',
@@ -1,4 +1,5 @@
1
1
  import { cellularRenewal } from './entry';
2
+ import type { ToolDefinition } from '../../types';
2
3
  export * from './entry';
3
4
  export const CELLULAR_RENEWAL_TOOL: ToolDefinition = {
4
5
  entry: cellularRenewal,
@@ -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 { colonyCounter } 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 colonyCounter.i18n[locale]?.();
12
12
  ---
13
13
 
14
- {content && <Bibliography links={content.bibliography} />}
14
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -19,7 +19,7 @@ const howTo = [
19
19
  const faq = [
20
20
  {
21
21
  question: 'Что такое подсчет КОЕ?',
22
- answer: 'Колониеобразующие единицы (КОЕ) это показатель, позволяющий оценить количество жизнеспособных бактерий или грибков в образце. Предполагается, что каждая видимая колония выросла из одной клетки или группы клеток.',
22
+ answer: 'Колониеобразующие единицы (КОЕ) - это показатель, позволяющий оценить количество жизнеспособных бактерий или грибков в образце. Предполагается, что каждая видимая колония выросла из одной клетки или группы клеток.',
23
23
  },
24
24
  {
25
25
  question: 'Почему цифровой счетчик лучше ручного подсчета?',
@@ -30,8 +30,8 @@ const faq = [
30
30
  answer: 'Количество подсчитанных колоний умножается на обратный коэффициент разведения. Например, если вы насчитали 30 колоний при разведении 1:100, исходный образец содержит 3000 КОЕ/мл.',
31
31
  },
32
32
  {
33
- question: 'Когда чашка считается «неподлежащей подсчету»?',
34
- answer: 'В стандартной микробиологии, если на чашке более 250300 колоний, она считается слишком плотно заселенной (TNTC Too Numerous To Count), и данные считаются ненадежными из-за конкуренции между колониями.',
33
+ question: 'Когда чашка считается "неподлежащей подсчету"?',
34
+ answer: 'В стандартной микробиологии, если на чашке более 250-300 колоний, она считается слишком плотно заселенной (TNTC - Too Numerous To Count), и данные считаются ненадежными из-за конкуренции между колониями.',
35
35
  },
36
36
  ];
37
37
  import { bibliography } from '../bibliography';
@@ -68,7 +68,7 @@ export const content: ToolLocaleContent = {
68
68
  },
69
69
  {
70
70
  type: 'paragraph',
71
- html: 'Подсчет колоний бактерий в чашках Петри это фундаментальный метод микробиологии. Традиционно выполняемый с помощью ручного счетчика и маркера, он часто приводит к ошибкам, когда исследователь сбивается со счета или отмечает одну и ту же колонию дважды. Этот цифровой инструмент исключает такие ошибки и позволяет визуально различать типы колоний.',
71
+ html: 'Подсчет колоний бактерий в чашках Петри - это фундаментальный метод микробиологии. Традиционно выполняемый с помощью ручного счетчика и маркера, он часто приводит к ошибкам, когда исследователь сбивается со счета или отмечает одну и ту же колонию дважды. Этот цифровой инструмент исключает такие ошибки и позволяет визуально различать типы колоний.',
72
72
  },
73
73
  {
74
74
  type: 'title',
@@ -113,7 +113,7 @@ export const content: ToolLocaleContent = {
113
113
  },
114
114
  {
115
115
  type: 'paragraph',
116
- html: 'Идеальный диапазон для ручного подсчета составляет от <strong>30 до 300 колоний</strong> на чашку. Если колоний менее 30, статистическая погрешность слишком велика. Если более 300 колонии начинают сливаться, и их индивидуальное различение становится невозможным.',
116
+ html: 'Идеальный диапазон для ручного подсчета составляет от <strong>30 до 300 колоний</strong> на чашку. Если колоний менее 30, статистическая погрешность слишком велика. Если более 300 - колонии начинают сливаться, и их индивидуальное различение становится невозможным.',
117
117
  },
118
118
  {
119
119
  type: 'title',
@@ -23,14 +23,14 @@ const faq = [
23
23
  },
24
24
  {
25
25
  question: '为什么数字计数器比手动计数更好?',
26
- answer: '数字计数避免了在视觉标记菌落过程中“跟丢进度”的人为错误。此外,我们的工具允许按颜色区分菌落类型,从而简化了混合培养皿的分析。',
26
+ answer: '数字计数避免了在视觉标记菌落过程中"跟丢进度"的人为错误。此外,我们的工具允许按颜色区分菌落类型,从而简化了混合培养皿的分析。',
27
27
  },
28
28
  {
29
29
  question: '如何计算每毫升的 CFU?',
30
30
  answer: '将计得的菌落数乘以稀释倍数。例如,如果您在 1:100 稀释液中数得 30 个菌落,则原始样品含有 3000 CFU/ml。',
31
31
  },
32
32
  {
33
- question: '什么时候培养皿被认为是“不可计数的”?',
33
+ question: '什么时候培养皿被认为是"不可计数的"?',
34
34
  answer: '在标准微生物学中,如果菌落超过 250-300 个,则认为培养皿过于拥挤(多不可计,TNTC),且由于菌落竞争,数据不可靠。',
35
35
  },
36
36
  ];
@@ -1,4 +1,5 @@
1
1
  import { colonyCounter } from './entry';
2
+ import type { ToolDefinition } from '../../types';
2
3
  export * from './entry';
3
4
  export const COLONY_COUNTER_TOOL: ToolDefinition = {
4
5
  entry: colonyCounter,
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { cosmicInflation } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await cosmicInflation.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Inflationary universe: A possible solution to the horizon and flatness problems',
6
+ url: 'https://journals.aps.org/prd/abstract/10.1103/PhysRevD.23.347',
7
+ },
8
+ {
9
+ name: 'A new inflationary universe scenario: A possible solution of the horizon, flatness, homogeneity, isotropy and primordial monopole problems',
10
+ url: 'https://www.sciencedirect.com/science/article/pii/0370269382912199',
11
+ },
12
+ ];
@@ -0,0 +1,270 @@
1
+ ---
2
+ import './cosmic-inflation-calculator.css';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div class="cosmic-calculator-root" id="cosmic-inflation-root">
12
+ <div class="cosmic-presets">
13
+ <button class="cosmic-preset-btn active" data-efolds="60" data-energy="16">{ui.presetGuth}</button>
14
+ <button class="cosmic-preset-btn" data-efolds="70" data-energy="15">{ui.presetChaotic}</button>
15
+ <button class="cosmic-preset-btn" data-efolds="110" data-energy="18">{ui.presetExtreme}</button>
16
+ </div>
17
+
18
+ <div class="cosmic-controls-section">
19
+ <div class="cosmic-input-group">
20
+ <div class="cosmic-input-header">
21
+ <label for="cosmic-efolds">{ui.efoldsLabel}</label>
22
+ <span id="cosmic-efolds-val" class="cosmic-input-val">60</span>
23
+ </div>
24
+ <input type="range" id="cosmic-efolds" class="cosmic-slider" min="10" max="120" value="60" step="1" />
25
+ <span class="cosmic-tooltip">{ui.efoldsTooltip}</span>
26
+ </div>
27
+
28
+ <div class="cosmic-input-group">
29
+ <div class="cosmic-input-header">
30
+ <label for="cosmic-energy">{ui.energyLabel}</label>
31
+ <span id="cosmic-energy-val" class="cosmic-input-val">10^16</span>
32
+ </div>
33
+ <input type="range" id="cosmic-energy" class="cosmic-slider" min="10" max="19" value="16" step="1" />
34
+ <span class="cosmic-tooltip">{ui.energyTooltip}</span>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="cosmic-chart-section">
39
+ <h3>{ui.chartTitle}</h3>
40
+ <div class="cosmic-canvas-container">
41
+ <canvas id="cosmic-chart-canvas"></canvas>
42
+ </div>
43
+ </div>
44
+
45
+ <div class="cosmic-results-section">
46
+ <div class="cosmic-result-block">
47
+ <span class="cosmic-result-label">{ui.scaleFactorResult}</span>
48
+ <span id="cosmic-scale-factor" class="cosmic-result-number">---</span>
49
+ <span class="cosmic-tooltip">{ui.scaleFactorTooltip}</span>
50
+ <span id="cosmic-analogy-text" class="cosmic-analogy"></span>
51
+ </div>
52
+
53
+ <div class="cosmic-divider-line"></div>
54
+
55
+ <div class="cosmic-result-block">
56
+ <span class="cosmic-result-label">{ui.reheatingTempResult}</span>
57
+ <span id="cosmic-reheating" class="cosmic-result-number">---</span>
58
+ <span class="cosmic-tooltip">{ui.reheatingTooltip}</span>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <script>
64
+ import { CosmicInflationEngine } from './logic/CosmicInflationEngine';
65
+
66
+ const root = document.getElementById('cosmic-inflation-root');
67
+ if (root) {
68
+ const efoldsInput = document.getElementById('cosmic-efolds') as HTMLInputElement;
69
+ const energyInput = document.getElementById('cosmic-energy') as HTMLInputElement;
70
+ const efoldsVal = document.getElementById('cosmic-efolds-val');
71
+ const energyVal = document.getElementById('cosmic-energy-val');
72
+ const scaleFactorVal = document.getElementById('cosmic-scale-factor');
73
+ const reheatingVal = document.getElementById('cosmic-reheating');
74
+ const analogyText = document.getElementById('cosmic-analogy-text');
75
+ const canvas = document.getElementById('cosmic-chart-canvas') as HTMLCanvasElement;
76
+ const presetBtns = document.querySelectorAll('.cosmic-preset-btn');
77
+
78
+ const engine = new CosmicInflationEngine();
79
+
80
+ const analogyInsuff = 'Moderate inflation. This expansion is <span class="highlight">insufficient</span> to solve the horizon problem.';
81
+ const analogyProton = 'The universe expanded from the size of a <span class="highlight">proton</span> to the size of a <span class="highlight">galaxy</span> in a fraction of a second.';
82
+ const analogyObservable = 'The universe expanded from a <span class="highlight">subatomic scale</span> to larger than the <span class="highlight">observable universe</span> in 10^-32 seconds.';
83
+
84
+ function updateSliderFill(slider: HTMLInputElement) {
85
+ const min = parseFloat(slider.min) || 0;
86
+ const max = parseFloat(slider.max) || 100;
87
+ const val = parseFloat(slider.value) || 0;
88
+ const percentage = ((val - min) / (max - min)) * 100;
89
+ slider.style.background = `linear-gradient(to right, var(--cosmic-highlight) 0%, var(--cosmic-highlight) ${percentage}%, var(--cosmic-input-border) ${percentage}%, var(--cosmic-input-border) 100%)`;
90
+ }
91
+
92
+ function renderAnalogy(efolds: number) {
93
+ if (!analogyText) return;
94
+ if (efolds >= 60) {
95
+ analogyText.innerHTML = analogyObservable;
96
+ } else if (efolds >= 30) {
97
+ analogyText.innerHTML = analogyProton;
98
+ } else {
99
+ analogyText.innerHTML = analogyInsuff;
100
+ }
101
+ }
102
+
103
+ function update() {
104
+ const efolds = parseFloat(efoldsInput.value) || 60;
105
+ const energyPower = parseFloat(energyInput.value) || 16;
106
+ const energy = Math.pow(10, energyPower);
107
+
108
+ if (efoldsVal) efoldsVal.textContent = efolds.toString();
109
+ if (energyVal) energyVal.textContent = `10^${energyPower}`;
110
+
111
+ updateSliderFill(efoldsInput);
112
+ updateSliderFill(energyInput);
113
+
114
+ const result = engine.calculate(efolds, energy);
115
+
116
+ if (scaleFactorVal) {
117
+ scaleFactorVal.textContent = formatCosmicNumber(result.scaleFactorRatio);
118
+ }
119
+ if (reheatingVal) {
120
+ reheatingVal.textContent = formatCosmicNumber(result.reheatingTemperature);
121
+ }
122
+
123
+ renderAnalogy(efolds);
124
+ drawSpaceTime(efolds);
125
+ }
126
+
127
+ function formatCosmicNumber(val: number): string {
128
+ if (val >= 1e6 || val <= 1e-4) {
129
+ const str = val.toExponential(2);
130
+ return str.replace('e+', ' × 10^').replace('e-', ' × 10^-');
131
+ }
132
+ return val.toLocaleString(undefined, { maximumFractionDigits: 2 });
133
+ } interface DrawingContext {
134
+ ctx: CanvasRenderingContext2D;
135
+ w: number;
136
+ h: number;
137
+ centerX: number;
138
+ centerY: number;
139
+ scale: number;
140
+ isExtreme: boolean;
141
+ efolds: number;
142
+ gridColor: string;
143
+ }
144
+
145
+ function drawVerticalGridLines(c: DrawingContext) {
146
+ const cols = 24;
147
+ for (let i = 0; i <= cols; i++) {
148
+ c.ctx.beginPath();
149
+ for (let j = 0; j <= c.h; j += 5) {
150
+ const rawX = (c.w / cols) * i;
151
+ const rawY = j;
152
+
153
+ const dx = rawX - c.centerX;
154
+ const dy = rawY - c.centerY;
155
+
156
+ const dist = Math.sqrt(dx * dx + dy * dy);
157
+ const factor = 1 + (dist / 100) * (c.scale * 0.05);
158
+
159
+ let x = c.centerX + dx * factor;
160
+ const y = c.centerY + dy * factor;
161
+
162
+ if (c.isExtreme) {
163
+ const perturbation = Math.sin(j * 0.05 + c.efolds) * (c.efolds - 80) * 0.3;
164
+ x += perturbation;
165
+ }
166
+
167
+ if (j === 0) c.ctx.moveTo(x, y);
168
+ else c.ctx.lineTo(x, y);
169
+ }
170
+ c.ctx.stroke();
171
+ }
172
+ }
173
+
174
+ function drawHorizontalGridLines(c: DrawingContext) {
175
+ const rows = 16;
176
+ for (let i = 0; i <= rows; i++) {
177
+ c.ctx.beginPath();
178
+ for (let j = 0; j <= c.w; j += 5) {
179
+ const rawX = j;
180
+ const rawY = (c.h / rows) * i;
181
+
182
+ const dx = rawX - c.centerX;
183
+ const dy = rawY - c.centerY;
184
+
185
+ const dist = Math.sqrt(dx * dx + dy * dy);
186
+ const factor = 1 + (dist / 100) * (c.scale * 0.05);
187
+
188
+ const x = c.centerX + dx * factor;
189
+ let y = c.centerY + dy * factor;
190
+
191
+ if (c.isExtreme) {
192
+ const perturbation = Math.cos(j * 0.05 + c.efolds) * (c.efolds - 80) * 0.3;
193
+ y += perturbation;
194
+ }
195
+
196
+ if (j === 0) c.ctx.moveTo(x, y);
197
+ else c.ctx.lineTo(x, y);
198
+ }
199
+ c.ctx.stroke();
200
+ }
201
+ }
202
+
203
+ function drawRadialPulses(c: DrawingContext) {
204
+ const pulseCount = 3;
205
+ for (let p = 1; p <= pulseCount; p++) {
206
+ const radius = ((p * 40 + (c.efolds * 2)) % 150) + 10;
207
+ c.ctx.beginPath();
208
+ c.ctx.strokeStyle = c.gridColor + (1 - radius / 160) * 0.3 + ')';
209
+ c.ctx.lineWidth = 1.5;
210
+ c.ctx.arc(c.centerX, c.centerY, radius, 0, Math.PI * 2);
211
+ c.ctx.stroke();
212
+ }
213
+ }
214
+
215
+ function drawSpaceTime(efolds: number) {
216
+ if (!canvas) return;
217
+ const ctx = canvas.getContext('2d');
218
+ if (!ctx) return;
219
+ const dpr = window.devicePixelRatio || 1;
220
+ canvas.width = canvas.clientWidth * dpr;
221
+ canvas.height = canvas.clientHeight * dpr;
222
+ ctx.scale(dpr, dpr);
223
+ const w = canvas.width / dpr;
224
+ const h = canvas.height / dpr;
225
+ ctx.clearRect(0, 0, w, h);
226
+ const isDark = document.body.classList.contains('theme-dark') || document.documentElement.classList.contains('theme-dark');
227
+ ctx.strokeStyle = isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.05)';
228
+ ctx.lineWidth = 0.5;
229
+ const drawingCtx: DrawingContext = {
230
+ ctx, w, h,
231
+ centerX: w / 2, centerY: h / 2,
232
+ scale: 1 + (efolds - 10) / 10,
233
+ isExtreme: efolds > 80,
234
+ efolds,
235
+ gridColor: isDark ? 'rgba(244, 114, 182, ' : 'rgba(236, 72, 153, '
236
+ };
237
+ drawVerticalGridLines(drawingCtx);
238
+ drawHorizontalGridLines(drawingCtx);
239
+ drawRadialPulses(drawingCtx);
240
+ }
241
+
242
+ efoldsInput.addEventListener('input', () => {
243
+ presetBtns.forEach(b => b.classList.remove('active'));
244
+ update();
245
+ });
246
+ energyInput.addEventListener('input', () => {
247
+ presetBtns.forEach(b => b.classList.remove('active'));
248
+ update();
249
+ });
250
+
251
+ presetBtns.forEach(btn => {
252
+ btn.addEventListener('click', () => {
253
+ presetBtns.forEach(b => b.classList.remove('active'));
254
+ btn.classList.add('active');
255
+
256
+ const efolds = btn.getAttribute('data-efolds');
257
+ const energy = btn.getAttribute('data-energy');
258
+
259
+ if (efolds) efoldsInput.value = efolds;
260
+ if (energy) energyInput.value = energy;
261
+
262
+ update();
263
+ });
264
+ });
265
+
266
+ update();
267
+
268
+ window.addEventListener('resize', update);
269
+ }
270
+ </script>