@jjlmoya/utils-pets 1.19.0 → 1.20.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 +2 -1
- package/src/entries.ts +4 -1
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +5 -5
- package/src/tool/petWaterIntake/bibliography.astro +6 -0
- package/src/tool/petWaterIntake/bibliography.ts +16 -0
- package/src/tool/petWaterIntake/component.astro +103 -0
- package/src/tool/petWaterIntake/controller.ts +47 -0
- package/src/tool/petWaterIntake/dom-views.ts +54 -0
- package/src/tool/petWaterIntake/entry.ts +33 -0
- package/src/tool/petWaterIntake/i18n/de.ts +205 -0
- package/src/tool/petWaterIntake/i18n/en.ts +205 -0
- package/src/tool/petWaterIntake/i18n/es.ts +205 -0
- package/src/tool/petWaterIntake/i18n/fr.ts +205 -0
- package/src/tool/petWaterIntake/i18n/id.ts +237 -0
- package/src/tool/petWaterIntake/i18n/it.ts +237 -0
- package/src/tool/petWaterIntake/i18n/ja.ts +237 -0
- package/src/tool/petWaterIntake/i18n/ko.ts +237 -0
- package/src/tool/petWaterIntake/i18n/nl.ts +237 -0
- package/src/tool/petWaterIntake/i18n/pl.ts +237 -0
- package/src/tool/petWaterIntake/i18n/pt.ts +237 -0
- package/src/tool/petWaterIntake/i18n/ru.ts +237 -0
- package/src/tool/petWaterIntake/i18n/sv.ts +237 -0
- package/src/tool/petWaterIntake/i18n/tr.ts +237 -0
- package/src/tool/petWaterIntake/i18n/zh.ts +237 -0
- package/src/tool/petWaterIntake/index.ts +11 -0
- package/src/tool/petWaterIntake/logic.test.ts +52 -0
- package/src/tool/petWaterIntake/logic.ts +84 -0
- package/src/tool/petWaterIntake/pet-water-intake.css +144 -0
- package/src/tool/petWaterIntake/seo.astro +15 -0
- package/src/tool/petWaterIntake/ui.ts +55 -0
- package/src/tools.ts +3 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
3
|
+
import type { PetWaterIntakeLocaleContent, PetWaterIntakeUI } from '../entry';
|
|
4
|
+
|
|
5
|
+
const slug = 'dog-cat-water-intake-calculator';
|
|
6
|
+
const title = '猫狗每日饮水量计算器';
|
|
7
|
+
const description = '根据体重、饮食类型、活动量和环境温度,估算猫或狗每日所需的总水量及水碗饮水量。';
|
|
8
|
+
|
|
9
|
+
const ui: PetWaterIntakeUI = {
|
|
10
|
+
"journeyHint": "选择宠物类型,输入体重,然后设置当天的食物与气候条件。",
|
|
11
|
+
"instantHint": "每一个选项都会实时更新水位。结果仅供参考,不能替代兽医诊断。",
|
|
12
|
+
"unitLegend": "单位",
|
|
13
|
+
"metricUnit": "公制",
|
|
14
|
+
"imperialUnit": "英制",
|
|
15
|
+
"speciesLegend": "这是谁的水碗?",
|
|
16
|
+
"speciesDog": "狗",
|
|
17
|
+
"speciesCat": "猫",
|
|
18
|
+
"speciesDogMeta": "维持量参考 每公斤 60 mL",
|
|
19
|
+
"speciesCatMeta": "维持量参考 每公斤 40 mL",
|
|
20
|
+
"weightLabel": "体重",
|
|
21
|
+
"weightHint": "请填写宠物目前的健康体重。",
|
|
22
|
+
"weightKg": "kg",
|
|
23
|
+
"weightLb": "lb",
|
|
24
|
+
"dietLegend": "食物能提供多少水分?",
|
|
25
|
+
"dietDry": "以干粮为主",
|
|
26
|
+
"dietDryMeta": "绝大部分水分需要从水碗中饮用",
|
|
27
|
+
"dietMixed": "干湿混合喂养",
|
|
28
|
+
"dietMixedMeta": "一部分水分来自日常餐食",
|
|
29
|
+
"dietWet": "以湿粮(罐头)为主",
|
|
30
|
+
"dietWetMeta": "餐食提供了绝大部分水分",
|
|
31
|
+
"activityLegend": "今天的活动量",
|
|
32
|
+
"activityCalm": "安静 / 休息",
|
|
33
|
+
"activityTypical": "适中 / 正常",
|
|
34
|
+
"activityActive": "活跃 / 运动量大",
|
|
35
|
+
"heatLegend": "气候与温度",
|
|
36
|
+
"heatNormal": "舒适 / 正常",
|
|
37
|
+
"heatWarm": "偏热",
|
|
38
|
+
"heatHot": "炎热 / 酷暑",
|
|
39
|
+
"resultEyebrow": "水位与饮水量估算",
|
|
40
|
+
"totalRangeLabel": "全天所需总水量(包含所有来源)",
|
|
41
|
+
"drinkingRangeLabel": "水碗建议饮水量参考",
|
|
42
|
+
"totalRangeHint": "包含食物水分在内的全天总水分需求区间。",
|
|
43
|
+
"drinkingRangeHint": "估算的从水碗中直接饮用的水量。",
|
|
44
|
+
"statusEveryday": "日常标准状态",
|
|
45
|
+
"statusActive": "高活动量日",
|
|
46
|
+
"statusWarm": "偏热天气",
|
|
47
|
+
"statusDemanding": "高消耗环境",
|
|
48
|
+
"statusDetailEveryday": "基础维持模式,未叠加额外的炎热或剧烈运动消耗。",
|
|
49
|
+
"statusDetailActive": "运动会增加水分流失。请保持充足新鲜凉水供应。",
|
|
50
|
+
"statusDetailWarm": "高温会导致喘气散热增多。遮阴与通风比具体数值更重要。",
|
|
51
|
+
"statusDetailDemanding": "高运动量与高温叠加。请密切观察宠物状态。",
|
|
52
|
+
"resultEmpty": "请输入有效的体重以显示饮水量估计。",
|
|
53
|
+
"sceneAria": "所选宠物的储水容器",
|
|
54
|
+
"sceneTotal": "总水量",
|
|
55
|
+
"sceneBowl": "每日建议范围中值",
|
|
56
|
+
"methodTitle": "计算方法",
|
|
57
|
+
"methodText": "基于 AAHA 官方指引(犬 60 mL/kg,猫 40 mL/kg),并结合食物水分与气温系数调整。",
|
|
58
|
+
"noteTitle": "水碗不能替代兽医诊断",
|
|
59
|
+
"noteText": "请保持清洁新鲜的水随时可用。若发现饮水量或排尿量突然异常变化,请及时就医。",
|
|
60
|
+
"invalidWeight": "请输入有效的体重数值。",
|
|
61
|
+
"weightRange": "体重必须在 0.5 kg 至 100 kg 之间。"
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const faq: PetWaterIntakeLocaleContent['faq'] = [
|
|
65
|
+
{
|
|
66
|
+
"question": "猫狗每天需要喝多少水?",
|
|
67
|
+
"answer": "根据 AAHA 的兽医通用指引,犬类的基础维持水需求量约为每天每公斤体重 60 mL,猫类约为 40 mL。实际饮水量会根据饮食、气温和运动量有所浮动。"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"question": "喂湿粮会减少宠物在水碗里的饮水量吗?",
|
|
71
|
+
"answer": "是的。湿粮罐头的含水量通常在 75% 至 82% 之间,仅凭用餐就能满足很大一部分水分需求。因此吃湿粮的宠物去水碗喝水的次数变少是完全正常的。"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"question": "什么时候需要警惕宠物的饮水量变化?",
|
|
75
|
+
"answer": "如果您发现宠物突然大量饮水(多饮症)或完全拒绝喝水,应及时咨询兽医。这可能是糖尿病、慢性肾脏病等疾病的早期信号。"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"question": "高温天气和运动会使需水量翻倍吗?",
|
|
79
|
+
"answer": "是的。高温和剧烈运动会增加哈气散热造成的水分流失。请务必提供凉爽的阴凉处和充足的新鲜饮用水。"
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const howTo: PetWaterIntakeLocaleContent['howTo'] = [
|
|
84
|
+
{
|
|
85
|
+
"name": "选择物种与体重",
|
|
86
|
+
"text": "选择猫或狗,并输入当前的健康体重(千克或磅)。"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "设定主要饮食类型",
|
|
90
|
+
"text": "选择干粮、混合饮食或罐头湿粮,以计算食物所含的水分。"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "调整活动量与气温",
|
|
94
|
+
"text": "设定今天的运动强度和环境温度,折算哈气散热带来的水分消耗。"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "查看水分建议区间",
|
|
98
|
+
"text": "查看每日所需总水量以及预计需要从水碗中补充的水量。"
|
|
99
|
+
}
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const seo: PetWaterIntakeLocaleContent['seo'] = [
|
|
103
|
+
{
|
|
104
|
+
"type": "summary",
|
|
105
|
+
"title": "猫狗每日饮水量与健康管理指南",
|
|
106
|
+
"items": [
|
|
107
|
+
"基础维持量:狗约每天 60 mL/kg,猫约每天 40 mL/kg(根据 AAHA 官方标准)。",
|
|
108
|
+
"饮食影响:湿粮含水量高达 70-80%,会显著减少去水碗喝水的频率。",
|
|
109
|
+
"高温与运动:哈气散热会加速水分流失,需要额外补充凉爽的新鲜饮用水。",
|
|
110
|
+
"健康预警:饮水量突然暴增或骤减需及时就兽医检查。",
|
|
111
|
+
"充足供应:建议在家里多个安静地点放置干净的水碗。"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"type": "title",
|
|
116
|
+
"text": "猫狗每日饮水需求量计算与健康管理",
|
|
117
|
+
"level": 2
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"type": "paragraph",
|
|
121
|
+
"html": "充 足 的 水 分 对 猫 狗 的 新 陈 代 谢 、 消 化 吸 收 、 关 节 润 滑 和 肾 脏 健 康 至 关 重 要 。 成 年 猫 狗 体 重 的 6 0 % 至 7 0 % 由 水 分 组 成 , 即 使 是 轻 微 的 缺 水 也 会 干 扰 关 键 的 生 理 功 能 。 美 国 动 物 医 院 协 会 ( A A H A ) 等 权 威 文 献 指 出 , 犬 类 的 基 础 维 持 水 需 求 量 约 为 每 天 每 公 斤 体 重 6 0 m L , 猫 类 约 为 4 0 m L 。"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"type": "paragraph",
|
|
125
|
+
"html": "但 需 要 注 意 的 是 , 这 一 数 值 代 表 的 是 ' 每 日 总 水 分 需 求 ' , 而 不 等 同 于 宠 物 必 须 从 水 碗 里 喝 下 去 的 水 量 。 宠 物 的 每 日 总 水 分 来 源 包 括 : 直 接 饮 水 、 食 物 中 自 带 的 水 分 以 及 细 胞 呼 吸 产 生 的 代 谢 水 。 理 解 这 一 区 别 可 以 避 免 主 人 在 喂 食 湿 粮 时 因 宠 物 去 水 碗 喝 水 少 而 产 生 不 必 要 的 焦 虑 。"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"type": "title",
|
|
129
|
+
"text": "总水分需求与水碗饮水量的区别",
|
|
130
|
+
"level": 2
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"type": "paragraph",
|
|
134
|
+
"html": "普 通 干 粮 的 含 水 量 仅 为 6 % 至 1 0 % , 因 此 吃 干 粮 的 宠 物 绝 大 部 分 水 分 都 需 要 靠 去 水 碗 喝 水 来 补 充 。 而 罐 头 湿 粮 的 含 水 量 高 达 7 5 % 至 8 2 % 左 右 , 吃 湿 粮 的 宠 物 可 以 在 用 餐 时 直 接 获 取 大 部 分 水 分 , 导 致 去 水 碗 喝 水 的 次 数 非 常 少 , 这 在 生 理 上 是 完 全 正 常 的 。"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"type": "table",
|
|
138
|
+
"headers": [
|
|
139
|
+
"影响因素",
|
|
140
|
+
"对水分需求的影响",
|
|
141
|
+
"核心注意事项"
|
|
142
|
+
],
|
|
143
|
+
"rows": [
|
|
144
|
+
[
|
|
145
|
+
"物种",
|
|
146
|
+
"决定基础代谢水率",
|
|
147
|
+
"狗约 60 mL/kg,猫约 40 mL/kg"
|
|
148
|
+
],
|
|
149
|
+
[
|
|
150
|
+
"体重",
|
|
151
|
+
"按比例决定总量",
|
|
152
|
+
"建议按理想健康体重计算"
|
|
153
|
+
],
|
|
154
|
+
[
|
|
155
|
+
"食物含水量",
|
|
156
|
+
"决定食物提供的水分比例",
|
|
157
|
+
"湿粮能极大减少水碗饮水需求"
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
"气温与运动",
|
|
161
|
+
"增加哈气蒸散流失",
|
|
162
|
+
"高温天气需大幅增加凉水供应"
|
|
163
|
+
]
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"type": "title",
|
|
168
|
+
"text": "日常饮水管理建议",
|
|
169
|
+
"level": 2
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"type": "paragraph",
|
|
173
|
+
"html": "宠 物 主 人 应 致 力 于 随 时 提 供 干 净 新 鲜 的 饮 用 水 。 对 于 天 生 渴 感 较 弱 的 猫 咪 , 使 用 流 动 宠 物 饮 水 机 或 宽 浅 的 猫 水 碗 有 助 于 鼓 励 自 主 饮 水 。"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"type": "card",
|
|
177
|
+
"title": "干粮喂养",
|
|
178
|
+
"icon": "mdi:food-drumstick",
|
|
179
|
+
"html": "吃 干 粮 的 宠 物 高 度 依 赖 水 碗 饮 水 。 请 保 证 水 碗 清 洁 , 并 放 置 在 安 静 易 到 达 的 位 置 。"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"type": "card",
|
|
183
|
+
"title": "湿粮喂养",
|
|
184
|
+
"icon": "mdi:food-drumstick",
|
|
185
|
+
"html": "湿 粮 能 提 供 丰 富 的 水 分 。 即 使 宠 物 去 水 碗 喝 水 较 少 也 是 正 常 的 , 但 仍 需 保 证 随 时 有 水 。"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"type": "card",
|
|
189
|
+
"title": "炎热与运动",
|
|
190
|
+
"icon": "mdi:weather-sunny",
|
|
191
|
+
"html": "高 温 运 动 时 哈 气 散 热 剧 烈 。 请 提 供 遮 阴 休 息 区 并 及 时 补 水 , 防 止 中 暑 脱 水 。"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"type": "tip",
|
|
195
|
+
"title": "需要就医的异常信号",
|
|
196
|
+
"html": "饮 水 量 突 然 暴 增 ( 多 饮 ) 或 完 全 拒 绝 喝 水 都 是 重 要 的 临 床 信 号 。 如 伴 有 呕 吐 、 精 神 萎 靡 , 请 立 即 送 医 。"
|
|
197
|
+
}
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const schemas: PetWaterIntakeLocaleContent['schemas'] = [
|
|
201
|
+
{
|
|
202
|
+
'@context': 'https://schema.org',
|
|
203
|
+
'@type': 'SoftwareApplication',
|
|
204
|
+
name: title,
|
|
205
|
+
description,
|
|
206
|
+
applicationCategory: 'HealthApplication',
|
|
207
|
+
operatingSystem: 'Web',
|
|
208
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
209
|
+
} as WithContext<SoftwareApplication>,
|
|
210
|
+
{
|
|
211
|
+
'@context': 'https://schema.org',
|
|
212
|
+
'@type': 'FAQPage',
|
|
213
|
+
mainEntity: faq.map((item) => ({
|
|
214
|
+
'@type': 'Question',
|
|
215
|
+
name: item.question,
|
|
216
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
217
|
+
})),
|
|
218
|
+
} as WithContext<FAQPage>,
|
|
219
|
+
{
|
|
220
|
+
'@context': 'https://schema.org',
|
|
221
|
+
'@type': 'HowTo',
|
|
222
|
+
name: title,
|
|
223
|
+
step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })),
|
|
224
|
+
} as WithContext<HowTo>,
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
export const content: PetWaterIntakeLocaleContent = {
|
|
228
|
+
slug,
|
|
229
|
+
title,
|
|
230
|
+
description,
|
|
231
|
+
ui,
|
|
232
|
+
seo,
|
|
233
|
+
faq,
|
|
234
|
+
bibliography,
|
|
235
|
+
howTo,
|
|
236
|
+
schemas,
|
|
237
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../types';
|
|
2
|
+
import { petWaterIntake } from './entry';
|
|
3
|
+
|
|
4
|
+
export * from './entry';
|
|
5
|
+
|
|
6
|
+
export const PET_WATER_INTAKE_TOOL: ToolDefinition = {
|
|
7
|
+
entry: petWaterIntake,
|
|
8
|
+
Component: () => import('./component.astro'),
|
|
9
|
+
SEOComponent: () => import('./seo.astro'),
|
|
10
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
11
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { calculatePetWaterIntake, kgToLb, lbToKg } from './logic';
|
|
3
|
+
|
|
4
|
+
describe('pet water intake logic', () => {
|
|
5
|
+
it('calculates baseline water for 10kg dog on dry diet', () => {
|
|
6
|
+
const res = calculatePetWaterIntake({
|
|
7
|
+
species: 'dog',
|
|
8
|
+
weightKg: 10,
|
|
9
|
+
diet: 'dry',
|
|
10
|
+
activity: 'calm',
|
|
11
|
+
heat: 'normal',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
expect(res.totalMlMin).toBe(540);
|
|
15
|
+
expect(res.totalMlMax).toBe(660);
|
|
16
|
+
expect(res.bowlMlMin).toBe(459);
|
|
17
|
+
expect(res.bowlMlMax).toBe(627);
|
|
18
|
+
expect(res.statusKey).toBe('statusEveryday');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('calculates baseline water for 4kg cat on wet diet', () => {
|
|
22
|
+
const res = calculatePetWaterIntake({
|
|
23
|
+
species: 'cat',
|
|
24
|
+
weightKg: 4,
|
|
25
|
+
diet: 'wet',
|
|
26
|
+
activity: 'calm',
|
|
27
|
+
heat: 'normal',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(res.totalMlMin).toBe(144);
|
|
31
|
+
expect(res.totalMlMax).toBe(176);
|
|
32
|
+
expect(res.bowlMlMin).toBe(29);
|
|
33
|
+
expect(res.bowlMlMax).toBe(70);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('handles active + hot demanding status', () => {
|
|
37
|
+
const res = calculatePetWaterIntake({
|
|
38
|
+
species: 'dog',
|
|
39
|
+
weightKg: 20,
|
|
40
|
+
diet: 'mixed',
|
|
41
|
+
activity: 'active',
|
|
42
|
+
heat: 'hot',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(res.statusKey).toBe('statusDemanding');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('converts weight units correctly', () => {
|
|
49
|
+
expect(kgToLb(10)).toBe(22.0);
|
|
50
|
+
expect(lbToKg(22)).toBe(10.0);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export type PetSpecies = 'dog' | 'cat';
|
|
2
|
+
export type UnitSystem = 'metric' | 'imperial';
|
|
3
|
+
export type DietType = 'dry' | 'mixed' | 'wet';
|
|
4
|
+
export type ActivityLevel = 'calm' | 'typical' | 'active';
|
|
5
|
+
export type HeatContext = 'normal' | 'warm' | 'hot';
|
|
6
|
+
|
|
7
|
+
export interface WaterCalculationInput {
|
|
8
|
+
species: PetSpecies;
|
|
9
|
+
weightKg: number;
|
|
10
|
+
diet: DietType;
|
|
11
|
+
activity: ActivityLevel;
|
|
12
|
+
heat: HeatContext;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WaterCalculationResult {
|
|
16
|
+
species: PetSpecies;
|
|
17
|
+
weightKg: number;
|
|
18
|
+
totalMlMin: number;
|
|
19
|
+
totalMlMax: number;
|
|
20
|
+
bowlMlMin: number;
|
|
21
|
+
bowlMlMax: number;
|
|
22
|
+
statusKey: 'statusEveryday' | 'statusActive' | 'statusWarm' | 'statusDemanding';
|
|
23
|
+
statusDetailKey: 'statusDetailEveryday' | 'statusDetailActive' | 'statusDetailWarm' | 'statusDetailDemanding';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const BASE_ML_PER_KG: Record<PetSpecies, number> = {
|
|
27
|
+
dog: 60,
|
|
28
|
+
cat: 40,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const DIET_BOWL_SHARE: Record<DietType, { min: number; max: number }> = {
|
|
32
|
+
dry: { min: 0.85, max: 0.95 },
|
|
33
|
+
mixed: { min: 0.50, max: 0.70 },
|
|
34
|
+
wet: { min: 0.20, max: 0.40 },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const ACTIVITY_MULTIPLIER: Record<ActivityLevel, number> = {
|
|
38
|
+
calm: 1.0,
|
|
39
|
+
typical: 1.1,
|
|
40
|
+
active: 1.3,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const HEAT_MULTIPLIER: Record<HeatContext, number> = {
|
|
44
|
+
normal: 1.0,
|
|
45
|
+
warm: 1.2,
|
|
46
|
+
hot: 1.4,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function getWaterStatusKeys(activity: ActivityLevel, heat: HeatContext) {
|
|
50
|
+
if (activity === 'active' && heat === 'hot') {
|
|
51
|
+
return { statusKey: 'statusDemanding' as const, statusDetailKey: 'statusDetailDemanding' as const };
|
|
52
|
+
}
|
|
53
|
+
if (activity === 'active') {
|
|
54
|
+
return { statusKey: 'statusActive' as const, statusDetailKey: 'statusDetailActive' as const };
|
|
55
|
+
}
|
|
56
|
+
if (heat === 'hot' || heat === 'warm') {
|
|
57
|
+
return { statusKey: 'statusWarm' as const, statusDetailKey: 'statusDetailWarm' as const };
|
|
58
|
+
}
|
|
59
|
+
return { statusKey: 'statusEveryday' as const, statusDetailKey: 'statusDetailEveryday' as const };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function calculatePetWaterIntake(input: WaterCalculationInput): WaterCalculationResult {
|
|
63
|
+
const { species, weightKg, diet, activity, heat } = input;
|
|
64
|
+
const baseMl = weightKg * BASE_ML_PER_KG[species];
|
|
65
|
+
const totalCenter = baseMl * ACTIVITY_MULTIPLIER[activity] * HEAT_MULTIPLIER[heat];
|
|
66
|
+
const totalMlMin = Math.round(totalCenter * 0.9);
|
|
67
|
+
const totalMlMax = Math.round(totalCenter * 1.1);
|
|
68
|
+
|
|
69
|
+
const bowlShare = DIET_BOWL_SHARE[diet];
|
|
70
|
+
const bowlMlMin = Math.round(totalMlMin * bowlShare.min);
|
|
71
|
+
const bowlMlMax = Math.round(totalMlMax * bowlShare.max);
|
|
72
|
+
|
|
73
|
+
const { statusKey, statusDetailKey } = getWaterStatusKeys(activity, heat);
|
|
74
|
+
|
|
75
|
+
return { species, weightKg, totalMlMin, totalMlMax, bowlMlMin, bowlMlMax, statusKey, statusDetailKey };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function kgToLb(kg: number): number {
|
|
79
|
+
return Math.round(kg * 2.20462 * 10) / 10;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function lbToKg(lb: number): number {
|
|
83
|
+
return Math.round((lb / 2.20462) * 10) / 10;
|
|
84
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
.pet-water-intake {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: 1.5rem;
|
|
5
|
+
padding: 1rem;
|
|
6
|
+
max-width: 1000px;
|
|
7
|
+
margin: 0 auto;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.pet-water-journey {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: 0.75rem;
|
|
14
|
+
padding: 1rem;
|
|
15
|
+
background-color: var(--color-surface-2, #f3f4f6);
|
|
16
|
+
border-radius: 0.5rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.pet-water-journey-mark {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
width: 1.5rem;
|
|
24
|
+
height: 1.5rem;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
background: var(--color-primary, #2563eb);
|
|
27
|
+
color: #fff;
|
|
28
|
+
font-weight: bold;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.pet-water-workbench {
|
|
32
|
+
display: grid;
|
|
33
|
+
grid-template-columns: 1fr 1fr;
|
|
34
|
+
gap: 1.5rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@media (max-width: 768px) {
|
|
38
|
+
.pet-water-workbench {
|
|
39
|
+
grid-template-columns: 1fr;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.pet-water-controls {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 1rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.pet-water-control {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: 0.5rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.pet-water-label {
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
font-size: 0.9rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.pet-water-input, .pet-water-select {
|
|
61
|
+
padding: 0.5rem 0.75rem;
|
|
62
|
+
border: 1rem solid var(--color-border, #d1d5db);
|
|
63
|
+
border-radius: 0.375rem;
|
|
64
|
+
font-size: 1rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.pet-water-radio-group {
|
|
68
|
+
display: flex;
|
|
69
|
+
gap: 0.5rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.pet-water-radio-group label {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: 0.25rem;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.pet-water-scene {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
background: var(--color-surface-2, #f9fafb);
|
|
85
|
+
border-radius: 0.5rem;
|
|
86
|
+
padding: 1.5rem;
|
|
87
|
+
min-height: 250px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.pet-water-reservoir {
|
|
91
|
+
width: 120px;
|
|
92
|
+
height: 160px;
|
|
93
|
+
border: 4px solid var(--color-primary, #2563eb);
|
|
94
|
+
border-radius: 0 0 1rem 1rem;
|
|
95
|
+
position: relative;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
background: #e0f2fe;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.pet-water-fill {
|
|
101
|
+
position: absolute;
|
|
102
|
+
bottom: 0;
|
|
103
|
+
left: 0;
|
|
104
|
+
right: 0;
|
|
105
|
+
background: #0284c7;
|
|
106
|
+
transition: height 0.3s ease;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.pet-water-badge {
|
|
110
|
+
margin-top: 1rem;
|
|
111
|
+
text-align: center;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.pet-water-badge-value {
|
|
115
|
+
font-size: 1.25rem;
|
|
116
|
+
font-weight: bold;
|
|
117
|
+
color: #0284c7;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.pet-water-result-card {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
gap: 1rem;
|
|
124
|
+
padding: 1.5rem;
|
|
125
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
126
|
+
border-radius: 0.5rem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.pet-water-metrics {
|
|
130
|
+
display: grid;
|
|
131
|
+
grid-template-columns: 1fr 1fr;
|
|
132
|
+
gap: 1rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.pet-water-metric {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.pet-water-metric-value {
|
|
141
|
+
font-size: 1.5rem;
|
|
142
|
+
font-weight: bold;
|
|
143
|
+
color: var(--color-primary, #2563eb);
|
|
144
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { petWaterIntake } from './entry';
|
|
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 petWaterIntake.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface PetWaterIntakeUI {
|
|
2
|
+
[key: string]: string;
|
|
3
|
+
journeyHint: string;
|
|
4
|
+
instantHint: string;
|
|
5
|
+
unitLegend: string;
|
|
6
|
+
metricUnit: string;
|
|
7
|
+
imperialUnit: string;
|
|
8
|
+
speciesLegend: string;
|
|
9
|
+
speciesDog: string;
|
|
10
|
+
speciesCat: string;
|
|
11
|
+
speciesDogMeta: string;
|
|
12
|
+
speciesCatMeta: string;
|
|
13
|
+
weightLabel: string;
|
|
14
|
+
weightHint: string;
|
|
15
|
+
weightKg: string;
|
|
16
|
+
weightLb: string;
|
|
17
|
+
dietLegend: string;
|
|
18
|
+
dietDry: string;
|
|
19
|
+
dietDryMeta: string;
|
|
20
|
+
dietMixed: string;
|
|
21
|
+
dietMixedMeta: string;
|
|
22
|
+
dietWet: string;
|
|
23
|
+
dietWetMeta: string;
|
|
24
|
+
activityLegend: string;
|
|
25
|
+
activityCalm: string;
|
|
26
|
+
activityTypical: string;
|
|
27
|
+
activityActive: string;
|
|
28
|
+
heatLegend: string;
|
|
29
|
+
heatNormal: string;
|
|
30
|
+
heatWarm: string;
|
|
31
|
+
heatHot: string;
|
|
32
|
+
resultEyebrow: string;
|
|
33
|
+
totalRangeLabel: string;
|
|
34
|
+
drinkingRangeLabel: string;
|
|
35
|
+
totalRangeHint: string;
|
|
36
|
+
drinkingRangeHint: string;
|
|
37
|
+
statusEveryday: string;
|
|
38
|
+
statusActive: string;
|
|
39
|
+
statusWarm: string;
|
|
40
|
+
statusDemanding: string;
|
|
41
|
+
statusDetailEveryday: string;
|
|
42
|
+
statusDetailActive: string;
|
|
43
|
+
statusDetailWarm: string;
|
|
44
|
+
statusDetailDemanding: string;
|
|
45
|
+
resultEmpty: string;
|
|
46
|
+
sceneAria: string;
|
|
47
|
+
sceneTotal: string;
|
|
48
|
+
sceneBowl: string;
|
|
49
|
+
methodTitle: string;
|
|
50
|
+
methodText: string;
|
|
51
|
+
noteTitle: string;
|
|
52
|
+
noteText: string;
|
|
53
|
+
invalidWeight: string;
|
|
54
|
+
weightRange: string;
|
|
55
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { PET_AGE_TOOL } from './tool/petAge';
|
|
|
3
3
|
import { PET_RATION_TOOL } from './tool/petRation';
|
|
4
4
|
import { PET_GESTATION_TOOL } from './tool/petGestation';
|
|
5
5
|
import { PET_TOXICITY_TOOL } from './tool/petToxicity';
|
|
6
|
+
import { PET_WATER_INTAKE_TOOL } from './tool/petWaterIntake';
|
|
6
7
|
import type { ToolDefinition } from './types';
|
|
7
8
|
|
|
8
9
|
export const ALL_TOOLS: ToolDefinition[] = [
|
|
@@ -10,6 +11,7 @@ export const ALL_TOOLS: ToolDefinition[] = [
|
|
|
10
11
|
PET_RATION_TOOL,
|
|
11
12
|
PET_GESTATION_TOOL,
|
|
12
13
|
PET_TOXICITY_TOOL,
|
|
14
|
+
PET_WATER_INTAKE_TOOL,
|
|
13
15
|
];
|
|
14
16
|
|
|
15
17
|
export {
|
|
@@ -17,4 +19,5 @@ export {
|
|
|
17
19
|
PET_RATION_TOOL,
|
|
18
20
|
PET_GESTATION_TOOL,
|
|
19
21
|
PET_TOXICITY_TOOL,
|
|
22
|
+
PET_WATER_INTAKE_TOOL,
|
|
20
23
|
};
|