@jjlmoya/utils-science 1.19.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 (164) 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 +32 -15
  10. package/src/tests/locale_completeness.test.ts +5 -22
  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/shared-test-helpers.ts +56 -0
  14. package/src/tests/title_quality.test.ts +1 -1
  15. package/src/tests/tool_exports.test.ts +34 -0
  16. package/src/tests/tool_validation.test.ts +2 -2
  17. package/src/tool/asteroid-impact/bibliography.astro +2 -2
  18. package/src/tool/asteroid-impact/bibliography.ts +24 -0
  19. package/src/tool/asteroid-impact/component.astro +16 -9
  20. package/src/tool/asteroid-impact/i18n/de.ts +4 -24
  21. package/src/tool/asteroid-impact/i18n/en.ts +4 -24
  22. package/src/tool/asteroid-impact/i18n/es.ts +4 -24
  23. package/src/tool/asteroid-impact/i18n/fr.ts +10 -30
  24. package/src/tool/asteroid-impact/i18n/id.ts +4 -24
  25. package/src/tool/asteroid-impact/i18n/it.ts +4 -24
  26. package/src/tool/asteroid-impact/i18n/ja.ts +4 -24
  27. package/src/tool/asteroid-impact/i18n/ko.ts +4 -24
  28. package/src/tool/asteroid-impact/i18n/nl.ts +4 -24
  29. package/src/tool/asteroid-impact/i18n/pl.ts +4 -24
  30. package/src/tool/asteroid-impact/i18n/pt.ts +4 -24
  31. package/src/tool/asteroid-impact/i18n/ru.ts +8 -28
  32. package/src/tool/asteroid-impact/i18n/sv.ts +4 -24
  33. package/src/tool/asteroid-impact/i18n/tr.ts +4 -24
  34. package/src/tool/asteroid-impact/i18n/zh.ts +4 -24
  35. package/src/tool/asteroid-impact/index.ts +1 -0
  36. package/src/tool/asteroid-impact/script.ts +13 -7
  37. package/src/tool/asteroid-impact/seo.astro +1 -1
  38. package/src/tool/cellular-renewal/bibliography.astro +2 -2
  39. package/src/tool/cellular-renewal/bibliography.ts +24 -0
  40. package/src/tool/cellular-renewal/i18n/de.ts +3 -24
  41. package/src/tool/cellular-renewal/i18n/en.ts +3 -24
  42. package/src/tool/cellular-renewal/i18n/es.ts +3 -24
  43. package/src/tool/cellular-renewal/i18n/fr.ts +16 -37
  44. package/src/tool/cellular-renewal/i18n/id.ts +3 -24
  45. package/src/tool/cellular-renewal/i18n/it.ts +3 -24
  46. package/src/tool/cellular-renewal/i18n/ja.ts +3 -24
  47. package/src/tool/cellular-renewal/i18n/ko.ts +3 -24
  48. package/src/tool/cellular-renewal/i18n/nl.ts +3 -24
  49. package/src/tool/cellular-renewal/i18n/pl.ts +3 -24
  50. package/src/tool/cellular-renewal/i18n/pt.ts +3 -24
  51. package/src/tool/cellular-renewal/i18n/ru.ts +20 -41
  52. package/src/tool/cellular-renewal/i18n/sv.ts +3 -24
  53. package/src/tool/cellular-renewal/i18n/tr.ts +3 -24
  54. package/src/tool/cellular-renewal/i18n/zh.ts +12 -33
  55. package/src/tool/cellular-renewal/index.ts +1 -0
  56. package/src/tool/cellular-renewal/seo.astro +2 -1
  57. package/src/tool/colony-counter/bibliography.astro +2 -2
  58. package/src/tool/colony-counter/bibliography.ts +12 -0
  59. package/src/tool/colony-counter/i18n/de.ts +3 -12
  60. package/src/tool/colony-counter/i18n/en.ts +3 -12
  61. package/src/tool/colony-counter/i18n/es.ts +3 -12
  62. package/src/tool/colony-counter/i18n/fr.ts +3 -12
  63. package/src/tool/colony-counter/i18n/id.ts +3 -12
  64. package/src/tool/colony-counter/i18n/it.ts +3 -12
  65. package/src/tool/colony-counter/i18n/ja.ts +3 -12
  66. package/src/tool/colony-counter/i18n/ko.ts +3 -12
  67. package/src/tool/colony-counter/i18n/nl.ts +3 -12
  68. package/src/tool/colony-counter/i18n/pl.ts +3 -12
  69. package/src/tool/colony-counter/i18n/pt.ts +3 -12
  70. package/src/tool/colony-counter/i18n/ru.ts +8 -17
  71. package/src/tool/colony-counter/i18n/sv.ts +3 -12
  72. package/src/tool/colony-counter/i18n/tr.ts +3 -12
  73. package/src/tool/colony-counter/i18n/zh.ts +5 -14
  74. package/src/tool/colony-counter/index.ts +1 -0
  75. package/src/tool/colony-counter/seo.astro +1 -1
  76. package/src/tool/cosmic-inflation/bibliography.astro +14 -0
  77. package/src/tool/cosmic-inflation/bibliography.ts +12 -0
  78. package/src/tool/cosmic-inflation/component.astro +270 -0
  79. package/src/tool/cosmic-inflation/cosmic-inflation-calculator.css +277 -0
  80. package/src/tool/cosmic-inflation/entry.ts +26 -0
  81. package/src/tool/cosmic-inflation/i18n/de.ts +188 -0
  82. package/src/tool/cosmic-inflation/i18n/en.ts +188 -0
  83. package/src/tool/cosmic-inflation/i18n/es.ts +168 -0
  84. package/src/tool/cosmic-inflation/i18n/fr.ts +188 -0
  85. package/src/tool/cosmic-inflation/i18n/id.ts +188 -0
  86. package/src/tool/cosmic-inflation/i18n/it.ts +188 -0
  87. package/src/tool/cosmic-inflation/i18n/ja.ts +188 -0
  88. package/src/tool/cosmic-inflation/i18n/ko.ts +188 -0
  89. package/src/tool/cosmic-inflation/i18n/nl.ts +188 -0
  90. package/src/tool/cosmic-inflation/i18n/pl.ts +188 -0
  91. package/src/tool/cosmic-inflation/i18n/pt.ts +188 -0
  92. package/src/tool/cosmic-inflation/i18n/ru.ts +188 -0
  93. package/src/tool/cosmic-inflation/i18n/sv.ts +188 -0
  94. package/src/tool/cosmic-inflation/i18n/tr.ts +188 -0
  95. package/src/tool/cosmic-inflation/i18n/zh.ts +188 -0
  96. package/src/tool/cosmic-inflation/index.ts +11 -0
  97. package/src/tool/cosmic-inflation/logic/CosmicInflationEngine.ts +21 -0
  98. package/src/tool/cosmic-inflation/seo.astro +15 -0
  99. package/src/tool/microwave-detector/bibliography.astro +2 -2
  100. package/src/tool/microwave-detector/bibliography.ts +16 -0
  101. package/src/tool/microwave-detector/component.astro +9 -7
  102. package/src/tool/microwave-detector/i18n/de.ts +3 -16
  103. package/src/tool/microwave-detector/i18n/en.ts +3 -16
  104. package/src/tool/microwave-detector/i18n/es.ts +3 -16
  105. package/src/tool/microwave-detector/i18n/fr.ts +7 -20
  106. package/src/tool/microwave-detector/i18n/id.ts +3 -16
  107. package/src/tool/microwave-detector/i18n/it.ts +3 -16
  108. package/src/tool/microwave-detector/i18n/ja.ts +3 -16
  109. package/src/tool/microwave-detector/i18n/ko.ts +3 -16
  110. package/src/tool/microwave-detector/i18n/nl.ts +3 -16
  111. package/src/tool/microwave-detector/i18n/pl.ts +3 -16
  112. package/src/tool/microwave-detector/i18n/pt.ts +3 -16
  113. package/src/tool/microwave-detector/i18n/ru.ts +21 -34
  114. package/src/tool/microwave-detector/i18n/sv.ts +3 -16
  115. package/src/tool/microwave-detector/i18n/tr.ts +3 -16
  116. package/src/tool/microwave-detector/i18n/zh.ts +13 -26
  117. package/src/tool/microwave-detector/index.ts +1 -0
  118. package/src/tool/microwave-detector/logic/MicrowaveEngine.ts +5 -1
  119. package/src/tool/microwave-detector/microwave-leak-detector.css +22 -25
  120. package/src/tool/microwave-detector/seo.astro +2 -1
  121. package/src/tool/simulation-probability/bibliography.astro +2 -2
  122. package/src/tool/simulation-probability/bibliography.ts +24 -0
  123. package/src/tool/simulation-probability/i18n/de.ts +3 -24
  124. package/src/tool/simulation-probability/i18n/en.ts +3 -24
  125. package/src/tool/simulation-probability/i18n/es.ts +3 -24
  126. package/src/tool/simulation-probability/i18n/fr.ts +8 -29
  127. package/src/tool/simulation-probability/i18n/id.ts +3 -24
  128. package/src/tool/simulation-probability/i18n/it.ts +3 -24
  129. package/src/tool/simulation-probability/i18n/ja.ts +3 -24
  130. package/src/tool/simulation-probability/i18n/ko.ts +3 -24
  131. package/src/tool/simulation-probability/i18n/nl.ts +3 -24
  132. package/src/tool/simulation-probability/i18n/pl.ts +3 -24
  133. package/src/tool/simulation-probability/i18n/pt.ts +3 -24
  134. package/src/tool/simulation-probability/i18n/ru.ts +10 -31
  135. package/src/tool/simulation-probability/i18n/sv.ts +3 -24
  136. package/src/tool/simulation-probability/i18n/tr.ts +3 -24
  137. package/src/tool/simulation-probability/i18n/zh.ts +7 -28
  138. package/src/tool/simulation-probability/index.ts +1 -0
  139. package/src/tool/simulation-probability/seo.astro +2 -1
  140. package/src/tool/temperature-timeline/bibliography.astro +14 -0
  141. package/src/tool/temperature-timeline/bibliography.ts +12 -0
  142. package/src/tool/temperature-timeline/component.astro +289 -0
  143. package/src/tool/temperature-timeline/entry.ts +26 -0
  144. package/src/tool/temperature-timeline/i18n/de.ts +213 -0
  145. package/src/tool/temperature-timeline/i18n/en.ts +213 -0
  146. package/src/tool/temperature-timeline/i18n/es.ts +178 -0
  147. package/src/tool/temperature-timeline/i18n/fr.ts +213 -0
  148. package/src/tool/temperature-timeline/i18n/id.ts +213 -0
  149. package/src/tool/temperature-timeline/i18n/it.ts +213 -0
  150. package/src/tool/temperature-timeline/i18n/ja.ts +213 -0
  151. package/src/tool/temperature-timeline/i18n/ko.ts +213 -0
  152. package/src/tool/temperature-timeline/i18n/nl.ts +213 -0
  153. package/src/tool/temperature-timeline/i18n/pl.ts +213 -0
  154. package/src/tool/temperature-timeline/i18n/pt.ts +213 -0
  155. package/src/tool/temperature-timeline/i18n/ru.ts +213 -0
  156. package/src/tool/temperature-timeline/i18n/sv.ts +213 -0
  157. package/src/tool/temperature-timeline/i18n/tr.ts +213 -0
  158. package/src/tool/temperature-timeline/i18n/zh.ts +213 -0
  159. package/src/tool/temperature-timeline/index.ts +11 -0
  160. package/src/tool/temperature-timeline/logic/TemperatureTimelineEngine.ts +58 -0
  161. package/src/tool/temperature-timeline/planet-temperature-timeline.css +158 -0
  162. package/src/tool/temperature-timeline/seo.astro +15 -0
  163. package/src/tools.ts +4 -0
  164. package/src/types.ts +1 -3
