@jjlmoya/utils-science 1.37.0 → 1.38.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 (53) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +5 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/dyson-sphere-energy-capture/bibliography.astro +14 -0
  8. package/src/tool/dyson-sphere-energy-capture/bibliography.ts +16 -0
  9. package/src/tool/dyson-sphere-energy-capture/component.astro +253 -0
  10. package/src/tool/dyson-sphere-energy-capture/dyson-sphere-energy-capture.css +502 -0
  11. package/src/tool/dyson-sphere-energy-capture/entry.ts +26 -0
  12. package/src/tool/dyson-sphere-energy-capture/i18n/de.ts +195 -0
  13. package/src/tool/dyson-sphere-energy-capture/i18n/en.ts +195 -0
  14. package/src/tool/dyson-sphere-energy-capture/i18n/es.ts +195 -0
  15. package/src/tool/dyson-sphere-energy-capture/i18n/fr.ts +195 -0
  16. package/src/tool/dyson-sphere-energy-capture/i18n/id.ts +195 -0
  17. package/src/tool/dyson-sphere-energy-capture/i18n/it.ts +195 -0
  18. package/src/tool/dyson-sphere-energy-capture/i18n/ja.ts +71 -0
  19. package/src/tool/dyson-sphere-energy-capture/i18n/ko.ts +71 -0
  20. package/src/tool/dyson-sphere-energy-capture/i18n/nl.ts +197 -0
  21. package/src/tool/dyson-sphere-energy-capture/i18n/pl.ts +197 -0
  22. package/src/tool/dyson-sphere-energy-capture/i18n/pt.ts +195 -0
  23. package/src/tool/dyson-sphere-energy-capture/i18n/ru.ts +195 -0
  24. package/src/tool/dyson-sphere-energy-capture/i18n/sv.ts +195 -0
  25. package/src/tool/dyson-sphere-energy-capture/i18n/tr.ts +195 -0
  26. package/src/tool/dyson-sphere-energy-capture/i18n/zh.ts +71 -0
  27. package/src/tool/dyson-sphere-energy-capture/index.ts +11 -0
  28. package/src/tool/dyson-sphere-energy-capture/logic.ts +120 -0
  29. package/src/tool/dyson-sphere-energy-capture/seo.astro +15 -0
  30. package/src/tool/global-albedo-snowball-simulator/bibliography.astro +14 -0
  31. package/src/tool/global-albedo-snowball-simulator/bibliography.ts +16 -0
  32. package/src/tool/global-albedo-snowball-simulator/component.astro +278 -0
  33. package/src/tool/global-albedo-snowball-simulator/entry.ts +26 -0
  34. package/src/tool/global-albedo-snowball-simulator/global-albedo-snowball-simulator.css +530 -0
  35. package/src/tool/global-albedo-snowball-simulator/i18n/de.ts +169 -0
  36. package/src/tool/global-albedo-snowball-simulator/i18n/en.ts +169 -0
  37. package/src/tool/global-albedo-snowball-simulator/i18n/es.ts +169 -0
  38. package/src/tool/global-albedo-snowball-simulator/i18n/fr.ts +169 -0
  39. package/src/tool/global-albedo-snowball-simulator/i18n/id.ts +169 -0
  40. package/src/tool/global-albedo-snowball-simulator/i18n/it.ts +87 -0
  41. package/src/tool/global-albedo-snowball-simulator/i18n/ja.ts +87 -0
  42. package/src/tool/global-albedo-snowball-simulator/i18n/ko.ts +169 -0
  43. package/src/tool/global-albedo-snowball-simulator/i18n/nl.ts +169 -0
  44. package/src/tool/global-albedo-snowball-simulator/i18n/pl.ts +169 -0
  45. package/src/tool/global-albedo-snowball-simulator/i18n/pt.ts +169 -0
  46. package/src/tool/global-albedo-snowball-simulator/i18n/ru.ts +169 -0
  47. package/src/tool/global-albedo-snowball-simulator/i18n/sv.ts +169 -0
  48. package/src/tool/global-albedo-snowball-simulator/i18n/tr.ts +169 -0
  49. package/src/tool/global-albedo-snowball-simulator/i18n/zh.ts +169 -0
  50. package/src/tool/global-albedo-snowball-simulator/index.ts +11 -0
  51. package/src/tool/global-albedo-snowball-simulator/logic.ts +88 -0
  52. package/src/tool/global-albedo-snowball-simulator/seo.astro +15 -0
  53. package/src/tools.ts +4 -0
