@jjlmoya/utils-pets 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 +60 -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 +13 -0
- package/src/category/seo.astro +14 -0
- package/src/components/PreviewNavSidebar.astro +115 -0
- package/src/components/PreviewToolbar.astro +142 -0
- package/src/data.ts +15 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +20 -0
- package/src/layouts/PreviewLayout.astro +116 -0
- package/src/pages/[locale]/[slug].astro +148 -0
- package/src/pages/[locale].astro +270 -0
- package/src/pages/index.astro +3 -0
- package/src/tests/faq_count.test.ts +23 -0
- package/src/tests/mocks/astro_mock.js +1 -0
- package/src/tests/seo_length.test.ts +54 -0
- package/src/tests/tool_validation.test.ts +139 -0
- package/src/tool/petAge/bibliography.astro +14 -0
- package/src/tool/petAge/component.astro +267 -0
- package/src/tool/petAge/handlers.ts +142 -0
- package/src/tool/petAge/i18n/en.ts +160 -0
- package/src/tool/petAge/i18n/es.ts +160 -0
- package/src/tool/petAge/i18n/fr.ts +160 -0
- package/src/tool/petAge/index.ts +64 -0
- package/src/tool/petAge/logic.ts +59 -0
- package/src/tool/petAge/seo.astro +61 -0
- package/src/tool/petAge/style.css +649 -0
- package/src/tool/petRation/bibliography.astro +14 -0
- package/src/tool/petRation/component.astro +181 -0
- package/src/tool/petRation/handlers.ts +139 -0
- package/src/tool/petRation/i18n/en.ts +161 -0
- package/src/tool/petRation/i18n/es.ts +161 -0
- package/src/tool/petRation/i18n/fr.ts +161 -0
- package/src/tool/petRation/index.ts +63 -0
- package/src/tool/petRation/logic.ts +44 -0
- package/src/tool/petRation/seo.astro +61 -0
- package/src/tool/petRation/style.css +427 -0
- package/src/tools.ts +13 -0
- package/src/types.ts +71 -0
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jjlmoya/utils-pets",
|
|
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
|
+
"devDependencies": {
|
|
45
|
+
"@astrojs/check": "^0.9.8",
|
|
46
|
+
"eslint": "^9.39.4",
|
|
47
|
+
"eslint-plugin-astro": "^1.6.0",
|
|
48
|
+
"eslint-plugin-no-comments": "^1.1.10",
|
|
49
|
+
"husky": "^9.1.7",
|
|
50
|
+
"lint-staged": "^16.4.0",
|
|
51
|
+
"postcss-html": "^1.8.1",
|
|
52
|
+
"schema-dts": "^1.1.2",
|
|
53
|
+
"stylelint": "^17.6.0",
|
|
54
|
+
"stylelint-config-standard": "^40.0.0",
|
|
55
|
+
"stylelint-declaration-strict-value": "^1.11.1",
|
|
56
|
+
"typescript": "^5.4.0",
|
|
57
|
+
"typescript-eslint": "^8.58.0",
|
|
58
|
+
"vitest": "^4.1.2"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'pets',
|
|
5
|
+
title: 'Pet Tools',
|
|
6
|
+
description: 'Free calculators and utilities for pet owners. Find out your dog or cat\'s real age and calculate their daily ration with veterinary precision.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Animal Nutrition and Wellbeing: Science in the Service of Your Pet', level: 2 },
|
|
9
|
+
{
|
|
10
|
+
type: 'paragraph',
|
|
11
|
+
html: 'Caring for a dog, cat, or rabbit is much more than giving them food and affection. It involves understanding their biology, their aging rhythm, and their specific energy needs. In this section we offer <strong>free veterinary calculators</strong> that apply validated scientific formulas to help you make better decisions about your companion\'s health.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: 'paragraph',
|
|
15
|
+
html: 'From the curiosity of knowing how many human years your dog really has, to the precise calculation of the daily ration based on RER (Resting Energy Requirement), our tools transform complex veterinary concepts into clear, actionable answers for the responsible pet owner.',
|
|
16
|
+
},
|
|
17
|
+
{ type: 'title', text: 'Your Pet\'s Real Age: Beyond the "Times 7" Myth', level: 2 },
|
|
18
|
+
{
|
|
19
|
+
type: 'paragraph',
|
|
20
|
+
html: 'The popular belief that one dog year equals seven human years has been debunked by modern veterinary science for decades. The reality is that dogs mature exponentially in their first two years of life, reaching the equivalent of 15 human years at 12 months and 24 years at 24 months. Afterwards, the pace slows and depends enormously on the <strong>size of the animal</strong>: a giant dog over 45 kg can age up to 60% faster than a small breed.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'paragraph',
|
|
24
|
+
html: 'Cats, on the other hand, follow a more linear aging curve: after the first two years of accelerated development, each feline year is approximately equivalent to four human years. Knowing your pet\'s life stage is not just curiosity, it is relevant medical information: it determines when to schedule geriatric checkups, when to adjust the diet, and what health signs you should monitor.',
|
|
25
|
+
},
|
|
26
|
+
{ type: 'title', text: 'Precision Nutrition: Calculate the Exact Daily Ration', level: 2 },
|
|
27
|
+
{
|
|
28
|
+
type: 'paragraph',
|
|
29
|
+
html: 'Obesity affects more than 50% of domestic pets in developed countries and is the direct cause of joint and cardiovascular problems and a reduced life expectancy. The problem is not a lack of love from owners, but the <strong>absence of precise caloric references</strong>. Our daily ration calculator applies the standard veterinary RER formula (70 × weight^0.75) and multiplies it by the corresponding DER factor for your animal\'s species, life stage, and activity level.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'list',
|
|
33
|
+
items: [
|
|
34
|
+
'<strong>Pet Age Calculator:</strong> Converts chronological years to human equivalent based on species and size, with life stage and next veterinary milestone.',
|
|
35
|
+
'<strong>Daily Ration Calculator:</strong> Calculates grams of dry and wet food based on RER and DER for dogs, cats, and rabbits.',
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'tip',
|
|
40
|
+
title: 'Veterinary Advice',
|
|
41
|
+
html: 'These tools provide scientific starting references, but every animal is unique. Always consult your vet to adjust the diet and health plan to your pet\'s individual needs.',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'Investing in knowledge about your pet\'s biology is the most effective form of preventive medicine. A healthy weight and timely veterinary care can add years of quality life to your faithful companion.',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'mascotas',
|
|
5
|
+
title: 'Herramientas para Mascotas',
|
|
6
|
+
description: 'Calculadoras y utilidades gratuitas para propietarios de mascotas. Descubre la edad real de tu perro o gato y calcula su ración diaria con precisión veterinaria.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Nutrición y Bienestar Animal: Ciencia al Servicio de tu Mascota', level: 2 },
|
|
9
|
+
{
|
|
10
|
+
type: 'paragraph',
|
|
11
|
+
html: 'Cuidar a un perro, un gato o un conejo es mucho más que darles comida y cariño. Implica entender su biología, su ritmo de envejecimiento y sus necesidades energéticas concretas. En esta sección ofrecemos <strong>calculadoras veterinarias gratuitas</strong> que aplican fórmulas científicas validadas para ayudarte a tomar mejores decisiones sobre la salud de tu compañero.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: 'paragraph',
|
|
15
|
+
html: 'Desde la curiosidad de saber cuántos años humanos tiene realmente tu perro hasta el cálculo preciso de la ración diaria basado en el RER (Resting Energy Requirement), nuestras herramientas transforman conceptos veterinarios complejos en respuestas claras y accionables para el propietario responsable.',
|
|
16
|
+
},
|
|
17
|
+
{ type: 'title', text: 'Edad Real de tu Mascota: Más Allá del Mito del "Por 7"', level: 2 },
|
|
18
|
+
{
|
|
19
|
+
type: 'paragraph',
|
|
20
|
+
html: 'La popular creencia de que un año de perro equivale a siete años humanos lleva décadas siendo desmentida por la veterinaria moderna. La realidad es que los perros maduran de forma exponencial en sus primeros dos años de vida, alcanzando la equivalencia de 15 años humanos a los 12 meses y de 24 años a los 24 meses. Después, el ritmo se ralentiza y depende enormemente del <strong>tamaño del animal</strong>: un perro gigante de más de 45 kg puede envejecer hasta un 60% más rápido que uno de talla pequeña.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'paragraph',
|
|
24
|
+
html: 'Los gatos, por su parte, siguen una curva de envejecimiento más lineal: tras los dos primeros años de desarrollo acelerado, cada año felino equivale aproximadamente a cuatro años humanos. Conocer la etapa vital de tu mascota no es solo curiosidad, es información médica relevante: determina cuándo programar chequeos geriátricos, cuándo ajustar la dieta y qué señales de salud debes vigilar.',
|
|
25
|
+
},
|
|
26
|
+
{ type: 'title', text: 'Nutrición de Precisión: Calcula la Ración Diaria Exacta', level: 2 },
|
|
27
|
+
{
|
|
28
|
+
type: 'paragraph',
|
|
29
|
+
html: 'La obesidad afecta a más del 50% de las mascotas domésticas en países desarrollados y es la causa directa de problemas articulares, cardiovasculares y una esperanza de vida reducida. El problema no es la falta de amor de los propietarios, sino la <strong>ausencia de referencias calóricas precisas</strong>. Nuestra calculadora de ración diaria aplica la fórmula veterinaria estándar RER (70 × peso^0.75) y la multiplica por el factor DER correspondiente a la especie, etapa vital y nivel de actividad de tu animal.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'list',
|
|
33
|
+
items: [
|
|
34
|
+
'<strong>Calculadora de Edad de Mascotas:</strong> Convierte años cronológicos a equivalente humano según especie y tamaño, con etapa vital y próximo hito veterinario.',
|
|
35
|
+
'<strong>Calculadora de Ración Diaria:</strong> Calcula gramos de pienso seco y húmedo basándose en RER y DER para perros, gatos y conejos.',
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'tip',
|
|
40
|
+
title: 'Consejo Veterinario',
|
|
41
|
+
html: 'Estas herramientas ofrecen referencias científicas de partida, pero cada animal es único. Consulta siempre con tu veterinario para ajustar la dieta y el plan de salud a las necesidades individuales de tu mascota.',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'Invertir en conocimiento sobre la biología de tu mascota es la forma más efectiva de medicina preventiva. Un peso saludable y una atención veterinaria en el momento correcto pueden añadir años de calidad de vida a tu compañero fiel.',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'animaux',
|
|
5
|
+
title: 'Outils pour Animaux de Compagnie',
|
|
6
|
+
description: 'Calculatrices et utilitaires gratuits pour les propriétaires d\'animaux. Découvrez l\'âge réel de votre chien ou chat et calculez leur ration quotidienne avec précision vétérinaire.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Nutrition et Bien-être Animal : La Science au Service de votre Animal', level: 2 },
|
|
9
|
+
{
|
|
10
|
+
type: 'paragraph',
|
|
11
|
+
html: 'Prendre soin d\'un chien, d\'un chat ou d\'un lapin va bien au-delà de leur donner de la nourriture et de l\'affection. Cela implique de comprendre leur biologie, leur rythme de vieillissement et leurs besoins énergétiques spécifiques. Dans cette section, nous proposons des <strong>calculatrices vétérinaires gratuites</strong> qui appliquent des formules scientifiques validées pour vous aider à prendre de meilleures décisions concernant la santé de votre compagnon.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: 'paragraph',
|
|
15
|
+
html: 'De la curiosité de savoir combien d\'années humaines a réellement votre chien, jusqu\'au calcul précis de la ration quotidienne basé sur le RER (Resting Energy Requirement), nos outils transforment des concepts vétérinaires complexes en réponses claires et exploitables pour le propriétaire responsable.',
|
|
16
|
+
},
|
|
17
|
+
{ type: 'title', text: 'L\'Âge Réel de votre Animal : Au-delà du Mythe du "Fois 7"', level: 2 },
|
|
18
|
+
{
|
|
19
|
+
type: 'paragraph',
|
|
20
|
+
html: 'La croyance populaire qu\'une année de chien équivaut à sept années humaines est démystifiée depuis des décennies par la médecine vétérinaire moderne. La réalité est que les chiens mûrissent de façon exponentielle au cours de leurs deux premières années de vie, atteignant l\'équivalent de 15 ans humains à 12 mois et de 24 ans à 24 mois. Ensuite, le rythme ralentit et dépend énormément de la <strong>taille de l\'animal</strong>.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'paragraph',
|
|
24
|
+
html: 'Les chats, quant à eux, suivent une courbe de vieillissement plus linéaire : après les deux premières années de développement accéléré, chaque année féline équivaut approximativement à quatre années humaines. Connaître le stade de vie de votre animal n\'est pas seulement une curiosité, c\'est une information médicale pertinente qui détermine le moment des bilans gériatriques et l\'ajustement du régime alimentaire.',
|
|
25
|
+
},
|
|
26
|
+
{ type: 'title', text: 'Nutrition de Précision : Calculez la Ration Quotidienne Exacte', level: 2 },
|
|
27
|
+
{
|
|
28
|
+
type: 'paragraph',
|
|
29
|
+
html: 'L\'obésité touche plus de 50 % des animaux de compagnie dans les pays développés et est la cause directe de problèmes articulaires, cardiovasculaires et d\'une espérance de vie réduite. Notre calculatrice de ration quotidienne applique la formule vétérinaire standard RER (70 × poids^0.75) multipliée par le facteur DER correspondant à l\'espèce, au stade de vie et au niveau d\'activité de votre animal.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'list',
|
|
33
|
+
items: [
|
|
34
|
+
'<strong>Calculatrice d\'Âge des Animaux :</strong> Convertit les années chronologiques en équivalent humain selon l\'espèce et la taille.',
|
|
35
|
+
'<strong>Calculatrice de Ration Quotidienne :</strong> Calcule les grammes de nourriture sèche et humide basés sur le RER et le DER pour chiens, chats et lapins.',
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'tip',
|
|
40
|
+
title: 'Conseil Vétérinaire',
|
|
41
|
+
html: 'Ces outils fournissent des références scientifiques de départ, mais chaque animal est unique. Consultez toujours votre vétérinaire pour adapter le régime alimentaire et le plan de santé aux besoins individuels de votre animal.',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'Investir dans la connaissance de la biologie de votre animal est la forme la plus efficace de médecine préventive. Un poids sain et des soins vétérinaires en temps voulu peuvent ajouter des années de vie de qualité à votre fidèle compagnon.',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PetCategoryEntry } from '../types';
|
|
2
|
+
import { petAge } from '../tool/petAge';
|
|
3
|
+
import { petRation } from '../tool/petRation';
|
|
4
|
+
|
|
5
|
+
export const petsCategory: PetCategoryEntry = {
|
|
6
|
+
icon: 'mdi:paw',
|
|
7
|
+
tools: [petAge, petRation],
|
|
8
|
+
i18n: {
|
|
9
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
10
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
11
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { petsCategory } 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 petsCategory.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,115 @@
|
|
|
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>
|
|
@@ -0,0 +1,142 @@
|
|
|
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>
|
package/src/data.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { petAge } from './tool/petAge';
|
|
2
|
+
export { petRation } from './tool/petRation';
|
|
3
|
+
export { petsCategory } from './category';
|
|
4
|
+
|
|
5
|
+
export type { PetAgeUI, PetAgeLocaleContent } from './tool/petAge';
|
|
6
|
+
export type { PetRationUI, PetRationLocaleContent } from './tool/petRation';
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
KnownLocale,
|
|
10
|
+
ToolLocaleContent,
|
|
11
|
+
CategoryLocaleContent,
|
|
12
|
+
LocaleMap,
|
|
13
|
+
PetToolEntry,
|
|
14
|
+
PetCategoryEntry,
|
|
15
|
+
} from './types';
|
package/src/env.d.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export * from './tool/petAge';
|
|
2
|
+
export * from './tool/petRation';
|
|
3
|
+
|
|
4
|
+
export { petsCategory } from './category';
|
|
5
|
+
export { default as PetsCategorySEO } from './category/seo.astro';
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
KnownLocale,
|
|
9
|
+
FAQItem,
|
|
10
|
+
BibliographyEntry,
|
|
11
|
+
HowToStep,
|
|
12
|
+
ToolLocaleContent,
|
|
13
|
+
CategoryLocaleContent,
|
|
14
|
+
LocaleLoader,
|
|
15
|
+
LocaleMap,
|
|
16
|
+
PetToolEntry,
|
|
17
|
+
PetCategoryEntry,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
export { ALL_TOOLS } from './tools';
|
|
@@ -0,0 +1,116 @@
|
|
|
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>
|