@jjlmoya/utils-pets 1.20.0 → 1.22.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/index.ts +1 -0
- package/src/tests/i18n_coverage.test.ts +5 -2
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/qa-test-helpers.ts +32 -0
- package/src/tests/qa_bibliography_links.test.ts +40 -0
- package/src/tests/qa_claim_evidence.test.ts +69 -0
- package/src/tests/qa_logic_reference_coverage.test.ts +46 -0
- package/src/tests/qa_runtime_i18n.test.ts +100 -0
- package/src/tests/tool_validation.test.ts +3 -3
- package/src/tool/petCarrierCrateSizePlanner/bibliography.astro +6 -0
- package/src/tool/petCarrierCrateSizePlanner/bibliography.ts +12 -0
- package/src/tool/petCarrierCrateSizePlanner/component.astro +89 -0
- package/src/tool/petCarrierCrateSizePlanner/controller.ts +128 -0
- package/src/tool/petCarrierCrateSizePlanner/dom-views.ts +96 -0
- package/src/tool/petCarrierCrateSizePlanner/entry.ts +27 -0
- package/src/tool/petCarrierCrateSizePlanner/evaluator.ts +14 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/de.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/en.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/es.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/fr.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/id.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/it.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/ja.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/ko.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/nl.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/pl.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/pt.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/ru.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/sv.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/tr.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/i18n/zh.ts +171 -0
- package/src/tool/petCarrierCrateSizePlanner/index.ts +12 -0
- package/src/tool/petCarrierCrateSizePlanner/logic.test.ts +49 -0
- package/src/tool/petCarrierCrateSizePlanner/logic.ts +77 -0
- package/src/tool/petCarrierCrateSizePlanner/pet-carrier-crate-size-planner.css +639 -0
- package/src/tool/petCarrierCrateSizePlanner/seo.astro +14 -0
- package/src/tool/petCarrierCrateSizePlanner/storage.ts +31 -0
- package/src/tool/petCarrierCrateSizePlanner/ui.ts +59 -0
- package/src/tools.ts +3 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
3
|
+
import type { PetCarrierCrateSizePlannerLocaleContent } from '../entry';
|
|
4
|
+
import type { PetCarrierCrateSizePlannerUI } from '../ui';
|
|
5
|
+
|
|
6
|
+
const slug = 'pet-carrier-crate-size-planner';
|
|
7
|
+
const title = '宠物航空箱与出行航空箱尺寸规划器';
|
|
8
|
+
const description = '根据猫狗的6项身体测量数据计算理想的航空箱或出行箱内部尺寸,包含航空托运说明和舒适度检查清单。';
|
|
9
|
+
|
|
10
|
+
const ui: PetCarrierCrateSizePlannerUI = {
|
|
11
|
+
heroEyebrow: '先测量,后选购。出行更安心。',
|
|
12
|
+
journeyHint: '选择宠物种类与出行方式,输入体长等测量数据,即可生成内部空间与尺寸参考框。',
|
|
13
|
+
unitLegend: '显示单位',
|
|
14
|
+
speciesStep: '选择宠物类型',
|
|
15
|
+
metricUnit: '公制',
|
|
16
|
+
imperialUnit: '英制',
|
|
17
|
+
speciesLegend: '谁要出行?',
|
|
18
|
+
speciesDog: '犬类',
|
|
19
|
+
speciesCat: '猫类',
|
|
20
|
+
modeLegend: '主要出行方式',
|
|
21
|
+
modeCar: '车载',
|
|
22
|
+
modeAir: '航空托运',
|
|
23
|
+
measurementsLegend: '输入测量数据',
|
|
24
|
+
noseTailLabel: '鼻尖至尾巴根部长度',
|
|
25
|
+
noseTailHint: '测量时不包含尾巴自身的弯曲长度',
|
|
26
|
+
elbowHeightLabel: '地面至肘关节高度',
|
|
27
|
+
shoulderWidthLabel: '肩宽',
|
|
28
|
+
standingHeightLabel: '站立自然高度',
|
|
29
|
+
beddingLabel: '垫子或垫褥厚度',
|
|
30
|
+
weightLabel: '宠物体重',
|
|
31
|
+
cmUnit: '厘米',
|
|
32
|
+
inchUnit: '英寸',
|
|
33
|
+
kgUnit: '公斤',
|
|
34
|
+
lbUnit: '磅',
|
|
35
|
+
snubNosedLabel: '短吻犬猫品种(扁脸品种)',
|
|
36
|
+
snubNosedHint: '航空运输需要遵循IATA特别调整说明。请咨询兽医师与航空公司。',
|
|
37
|
+
presetLegend: '选择预设模板',
|
|
38
|
+
presetCat: '猫咪',
|
|
39
|
+
presetSmallDog: '小型犬',
|
|
40
|
+
presetMediumDog: '中型犬',
|
|
41
|
+
presetLargeDog: '大型犬',
|
|
42
|
+
resultEyebrow: '推荐测量框架',
|
|
43
|
+
resultTitle: '能够自由转身的活动空间',
|
|
44
|
+
resultDimensionLabel: '建议最小内部起始尺寸',
|
|
45
|
+
lengthLabel: '长度(深度)',
|
|
46
|
+
widthLabel: '宽度',
|
|
47
|
+
heightLabel: '高度',
|
|
48
|
+
petWeightLabel: '宠物体重',
|
|
49
|
+
journeyLabel: '出行方式',
|
|
50
|
+
statusComfort: '基础舒适标准',
|
|
51
|
+
statusAirReview: '航空托运审查事项',
|
|
52
|
+
statusSnub: '短吻品种已调整',
|
|
53
|
+
resultDetail: '购买或出发前,请务必核对航空箱内部真实净空、箱门尺寸、通风口及承运商规定。',
|
|
54
|
+
checklistTitle: '四项身体姿态检查',
|
|
55
|
+
checklistStand: '宠物可以自然站立和端坐,头部或耳朵不触及顶部。',
|
|
56
|
+
checklistTurn: '宠物可以在站立状态下顺畅地原地转身。',
|
|
57
|
+
checklistLie: '宠物可以在垫子上以自然姿势舒适卧躺。',
|
|
58
|
+
checklistAirline: '托运时请确认航空公司限制、通风孔比例、门锁牢固度及防漏垫。',
|
|
59
|
+
invalidInput: '请输入大于零的测量数值与体重。',
|
|
60
|
+
noteTitle: '本工具用于尺寸评估,不代表航班托运许可',
|
|
61
|
+
noteText: '各航空公司、车辆与路线的具体要求有所差异。对于短吻品种或有健康隐患的宠物,请咨询兽医评估出行适宜性。',
|
|
62
|
+
methodTitle: '计算方法',
|
|
63
|
+
methodText: '计算依据IATA(国际航空运输协会)指导标准:最小内部长度为鼻尖至尾根长加上二分之一肘高;宽度为肩宽的2倍;高度为站立高度加上垫子厚度。',
|
|
64
|
+
blueprintLabel: '带有内部尺寸导向线及宠物示意图的航空箱测量图',
|
|
65
|
+
dimensionInside: '内部尺寸',
|
|
66
|
+
checkMark: '确认',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const faq: PetCarrierCrateSizePlannerLocaleContent['faq'] = [
|
|
70
|
+
{
|
|
71
|
+
question: '如何为猫狗测量选购航空箱所需的体形数据?',
|
|
72
|
+
answer: '测量从鼻尖到尾巴根部的长度、地面到肘关节的高度、双肩最宽处的宽度,以及地面到头顶或耳尖最高点的高度。垫子厚度需单独加算。',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
question: '本规划器计算出哪些内部尺寸?',
|
|
76
|
+
answer: '根据体长和肘高估算所需的最小内部长度,根据肩宽估算内部宽度,并计算包含垫子在内的总高度。',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
question: '计算结果是否保证能通过航空公司的托运审核?',
|
|
80
|
+
answer: '不能。航空模式采用IATA参考公式,但各航空公司有各自的具体规定。出行前请务必确认承运商的最新要求。',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
question: '为什么短吻品种在航空托运时需要更大的箱体?',
|
|
84
|
+
answer: 'IATA指南建议为短吻品种提供更充裕的空间,以降低飞行过程中发生过热和呼吸不畅的风险。',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
question: '车载航空箱是不是越大越好?',
|
|
88
|
+
answer: '不是。航空箱应保证宠物能站立、转身和躺下,同时尺寸应适中,以便在车内使用安全带或固定装置牢固安置。',
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const howTo: PetCarrierCrateSizePlannerLocaleContent['howTo'] = [
|
|
93
|
+
{ name: '选择出行方式', text: '选择车载或航空托运,以设置对应的审查标准。' },
|
|
94
|
+
{ name: '输入6项测量数据', text: '在宠物情绪平稳自然站立时测量各项身体尺寸与体重。' },
|
|
95
|
+
{ name: '查看内部尺寸建议', text: '将生成的长度、宽度和高度作为比对市售航空箱的最小内径指标。' },
|
|
96
|
+
{ name: '核对规范与结构', text: '购买前确认门锁安全性、通风口配置以及航空或车辆固定要求。' },
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const seo: PetCarrierCrateSizePlannerLocaleContent['seo'] = [
|
|
100
|
+
{
|
|
101
|
+
type: 'summary',
|
|
102
|
+
title: '宠物 航空箱 与 出行箱 尺寸 挑选 全 指南 详解',
|
|
103
|
+
items: [
|
|
104
|
+
'切勿 仅凭 体重 选箱 务必 根据 身体 尺寸 准确 测量',
|
|
105
|
+
'对比 真实 产品 时 应以 内部 有效 净空 尺寸 为准',
|
|
106
|
+
'提前 检查 箱门 锁扣 及 通风口 分布 安全 情况',
|
|
107
|
+
'航空 托运 需要 特别 准备 并 咨询 兽医师 专业 意见',
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{ type: 'title', text: '如何 规划 航空箱 内部 尺寸 与 规范', level: 2 },
|
|
111
|
+
{
|
|
112
|
+
type: 'paragraph',
|
|
113
|
+
html: '合格的 出行箱 必须 满足 四大 自然 动作 需求 站立 端坐 顺畅 转身 以及 舒适 躺卧 。 本 计算器 根据 体型 数据 为您 推算 所需的 内部 净空 尺寸 。 为 确保 宠物 旅途 安全 舒适 请 仔细 测量 各项 身体 数据 。',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
type: 'paragraph',
|
|
117
|
+
html: '计算 出的 数值 代表 内部 有效 空间 。 请 注意 厚垫子 或 挂式 水碗 会 占用 部分 内部 空间 。 选购 商品 时 建议 比对 实际 最窄 处 的 净空 尺寸 。 如有 疑问 建议 选择 大一号 型号 。',
|
|
118
|
+
},
|
|
119
|
+
{ type: 'title', text: '各项 身体 测量 数据 的 用途 与 意义', level: 2 },
|
|
120
|
+
{
|
|
121
|
+
type: 'table',
|
|
122
|
+
headers: ['测量 项目', '作用 说明', '在 计算 中的 应用'],
|
|
123
|
+
rows: [
|
|
124
|
+
['鼻尖 至 尾根长', '确定 不含 尾巴 的 身体 基础 长度', '长度'],
|
|
125
|
+
['地面 至 肘高', '确保 转身 时 腿部 的 活动 余量', '长度'],
|
|
126
|
+
['肩宽', '保障 体幅 最宽处 的 横向 空间', '宽度'],
|
|
127
|
+
['站立 高度', '防止 头顶 或 耳朵 触及 箱顶', '高度'],
|
|
128
|
+
['垫子 厚度', '铺设 铺垫 后 维持 有效 净空', '高度'],
|
|
129
|
+
['宠物 体重', '用于 核对 航空箱 的 最大 承重 限制', '审查'],
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'paragraph',
|
|
134
|
+
html: '计算 公式 参考 IATA 活体 动物 运输 标准 内部 长度 为 体长 加上 二分之一 肘高 宽度 为 肩宽 的 2倍 高度 为 站立 高度 加上 垫子 厚度 。 科学 的 尺寸 规划 能 有效 缓解 宠物 长途 出行 的 焦虑 感 。',
|
|
135
|
+
},
|
|
136
|
+
{ type: 'title', text: '航空 托运 注意 事项 与 短吻 品种 评估', level: 2 },
|
|
137
|
+
{
|
|
138
|
+
type: 'paragraph',
|
|
139
|
+
html: '仅凭 尺寸 计算 并不 等同于 获得了 航空公司 的 登机 许可 。 机型 季节 以及 通风 条件 等 因素 均需 综合 考虑 。 短吻 犬猫 因 呼吸 结构 特殊 需要 更为 宽敞 的 箱体 与 兽医 评估 。 请在 出行 前 提前 联系 承运 航空公司 核实 最新 的 托运 标准 。',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'tip',
|
|
143
|
+
title: '实际 空间 测试 建议 与 家中 评估',
|
|
144
|
+
html: '在 安静 环境 中 让 宠物 进入 箱内 观察 其 活动 姿态 。 若 显 拥挤 请 选择 更大号 的 型号 。 守护 宠物 的 健康 与 安全 是 出行 的 首要 任务 。',
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const schemas: PetCarrierCrateSizePlannerLocaleContent['schemas'] = [
|
|
149
|
+
{
|
|
150
|
+
'@context': 'https://schema.org',
|
|
151
|
+
'@type': 'SoftwareApplication',
|
|
152
|
+
name: title,
|
|
153
|
+
description,
|
|
154
|
+
applicationCategory: 'LifestyleApplication',
|
|
155
|
+
operatingSystem: 'Web',
|
|
156
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
157
|
+
} as WithContext<SoftwareApplication>,
|
|
158
|
+
{
|
|
159
|
+
'@context': 'https://schema.org',
|
|
160
|
+
'@type': 'FAQPage',
|
|
161
|
+
mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })),
|
|
162
|
+
} as WithContext<FAQPage>,
|
|
163
|
+
{
|
|
164
|
+
'@context': 'https://schema.org',
|
|
165
|
+
'@type': 'HowTo',
|
|
166
|
+
name: title,
|
|
167
|
+
step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })),
|
|
168
|
+
} as WithContext<HowTo>,
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
export const content: PetCarrierCrateSizePlannerLocaleContent = { slug, title, description, ui, seo, faq, bibliography, howTo, schemas };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../types';
|
|
2
|
+
import { petCarrierCrateSizePlanner } from './entry';
|
|
3
|
+
|
|
4
|
+
export * from './entry';
|
|
5
|
+
export { calculateCarrier } from './logic';
|
|
6
|
+
|
|
7
|
+
export const PET_CARRIER_CRATE_SIZE_PLANNER_TOOL: ToolDefinition = {
|
|
8
|
+
entry: petCarrierCrateSizePlanner,
|
|
9
|
+
Component: () => import('./component.astro'),
|
|
10
|
+
SEOComponent: () => import('./seo.astro'),
|
|
11
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
12
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { calculateCarrier, convertLength, convertWeight, formatInputValue } from './logic';
|
|
3
|
+
|
|
4
|
+
const dogInput = {
|
|
5
|
+
species: 'dog' as const,
|
|
6
|
+
mode: 'air' as const,
|
|
7
|
+
unit: 'metric' as const,
|
|
8
|
+
noseToTail: 70,
|
|
9
|
+
elbowHeight: 30,
|
|
10
|
+
shoulderWidth: 22,
|
|
11
|
+
standingHeight: 55,
|
|
12
|
+
bedding: 3,
|
|
13
|
+
weight: 18,
|
|
14
|
+
snubNosed: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('pet carrier crate size logic', () => {
|
|
18
|
+
it('uses the documented internal dimension formulas', () => {
|
|
19
|
+
const result = calculateCarrier(dogInput);
|
|
20
|
+
expect(result.dimensions).toEqual({ lengthCm: 85, widthCm: 44, heightCm: 58, isSnubNosedAdjusted: false });
|
|
21
|
+
expect(result.totalVolumeLitres).toBeCloseTo(216.92);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('adds ten percent for an air journey with a short-nosed pet', () => {
|
|
25
|
+
const result = calculateCarrier({ ...dogInput, snubNosed: true });
|
|
26
|
+
expect(result.dimensions.lengthCm).toBeCloseTo(93.5);
|
|
27
|
+
expect(result.dimensions.isSnubNosedAdjusted).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('converts imperial inputs before calculating', () => {
|
|
31
|
+
const result = calculateCarrier({ ...dogInput, unit: 'imperial', noseToTail: 27.56, elbowHeight: 11.81, shoulderWidth: 8.66, standingHeight: 21.65, bedding: 1.18, weight: 39.68 });
|
|
32
|
+
expect(result.dimensions.lengthCm).toBeCloseTo(85, 0);
|
|
33
|
+
expect(result.dimensions.widthCm).toBeCloseTo(44, 0);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('exposes safe unit conversions and formatting', () => {
|
|
37
|
+
expect(convertLength(100, 'metric', 'imperial')).toBeCloseTo(39.3701);
|
|
38
|
+
expect(convertLength(39.37, 'imperial', 'metric')).toBeCloseTo(100, 0);
|
|
39
|
+
expect(convertWeight(10, 'metric', 'imperial')).toBeCloseTo(22.0462);
|
|
40
|
+
expect(convertWeight(22.0462, 'imperial', 'metric')).toBeCloseTo(10, 0);
|
|
41
|
+
expect(formatInputValue(12)).toBe('12');
|
|
42
|
+
expect(formatInputValue(12.5)).toBe('12.5');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('rejects missing and negative measurements', () => {
|
|
46
|
+
expect(() => calculateCarrier({ ...dogInput, noseToTail: 0 })).toThrow('Missing measurement');
|
|
47
|
+
expect(() => calculateCarrier({ ...dogInput, weight: -1 })).toThrow('Invalid measurement');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export type PetSpecies = 'dog' | 'cat';
|
|
2
|
+
export type TravelMode = 'car' | 'air';
|
|
3
|
+
export type UnitSystem = 'metric' | 'imperial';
|
|
4
|
+
|
|
5
|
+
export interface CarrierInput {
|
|
6
|
+
species: PetSpecies;
|
|
7
|
+
mode: TravelMode;
|
|
8
|
+
unit: UnitSystem;
|
|
9
|
+
noseToTail: number;
|
|
10
|
+
elbowHeight: number;
|
|
11
|
+
shoulderWidth: number;
|
|
12
|
+
standingHeight: number;
|
|
13
|
+
bedding: number;
|
|
14
|
+
weight: number;
|
|
15
|
+
snubNosed: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CarrierDimensions {
|
|
19
|
+
lengthCm: number;
|
|
20
|
+
widthCm: number;
|
|
21
|
+
heightCm: number;
|
|
22
|
+
isSnubNosedAdjusted: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CarrierResult {
|
|
26
|
+
input: CarrierInput;
|
|
27
|
+
dimensions: CarrierDimensions;
|
|
28
|
+
totalVolumeLitres: number;
|
|
29
|
+
airReviewRequired: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const INCH_TO_CM = 2.54;
|
|
33
|
+
const LB_TO_KG = 0.45359237;
|
|
34
|
+
|
|
35
|
+
function toMetric(value: number, unit: UnitSystem, factor: number): number {
|
|
36
|
+
return unit === 'metric' ? value : value * factor;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validateInput(input: CarrierInput): void {
|
|
40
|
+
const measurements = [input.noseToTail, input.elbowHeight, input.shoulderWidth, input.standingHeight, input.bedding, input.weight];
|
|
41
|
+
if (measurements.some((value) => !Number.isFinite(value) || value < 0)) throw new Error('Invalid measurement');
|
|
42
|
+
if (input.noseToTail === 0 || input.elbowHeight === 0 || input.shoulderWidth === 0 || input.standingHeight === 0 || input.weight === 0) {
|
|
43
|
+
throw new Error('Missing measurement');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function calculateCarrier(input: CarrierInput): CarrierResult {
|
|
48
|
+
validateInput(input);
|
|
49
|
+
const noseToTail = toMetric(input.noseToTail, input.unit, INCH_TO_CM);
|
|
50
|
+
const elbowHeight = toMetric(input.elbowHeight, input.unit, INCH_TO_CM);
|
|
51
|
+
const shoulderWidth = toMetric(input.shoulderWidth, input.unit, INCH_TO_CM);
|
|
52
|
+
const standingHeight = toMetric(input.standingHeight, input.unit, INCH_TO_CM);
|
|
53
|
+
const bedding = toMetric(input.bedding, input.unit, INCH_TO_CM);
|
|
54
|
+
const multiplier = input.mode === 'air' && input.snubNosed ? 1.1 : 1;
|
|
55
|
+
const dimensions = {
|
|
56
|
+
lengthCm: (noseToTail + elbowHeight / 2) * multiplier,
|
|
57
|
+
widthCm: shoulderWidth * 2 * multiplier,
|
|
58
|
+
heightCm: (standingHeight + bedding) * multiplier,
|
|
59
|
+
isSnubNosedAdjusted: multiplier > 1,
|
|
60
|
+
};
|
|
61
|
+
const totalVolumeLitres = dimensions.lengthCm * dimensions.widthCm * dimensions.heightCm / 1000;
|
|
62
|
+
return { input, dimensions, totalVolumeLitres, airReviewRequired: input.mode === 'air' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function convertLength(value: number, from: UnitSystem, to: UnitSystem): number {
|
|
66
|
+
if (from === to) return value;
|
|
67
|
+
return from === 'metric' ? value / INCH_TO_CM : value * INCH_TO_CM;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function convertWeight(value: number, from: UnitSystem, to: UnitSystem): number {
|
|
71
|
+
if (from === to) return value;
|
|
72
|
+
return from === 'metric' ? value / LB_TO_KG : value * LB_TO_KG;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function formatInputValue(value: number): string {
|
|
76
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
|
|
77
|
+
}
|