@jjlmoya/utils-babies 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 +69 -0
- package/src/category/i18n/en.ts +48 -0
- package/src/category/i18n/es.ts +48 -0
- package/src/category/i18n/fr.ts +48 -0
- package/src/category/index.ts +24 -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 +30 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +19 -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 +23 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/baby-feeding-calculator/bibliography.astro +7 -0
- package/src/tool/baby-feeding-calculator/component.astro +210 -0
- package/src/tool/baby-feeding-calculator/i18n/en.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/es.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/fr.ts +162 -0
- package/src/tool/baby-feeding-calculator/index.ts +47 -0
- package/src/tool/baby-feeding-calculator/logic.ts +85 -0
- package/src/tool/baby-feeding-calculator/seo.astro +58 -0
- package/src/tool/baby-feeding-calculator/style.css +329 -0
- package/src/tool/baby-percentile-calculator/bibliography.astro +7 -0
- package/src/tool/baby-percentile-calculator/component.astro +388 -0
- package/src/tool/baby-percentile-calculator/i18n/en.ts +244 -0
- package/src/tool/baby-percentile-calculator/i18n/es.ts +244 -0
- package/src/tool/baby-percentile-calculator/i18n/fr.ts +244 -0
- package/src/tool/baby-percentile-calculator/index.ts +54 -0
- package/src/tool/baby-percentile-calculator/lmsData.ts +80 -0
- package/src/tool/baby-percentile-calculator/logic.ts +85 -0
- package/src/tool/baby-percentile-calculator/seo.astro +36 -0
- package/src/tool/baby-percentile-calculator/style.css +393 -0
- package/src/tool/baby-size-converter/bibliography.astro +7 -0
- package/src/tool/baby-size-converter/component.astro +289 -0
- package/src/tool/baby-size-converter/data.json +11 -0
- package/src/tool/baby-size-converter/i18n/en.ts +203 -0
- package/src/tool/baby-size-converter/i18n/es.ts +203 -0
- package/src/tool/baby-size-converter/i18n/fr.ts +203 -0
- package/src/tool/baby-size-converter/index.ts +53 -0
- package/src/tool/baby-size-converter/logic.ts +44 -0
- package/src/tool/baby-size-converter/seo.astro +36 -0
- package/src/tool/baby-size-converter/style.css +394 -0
- package/src/tool/fertile-days-estimator/bibliography.astro +7 -0
- package/src/tool/fertile-days-estimator/component.astro +265 -0
- package/src/tool/fertile-days-estimator/i18n/en.ts +258 -0
- package/src/tool/fertile-days-estimator/i18n/es.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/fr.ts +258 -0
- package/src/tool/fertile-days-estimator/index.ts +47 -0
- package/src/tool/fertile-days-estimator/logic.ts +58 -0
- package/src/tool/fertile-days-estimator/seo.astro +36 -0
- package/src/tool/fertile-days-estimator/style.css +419 -0
- package/src/tool/pregnancy-calculator/bibliography.astro +7 -0
- package/src/tool/pregnancy-calculator/calculator.ts +41 -0
- package/src/tool/pregnancy-calculator/component.astro +432 -0
- package/src/tool/pregnancy-calculator/i18n/en.ts +315 -0
- package/src/tool/pregnancy-calculator/i18n/es.ts +319 -0
- package/src/tool/pregnancy-calculator/i18n/fr.ts +315 -0
- package/src/tool/pregnancy-calculator/index.ts +55 -0
- package/src/tool/pregnancy-calculator/milestones.ts +153 -0
- package/src/tool/pregnancy-calculator/seo.astro +36 -0
- package/src/tool/pregnancy-calculator/store.ts +60 -0
- package/src/tool/pregnancy-calculator/style.css +807 -0
- package/src/tool/vaccination-calendar/bibliography.astro +7 -0
- package/src/tool/vaccination-calendar/component.astro +286 -0
- package/src/tool/vaccination-calendar/i18n/en.ts +170 -0
- package/src/tool/vaccination-calendar/i18n/es.ts +174 -0
- package/src/tool/vaccination-calendar/i18n/fr.ts +170 -0
- package/src/tool/vaccination-calendar/index.ts +47 -0
- package/src/tool/vaccination-calendar/logic.ts +59 -0
- package/src/tool/vaccination-calendar/seo.astro +36 -0
- package/src/tool/vaccination-calendar/style.css +316 -0
- package/src/tool/vaccination-calendar/vaccinationData.ts +21 -0
- package/src/tools.ts +17 -0
- package/src/types.ts +72 -0
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jjlmoya/utils-babies",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./data": "./src/data.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "astro dev",
|
|
19
|
+
"start": "astro dev",
|
|
20
|
+
"build": "astro build",
|
|
21
|
+
"preview": "astro preview",
|
|
22
|
+
"astro": "astro",
|
|
23
|
+
"lint": "eslint src/ --max-warnings 0 && stylelint \"src/**/*.{css,astro}\"",
|
|
24
|
+
"check": "astro check",
|
|
25
|
+
"type-check": "astro check",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"preversion": "npm run lint && npm run test",
|
|
28
|
+
"postversion": "git push && git push --tags",
|
|
29
|
+
"patch": "npm version patch",
|
|
30
|
+
"minor": "npm version minor",
|
|
31
|
+
"major": "npm version major"
|
|
32
|
+
},
|
|
33
|
+
"lint-staged": {
|
|
34
|
+
"*.{ts,tsx,astro}": [
|
|
35
|
+
"eslint --fix"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@iconify-json/mdi": "^1.2.3",
|
|
40
|
+
"@jjlmoya/utils-shared": "1.0.2",
|
|
41
|
+
"astro": "^6.1.2",
|
|
42
|
+
"astro-icon": "^1.1.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"chart.js": ">=4.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"chart.js": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@astrojs/check": "^0.9.8",
|
|
54
|
+
"chart.js": "^4.4.0",
|
|
55
|
+
"eslint": "^9.39.4",
|
|
56
|
+
"eslint-plugin-astro": "^1.6.0",
|
|
57
|
+
"eslint-plugin-no-comments": "^1.1.10",
|
|
58
|
+
"husky": "^9.1.7",
|
|
59
|
+
"lint-staged": "^16.4.0",
|
|
60
|
+
"postcss-html": "^1.8.1",
|
|
61
|
+
"schema-dts": "^1.1.2",
|
|
62
|
+
"stylelint": "^17.6.0",
|
|
63
|
+
"stylelint-config-standard": "^40.0.0",
|
|
64
|
+
"stylelint-declaration-strict-value": "^1.11.1",
|
|
65
|
+
"typescript": "^5.4.0",
|
|
66
|
+
"typescript-eslint": "^8.58.0",
|
|
67
|
+
"vitest": "^4.1.2"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'babies',
|
|
5
|
+
title: 'Baby Calculators',
|
|
6
|
+
description: 'Tools and calculators for the care and development tracking of your baby.',
|
|
7
|
+
seo: [
|
|
8
|
+
{
|
|
9
|
+
type: 'summary',
|
|
10
|
+
title: 'Available tools',
|
|
11
|
+
items: [
|
|
12
|
+
'Feeding calculator by age and weight',
|
|
13
|
+
'Growth percentile calculator (WHO)',
|
|
14
|
+
'Baby clothing size converter by brand',
|
|
15
|
+
'Fertile days estimator',
|
|
16
|
+
'Personalized vaccination calendar',
|
|
17
|
+
'Pregnancy and gestational weeks calculator',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'title',
|
|
22
|
+
text: "Tracking your baby's development",
|
|
23
|
+
level: 2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'paragraph',
|
|
27
|
+
html: "Baby calculators help you accurately track your child's growth and development. From calculating milk feeds based on age and weight to checking WHO percentiles, these tools are designed to give you useful information at every stage.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: 'Feeding and nutrition',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: 'The feeding calculator estimates the amount of breast milk or formula your baby needs based on their age in days, weeks, or months and their current weight. Calculations follow standard pediatric guidelines to ensure adequate nutrition.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: 'Growth and percentiles',
|
|
41
|
+
level: 2,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: "The percentile calculator uses the World Health Organization (WHO) reference tables to place your baby's weight, height, and BMI within the distribution of the child population. A 50th percentile indicates the baby is at the average.",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'bebes',
|
|
5
|
+
title: 'Calculadoras para Bebés',
|
|
6
|
+
description: 'Herramientas y calculadoras para el cuidado y seguimiento del desarrollo de tu bebé.',
|
|
7
|
+
seo: [
|
|
8
|
+
{
|
|
9
|
+
type: 'summary',
|
|
10
|
+
title: 'Herramientas disponibles',
|
|
11
|
+
items: [
|
|
12
|
+
'Calculadora de alimentación por edad y peso',
|
|
13
|
+
'Calculadora de percentiles de crecimiento (OMS)',
|
|
14
|
+
'Convertidor de tallas de ropa por marca',
|
|
15
|
+
'Estimador de días fértiles',
|
|
16
|
+
'Calendario de vacunación personalizado',
|
|
17
|
+
'Calculadora de embarazo y semanas de gestación',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'title',
|
|
22
|
+
text: 'Seguimiento del desarrollo de tu bebé',
|
|
23
|
+
level: 2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'paragraph',
|
|
27
|
+
html: 'Las calculadoras para bebés te ayudan a realizar un seguimiento preciso del crecimiento y desarrollo de tu hijo. Desde calcular las tomas de leche según la edad y el peso hasta consultar los percentiles de la OMS, estas herramientas están diseñadas para darte información útil en cada etapa.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: 'Alimentación y nutrición',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: 'La calculadora de alimentación estima la cantidad de leche materna o de fórmula que necesita tu bebé según su edad en días, semanas o meses y su peso actual. Los cálculos siguen las pautas pediátricas estándar para garantizar una nutrición adecuada.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: 'Crecimiento y percentiles',
|
|
41
|
+
level: 2,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'La calculadora de percentiles utiliza las tablas de referencia de la Organización Mundial de la Salud (OMS) para situar el peso, la talla y el IMC de tu bebé dentro de la distribución de la población infantil. Un percentil 50 indica que el bebé está en la media.',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'bebes',
|
|
5
|
+
title: 'Calculatrices pour Bébés',
|
|
6
|
+
description: "Outils et calculatrices pour le suivi du développement et des soins de votre bébé.",
|
|
7
|
+
seo: [
|
|
8
|
+
{
|
|
9
|
+
type: 'summary',
|
|
10
|
+
title: 'Outils disponibles',
|
|
11
|
+
items: [
|
|
12
|
+
"Calculatrice d'alimentation par âge et poids",
|
|
13
|
+
'Calculatrice de percentiles de croissance (OMS)',
|
|
14
|
+
'Convertisseur de tailles de vêtements par marque',
|
|
15
|
+
'Estimateur de jours fertiles',
|
|
16
|
+
'Calendrier de vaccination personnalisé',
|
|
17
|
+
"Calculatrice de grossesse et semaines de gestation",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'title',
|
|
22
|
+
text: "Suivi du développement de votre bébé",
|
|
23
|
+
level: 2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'paragraph',
|
|
27
|
+
html: "Les calculatrices pour bébés vous aident à suivre avec précision la croissance et le développement de votre enfant. Du calcul des tétées selon l'âge et le poids à la consultation des percentiles de l'OMS, ces outils sont conçus pour vous fournir des informations utiles à chaque étape.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: 'Alimentation et nutrition',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: "La calculatrice d'alimentation estime la quantité de lait maternel ou de lait maternisé dont votre bébé a besoin selon son âge en jours, semaines ou mois et son poids actuel. Les calculs suivent les directives pédiatriques standard pour garantir une nutrition adéquate.",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: 'Croissance et percentiles',
|
|
41
|
+
level: 2,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: "La calculatrice de percentiles utilise les tableaux de référence de l'Organisation mondiale de la santé (OMS) pour situer le poids, la taille et l'IMC de votre bébé dans la distribution de la population infantile. Un percentile 50 indique que le bébé est dans la moyenne.",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BabiesCategoryEntry } from '../types';
|
|
2
|
+
import { babyFeedingCalculator } from '../tool/baby-feeding-calculator';
|
|
3
|
+
import { babySizeConverter } from '../tool/baby-size-converter';
|
|
4
|
+
import { vaccinationCalendar } from '../tool/vaccination-calendar';
|
|
5
|
+
import { fertileDaysEstimator } from '../tool/fertile-days-estimator';
|
|
6
|
+
import { babyPercentileCalculator } from '../tool/baby-percentile-calculator';
|
|
7
|
+
import { pregnancyCalculator } from '../tool/pregnancy-calculator';
|
|
8
|
+
|
|
9
|
+
export const babiesCategory: BabiesCategoryEntry = {
|
|
10
|
+
icon: 'mdi:baby-carriage',
|
|
11
|
+
tools: [
|
|
12
|
+
babyFeedingCalculator,
|
|
13
|
+
babySizeConverter,
|
|
14
|
+
vaccinationCalendar,
|
|
15
|
+
fertileDaysEstimator,
|
|
16
|
+
babyPercentileCalculator,
|
|
17
|
+
pregnancyCalculator,
|
|
18
|
+
],
|
|
19
|
+
i18n: {
|
|
20
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
21
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
22
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { babiesCategory } 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 babiesCategory.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
15
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface NavItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
href: string;
|
|
6
|
+
isActive?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
categoryTitle: string;
|
|
11
|
+
tools?: NavItem[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { categoryTitle, tools = [] } = Astro.props;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<nav class="preview-nav-sidebar">
|
|
18
|
+
<div class="sidebar-header">
|
|
19
|
+
<h3>{categoryTitle}</h3>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<ul class="tools-list">
|
|
23
|
+
{tools.map((tool) => (
|
|
24
|
+
<li>
|
|
25
|
+
<a
|
|
26
|
+
href={tool.href}
|
|
27
|
+
class:list={['tool-link', { active: tool.isActive }]}
|
|
28
|
+
>
|
|
29
|
+
<span class="tool-title">{tool.title}</span>
|
|
30
|
+
</a>
|
|
31
|
+
</li>
|
|
32
|
+
))}
|
|
33
|
+
</ul>
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.preview-nav-sidebar {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
height: 100%;
|
|
41
|
+
background: var(--bg-surface, #0f172a);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.sidebar-header {
|
|
45
|
+
padding: 2rem 1.5rem 1.5rem;
|
|
46
|
+
border-bottom: 1px solid var(--border-color, #1e293b);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sidebar-header h3 {
|
|
50
|
+
margin: 0;
|
|
51
|
+
font-size: 0.75rem;
|
|
52
|
+
font-weight: 900;
|
|
53
|
+
text-transform: uppercase;
|
|
54
|
+
letter-spacing: 0.1em;
|
|
55
|
+
color: var(--text-muted, #94a3b8);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.tools-list {
|
|
59
|
+
list-style: none;
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding: 0.5rem 0;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: 0.25rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.tools-list li {
|
|
68
|
+
margin: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.tool-link {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
padding: 0.75rem 1.5rem;
|
|
75
|
+
color: var(--text-muted, #94a3b8);
|
|
76
|
+
text-decoration: none;
|
|
77
|
+
font-size: 0.9375rem;
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
|
80
|
+
border-left: 3px solid transparent;
|
|
81
|
+
position: relative;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.tool-link:hover {
|
|
85
|
+
color: var(--text-base, #f1f5f9);
|
|
86
|
+
background: rgba(255, 255, 255, 0.08);
|
|
87
|
+
padding-left: 1.75rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.tool-link.active {
|
|
91
|
+
color: var(--accent, #f43f5e);
|
|
92
|
+
background: rgba(244, 63, 94, 0.15);
|
|
93
|
+
border-left-color: var(--accent, #f43f5e);
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.tool-link.active::before {
|
|
98
|
+
content: '';
|
|
99
|
+
position: absolute;
|
|
100
|
+
left: 0;
|
|
101
|
+
top: 50%;
|
|
102
|
+
transform: translateY(-50%);
|
|
103
|
+
width: 3px;
|
|
104
|
+
height: 24px;
|
|
105
|
+
background: var(--accent, #f43f5e);
|
|
106
|
+
border-radius: 0 2px 2px 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.tool-title {
|
|
110
|
+
display: block;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
text-overflow: ellipsis;
|
|
113
|
+
white-space: nowrap;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
116
|
+
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { KnownLocale } from '../types';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
currentLocale: KnownLocale;
|
|
6
|
+
localeUrls?: Partial<Record<KnownLocale, string>>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { currentLocale, localeUrls = {} } = Astro.props;
|
|
10
|
+
|
|
11
|
+
const otherLocales = Object.entries(localeUrls).filter(([l]) => l !== currentLocale) as [
|
|
12
|
+
KnownLocale,
|
|
13
|
+
string,
|
|
14
|
+
][];
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<nav class="preview-toolbar">
|
|
18
|
+
{
|
|
19
|
+
otherLocales.length > 0 && (
|
|
20
|
+
<div class="locale-group">
|
|
21
|
+
<span class="locale-current">{currentLocale.toUpperCase()}</span>
|
|
22
|
+
{otherLocales.map(([locale, url]) => (
|
|
23
|
+
<a href={url} class="btn-locale">
|
|
24
|
+
{locale.toUpperCase()}
|
|
25
|
+
</a>
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
<button id="theme-toggle" class="btn-theme" aria-label="Toggle theme" title="Toggle theme">
|
|
32
|
+
<span class="theme-icon"></span>
|
|
33
|
+
</button>
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
function initToolbar() {
|
|
38
|
+
const btn = document.getElementById('theme-toggle');
|
|
39
|
+
if (!btn) return;
|
|
40
|
+
|
|
41
|
+
const applyTheme = (theme: string) => {
|
|
42
|
+
document.documentElement.classList.remove('theme-light', 'theme-dark');
|
|
43
|
+
document.documentElement.classList.add(theme);
|
|
44
|
+
localStorage.setItem('theme', theme);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
btn.addEventListener('click', () => {
|
|
48
|
+
const next = document.documentElement.classList.contains('theme-dark')
|
|
49
|
+
? 'theme-light'
|
|
50
|
+
: 'theme-dark';
|
|
51
|
+
applyTheme(next);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
initToolbar();
|
|
56
|
+
document.addEventListener('astro:after-swap', initToolbar);
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<style>
|
|
60
|
+
.preview-toolbar {
|
|
61
|
+
position: fixed;
|
|
62
|
+
top: 1rem;
|
|
63
|
+
right: 1.5rem;
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
z-index: 1000;
|
|
68
|
+
background: var(--bg-surface);
|
|
69
|
+
padding: 0.4rem;
|
|
70
|
+
border-radius: 1rem;
|
|
71
|
+
border: 1px solid var(--border-color);
|
|
72
|
+
backdrop-filter: blur(12px);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.locale-group {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 0.25rem;
|
|
79
|
+
padding-right: 0.5rem;
|
|
80
|
+
border-right: 1px solid var(--border-color);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.locale-current {
|
|
84
|
+
font-size: 0.7rem;
|
|
85
|
+
font-weight: 900;
|
|
86
|
+
color: var(--text-muted);
|
|
87
|
+
padding: 0.4rem 0.6rem;
|
|
88
|
+
letter-spacing: 0.08em;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.btn-locale {
|
|
92
|
+
background: transparent;
|
|
93
|
+
border: 1px solid var(--border-color);
|
|
94
|
+
color: var(--text-main);
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
padding: 0.4rem 0.7rem;
|
|
97
|
+
border-radius: 0.5rem;
|
|
98
|
+
font-weight: 700;
|
|
99
|
+
font-size: 0.7rem;
|
|
100
|
+
text-decoration: none;
|
|
101
|
+
letter-spacing: 0.08em;
|
|
102
|
+
transition: all 0.2s ease;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.btn-locale:hover {
|
|
106
|
+
border-color: var(--accent);
|
|
107
|
+
color: var(--accent);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.btn-theme {
|
|
111
|
+
background: transparent;
|
|
112
|
+
border: none;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
padding: 0.5rem;
|
|
115
|
+
border-radius: 0.6rem;
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
transition: background 0.2s ease;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.btn-theme:hover {
|
|
123
|
+
background: rgba(255, 255, 255, 0.08);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.theme-icon {
|
|
127
|
+
display: block;
|
|
128
|
+
width: 16px;
|
|
129
|
+
height: 16px;
|
|
130
|
+
border-radius: 50%;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
:global(.theme-light) .theme-icon {
|
|
134
|
+
background: #f59e0b;
|
|
135
|
+
box-shadow: 0 0 8px #fbbf24;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:global(.theme-dark) .theme-icon {
|
|
139
|
+
background: #94a3b8;
|
|
140
|
+
box-shadow: inset -4px -2px 0 #1e293b;
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
143
|
+
|
package/src/data.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { babiesCategory } from './category';
|
|
2
|
+
export { ALL_TOOLS } from './tools';
|
|
3
|
+
|
|
4
|
+
export { babyFeedingCalculator } from './tool/baby-feeding-calculator';
|
|
5
|
+
export type { BabyFeedingCalculatorUI } from './tool/baby-feeding-calculator';
|
|
6
|
+
|
|
7
|
+
export { babySizeConverter } from './tool/baby-size-converter';
|
|
8
|
+
export type { BabySizeConverterUI } from './tool/baby-size-converter';
|
|
9
|
+
|
|
10
|
+
export { vaccinationCalendar } from './tool/vaccination-calendar';
|
|
11
|
+
export type { VaccinationCalendarUI } from './tool/vaccination-calendar';
|
|
12
|
+
|
|
13
|
+
export { fertileDaysEstimator } from './tool/fertile-days-estimator';
|
|
14
|
+
export type { FertileDaysEstimatorUI } from './tool/fertile-days-estimator';
|
|
15
|
+
|
|
16
|
+
export { babyPercentileCalculator } from './tool/baby-percentile-calculator';
|
|
17
|
+
export type { BabyPercentileCalculatorUI } from './tool/baby-percentile-calculator';
|
|
18
|
+
|
|
19
|
+
export { pregnancyCalculator } from './tool/pregnancy-calculator';
|
|
20
|
+
export type { PregnancyCalculatorUI } from './tool/pregnancy-calculator';
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
KnownLocale,
|
|
24
|
+
ToolLocaleContent,
|
|
25
|
+
CategoryLocaleContent,
|
|
26
|
+
LocaleMap,
|
|
27
|
+
BabiesToolEntry,
|
|
28
|
+
BabiesCategoryEntry,
|
|
29
|
+
ToolDefinition,
|
|
30
|
+
} from './types';
|
package/src/env.d.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { babiesCategory } from './category';
|
|
2
|
+
export { default as babiesCategorySEO } from './category/seo.astro';
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
KnownLocale,
|
|
6
|
+
FAQItem,
|
|
7
|
+
BibliographyEntry,
|
|
8
|
+
HowToStep,
|
|
9
|
+
ToolLocaleContent,
|
|
10
|
+
CategoryLocaleContent,
|
|
11
|
+
LocaleLoader,
|
|
12
|
+
LocaleMap,
|
|
13
|
+
BabiesToolEntry,
|
|
14
|
+
BabiesCategoryEntry,
|
|
15
|
+
ToolDefinition,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
export { ALL_TOOLS } from './tools';
|
|
19
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
import "@jjlmoya/utils-shared/theme.css";
|
|
3
|
+
import PreviewToolbar from "../components/PreviewToolbar.astro";
|
|
4
|
+
import type { KnownLocale } from "../types";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
currentLocale?: KnownLocale;
|
|
9
|
+
localeUrls?: Partial<Record<KnownLocale, string>>;
|
|
10
|
+
hasSidebar?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { title, currentLocale = "es", localeUrls = {}, hasSidebar = false } = Astro.props;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!doctype html>
|
|
17
|
+
<html lang={currentLocale}>
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="UTF-8" />
|
|
20
|
+
<meta name="viewport" content="width=device-width" />
|
|
21
|
+
<title>{title} · preview</title>
|
|
22
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
23
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
24
|
+
<link
|
|
25
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap"
|
|
26
|
+
rel="stylesheet"
|
|
27
|
+
/>
|
|
28
|
+
|
|
29
|
+
<script is:inline>
|
|
30
|
+
(function () {
|
|
31
|
+
const saved = localStorage.getItem("theme") || "theme-dark";
|
|
32
|
+
document.documentElement.classList.add(saved);
|
|
33
|
+
})();
|
|
34
|
+
</script>
|
|
35
|
+
<slot name="head" />
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<PreviewToolbar currentLocale={currentLocale} localeUrls={localeUrls} />
|
|
39
|
+
<div class:list={["page-wrapper", { "with-sidebar": hasSidebar }]}>
|
|
40
|
+
{
|
|
41
|
+
hasSidebar && (
|
|
42
|
+
<aside class="sidebar-area">
|
|
43
|
+
<slot name="sidebar" />
|
|
44
|
+
</aside>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
<main>
|
|
48
|
+
<slot />
|
|
49
|
+
</main>
|
|
50
|
+
</div>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
|
|
54
|
+
<style is:global>
|
|
55
|
+
:root {
|
|
56
|
+
--accent: #f43f5e;
|
|
57
|
+
--primary-base: #9f1239;
|
|
58
|
+
--cyan: #06b6d4;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.theme-dark,
|
|
62
|
+
.theme-light {
|
|
63
|
+
--text-main: var(--text-base);
|
|
64
|
+
--border-color: var(--border-base);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
*,
|
|
68
|
+
*::before,
|
|
69
|
+
*::after {
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
body {
|
|
74
|
+
background-color: var(--bg-page);
|
|
75
|
+
color: var(--text-base);
|
|
76
|
+
margin: 0;
|
|
77
|
+
min-height: 100vh;
|
|
78
|
+
transition:
|
|
79
|
+
background-color 0.3s ease,
|
|
80
|
+
color 0.3s ease;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main {
|
|
84
|
+
padding: 0 2rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.page-wrapper {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.page-wrapper.with-sidebar {
|
|
93
|
+
display: grid;
|
|
94
|
+
grid-template-columns: 240px 1fr;
|
|
95
|
+
min-height: 100vh;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.sidebar-area {
|
|
99
|
+
position: sticky;
|
|
100
|
+
top: 0;
|
|
101
|
+
height: 100vh;
|
|
102
|
+
overflow-y: auto;
|
|
103
|
+
border-right: 1px solid var(--border-color);
|
|
104
|
+
background: var(--bg-page);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@media (max-width: 768px) {
|
|
108
|
+
.page-wrapper.with-sidebar {
|
|
109
|
+
grid-template-columns: 1fr;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.sidebar-area {
|
|
113
|
+
display: none;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
117
|
+
|