@@ -0,0 +1,169 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+
4
+ const slug = 'global-albedo-snowball-simulator';
5
+ const title = '全球反照率与雪球地球模拟器';
6
+ const description = '探索地球热辐射平衡、太阳常数变化、温室气体浓度以及冰反照率反馈,观察冰盖是退缩、稳定还是触发雪球气候。';
7
+
8
+ const howTo = [
9
+ {
10
+ name: '设置入射阳光',
11
+ text: '移动太阳常数滑块,测试暗淡年轻太阳场景、当前地球阳光或更明亮的未来强迫。',
12
+ },
13
+ {
14
+ name: '调整温室气体浓度',
15
+ text: '改变温室气体浓度,观察辐射强迫如何与更高的行星反照率竞争。',
16
+ },
17
+ {
18
+ name: '用冰播种行星',
19
+ text: '从小极冠或大范围冰封的世界开始。模型迭代反馈循环,显示冰是前进还是退缩。',
20
+ },
21
+ {
22
+ name: '读取气候状态',
23
+ text: '使用温度、吸收辐射、最终冰覆盖和时间线曲线来比较温带、雪球和温室结果。',
24
+ },
25
+ ];
26
+
27
+ const faq = [
28
+ {
29
+ question: '什么是冰反照率反馈?',
30
+ answer: '冰和雪比海洋或陆地反射更多的阳光。当冰扩张时,行星反照率上升,吸收的太阳能下降,冷却可能允许更多的冰形成。当冰退缩时,更暗的表面吸收更多能量,变暖加速。',
31
+ },
32
+ {
33
+ question: '雪球地球是什么意思?',
34
+ answer: '雪球地球是一种假想的气候状态,冰到达低纬度或几乎全球覆盖。地质证据表明,地球在新元古代可能接近过这种状态。',
35
+ },
36
+ {
37
+ question: '这是一个完整的气候模型吗?',
38
+ answer: '不是。它是一个用于学习的紧凑能量平衡模型。它忽略大气环流、云、海洋热传输、季节、地理和碳循环反馈,但捕捉了反照率反馈的核心辐射逻辑。',
39
+ },
40
+ {
41
+ question: '为什么温室气体可以打破雪球状态?',
42
+ answer: '温室气体通过增加辐射强迫来减少向外长波冷却。在雪球地球场景中,当硅酸盐风化减慢时,火山二氧化碳可以积累,最终使地球变暖到足以融化低纬度冰。',
43
+ },
44
+ ];
45
+
46
+ export const content: ToolLocaleContent = {
47
+ slug,
48
+ title,
49
+ description,
50
+ ui: {
51
+ controls: '气候控制',
52
+ solarConstant: '太阳常数',
53
+ greenhouse: '温室气体',
54
+ initialIce: '初始冰覆盖',
55
+ temperature: '平衡温度',
56
+ absorbed: '吸收的阳光',
57
+ finalIce: '最终冰覆盖',
58
+ albedo: '行星反照率',
59
+ forcing: '温室强迫',
60
+ state: '气候状态',
61
+ timeline: '冰覆盖变化',
62
+ years: '模型年',
63
+ snowball: '雪球锁定',
64
+ temperate: '温带平衡',
65
+ hothouse: '温室退缩',
66
+ retreating: '冰在退缩',
67
+ advancing: '冰在前进',
68
+ stable: '接近平衡',
69
+ watts: '{value} W/m2',
70
+ ppm: '{value} ppm',
71
+ percent: '{value}%',
72
+ celsius: '{value} C',
73
+ diagnostics: '辐射诊断',
74
+ },
75
+ seo: [
76
+ {
77
+ type: 'title',
78
+ text: '用于冰反照率反馈和雪球地球的全球反照率模拟器',
79
+ level: 2,
80
+ },
81
+ {
82
+ type: 'paragraph',
83
+ html: '这个模拟器探索行星气候中最重要的反馈循环之一:冰覆盖、反射率和吸收阳光之间的联系。具有明亮冰的行星将更多入射太阳辐射反射回太空。这种冷却可以保持或扩大冰,进一步提高反照率,并将系统推向雪球地球状态。冰较少的行星吸收更多阳光,这可能加速冰消作用。',
84
+ },
85
+ {
86
+ type: 'paragraph',
87
+ html: '使用滑块改变太阳常数、温室气体浓度和初始冰覆盖。然后模型迭代一个简单的全球能量平衡,并显示气候是向广泛冰川作用、温带平衡还是高温低冰状态移动。它设计用于快速直觉:每个控制直接映射到辐射收支中的一个物理项。',
88
+ },
89
+ {
90
+ type: 'title',
91
+ text: '能量平衡如何估算',
92
+ level: 3,
93
+ },
94
+ {
95
+ type: 'paragraph',
96
+ html: '吸收的短波辐射估算为 S(1 - a) / 4,其中 S 是太阳常数,a 是行星反照率。除以四将地球圆盘拦截的阳光转换为整个球形表面的平均值。更高的反照率降低吸收的能量;更高的温室气体浓度增加正辐射强迫,从而提高地表温度估算。',
97
+ },
98
+ {
99
+ type: 'table',
100
+ headers: ['控制', '物理意义', '气候效应'],
101
+ rows: [
102
+ ['太阳常数', '地球轨道处的入射恒星能量', '更高的值使行星变暖并缩小冰。'],
103
+ ['温室气体', '相对于参考大气层的长波辐射强迫', '更高的值使雪球锁定更困难。'],
104
+ ['初始冰覆盖', '行星的初始反射率', '高值可以通过反照率反馈触发失控冷却。'],
105
+ ],
106
+ },
107
+ {
108
+ type: 'title',
109
+ text: '为什么反照率反馈可能变得非线性',
110
+ level: 3,
111
+ },
112
+ {
113
+ type: 'paragraph',
114
+ html: '冰反照率循环不是一个温和的单向调整。一旦冰到达行星的足够部分,更亮的表面可以去除如此多的吸收阳光,以至于夏季融化变得微弱。在相反方向,退缩的冰暴露出更暗的海洋和陆地,增加吸收,将行星推离冰川作用。这就是为什么类似的强迫可以根据初始冰覆盖产生不同的结果。',
115
+ },
116
+ {
117
+ type: 'paragraph',
118
+ html: '真实地球增加了许多复杂性:云、冰上的尘埃、海洋热传输、大陆位置、季节性阳光、海冰动态和碳循环。简单模型仍然有价值,因为它隔离了一阶辐射收支。它让你看到为什么雪球地球假说既依赖于微弱的阳光或高反照率触发因素,也依赖于后来用于逃脱的温室气体积累。',
119
+ },
120
+ {
121
+ type: 'title',
122
+ text: '模拟器解读',
123
+ level: 3,
124
+ },
125
+ {
126
+ type: 'list',
127
+ items: [
128
+ '<strong>雪球锁定:</strong>最终冰覆盖非常高,平衡温度仍远低于冰点。',
129
+ '<strong>温带平衡:</strong>模型以部分冰覆盖和中等吸收辐射稳定下来。',
130
+ '<strong>温室退缩:</strong>冰崩溃到非常小的比例,而温室强迫和吸收的阳光保持高位。',
131
+ ],
132
+ },
133
+ ],
134
+ faq,
135
+ bibliography,
136
+ howTo,
137
+ schemas: [
138
+ {
139
+ '@context': 'https://schema.org',
140
+ '@type': 'SoftwareApplication',
141
+ name: title,
142
+ description,
143
+ applicationCategory: 'ScientificApplication',
144
+ operatingSystem: 'Any',
145
+ },
146
+ {
147
+ '@context': 'https://schema.org',
148
+ '@type': 'FAQPage',
149
+ mainEntity: faq.map((item) => ({
150
+ '@type': 'Question',
151
+ name: item.question,
152
+ acceptedAnswer: {
153
+ '@type': 'Answer',
154
+ text: item.answer,
155
+ },
156
+ })),
157
+ },
158
+ {
159
+ '@context': 'https://schema.org',
160
+ '@type': 'HowTo',
161
+ name: title,
162
+ step: howTo.map((step) => ({
163
+ '@type': 'HowToStep',
164
+ name: step.name,
165
+ text: step.text,
166
+ })),
167
+ },
168
+ ],
169
+ };
@@ -0,0 +1,11 @@
1
+ import { globalAlbedoSnowballSimulator } from './entry';
2
+ import type { ToolDefinition } from '../../types';
3
+
4
+ export * from './entry';
5
+
6
+ export const GLOBAL_ALBEDO_SNOWBALL_SIMULATOR_TOOL: ToolDefinition = {
7
+ entry: globalAlbedoSnowballSimulator,
8
+ Component: () => import('./component.astro'),
9
+ SEOComponent: () => import('./seo.astro'),
10
+ BibliographyComponent: () => import('./bibliography.astro'),
11
+ };
@@ -0,0 +1,88 @@
1
+ export interface AlbedoInput {
2
+ solarConstant: number;
3
+ greenhousePpm: number;
4
+ initialIceCover: number;
5
+ }
6
+
7
+ export interface AlbedoStep {
8
+ year: number;
9
+ temperatureC: number;
10
+ iceCover: number;
11
+ albedo: number;
12
+ }
13
+
14
+ export interface AlbedoResult {
15
+ absorbedWattsM2: number;
16
+ greenhouseForcingWattsM2: number;
17
+ equilibriumTemperatureC: number;
18
+ finalIceCover: number;
19
+ finalAlbedo: number;
20
+ stability: 'snowball' | 'temperate' | 'hothouse';
21
+ steps: AlbedoStep[];
22
+ }
23
+
24
+ const STEFAN_BOLTZMANN = 5.670374419e-8;
25
+ const REFERENCE_CO2_PPM = 280;
26
+ const ICE_ALBEDO = 0.62;
27
+ const OCEAN_LAND_ALBEDO = 0.24;
28
+ const EARTH_GREENHOUSE_OFFSET_C = 33;
29
+
30
+ function clamp(value: number, min: number, max: number): number {
31
+ return Math.min(max, Math.max(min, value));
32
+ }
33
+
34
+ export function albedoFromIceCover(iceCover: number): number {
35
+ const iceFraction = clamp(iceCover / 100, 0, 1);
36
+ return OCEAN_LAND_ALBEDO + (ICE_ALBEDO - OCEAN_LAND_ALBEDO) * iceFraction;
37
+ }
38
+
39
+ export function greenhouseForcing(greenhousePpm: number): number {
40
+ return 5.35 * Math.log(Math.max(1, greenhousePpm) / REFERENCE_CO2_PPM);
41
+ }
42
+
43
+ export function radiativeTemperatureC(solarConstant: number, albedo: number, greenhousePpm: number): number {
44
+ const absorbed = solarConstant * (1 - albedo) / 4;
45
+ const effectiveTemperatureK = Math.pow(absorbed / STEFAN_BOLTZMANN, 0.25);
46
+ const greenhouseOffset = EARTH_GREENHOUSE_OFFSET_C + greenhouseForcing(greenhousePpm) * 0.82;
47
+
48
+ return effectiveTemperatureK - 273.15 + greenhouseOffset;
49
+ }
50
+
51
+ function nextIceCover(iceCover: number, temperatureC: number): number {
52
+ const freezePressure = clamp((-temperatureC - 2) * 1.18, -9, 14);
53
+ const meltPressure = clamp((temperatureC - 7) * 0.72, 0, 9);
54
+ const albedoMomentum = iceCover > 55 && temperatureC < 2 ? 3.8 : 0;
55
+
56
+ return clamp(iceCover + freezePressure - meltPressure + albedoMomentum, 0, 100);
57
+ }
58
+
59
+ function classify(finalIceCover: number, temperatureC: number): AlbedoResult['stability'] {
60
+ if (finalIceCover >= 82 && temperatureC < -8) return 'snowball';
61
+ if (finalIceCover <= 8 && temperatureC > 27) return 'hothouse';
62
+ return 'temperate';
63
+ }
64
+
65
+ export function simulateAlbedo(input: AlbedoInput, years = 80): AlbedoResult {
66
+ let iceCover = clamp(input.initialIceCover, 0, 100);
67
+ const steps: AlbedoStep[] = [];
68
+
69
+ for (let year = 0; year <= years; year += 1) {
70
+ const albedo = albedoFromIceCover(iceCover);
71
+ const temperatureC = radiativeTemperatureC(input.solarConstant, albedo, input.greenhousePpm);
72
+ steps.push({ year, temperatureC, iceCover, albedo });
73
+ iceCover = nextIceCover(iceCover, temperatureC);
74
+ }
75
+
76
+ const final = steps[steps.length - 1] ?? steps[0]!;
77
+ const finalAlbedo = final.albedo;
78
+
79
+ return {
80
+ absorbedWattsM2: input.solarConstant * (1 - finalAlbedo) / 4,
81
+ greenhouseForcingWattsM2: greenhouseForcing(input.greenhousePpm),
82
+ equilibriumTemperatureC: final.temperatureC,
83
+ finalIceCover: final.iceCover,
84
+ finalAlbedo,
85
+ stability: classify(final.iceCover, final.temperatureC),
86
+ steps,
87
+ };
88
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { globalAlbedoSnowballSimulator } 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 globalAlbedoSnowballSimulator.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
package/src/tools.ts CHANGED
@@ -19,6 +19,8 @@ import { MANDELBROT_FRACTAL_TOOL } from './tool/mandelbrot-fractal/index';
19
19
  import { PLANET_ATMOSPHERE_SURVIVAL_TOOL } from './tool/planet-atmosphere-survival/index';
20
20
  import { THREE_BODY_PROBLEM_TOOL } from './tool/three-body-problem/index';
21
21
  import { ROCHE_LIMIT_SATELLITE_DISRUPTION_TOOL } from './tool/roche-limit-satellite-disruption/index';
22
+ import { DYSON_SPHERE_ENERGY_CAPTURE_TOOL } from './tool/dyson-sphere-energy-capture/index';
23
+ import { GLOBAL_ALBEDO_SNOWBALL_SIMULATOR_TOOL } from './tool/global-albedo-snowball-simulator/index';
22
24
 
23
25
  export const ALL_TOOLS: ToolDefinition[] = [
24
26
  COLONY_COUNTER_TOOL,
@@ -40,4 +42,6 @@ export const ALL_TOOLS: ToolDefinition[] = [
40
42
  PLANET_ATMOSPHERE_SURVIVAL_TOOL,
41
43
  THREE_BODY_PROBLEM_TOOL,
42
44
  ROCHE_LIMIT_SATELLITE_DISRUPTION_TOOL,
45
+ DYSON_SPHERE_ENERGY_CAPTURE_TOOL,
46
+ GLOBAL_ALBEDO_SNOWBALL_SIMULATOR_TOOL,
43
47
  ];