@jjlmoya/utils-science 1.24.0 → 1.26.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 +1 -1
- package/src/category/index.ts +3 -1
- package/src/entries.ts +5 -1
- package/src/index.ts +2 -1
- package/src/tests/locale_completeness.test.ts +2 -3
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/lorenz-attractor/i18n/es.ts +12 -4
- package/src/tool/lorenz-attractor/lorenz-attractor.css +56 -25
- package/src/tool/radioactive-decay/bibliography.astro +15 -0
- package/src/tool/radioactive-decay/bibliography.ts +17 -0
- package/src/tool/radioactive-decay/component.astro +346 -0
- package/src/tool/radioactive-decay/entry.ts +26 -0
- package/src/tool/radioactive-decay/i18n/de.ts +78 -0
- package/src/tool/radioactive-decay/i18n/en.ts +223 -0
- package/src/tool/radioactive-decay/i18n/es.ts +106 -0
- package/src/tool/radioactive-decay/i18n/fr.ts +78 -0
- package/src/tool/radioactive-decay/i18n/id.ts +66 -0
- package/src/tool/radioactive-decay/i18n/it.ts +79 -0
- package/src/tool/radioactive-decay/i18n/ja.ts +65 -0
- package/src/tool/radioactive-decay/i18n/ko.ts +65 -0
- package/src/tool/radioactive-decay/i18n/nl.ts +72 -0
- package/src/tool/radioactive-decay/i18n/pl.ts +65 -0
- package/src/tool/radioactive-decay/i18n/pt.ts +78 -0
- package/src/tool/radioactive-decay/i18n/ru.ts +66 -0
- package/src/tool/radioactive-decay/i18n/sv.ts +66 -0
- package/src/tool/radioactive-decay/i18n/tr.ts +66 -0
- package/src/tool/radioactive-decay/i18n/zh.ts +65 -0
- package/src/tool/radioactive-decay/index.ts +12 -0
- package/src/tool/radioactive-decay/logic.test.ts +20 -0
- package/src/tool/radioactive-decay/logic.ts +120 -0
- package/src/tool/radioactive-decay/radioactive-decay-half-life-calculator.css +435 -0
- package/src/tool/radioactive-decay/seo.astro +16 -0
- package/src/tool/stellar-habitability-zone/bibliography.astro +14 -0
- package/src/tool/stellar-habitability-zone/bibliography.ts +12 -0
- package/src/tool/stellar-habitability-zone/component.astro +123 -0
- package/src/tool/stellar-habitability-zone/dom-updater.ts +94 -0
- package/src/tool/stellar-habitability-zone/entry.ts +26 -0
- package/src/tool/stellar-habitability-zone/i18n/de.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/en.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/es.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/fr.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/id.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/it.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/ja.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/ko.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/nl.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/pl.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/pt.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/ru.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/sv.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/tr.ts +189 -0
- package/src/tool/stellar-habitability-zone/i18n/zh.ts +189 -0
- package/src/tool/stellar-habitability-zone/index.ts +11 -0
- package/src/tool/stellar-habitability-zone/interaction.ts +45 -0
- package/src/tool/stellar-habitability-zone/logic/StellarHabitabilityEngine.ts +158 -0
- package/src/tool/stellar-habitability-zone/renderer.ts +241 -0
- package/src/tool/stellar-habitability-zone/script.ts +273 -0
- package/src/tool/stellar-habitability-zone/seo.astro +15 -0
- package/src/tool/stellar-habitability-zone/stellar-habitability-zone.css +375 -0
- package/src/tools.ts +4 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
|
|
4
|
+
const slug = 'radioactive-decay-half-life-calculator';
|
|
5
|
+
const title = '放射性衰变半衰期计算器';
|
|
6
|
+
const description = '使用真实同位素、半衰期公式、随机原子场、剩余数量和相对活度来模拟放射性衰变。';
|
|
7
|
+
|
|
8
|
+
const howTo = [
|
|
9
|
+
{ name: '选择同位素', text: '从碳-14、碘-131、铀-238、锝-99m 或氡-222 开始。每个预设都会载入真实的半衰期和常见科学用途。' },
|
|
10
|
+
{ name: '设置样本和时间', text: '调整显示的原子数量,并推进时间,观察剩余比例如何遵循半衰期的指数规律。' },
|
|
11
|
+
{ name: '比较精确计算和原子随机性', text: '把确定性结果作为期望值,再观察原子场,理解小样本为什么会围绕理论曲线波动。' },
|
|
12
|
+
{ name: '解读活度', text: '活度与未衰变原子核数量按相同比例下降,因此仪表显示相对于初始样本还剩多少辐射率。' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const faq = [
|
|
16
|
+
{ question: '什么是半衰期?', answer: '半衰期是样本中平均一半不稳定原子核发生衰变所需的时间。一个半衰期后剩 50%,两个后剩 25%,三个后剩 12.5%。' },
|
|
17
|
+
{ question: '为什么原子场不总是等于精确百分比?', answer: '放射性衰变是概率过程。公式给出大样本的期望比例,而原子场用随机阈值模拟单个原子。' },
|
|
18
|
+
{ question: '活度和剩余原子数是一回事吗?', answer: '对单一同位素来说,活度与未衰变原子核数量成正比。如果还剩 30% 的原子,瞬时活度也约为初始活度的 30%。' },
|
|
19
|
+
{ question: '这个计算器能用于碳十四测年吗?', answer: '可用于概念理解。真实实验室测年还需要校准曲线、污染修正和样本处理。' },
|
|
20
|
+
{ question: '每种同位素都会衰变成一个稳定产物吗?', answer: '不一定。有些同位素会通过包含多个子体产物的衰变链变化。本工具建模的是母体同位素的半衰期。' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export const content: ToolLocaleContent = {
|
|
24
|
+
slug,
|
|
25
|
+
title,
|
|
26
|
+
description,
|
|
27
|
+
ui: {
|
|
28
|
+
isotope: '同位素',
|
|
29
|
+
sampleAtoms: '样本原子',
|
|
30
|
+
elapsedTime: '经过时间',
|
|
31
|
+
halfLife: '半衰期',
|
|
32
|
+
remaining: '剩余',
|
|
33
|
+
decayed: '已衰变',
|
|
34
|
+
activity: '相对活度',
|
|
35
|
+
timeUnit: '时间单位',
|
|
36
|
+
expectedCurve: '期望曲线',
|
|
37
|
+
atomField: '原子场',
|
|
38
|
+
presetUse: '常见用途',
|
|
39
|
+
oneHalfLife: '1 个半衰期',
|
|
40
|
+
twoHalfLives: '2 个半衰期',
|
|
41
|
+
fourHalfLives: '4 个半衰期',
|
|
42
|
+
custom: '自定义',
|
|
43
|
+
liveAtoms: '未衰变原子',
|
|
44
|
+
decayedAtoms: '已衰变原子',
|
|
45
|
+
resetSeed: '新的原子图案',
|
|
46
|
+
},
|
|
47
|
+
seo: [
|
|
48
|
+
{ type: 'title', text: '放射性半衰期计算器: 剩余原子、活度和同位素示例', level: 2 },
|
|
49
|
+
{ type: 'paragraph', html: '这个放射性衰变计算器用于估算不稳定同位素在给定时间后还剩多少。它覆盖常见搜索意图: 半衰期公式、真实同位素计算、剩余母核、已衰变原子核以及活度下降。' },
|
|
50
|
+
{ type: 'paragraph', html: '使用的公式是 <strong>N(t) = N0 x (1/2)^(t / T1/2)</strong>。<strong>N0</strong> 是初始母核数量,<strong>N(t)</strong> 是时间 <strong>t</strong> 后的期望剩余量,<strong>T1/2</strong> 是半衰期。' },
|
|
51
|
+
{ type: 'table', headers: ['经过时间', '公式因子', '剩余母核', '相对活度'], rows: [['0 个半衰期', '(1/2)^0', '100%', '100%'], ['1 个半衰期', '(1/2)^1', '50%', '50%'], ['2 个半衰期', '(1/2)^2', '25%', '25%'], ['3 个半衰期', '(1/2)^3', '12.5%', '12.5%'], ['5 个半衰期', '(1/2)^5', '3.125%', '3.125%'], ['10 个半衰期', '(1/2)^10', '0.098%', '0.098%']] },
|
|
52
|
+
{ type: 'title', text: '同位素示例和结果解读', level: 3 },
|
|
53
|
+
{ type: 'table', headers: ['同位素', '近似半衰期', '常见用途', '结果说明'], rows: [['碳-14', '5,730 年', '放射性碳测年', '曾经属于生物的材料中还剩多少碳-14 活度。'], ['碘-131', '8.02 天', '医学治疗和核事件', '活度在数天内下降得多快。'], ['锝-99m', '6.01 小时', '诊断成像', '有用的医学活度为什么会在一个临床工作日内下降。'], ['铀-238', '44.7 亿年', '地质年代测定', '长寿命同位素为什么能在地球历史尺度上被测量。'], ['氡-222', '3.82 天', '室内辐射', '气态暴露源如何变化。']] },
|
|
54
|
+
{ type: 'paragraph', html: '对于单一母体同位素,活度与未衰变原子核数量成正比。原子场展示单个衰变事件的随机性,而大样本会接近平滑的指数曲线。' },
|
|
55
|
+
{ type: 'paragraph', html: '该模型用单一半衰期描述母体同位素。真实测量可能需要校正探测器效率、背景辐射、分支比、子体产物、生物清除、化学形态、屏蔽和校准曲线。它适合作为教学估算,不应替代医疗指示、剂量评估或实验室分析。' },
|
|
56
|
+
],
|
|
57
|
+
faq,
|
|
58
|
+
bibliography,
|
|
59
|
+
howTo,
|
|
60
|
+
schemas: [
|
|
61
|
+
{ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'ScientificApplication', operatingSystem: 'Any' },
|
|
62
|
+
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) },
|
|
63
|
+
{ '@context': 'https://schema.org', '@type': 'HowTo', name: title, step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) },
|
|
64
|
+
],
|
|
65
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { radioactiveDecay } from './entry';
|
|
2
|
+
import type { ToolDefinition } from '../../types';
|
|
3
|
+
|
|
4
|
+
export * from './entry';
|
|
5
|
+
|
|
6
|
+
export const RADIOACTIVE_DECAY_TOOL: ToolDefinition = {
|
|
7
|
+
entry: radioactiveDecay,
|
|
8
|
+
Component: () => import('./component.astro'),
|
|
9
|
+
SEOComponent: () => import('./seo.astro'),
|
|
10
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
11
|
+
};
|
|
12
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { calculateRemainingFraction, simulateDecay } from './logic';
|
|
3
|
+
|
|
4
|
+
describe('radioactive decay logic', () => {
|
|
5
|
+
it('halves the remaining fraction every half-life', () => {
|
|
6
|
+
expect(calculateRemainingFraction(0, 10)).toBe(1);
|
|
7
|
+
expect(calculateRemainingFraction(10, 10)).toBeCloseTo(0.5);
|
|
8
|
+
expect(calculateRemainingFraction(20, 10)).toBeCloseTo(0.25);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('creates deterministic stochastic atom states from a seed', () => {
|
|
12
|
+
const first = simulateDecay({ initialAtoms: 80, halfLife: 8, elapsedTime: 8, seed: 11 });
|
|
13
|
+
const second = simulateDecay({ initialAtoms: 80, halfLife: 8, elapsedTime: 8, seed: 11 });
|
|
14
|
+
|
|
15
|
+
expect(first.decayedAtoms).toBe(second.decayedAtoms);
|
|
16
|
+
expect(first.atoms).toEqual(second.atoms);
|
|
17
|
+
expect(first.remainingFraction).toBeCloseTo(0.5);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export interface IsotopePreset {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
halfLife: number;
|
|
5
|
+
unit: TimeUnit;
|
|
6
|
+
useCase: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type TimeUnit = 'seconds' | 'minutes' | 'hours' | 'days' | 'years';
|
|
10
|
+
|
|
11
|
+
export interface DecayInput {
|
|
12
|
+
initialAtoms: number;
|
|
13
|
+
halfLife: number;
|
|
14
|
+
elapsedTime: number;
|
|
15
|
+
seed?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AtomState {
|
|
19
|
+
id: number;
|
|
20
|
+
decayed: boolean;
|
|
21
|
+
threshold: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DecayResult {
|
|
25
|
+
remainingAtoms: number;
|
|
26
|
+
decayedAtoms: number;
|
|
27
|
+
remainingFraction: number;
|
|
28
|
+
decayedFraction: number;
|
|
29
|
+
activityFraction: number;
|
|
30
|
+
atoms: AtomState[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const ISOTOPE_PRESETS: IsotopePreset[] = [
|
|
34
|
+
{
|
|
35
|
+
id: 'carbon-14',
|
|
36
|
+
name: 'Carbon-14',
|
|
37
|
+
halfLife: 5730,
|
|
38
|
+
unit: 'years',
|
|
39
|
+
useCase: 'Archaeology and radiocarbon dating',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'iodine-131',
|
|
43
|
+
name: 'Iodine-131',
|
|
44
|
+
halfLife: 8.02,
|
|
45
|
+
unit: 'days',
|
|
46
|
+
useCase: 'Medical thyroid diagnostics and therapy',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'uranium-238',
|
|
50
|
+
name: 'Uranium-238',
|
|
51
|
+
halfLife: 4_468_000_000,
|
|
52
|
+
unit: 'years',
|
|
53
|
+
useCase: 'Geologic dating and nuclear fuel cycles',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'technetium-99m',
|
|
57
|
+
name: 'Technetium-99m',
|
|
58
|
+
halfLife: 6.01,
|
|
59
|
+
unit: 'hours',
|
|
60
|
+
useCase: 'Nuclear medicine imaging',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'radon-222',
|
|
64
|
+
name: 'Radon-222',
|
|
65
|
+
halfLife: 3.82,
|
|
66
|
+
unit: 'days',
|
|
67
|
+
useCase: 'Indoor radiation exposure studies',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
export function calculateRemainingFraction(elapsedTime: number, halfLife: number): number {
|
|
72
|
+
if (halfLife <= 0) {
|
|
73
|
+
throw new Error('Half-life must be greater than zero.');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (elapsedTime <= 0) {
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return Math.pow(0.5, elapsedTime / halfLife);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function seededRandom(seed: number): () => number {
|
|
84
|
+
let state = seed >>> 0;
|
|
85
|
+
|
|
86
|
+
return () => {
|
|
87
|
+
state = (state * 1664525 + 1013904223) >>> 0;
|
|
88
|
+
return state / 4294967296;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function simulateDecay(input: DecayInput): DecayResult {
|
|
93
|
+
const atomCount = Math.max(1, Math.round(input.initialAtoms));
|
|
94
|
+
const remainingFraction = calculateRemainingFraction(input.elapsedTime, input.halfLife);
|
|
95
|
+
const decayedFraction = 1 - remainingFraction;
|
|
96
|
+
const random = seededRandom(input.seed ?? 42);
|
|
97
|
+
|
|
98
|
+
const atoms = Array.from({ length: atomCount }, (_, index) => {
|
|
99
|
+
const threshold = random();
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
id: index,
|
|
103
|
+
threshold,
|
|
104
|
+
decayed: threshold < decayedFraction,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const decayedAtoms = atoms.filter((atom) => atom.decayed).length;
|
|
109
|
+
const remainingAtoms = atomCount - decayedAtoms;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
remainingAtoms,
|
|
113
|
+
decayedAtoms,
|
|
114
|
+
remainingFraction,
|
|
115
|
+
decayedFraction,
|
|
116
|
+
activityFraction: remainingFraction,
|
|
117
|
+
atoms,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
.decay-lab {
|
|
2
|
+
--decay-serif: "Playfair Display", "Bodoni 72", "Didot", georgia, serif;
|
|
3
|
+
--decay-sans: "Inter", "Avenir Next", "Segoe UI", arial, sans-serif;
|
|
4
|
+
--decay-ink: #161a16;
|
|
5
|
+
--decay-muted: #687166;
|
|
6
|
+
--decay-paper: #fbfaf6;
|
|
7
|
+
--decay-panel: rgba(247, 244, 234, 0.72);
|
|
8
|
+
--decay-line: rgba(22, 26, 22, 0.16);
|
|
9
|
+
--decay-live: #219f73;
|
|
10
|
+
--decay-spent: #262b31;
|
|
11
|
+
--decay-hot: #e34d3d;
|
|
12
|
+
--decay-gold: #d5a21b;
|
|
13
|
+
--decay-field-bg: rgba(255, 255, 255, 0.34);
|
|
14
|
+
--decay-control-bg: rgba(255, 255, 255, 0.66);
|
|
15
|
+
--decay-grid-line: rgba(22, 26, 22, 0.045);
|
|
16
|
+
--decay-curve-ghost: rgba(22, 26, 22, 0.12);
|
|
17
|
+
--decay-shadow: rgba(28, 24, 16, 0.12);
|
|
18
|
+
|
|
19
|
+
display: grid;
|
|
20
|
+
gap: clamp(1.75rem, 4vw, 3rem);
|
|
21
|
+
width: min(100%, 1120px);
|
|
22
|
+
padding: clamp(1.25rem, 3vw, 2.75rem);
|
|
23
|
+
color: var(--decay-ink);
|
|
24
|
+
background:
|
|
25
|
+
linear-gradient(90deg, var(--decay-grid-line) 1px, transparent 1px),
|
|
26
|
+
linear-gradient(0deg, var(--decay-grid-line) 1px, transparent 1px),
|
|
27
|
+
radial-gradient(circle at 18% 16%, rgba(227, 77, 61, 0.08), transparent 32%),
|
|
28
|
+
var(--decay-paper);
|
|
29
|
+
background-size: 22px 22px, 22px 22px, auto, auto;
|
|
30
|
+
box-shadow: 0 28px 90px var(--decay-shadow);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.theme-dark .decay-lab {
|
|
34
|
+
--decay-ink: #f5f1e3;
|
|
35
|
+
--decay-muted: #aeb8ad;
|
|
36
|
+
--decay-paper: #111611;
|
|
37
|
+
--decay-panel: rgba(27, 35, 29, 0.7);
|
|
38
|
+
--decay-line: rgba(245, 241, 227, 0.17);
|
|
39
|
+
--decay-live: #59dda0;
|
|
40
|
+
--decay-spent: #727883;
|
|
41
|
+
--decay-hot: #ff705c;
|
|
42
|
+
--decay-gold: #f0be45;
|
|
43
|
+
--decay-field-bg: rgba(6, 10, 8, 0.38);
|
|
44
|
+
--decay-control-bg: rgba(21, 28, 24, 0.82);
|
|
45
|
+
--decay-grid-line: rgba(245, 241, 227, 0.055);
|
|
46
|
+
--decay-curve-ghost: rgba(245, 241, 227, 0.15);
|
|
47
|
+
--decay-shadow: rgba(0, 0, 0, 0.36);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.decay-stage,
|
|
51
|
+
.decay-console {
|
|
52
|
+
min-width: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.decay-stage {
|
|
56
|
+
display: grid;
|
|
57
|
+
align-content: start;
|
|
58
|
+
min-height: 520px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.decay-stage-header {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: start;
|
|
64
|
+
justify-content: space-between;
|
|
65
|
+
gap: 1rem;
|
|
66
|
+
margin-bottom: clamp(1rem, 3vw, 2rem);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.decay-kicker,
|
|
70
|
+
.decay-field span,
|
|
71
|
+
.decay-results span {
|
|
72
|
+
margin: 0;
|
|
73
|
+
color: var(--decay-muted);
|
|
74
|
+
font-size: 0.72rem;
|
|
75
|
+
font-weight: 500;
|
|
76
|
+
letter-spacing: 0;
|
|
77
|
+
text-transform: uppercase;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.decay-kicker {
|
|
81
|
+
color: var(--decay-hot);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.decay-stage h2 {
|
|
85
|
+
max-width: 8ch;
|
|
86
|
+
margin: 0.25rem 0 0;
|
|
87
|
+
font-size: 3.6rem;
|
|
88
|
+
font-weight: 700;
|
|
89
|
+
letter-spacing: 0;
|
|
90
|
+
line-height: 0.82;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.decay-seed-button,
|
|
94
|
+
.decay-jumps button {
|
|
95
|
+
min-height: 42px;
|
|
96
|
+
border: 0;
|
|
97
|
+
color: var(--decay-ink);
|
|
98
|
+
background: transparent;
|
|
99
|
+
font-size: 0.72rem;
|
|
100
|
+
font-weight: 650;
|
|
101
|
+
letter-spacing: 0;
|
|
102
|
+
text-transform: uppercase;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.decay-seed-button {
|
|
107
|
+
padding: 0.4rem 0;
|
|
108
|
+
border-bottom: 1px solid var(--decay-hot);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.decay-curve {
|
|
112
|
+
width: 100%;
|
|
113
|
+
height: auto;
|
|
114
|
+
margin-bottom: clamp(0.8rem, 3vw, 1.6rem);
|
|
115
|
+
overflow: visible;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.decay-curve-ghost,
|
|
119
|
+
.decay-curve-line {
|
|
120
|
+
fill: none;
|
|
121
|
+
stroke-linecap: round;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.decay-curve-ghost {
|
|
125
|
+
stroke: var(--decay-curve-ghost);
|
|
126
|
+
stroke-width: 2;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.decay-curve-line {
|
|
130
|
+
stroke: var(--decay-live);
|
|
131
|
+
stroke-width: 4;
|
|
132
|
+
transition: stroke-dashoffset 400ms cubic-bezier(0.25, 1, 0.5, 1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.decay-curve-marker {
|
|
136
|
+
fill: var(--decay-hot);
|
|
137
|
+
stroke: var(--decay-paper);
|
|
138
|
+
stroke-width: 4;
|
|
139
|
+
filter: drop-shadow(0 0 14px rgba(227, 77, 61, 0.45));
|
|
140
|
+
transition:
|
|
141
|
+
cx 400ms cubic-bezier(0.25, 1, 0.5, 1),
|
|
142
|
+
cy 400ms cubic-bezier(0.25, 1, 0.5, 1),
|
|
143
|
+
opacity 400ms ease;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.decay-field-art {
|
|
147
|
+
width: 100%;
|
|
148
|
+
min-height: 280px;
|
|
149
|
+
overflow: visible;
|
|
150
|
+
background:
|
|
151
|
+
radial-gradient(circle at 50% 50%, var(--decay-field-bg), transparent 64%);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.decay-atom {
|
|
155
|
+
fill: var(--decay-live);
|
|
156
|
+
filter: drop-shadow(0 0 10px rgba(33, 159, 115, 0.32));
|
|
157
|
+
opacity: 0.94;
|
|
158
|
+
transform-origin: center;
|
|
159
|
+
transform-box: fill-box;
|
|
160
|
+
animation: atom-materialize 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
161
|
+
animation-delay: var(--delay);
|
|
162
|
+
transition:
|
|
163
|
+
fill 600ms ease,
|
|
164
|
+
opacity 600ms ease,
|
|
165
|
+
filter 600ms ease,
|
|
166
|
+
transform 600ms cubic-bezier(0.25, 1, 0.5, 1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.decay-atom-spent {
|
|
170
|
+
fill: var(--decay-spent);
|
|
171
|
+
filter: blur(4px) drop-shadow(0 0 4px rgba(38, 43, 49, 0.12));
|
|
172
|
+
opacity: 0.3;
|
|
173
|
+
transform: scale(0.82);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.decay-atom-decaying {
|
|
177
|
+
animation: atom-quantum-decay 600ms cubic-bezier(0.25, 1, 0.5, 1) both;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.decay-legend {
|
|
181
|
+
display: flex;
|
|
182
|
+
flex-wrap: wrap;
|
|
183
|
+
gap: 1rem;
|
|
184
|
+
color: var(--decay-muted);
|
|
185
|
+
font-size: 0.78rem;
|
|
186
|
+
font-weight: 500;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.decay-legend span {
|
|
190
|
+
display: inline-flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
gap: 0.45rem;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.decay-dot {
|
|
196
|
+
width: 0.7rem;
|
|
197
|
+
height: 0.7rem;
|
|
198
|
+
border-radius: 50%;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.decay-dot-live {
|
|
202
|
+
background: var(--decay-live);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.decay-dot-spent {
|
|
206
|
+
background: var(--decay-spent);
|
|
207
|
+
opacity: 0.48;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.decay-console {
|
|
211
|
+
display: grid;
|
|
212
|
+
align-content: start;
|
|
213
|
+
gap: clamp(1.25rem, 3vw, 2rem);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.decay-select-block,
|
|
217
|
+
.decay-field {
|
|
218
|
+
display: grid;
|
|
219
|
+
gap: 0.72rem;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.decay-field output,
|
|
223
|
+
.decay-results strong {
|
|
224
|
+
font-weight: 700;
|
|
225
|
+
letter-spacing: 0;
|
|
226
|
+
line-height: 0.9;
|
|
227
|
+
max-width: 100%;
|
|
228
|
+
overflow-wrap: anywhere;
|
|
229
|
+
font-variant-numeric: tabular-nums;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.decay-field output {
|
|
233
|
+
font-size: 2.15rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.decay-select-shell {
|
|
237
|
+
position: relative;
|
|
238
|
+
display: block;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.decay-select-shell::after {
|
|
242
|
+
position: absolute;
|
|
243
|
+
top: 50%;
|
|
244
|
+
right: 0.05rem;
|
|
245
|
+
width: 0.52rem;
|
|
246
|
+
height: 0.52rem;
|
|
247
|
+
border-right: 1px solid var(--decay-ink);
|
|
248
|
+
border-bottom: 1px solid var(--decay-ink);
|
|
249
|
+
content: "";
|
|
250
|
+
pointer-events: none;
|
|
251
|
+
transform: translateY(-70%) rotate(45deg);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.decay-field select {
|
|
255
|
+
width: 100%;
|
|
256
|
+
min-height: 56px;
|
|
257
|
+
padding: 0.15rem 2rem 0.15rem 0;
|
|
258
|
+
border: 0;
|
|
259
|
+
border-bottom: 1px solid var(--decay-line);
|
|
260
|
+
border-radius: 0;
|
|
261
|
+
outline: none;
|
|
262
|
+
appearance: none;
|
|
263
|
+
color: var(--decay-ink);
|
|
264
|
+
background: transparent;
|
|
265
|
+
font-size: 1.7rem;
|
|
266
|
+
font-weight: 700;
|
|
267
|
+
line-height: 1;
|
|
268
|
+
text-overflow: ellipsis;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.decay-field select option {
|
|
272
|
+
color: var(--decay-ink);
|
|
273
|
+
background: var(--decay-paper);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.theme-dark .decay-field select option {
|
|
277
|
+
color: #f5f1e3;
|
|
278
|
+
background: #111611;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.decay-field select::-ms-expand {
|
|
282
|
+
display: none;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.decay-field input[type='range'] {
|
|
286
|
+
width: 100%;
|
|
287
|
+
height: 30px;
|
|
288
|
+
margin: 0;
|
|
289
|
+
appearance: none;
|
|
290
|
+
background: transparent;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.decay-field input[type='range']::-webkit-slider-runnable-track {
|
|
294
|
+
height: 1px;
|
|
295
|
+
background: var(--decay-line);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.decay-field input[type='range']::-webkit-slider-thumb {
|
|
299
|
+
width: 18px;
|
|
300
|
+
height: 18px;
|
|
301
|
+
margin-top: -8.5px;
|
|
302
|
+
border: 3px solid var(--decay-paper);
|
|
303
|
+
border-radius: 50%;
|
|
304
|
+
appearance: none;
|
|
305
|
+
background: var(--decay-hot);
|
|
306
|
+
box-shadow: 0 0 0 1px var(--decay-hot), 0 10px 24px rgba(227, 77, 61, 0.24);
|
|
307
|
+
transition: transform 360ms ease, box-shadow 360ms ease;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.decay-field input[type='range']::-moz-range-track {
|
|
311
|
+
height: 1px;
|
|
312
|
+
background: var(--decay-line);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.decay-field input[type='range']::-moz-range-thumb {
|
|
316
|
+
width: 18px;
|
|
317
|
+
height: 18px;
|
|
318
|
+
border: 3px solid var(--decay-paper);
|
|
319
|
+
border-radius: 50%;
|
|
320
|
+
background: var(--decay-hot);
|
|
321
|
+
box-shadow: 0 0 0 1px var(--decay-hot), 0 10px 24px rgba(227, 77, 61, 0.24);
|
|
322
|
+
transition: transform 360ms ease, box-shadow 360ms ease;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.decay-field input[type='range']:is(:hover, :focus-visible)::-webkit-slider-thumb {
|
|
326
|
+
transform: scale(1.18);
|
|
327
|
+
box-shadow: 0 0 0 1px var(--decay-hot), 0 14px 30px rgba(227, 77, 61, 0.32);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.decay-field input[type='range']:is(:hover, :focus-visible)::-moz-range-thumb {
|
|
331
|
+
transform: scale(1.18);
|
|
332
|
+
box-shadow: 0 0 0 1px var(--decay-hot), 0 14px 30px rgba(227, 77, 61, 0.32);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.decay-use {
|
|
336
|
+
display: grid;
|
|
337
|
+
min-height: 58px;
|
|
338
|
+
place-items: center;
|
|
339
|
+
padding: 0.9rem 1.15rem;
|
|
340
|
+
color: var(--decay-muted);
|
|
341
|
+
background: var(--decay-panel);
|
|
342
|
+
font-size: 0.86rem;
|
|
343
|
+
font-weight: 450;
|
|
344
|
+
line-height: 1.35;
|
|
345
|
+
text-align: center;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.decay-jumps {
|
|
349
|
+
display: flex;
|
|
350
|
+
flex-wrap: wrap;
|
|
351
|
+
gap: 0.8rem;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.decay-jumps button {
|
|
355
|
+
padding: 0;
|
|
356
|
+
color: var(--decay-muted);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.decay-jumps button:hover {
|
|
360
|
+
color: var(--decay-hot);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.decay-results {
|
|
364
|
+
display: grid;
|
|
365
|
+
gap: 1.1rem;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.decay-metric {
|
|
369
|
+
display: grid;
|
|
370
|
+
gap: 0.18rem;
|
|
371
|
+
min-width: 0;
|
|
372
|
+
padding: 0;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.decay-results strong {
|
|
376
|
+
display: block;
|
|
377
|
+
font-size: 1.95rem;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
@keyframes atom-materialize {
|
|
381
|
+
from {
|
|
382
|
+
opacity: 0;
|
|
383
|
+
transform: scale(0.62);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@keyframes atom-quantum-decay {
|
|
388
|
+
0% {
|
|
389
|
+
fill: var(--decay-live);
|
|
390
|
+
opacity: 0.94;
|
|
391
|
+
filter: blur(0) drop-shadow(0 0 12px rgba(33, 159, 115, 0.34));
|
|
392
|
+
transform: scale(1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
38% {
|
|
396
|
+
fill: var(--decay-hot);
|
|
397
|
+
opacity: 1;
|
|
398
|
+
filter: blur(0.6px) drop-shadow(0 0 20px rgba(227, 77, 61, 0.36));
|
|
399
|
+
transform: scale(1.15);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
100% {
|
|
403
|
+
fill: var(--decay-spent);
|
|
404
|
+
opacity: 0.3;
|
|
405
|
+
filter: blur(4px) drop-shadow(0 0 2px rgba(38, 43, 49, 0.1));
|
|
406
|
+
transform: scale(0.82);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@media (min-width: 860px) {
|
|
411
|
+
.decay-lab {
|
|
412
|
+
grid-template-columns: minmax(0, 1.45fr) minmax(310px, 0.72fr);
|
|
413
|
+
align-items: start;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.decay-results {
|
|
417
|
+
margin-top: 0.2rem;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.decay-stage h2 {
|
|
421
|
+
font-size: 8rem;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.decay-field output {
|
|
425
|
+
font-size: 3.05rem;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.decay-field select {
|
|
429
|
+
font-size: 2.2rem;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.decay-results strong {
|
|
433
|
+
font-size: 2.55rem;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { radioactiveDecay } 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 radioactiveDecay.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
16
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { stellarHabitabilityZone } 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 stellarHabitabilityZone.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: 'Habitable Zones Around Main-Sequence Stars: New Estimates',
|
|
6
|
+
url: 'https://arxiv.org/abs/1301.6674',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'Habitable Zones Around Main-Sequence Stars: Dependence on Planetary Mass',
|
|
10
|
+
url: 'https://iopscience.iop.org/article/10.1088/2041-8205/787/2/L29',
|
|
11
|
+
},
|
|
12
|
+
];
|