@@ -12,4 +12,4 @@ const content = await colonyCounter.i18n[locale]?.();
12
12
  if (!content) return null;
13
13
  ---
14
14
 
15
- <SEORenderer content={{ sections: content.seo, locale }} />
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -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>
@@ -0,0 +1,277 @@
1
+ :root {
2
+ --cosmic-bg: #fff;
3
+ --cosmic-border: rgba(0, 0, 0, 0.06);
4
+ --cosmic-text: #0f172a;
5
+ --cosmic-text-muted: #4b5563;
6
+ --cosmic-input-border: rgba(0, 0, 0, 0.1);
7
+ --cosmic-divider: rgba(0, 0, 0, 0.08);
8
+ --cosmic-canvas-bg: #fdfdfd;
9
+ --cosmic-highlight: #ec4899;
10
+ --cosmic-number-color: #0f172a;
11
+ }
12
+
13
+ .theme-dark {
14
+ --cosmic-bg: #030712;
15
+ --cosmic-border: rgba(255, 255, 255, 0.05);
16
+ --cosmic-text: #f3f4f6;
17
+ --cosmic-text-muted: #d1d5db;
18
+ --cosmic-input-border: rgba(255, 255, 255, 0.15);
19
+ --cosmic-divider: rgba(255, 255, 255, 0.08);
20
+ --cosmic-canvas-bg: #090d16;
21
+ --cosmic-highlight: #f472b6;
22
+ --cosmic-number-color: #fff;
23
+ }
24
+
25
+ .cosmic-calculator-root {
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: 2rem;
29
+ padding: 1.5rem;
30
+ background: var(--cosmic-bg);
31
+ color: var(--cosmic-text);
32
+ border-radius: 2rem;
33
+ border: 1px solid var(--cosmic-border);
34
+ max-width: 960px;
35
+ margin: 0 auto;
36
+ }
37
+
38
+ @media (min-width: 769px) {
39
+ .cosmic-calculator-root {
40
+ display: grid;
41
+ grid-template-columns: 1fr 1fr;
42
+ grid-template-areas:
43
+ "presets presets"
44
+ "controls results"
45
+ "chart chart";
46
+ gap: 3rem;
47
+ padding: 2.5rem;
48
+ }
49
+ }
50
+
51
+ .cosmic-presets {
52
+ display: flex;
53
+ gap: 1rem;
54
+ border-bottom: 1px solid var(--cosmic-divider);
55
+ padding-bottom: 0.25rem;
56
+ }
57
+
58
+ @media (min-width: 769px) {
59
+ .cosmic-presets {
60
+ grid-area: presets;
61
+ gap: 2rem;
62
+ }
63
+ }
64
+
65
+ .cosmic-preset-btn {
66
+ background: transparent;
67
+ border: none;
68
+ border-bottom: 2px solid transparent;
69
+ border-radius: 0;
70
+ color: var(--cosmic-text);
71
+ font-size: 0.9rem;
72
+ font-weight: 500;
73
+ letter-spacing: 0.05em;
74
+ cursor: pointer;
75
+ transition: all 0.2s;
76
+ padding: 10px 16px;
77
+ opacity: 0.5;
78
+ }
79
+
80
+ .cosmic-preset-btn:hover {
81
+ opacity: 1;
82
+ }
83
+
84
+ .cosmic-preset-btn.active {
85
+ opacity: 1;
86
+ border-bottom-color: var(--cosmic-highlight);
87
+ color: var(--cosmic-text);
88
+ }
89
+
90
+ .cosmic-controls-section {
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: 2rem;
94
+ }
95
+
96
+ @media (min-width: 769px) {
97
+ .cosmic-controls-section {
98
+ grid-area: controls;
99
+ }
100
+ }
101
+
102
+ .cosmic-input-group {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 0.5rem;
106
+ }
107
+
108
+ .cosmic-input-group label {
109
+ font-size: 0.75rem;
110
+ font-weight: 600;
111
+ text-transform: uppercase;
112
+ letter-spacing: 0.1em;
113
+ color: var(--cosmic-text-muted);
114
+ }
115
+
116
+ .cosmic-input-header {
117
+ display: flex;
118
+ justify-content: space-between;
119
+ align-items: center;
120
+ }
121
+
122
+ .cosmic-input-val {
123
+ font-size: 1.25rem;
124
+ font-weight: 400;
125
+ color: var(--cosmic-highlight);
126
+ }
127
+
128
+ .cosmic-slider {
129
+ -webkit-appearance: none;
130
+ width: 100%;
131
+ height: 4px;
132
+ background: var(--cosmic-input-border);
133
+ outline: none;
134
+ border-radius: 2px;
135
+ padding: 12px 0;
136
+ background-clip: content-box;
137
+ cursor: pointer;
138
+ }
139
+
140
+ .cosmic-slider::-webkit-slider-thumb {
141
+ -webkit-appearance: none;
142
+ appearance: none;
143
+ width: 18px;
144
+ height: 18px;
145
+ border-radius: 50%;
146
+ background: var(--cosmic-highlight);
147
+ cursor: pointer;
148
+ transition: transform 0.1s;
149
+ margin-top: -7px;
150
+ }
151
+
152
+ .cosmic-slider::-webkit-slider-runnable-track {
153
+ width: 100%;
154
+ height: 4px;
155
+ border-radius: 2px;
156
+ }
157
+
158
+ .cosmic-slider::-webkit-slider-thumb:hover {
159
+ transform: scale(1.2);
160
+ }
161
+
162
+ .cosmic-tooltip {
163
+ font-size: 0.7rem;
164
+ color: var(--cosmic-text-muted);
165
+ line-height: 1.4;
166
+ margin-top: 0.25rem;
167
+ opacity: 0.9;
168
+ }
169
+
170
+ @media (max-width: 768px) {
171
+ .cosmic-tooltip {
172
+ display: none;
173
+ }
174
+ }
175
+
176
+ .cosmic-results-section {
177
+ display: flex;
178
+ flex-direction: column;
179
+ gap: 1.5rem;
180
+ }
181
+
182
+ @media (min-width: 769px) {
183
+ .cosmic-results-section {
184
+ grid-area: results;
185
+ gap: 2rem;
186
+ }
187
+ }
188
+
189
+ .cosmic-divider-line {
190
+ height: 1px;
191
+ background-color: var(--cosmic-divider);
192
+ width: 100%;
193
+ }
194
+
195
+ @media (max-width: 768px) {
196
+ .cosmic-results-section .cosmic-divider-line {
197
+ display: none;
198
+ }
199
+ }
200
+
201
+ .cosmic-result-block {
202
+ display: flex;
203
+ flex-direction: column;
204
+ }
205
+
206
+ .cosmic-result-label {
207
+ font-size: 0.65rem;
208
+ font-weight: 600;
209
+ text-transform: uppercase;
210
+ letter-spacing: 0.2em;
211
+ color: var(--cosmic-text-muted);
212
+ margin-bottom: 0.25rem;
213
+ }
214
+
215
+ .cosmic-result-number {
216
+ font-size: clamp(2rem, 7vw, 3rem);
217
+ font-weight: 200;
218
+ line-height: 1.1;
219
+ color: var(--cosmic-number-color);
220
+ letter-spacing: -0.03em;
221
+ }
222
+
223
+ .cosmic-analogy {
224
+ font-size: 0.85rem;
225
+ color: var(--cosmic-text-muted);
226
+ margin-top: 0.75rem;
227
+ line-height: 1.5;
228
+ opacity: 0.95;
229
+ }
230
+
231
+ .cosmic-analogy .highlight {
232
+ color: var(--cosmic-highlight);
233
+ font-weight: 600;
234
+ }
235
+
236
+ .cosmic-chart-section {
237
+ display: flex;
238
+ flex-direction: column;
239
+ gap: 1rem;
240
+ }
241
+
242
+ @media (min-width: 769px) {
243
+ .cosmic-chart-section {
244
+ grid-area: chart;
245
+ }
246
+ }
247
+
248
+ .cosmic-chart-section h3 {
249
+ font-size: 0.75rem;
250
+ font-weight: 600;
251
+ text-transform: uppercase;
252
+ letter-spacing: 0.15em;
253
+ color: var(--cosmic-text-muted);
254
+ margin: 0;
255
+ }
256
+
257
+ .cosmic-canvas-container {
258
+ position: relative;
259
+ width: 100%;
260
+ height: 240px;
261
+ background: var(--cosmic-canvas-bg);
262
+ border-radius: 1.5rem;
263
+ border: 1px solid var(--cosmic-border);
264
+ overflow: hidden;
265
+ }
266
+
267
+ @media (min-width: 769px) {
268
+ .cosmic-canvas-container {
269
+ height: 320px;
270
+ }
271
+ }
272
+
273
+ .cosmic-canvas-container canvas {
274
+ width: 100%;
275
+ height: 100%;
276
+ display: block;
277
+ }
@@ -0,0 +1,26 @@
1
+ import type { ScienceToolEntry } from '../../types';
2
+
3
+ export const cosmicInflation: ScienceToolEntry = {
4
+ id: 'cosmic-inflation-calculator',
5
+ icons: {
6
+ bg: 'mdi:rocket-launch-outline',
7
+ fg: 'mdi:chart-timeline-variant',
8
+ },
9
+ i18n: {
10
+ es: () => import('./i18n/es').then((m) => m.content),
11
+ en: () => import('./i18n/en').then((m) => m.content),
12
+ fr: () => import('./i18n/fr').then((m) => m.content),
13
+ de: () => import('./i18n/de').then((m) => m.content),
14
+ it: () => import('./i18n/it').then((m) => m.content),
15
+ pt: () => import('./i18n/pt').then((m) => m.content),
16
+ id: () => import('./i18n/id').then((m) => m.content),
17
+ ja: () => import('./i18n/ja').then((m) => m.content),
18
+ ko: () => import('./i18n/ko').then((m) => m.content),
19
+ nl: () => import('./i18n/nl').then((m) => m.content),
20
+ pl: () => import('./i18n/pl').then((m) => m.content),
21
+ ru: () => import('./i18n/ru').then((m) => m.content),
22
+ sv: () => import('./i18n/sv').then((m) => m.content),
23
+ tr: () => import('./i18n/tr').then((m) => m.content),
24
+ zh: () => import('./i18n/zh').then((m) => m.content),
25
+ },
26
+ };