@jjlmoya/utils-science 1.1.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 +64 -0
- package/src/category/i18n/en.ts +97 -0
- package/src/category/i18n/es.ts +97 -0
- package/src/category/i18n/fr.ts +96 -0
- package/src/category/index.ts +17 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +24 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/asteroid-impact/AsteroidImpact.css +799 -0
- package/src/tool/asteroid-impact/bibliography.astro +14 -0
- package/src/tool/asteroid-impact/component.astro +436 -0
- package/src/tool/asteroid-impact/constants.ts +67 -0
- package/src/tool/asteroid-impact/helpers.ts +17 -0
- package/src/tool/asteroid-impact/i18n/en.ts +153 -0
- package/src/tool/asteroid-impact/i18n/es.ts +153 -0
- package/src/tool/asteroid-impact/i18n/fr.ts +153 -0
- package/src/tool/asteroid-impact/index.ts +24 -0
- package/src/tool/asteroid-impact/logic/impactPhysics.ts +86 -0
- package/src/tool/asteroid-impact/script.ts +256 -0
- package/src/tool/asteroid-impact/seo.astro +15 -0
- package/src/tool/asteroid-impact/ui-helpers.ts +56 -0
- package/src/tool/asteroid-impact/ui.ts +69 -0
- package/src/tool/asteroid-impact/utils.ts +17 -0
- package/src/tool/cellular-renewal/CellularRenewal.css +1 -0
- package/src/tool/cellular-renewal/bibliography.astro +14 -0
- package/src/tool/cellular-renewal/component.astro +387 -0
- package/src/tool/cellular-renewal/i18n/en.ts +170 -0
- package/src/tool/cellular-renewal/i18n/es.ts +170 -0
- package/src/tool/cellular-renewal/i18n/fr.ts +170 -0
- package/src/tool/cellular-renewal/index.ts +24 -0
- package/src/tool/cellular-renewal/logic/CellularRenewalEngine.ts +50 -0
- package/src/tool/cellular-renewal/seo.astro +14 -0
- package/src/tool/colony-counter/ColonyCounter.css +473 -0
- package/src/tool/colony-counter/bibliography.astro +14 -0
- package/src/tool/colony-counter/component.astro +358 -0
- package/src/tool/colony-counter/i18n/en.ts +151 -0
- package/src/tool/colony-counter/i18n/es.ts +151 -0
- package/src/tool/colony-counter/i18n/fr.ts +151 -0
- package/src/tool/colony-counter/index.ts +27 -0
- package/src/tool/colony-counter/seo.astro +15 -0
- package/src/tool/microwave-detector/MicrowaveDetector.css +122 -0
- package/src/tool/microwave-detector/bibliography.astro +14 -0
- package/src/tool/microwave-detector/component.astro +650 -0
- package/src/tool/microwave-detector/i18n/en.ts +155 -0
- package/src/tool/microwave-detector/i18n/es.ts +155 -0
- package/src/tool/microwave-detector/i18n/fr.ts +155 -0
- package/src/tool/microwave-detector/index.ts +24 -0
- package/src/tool/microwave-detector/logic/MicrowaveEngine.ts +89 -0
- package/src/tool/microwave-detector/seo.astro +14 -0
- package/src/tool/simulation-probability/SimulationProbability.css +1 -0
- package/src/tool/simulation-probability/bibliography.astro +14 -0
- package/src/tool/simulation-probability/component.astro +348 -0
- package/src/tool/simulation-probability/i18n/en.ts +184 -0
- package/src/tool/simulation-probability/i18n/es.ts +184 -0
- package/src/tool/simulation-probability/i18n/fr.ts +184 -0
- package/src/tool/simulation-probability/index.ts +24 -0
- package/src/tool/simulation-probability/logic/SimulationEngine.ts +42 -0
- package/src/tool/simulation-probability/seo.astro +14 -0
- package/src/tools.ts +15 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { asteroidImpact } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await asteroidImpact.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<SEORenderer content={{ sections: content.seo, locale }} />
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Composition } from "./logic/impactPhysics";
|
|
2
|
+
import { formatSize, getCompositionLabel, getCompositionColor } from "./utils";
|
|
3
|
+
|
|
4
|
+
export function updateVisualState(config: { diameter: number; velocity: number; composition: Composition }, visual: HTMLElement | null, surface: HTMLElement | null) {
|
|
5
|
+
const scale = 1 + (config.diameter / 5000) * 0.5;
|
|
6
|
+
if (visual) {
|
|
7
|
+
visual.style.transform = `scale(${scale})`;
|
|
8
|
+
if (config.composition === "ice") {
|
|
9
|
+
visual.classList.add("ice-type");
|
|
10
|
+
} else {
|
|
11
|
+
visual.classList.remove("ice-type");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (surface) {
|
|
16
|
+
const spinDuration = Math.max(2, 8 - (config.velocity / 10));
|
|
17
|
+
surface.style.animation = `asteroid-spin ${spinDuration}s linear infinite`;
|
|
18
|
+
surface.style.background = getCompositionColor(config.composition);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function updateMaterialButtons(composition: Composition) {
|
|
23
|
+
const matBtns = document.querySelectorAll(".asteroid-material-btn");
|
|
24
|
+
matBtns.forEach((btn) => {
|
|
25
|
+
if (btn.getAttribute("data-type") === composition) {
|
|
26
|
+
btn.classList.add("active");
|
|
27
|
+
} else {
|
|
28
|
+
btn.classList.remove("active");
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function updateControlUI(config: { diameter: number; velocity: number; composition: Composition }) {
|
|
34
|
+
const displaySize = document.getElementById("display-size");
|
|
35
|
+
const displayVel = document.getElementById("display-velocity");
|
|
36
|
+
const sizeInput = document.getElementById("input-size") as HTMLInputElement;
|
|
37
|
+
const velInput = document.getElementById("input-velocity") as HTMLInputElement;
|
|
38
|
+
|
|
39
|
+
if (displaySize) displaySize.textContent = formatSize(config.diameter);
|
|
40
|
+
if (displayVel) displayVel.textContent = `${config.velocity} km/s`;
|
|
41
|
+
if (sizeInput) sizeInput.value = config.diameter.toString();
|
|
42
|
+
if (velInput) velInput.value = config.velocity.toString();
|
|
43
|
+
|
|
44
|
+
const paramSizeVal = document.getElementById("asteroid-param-size-val");
|
|
45
|
+
const paramVelVal = document.getElementById("asteroid-param-vel-val");
|
|
46
|
+
const paramTypeVal = document.getElementById("asteroid-param-type-val");
|
|
47
|
+
|
|
48
|
+
if (paramSizeVal) paramSizeVal.textContent = formatSize(config.diameter);
|
|
49
|
+
if (paramVelVal) paramVelVal.textContent = `${config.velocity} km/s`;
|
|
50
|
+
if (paramTypeVal) paramTypeVal.textContent = getCompositionLabel(config.composition);
|
|
51
|
+
|
|
52
|
+
const visual = document.getElementById("asteroid-visual");
|
|
53
|
+
const surface = document.getElementById("asteroid-surface");
|
|
54
|
+
updateVisualState(config, visual, surface);
|
|
55
|
+
updateMaterialButtons(config.composition);
|
|
56
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Composition } from "./logic/impactPhysics";
|
|
2
|
+
import { formatSize, getTypeLabel, getCompositionColor } from "./helpers";
|
|
3
|
+
|
|
4
|
+
let config = {
|
|
5
|
+
diameter: 10000,
|
|
6
|
+
velocity: 20,
|
|
7
|
+
composition: "rock" as Composition,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function getConfig() {
|
|
11
|
+
return config;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setConfig(newConfig: typeof config) {
|
|
15
|
+
config = newConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function updateMainDisplay() {
|
|
19
|
+
const displaySize = document.getElementById("display-size");
|
|
20
|
+
const displayVel = document.getElementById("display-velocity");
|
|
21
|
+
const sizeInput = document.getElementById("input-size") as HTMLInputElement;
|
|
22
|
+
const velInput = document.getElementById("input-velocity") as HTMLInputElement;
|
|
23
|
+
|
|
24
|
+
if (displaySize) displaySize.textContent = formatSize(config.diameter);
|
|
25
|
+
if (displayVel) displayVel.textContent = `${config.velocity} km/s`;
|
|
26
|
+
if (sizeInput) sizeInput.value = config.diameter.toString();
|
|
27
|
+
if (velInput) velInput.value = config.velocity.toString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function updateParamBadge() {
|
|
31
|
+
const paramSizeVal = document.getElementById("asteroid-param-size-val");
|
|
32
|
+
const paramVelVal = document.getElementById("asteroid-param-vel-val");
|
|
33
|
+
const paramTypeVal = document.getElementById("asteroid-param-type-val");
|
|
34
|
+
|
|
35
|
+
if (paramSizeVal) paramSizeVal.textContent = formatSize(config.diameter);
|
|
36
|
+
if (paramVelVal) paramVelVal.textContent = `${config.velocity} km/s`;
|
|
37
|
+
if (paramTypeVal) paramTypeVal.textContent = getTypeLabel(config.composition);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function updateVisual() {
|
|
41
|
+
const visual = document.getElementById("asteroid-visual");
|
|
42
|
+
const surface = document.getElementById("asteroid-surface");
|
|
43
|
+
const scale = 1 + (config.diameter / 5000) * 0.5;
|
|
44
|
+
|
|
45
|
+
if (visual) {
|
|
46
|
+
visual.style.transform = `scale(${scale})`;
|
|
47
|
+
visual.classList.toggle("ice-type", config.composition === "ice");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (surface) {
|
|
51
|
+
const spinDuration = Math.max(2, 8 - (config.velocity / 10));
|
|
52
|
+
surface.style.animation = `asteroid-spin ${spinDuration}s linear infinite`;
|
|
53
|
+
surface.style.background = getCompositionColor(config.composition);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function updateMaterialButtons() {
|
|
58
|
+
const matBtns = document.querySelectorAll(".asteroid-material-btn");
|
|
59
|
+
matBtns.forEach((btn) => {
|
|
60
|
+
btn.classList.toggle("active", btn.getAttribute("data-type") === config.composition);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function updateUI() {
|
|
65
|
+
updateMainDisplay();
|
|
66
|
+
updateParamBadge();
|
|
67
|
+
updateVisual();
|
|
68
|
+
updateMaterialButtons();
|
|
69
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Composition } from "./logic/impactPhysics";
|
|
2
|
+
|
|
3
|
+
export function formatSize(diameter: number): string {
|
|
4
|
+
return diameter >= 1000 ? `${(diameter / 1000).toFixed(1)} km` : `${diameter}m`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getCompositionLabel(composition: Composition): string {
|
|
8
|
+
if (composition === "ice") return "Hielo";
|
|
9
|
+
if (composition === "iron") return "Hierro";
|
|
10
|
+
return "Roca";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getCompositionColor(composition: Composition): string {
|
|
14
|
+
if (composition === "ice") return "#67e8f9";
|
|
15
|
+
if (composition === "iron") return "#1e293b";
|
|
16
|
+
return "#64748b";
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* Cellular Renewal Styles */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { cellularRenewal } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await cellularRenewal.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <Bibliography links={content.bibliography} />}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
---
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
<div class="cellular-app" id="cellular-app">
|
|
5
|
+
<div class="cellular-card">
|
|
6
|
+
<div class="input-section">
|
|
7
|
+
<div class="input-group">
|
|
8
|
+
<div class="input-label">Cronología Biológica</div>
|
|
9
|
+
<div class="age-display">
|
|
10
|
+
<span id="ageDisplay">25</span>
|
|
11
|
+
<span class="age-unit">años de evolución</span>
|
|
12
|
+
</div>
|
|
13
|
+
<input
|
|
14
|
+
type="range"
|
|
15
|
+
id="ageRange"
|
|
16
|
+
min="1"
|
|
17
|
+
max="105"
|
|
18
|
+
step="1"
|
|
19
|
+
value="25"
|
|
20
|
+
aria-label="Edad"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="results-grid">
|
|
26
|
+
<div class="main-stat">
|
|
27
|
+
<span class="main-stat-label">Tu materia es nueva en un</span>
|
|
28
|
+
<div class="main-stat-value">
|
|
29
|
+
<span id="newStatVal">93</span>
|
|
30
|
+
<span class="percent-sign">%</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="main-stat-sublabel">Renovación Atómica</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="tissue-list">
|
|
36
|
+
<div class="tissue-item">
|
|
37
|
+
<div class="tissue-header">
|
|
38
|
+
<span class="tissue-name">Epidermis y Sangre</span>
|
|
39
|
+
<span class="tissue-percentage" id="skinVal">100%</span>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="bar-container">
|
|
42
|
+
<div class="bar-fill" id="skinBar" style="width: 100%;"></div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="tissue-item">
|
|
47
|
+
<div class="tissue-header">
|
|
48
|
+
<span class="tissue-name">Remodelación Ósea</span>
|
|
49
|
+
<span class="tissue-percentage" id="boneVal">85%</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="bar-container">
|
|
52
|
+
<div class="bar-fill" id="boneBar" style="width: 85%;"></div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="tissue-item">
|
|
57
|
+
<div class="tissue-header">
|
|
58
|
+
<span class="tissue-name">Matriz Orgánica</span>
|
|
59
|
+
<span class="tissue-percentage" id="organVal">60%</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="bar-container">
|
|
62
|
+
<div class="bar-fill" id="organBar" style="width: 60%;"></div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="tissue-item">
|
|
67
|
+
<div class="tissue-header">
|
|
68
|
+
<span class="tissue-name">Células Perennes</span>
|
|
69
|
+
<span class="tissue-percentage" id="brainVal">12%</span>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="bar-container">
|
|
72
|
+
<div class="bar-fill" id="brainBar" style="width: 12%;"></div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<p class="disclaimer">
|
|
79
|
+
Los cálculos se basan en la vida media celular según estudios isotópicos. Mientras que la sangre y la piel se renuevan en semanas, las proteínas del cristalino y gran parte de tu corteza cerebral permanecen desde el desarrollo embrionario. Físicamente, eres una estructura dinámica en flujo constante.
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<style is:inline define:vars={{}}>
|
|
85
|
+
:root {
|
|
86
|
+
--cell-primary: #f43f5e;
|
|
87
|
+
--cell-secondary: #8b5cf6;
|
|
88
|
+
--cell-text: #0f172a;
|
|
89
|
+
--cell-text-muted: #475569;
|
|
90
|
+
--cell-bg: #fff;
|
|
91
|
+
--cell-bg-secondary: #f8fafc;
|
|
92
|
+
--cell-bg-tertiary: #f1f5f9;
|
|
93
|
+
--cell-border: #e2e8f0;
|
|
94
|
+
--cell-bar: linear-gradient(90deg, #f43f5e 0%, #8b5cf6 100%);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.theme-dark {
|
|
98
|
+
--cell-primary: #ff6b7a;
|
|
99
|
+
--cell-secondary: #a78bfa;
|
|
100
|
+
--cell-text: #fff;
|
|
101
|
+
--cell-text-muted: #a0aec0;
|
|
102
|
+
--cell-bg: #0f172a;
|
|
103
|
+
--cell-bg-secondary: #1c1f2e;
|
|
104
|
+
--cell-bg-tertiary: #151720;
|
|
105
|
+
--cell-border: #2d3748;
|
|
106
|
+
--cell-bar: linear-gradient(90deg, #ff6b7a 0%, #a78bfa 100%);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.cellular-app {
|
|
110
|
+
max-width: 850px;
|
|
111
|
+
margin: 0 auto;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.cellular-card {
|
|
115
|
+
background: var(--cell-bg);
|
|
116
|
+
border: 1px solid var(--cell-border);
|
|
117
|
+
border-radius: 24px;
|
|
118
|
+
padding: 2rem;
|
|
119
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.theme-dark .cellular-card {
|
|
123
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.input-section {
|
|
127
|
+
margin-bottom: 3.5rem;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.input-group {
|
|
131
|
+
background: var(--cell-bg-secondary);
|
|
132
|
+
padding: 2rem;
|
|
133
|
+
border-radius: 20px;
|
|
134
|
+
border: 1px solid var(--cell-border);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.input-label {
|
|
138
|
+
font-size: 0.875rem;
|
|
139
|
+
font-weight: 700;
|
|
140
|
+
color: var(--cell-text-muted);
|
|
141
|
+
text-transform: uppercase;
|
|
142
|
+
letter-spacing: 1px;
|
|
143
|
+
margin-bottom: 1rem;
|
|
144
|
+
display: block;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.age-display {
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: baseline;
|
|
150
|
+
gap: 0.75rem;
|
|
151
|
+
margin-bottom: 1.5rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#ageDisplay {
|
|
155
|
+
font-size: 3.5rem;
|
|
156
|
+
font-weight: 950;
|
|
157
|
+
color: var(--cell-primary);
|
|
158
|
+
line-height: 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.age-unit {
|
|
162
|
+
font-size: 0.95rem;
|
|
163
|
+
color: var(--cell-primary);
|
|
164
|
+
font-weight: 500;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
input[type="range"] {
|
|
168
|
+
-webkit-appearance: none;
|
|
169
|
+
appearance: none;
|
|
170
|
+
width: 100%;
|
|
171
|
+
height: 10px;
|
|
172
|
+
background: var(--cell-bg-tertiary);
|
|
173
|
+
border-radius: 8px;
|
|
174
|
+
outline: none;
|
|
175
|
+
border: 1px solid var(--cell-border);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
input[type="range"]::-webkit-slider-thumb {
|
|
179
|
+
-webkit-appearance: none;
|
|
180
|
+
width: 24px;
|
|
181
|
+
height: 24px;
|
|
182
|
+
background: var(--cell-primary);
|
|
183
|
+
border: 3px solid var(--cell-bg);
|
|
184
|
+
border-radius: 50%;
|
|
185
|
+
cursor: pointer;
|
|
186
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
187
|
+
transition: transform 0.1s;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
input[type="range"]::-webkit-slider-thumb:active {
|
|
191
|
+
transform: scale(1.2);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
input[type="range"]::-moz-range-thumb {
|
|
195
|
+
width: 24px;
|
|
196
|
+
height: 24px;
|
|
197
|
+
background: var(--cell-primary);
|
|
198
|
+
border: 3px solid var(--cell-bg);
|
|
199
|
+
border-radius: 50%;
|
|
200
|
+
cursor: pointer;
|
|
201
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
202
|
+
transition: transform 0.1s;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
input[type="range"]::-moz-range-thumb:active {
|
|
206
|
+
transform: scale(1.2);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.results-grid {
|
|
210
|
+
display: grid;
|
|
211
|
+
grid-template-columns: 1fr 1fr;
|
|
212
|
+
gap: 2rem;
|
|
213
|
+
margin-bottom: 2.5rem;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@media (max-width: 640px) {
|
|
217
|
+
.results-grid {
|
|
218
|
+
grid-template-columns: 1fr;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.main-stat {
|
|
223
|
+
background: var(--cell-bg-secondary);
|
|
224
|
+
padding: 2.5rem;
|
|
225
|
+
border-radius: 20px;
|
|
226
|
+
border: 2px solid var(--cell-primary);
|
|
227
|
+
text-align: center;
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
justify-content: center;
|
|
231
|
+
min-height: 280px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.main-stat-label {
|
|
235
|
+
font-size: 0.875rem;
|
|
236
|
+
color: var(--cell-text-muted);
|
|
237
|
+
font-weight: 600;
|
|
238
|
+
margin-bottom: 1.5rem;
|
|
239
|
+
line-height: 1.4;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.main-stat-value {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: baseline;
|
|
245
|
+
justify-content: center;
|
|
246
|
+
gap: 0.25rem;
|
|
247
|
+
margin-bottom: 1.5rem;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#newStatVal {
|
|
251
|
+
font-size: 5rem;
|
|
252
|
+
font-weight: 950;
|
|
253
|
+
color: var(--cell-primary);
|
|
254
|
+
line-height: 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.percent-sign {
|
|
258
|
+
font-size: 2.5rem;
|
|
259
|
+
font-weight: 700;
|
|
260
|
+
color: var(--cell-primary);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.main-stat-sublabel {
|
|
264
|
+
font-size: 0.75rem;
|
|
265
|
+
font-weight: 700;
|
|
266
|
+
color: var(--cell-primary);
|
|
267
|
+
letter-spacing: 2px;
|
|
268
|
+
text-transform: uppercase;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.tissue-list {
|
|
272
|
+
display: flex;
|
|
273
|
+
flex-direction: column;
|
|
274
|
+
gap: 1.25rem;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.tissue-item {
|
|
278
|
+
background: var(--cell-bg-secondary);
|
|
279
|
+
padding: 1.25rem;
|
|
280
|
+
border-radius: 14px;
|
|
281
|
+
border: 1px solid var(--cell-border);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.tissue-header {
|
|
285
|
+
display: flex;
|
|
286
|
+
justify-content: space-between;
|
|
287
|
+
align-items: center;
|
|
288
|
+
margin-bottom: 1rem;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.tissue-name {
|
|
292
|
+
font-size: 0.95rem;
|
|
293
|
+
font-weight: 600;
|
|
294
|
+
color: var(--cell-text);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.tissue-percentage {
|
|
298
|
+
font-size: 0.95rem;
|
|
299
|
+
font-weight: 700;
|
|
300
|
+
color: var(--cell-primary);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.bar-container {
|
|
304
|
+
width: 100%;
|
|
305
|
+
height: 10px;
|
|
306
|
+
background: var(--cell-bg-tertiary);
|
|
307
|
+
border-radius: 5px;
|
|
308
|
+
overflow: hidden;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.bar-fill {
|
|
312
|
+
height: 100%;
|
|
313
|
+
background: var(--cell-bar);
|
|
314
|
+
border-radius: 4px;
|
|
315
|
+
transition: width 0.3s ease;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.disclaimer {
|
|
319
|
+
font-size: 0.875rem;
|
|
320
|
+
color: #78660b;
|
|
321
|
+
line-height: 1.7;
|
|
322
|
+
padding: 1.5rem;
|
|
323
|
+
background-color: #fef9e7;
|
|
324
|
+
border-radius: 12px;
|
|
325
|
+
border: 1px solid #fce8aa;
|
|
326
|
+
margin: 2.5rem auto 0;
|
|
327
|
+
max-width: 600px;
|
|
328
|
+
text-align: center;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.theme-dark .disclaimer {
|
|
332
|
+
color: #78660b;
|
|
333
|
+
background-color: #fef9e7;
|
|
334
|
+
border-color: #fce8aa;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
@media (max-width: 640px) {
|
|
338
|
+
.cellular-card {
|
|
339
|
+
padding: 1.5rem;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#newStatVal {
|
|
343
|
+
font-size: 2.5rem;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.input-group {
|
|
347
|
+
padding: 1rem;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
</style>
|
|
351
|
+
|
|
352
|
+
<script>
|
|
353
|
+
import { CellularRenewalEngine } from "./logic/CellularRenewalEngine";
|
|
354
|
+
|
|
355
|
+
const ageInput = document.getElementById("ageRange") as HTMLInputElement;
|
|
356
|
+
const ageDisplay = document.getElementById("ageDisplay");
|
|
357
|
+
const newStatVal = document.getElementById("newStatVal");
|
|
358
|
+
const skinVal = document.getElementById("skinVal");
|
|
359
|
+
const skinBar = document.getElementById("skinBar");
|
|
360
|
+
const boneVal = document.getElementById("boneVal");
|
|
361
|
+
const boneBar = document.getElementById("boneBar");
|
|
362
|
+
const organVal = document.getElementById("organVal");
|
|
363
|
+
const organBar = document.getElementById("organBar");
|
|
364
|
+
const brainVal = document.getElementById("brainVal");
|
|
365
|
+
const brainBar = document.getElementById("brainBar");
|
|
366
|
+
|
|
367
|
+
function updateBar(valEl: HTMLElement | null, barEl: HTMLElement | null, percent: number) {
|
|
368
|
+
if (valEl) valEl.textContent = `${percent}%`;
|
|
369
|
+
if (barEl) barEl.style.width = `${percent}%`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function update() {
|
|
373
|
+
const age = parseInt(ageInput.value);
|
|
374
|
+
if (ageDisplay) ageDisplay.textContent = age.toString();
|
|
375
|
+
|
|
376
|
+
const result = CellularRenewalEngine.calculate(age);
|
|
377
|
+
if (newStatVal) newStatVal.textContent = result.total.toString();
|
|
378
|
+
|
|
379
|
+
updateBar(skinVal, skinBar, result.skin);
|
|
380
|
+
updateBar(boneVal, boneBar, result.bone);
|
|
381
|
+
updateBar(organVal, organBar, result.organ);
|
|
382
|
+
updateBar(brainVal, brainBar, result.brain);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
ageInput?.addEventListener("input", update);
|
|
386
|
+
update();
|
|
387
|
+
</script>
|