@jjlmoya/utils-pets 1.16.0 → 1.18.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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/data.ts +2 -0
  4. package/src/entries.ts +2 -1
  5. package/src/index.ts +3 -2
  6. package/src/tests/faq_count.test.ts +1 -1
  7. package/src/tests/locale_completeness.test.ts +1 -2
  8. package/src/tests/seo_translation_completeness.test.ts +69 -0
  9. package/src/tests/spanish_leakage.test.ts +25 -3
  10. package/src/tests/tool_validation.test.ts +4 -4
  11. package/src/tool/petAge/index.ts +1 -0
  12. package/src/tool/petGestation/bibliography.astro +6 -0
  13. package/src/tool/petGestation/bibliography.ts +12 -0
  14. package/src/tool/petGestation/component.astro +68 -0
  15. package/src/tool/petGestation/controller.ts +69 -0
  16. package/src/tool/petGestation/dom-views.ts +60 -0
  17. package/src/tool/petGestation/entry.ts +31 -0
  18. package/src/tool/petGestation/evaluator.ts +28 -0
  19. package/src/tool/petGestation/i18n/de.ts +209 -0
  20. package/src/tool/petGestation/i18n/en.ts +197 -0
  21. package/src/tool/petGestation/i18n/es.ts +197 -0
  22. package/src/tool/petGestation/i18n/fr.ts +209 -0
  23. package/src/tool/petGestation/i18n/id.ts +209 -0
  24. package/src/tool/petGestation/i18n/it.ts +209 -0
  25. package/src/tool/petGestation/i18n/ja.ts +209 -0
  26. package/src/tool/petGestation/i18n/ko.ts +209 -0
  27. package/src/tool/petGestation/i18n/nl.ts +209 -0
  28. package/src/tool/petGestation/i18n/pl.ts +209 -0
  29. package/src/tool/petGestation/i18n/pt.ts +209 -0
  30. package/src/tool/petGestation/i18n/ru.ts +209 -0
  31. package/src/tool/petGestation/i18n/sv.ts +209 -0
  32. package/src/tool/petGestation/i18n/tr.ts +209 -0
  33. package/src/tool/petGestation/i18n/zh.ts +209 -0
  34. package/src/tool/petGestation/index.ts +11 -0
  35. package/src/tool/petGestation/logic.test.ts +44 -0
  36. package/src/tool/petGestation/logic.ts +103 -0
  37. package/src/tool/petGestation/pet-gestation-calculator.css +401 -0
  38. package/src/tool/petGestation/seo.astro +15 -0
  39. package/src/tool/petGestation/storage.ts +25 -0
  40. package/src/tool/petGestation/ui.ts +42 -0
  41. package/src/tool/petRation/component.astro +1 -1
  42. package/src/tool/petRation/handlers.ts +1 -1
  43. package/src/tool/petRation/index.ts +1 -0
  44. package/src/tools.ts +3 -1
  45. package/src/types.ts +1 -1
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ calculateGestation,
4
+ formatDateInput,
5
+ getDateOffset,
6
+ GESTATION_PROFILES,
7
+ } from './logic';
8
+
9
+ const today = new Date(2026, 7, 24);
10
+
11
+ describe('pet gestation logic', () => {
12
+ it('keeps documented species profiles', () => {
13
+ expect(GESTATION_PROFILES.dog).toEqual({ minDays: 58, typicalDays: 63, maxDays: 72 });
14
+ expect(GESTATION_PROFILES.cat.typicalDays).toBe(65);
15
+ expect(GESTATION_PROFILES.rabbit.typicalDays).toBe(31);
16
+ expect(GESTATION_PROFILES.ferret.typicalDays).toBe(42);
17
+ });
18
+
19
+ it('calculates a dog window and progress before the due date', () => {
20
+ const result = calculateGestation({ species: 'dog', matingDate: '2026-07-01', today });
21
+ expect(result.windowStart).toBe('2026-08-28');
22
+ expect(result.dueDate).toBe('2026-09-02');
23
+ expect(result.windowEnd).toBe('2026-09-11');
24
+ expect(result.elapsedDays).toBe(54);
25
+ expect(result.remainingDays).toBe(9);
26
+ expect(result.state).toBe('waiting');
27
+ expect(result.progress).toBeCloseTo(54 / 63);
28
+ });
29
+
30
+ it('reports future, due window, and late states', () => {
31
+ expect(calculateGestation({ species: 'cat', matingDate: '2026-09-01', today }).state).toBe('future');
32
+ expect(calculateGestation({ species: 'cat', matingDate: '2026-06-20', today }).state).toBe('window');
33
+ expect(calculateGestation({ species: 'rabbit', matingDate: '2026-06-01', today }).state).toBe('late');
34
+ });
35
+
36
+ it('normalizes local dates for presets', () => {
37
+ expect(formatDateInput(today)).toBe('2026-08-24');
38
+ expect(getDateOffset(-7, today)).toBe('2026-08-17');
39
+ });
40
+
41
+ it('rejects impossible calendar dates', () => {
42
+ expect(() => calculateGestation({ species: 'dog', matingDate: '2026-02-30', today })).toThrow('Invalid date');
43
+ });
44
+ });
@@ -0,0 +1,103 @@
1
+ export type GestationSpecies = 'dog' | 'cat' | 'rabbit' | 'ferret';
2
+
3
+ export interface GestationProfile {
4
+ minDays: number;
5
+ typicalDays: number;
6
+ maxDays: number;
7
+ }
8
+
9
+ export interface GestationInput {
10
+ species: GestationSpecies;
11
+ matingDate: string;
12
+ today?: Date;
13
+ }
14
+
15
+ export interface GestationResult {
16
+ species: GestationSpecies;
17
+ matingDate: string;
18
+ windowStart: string;
19
+ dueDate: string;
20
+ windowEnd: string;
21
+ elapsedDays: number;
22
+ remainingDays: number;
23
+ progress: number;
24
+ state: 'future' | 'waiting' | 'window' | 'late';
25
+ }
26
+
27
+ export const GESTATION_PROFILES: Record<GestationSpecies, GestationProfile> = {
28
+ dog: { minDays: 58, typicalDays: 63, maxDays: 72 },
29
+ cat: { minDays: 64, typicalDays: 65, maxDays: 66 },
30
+ rabbit: { minDays: 31, typicalDays: 31, maxDays: 31 },
31
+ ferret: { minDays: 42, typicalDays: 42, maxDays: 42 },
32
+ };
33
+
34
+ function parseDateParts(value: string): [number, number, number] {
35
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
36
+ if (!match) throw new Error('Invalid date');
37
+ const year = Number(match[1]);
38
+ const month = Number(match[2]);
39
+ const day = Number(match[3]);
40
+ return [year, month, day];
41
+ }
42
+
43
+ function parseDate(value: string): Date {
44
+ const [year, month, day] = parseDateParts(value);
45
+ const date = new Date(Date.UTC(year, month - 1, day));
46
+ const valid = Number.isFinite(year) && Number.isFinite(month) && Number.isFinite(day)
47
+ && date.getUTCFullYear() === year
48
+ && date.getUTCMonth() === month - 1
49
+ && date.getUTCDate() === day;
50
+ if (!valid) throw new Error('Invalid date');
51
+ return date;
52
+ }
53
+
54
+ function toISODate(date: Date): string {
55
+ return date.toISOString().slice(0, 10);
56
+ }
57
+
58
+ function addDays(date: Date, days: number): Date {
59
+ const result = new Date(date.getTime());
60
+ result.setUTCDate(result.getUTCDate() + days);
61
+ return result;
62
+ }
63
+
64
+ function startOfUTCDate(date: Date): Date {
65
+ return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
66
+ }
67
+
68
+ function differenceInDays(later: Date, earlier: Date): number {
69
+ return Math.round((later.getTime() - earlier.getTime()) / 86400000);
70
+ }
71
+
72
+ function getState(elapsedDays: number, profile: GestationProfile): GestationResult['state'] {
73
+ if (elapsedDays < 0) return 'future';
74
+ if (elapsedDays < profile.minDays) return 'waiting';
75
+ if (elapsedDays <= profile.maxDays) return 'window';
76
+ return 'late';
77
+ }
78
+
79
+ export function calculateGestation({ species, matingDate, today = new Date() }: GestationInput): GestationResult {
80
+ const start = parseDate(matingDate);
81
+ const profile = GESTATION_PROFILES[species];
82
+ const todayDate = startOfUTCDate(today);
83
+ const elapsedDays = differenceInDays(todayDate, start);
84
+ return {
85
+ species,
86
+ matingDate,
87
+ windowStart: toISODate(addDays(start, profile.minDays)),
88
+ dueDate: toISODate(addDays(start, profile.typicalDays)),
89
+ windowEnd: toISODate(addDays(start, profile.maxDays)),
90
+ elapsedDays,
91
+ remainingDays: profile.typicalDays - elapsedDays,
92
+ progress: Math.max(0, Math.min(1, elapsedDays / profile.typicalDays)),
93
+ state: getState(elapsedDays, profile),
94
+ };
95
+ }
96
+
97
+ export function formatDateInput(date: Date): string {
98
+ return toISODate(startOfUTCDate(date));
99
+ }
100
+
101
+ export function getDateOffset(offset: number, today = new Date()): string {
102
+ return formatDateInput(addDays(startOfUTCDate(today), offset));
103
+ }
@@ -0,0 +1,401 @@
1
+ :root {
2
+ --n-ink: #1d2a27;
3
+ --n-muted: #61726c;
4
+ --n-paper: #f3efe6;
5
+ --n-card: #fffdf8;
6
+ --n-line: #d5ddd4;
7
+ --n-moss: #3d755f;
8
+ --n-moss-deep: #245243;
9
+ --n-sun: #db9c3f;
10
+ --n-sky: #a9cbc0;
11
+ --n-alert: #ae5848;
12
+ }
13
+
14
+ .theme-dark {
15
+ --n-ink: #eff6f0;
16
+ --n-muted: #b7c9bf;
17
+ --n-paper: #17211e;
18
+ --n-card: #22302b;
19
+ --n-line: #3a4a42;
20
+ --n-moss: #8bc4a8;
21
+ --n-moss-deep: #c7ead4;
22
+ --n-sun: #f0c36b;
23
+ --n-sky: #4b7165;
24
+ --n-alert: #ef907d;
25
+ }
26
+
27
+ .pet-gestation-tool {
28
+ --n-shadow: 0 1.5rem 4rem rgba(30, 58, 47, 0.12);
29
+
30
+ color: var(--n-ink);
31
+ background: var(--n-paper);
32
+ border: 1px solid var(--n-line);
33
+ border-radius: 2rem;
34
+ padding: clamp(1rem, 3vw, 2rem);
35
+ box-shadow: var(--n-shadow);
36
+ }
37
+
38
+ .gestation-tool-intro {
39
+ display: grid;
40
+ grid-template-columns: minmax(0, 1.2fr) minmax(12rem, 0.8fr);
41
+ gap: 1rem;
42
+ align-items: end;
43
+ margin-bottom: 1.5rem;
44
+ }
45
+
46
+ .gestation-journey,
47
+ .gestation-instant-hint,
48
+ .gestation-source-note,
49
+ .gestation-note,
50
+ .gestation-date-block > span,
51
+ .gestation-status-detail {
52
+ color: var(--n-muted);
53
+ }
54
+
55
+ .gestation-journey,
56
+ .gestation-instant-hint {
57
+ margin: 0;
58
+ }
59
+
60
+ .gestation-journey {
61
+ max-width: 42rem;
62
+ font-size: 1.05rem;
63
+ }
64
+
65
+ .gestation-instant-hint {
66
+ padding: 0.75rem 1rem;
67
+ border-left: 3px solid var(--n-sun);
68
+ font-size: 0.88rem;
69
+ }
70
+
71
+ .gestation-form {
72
+ display: grid;
73
+ gap: 1.25rem;
74
+ }
75
+
76
+ .gestation-species-fieldset {
77
+ border: 0;
78
+ padding: 0;
79
+ margin: 0;
80
+ }
81
+
82
+ .gestation-species-fieldset legend,
83
+ .gestation-date-block label {
84
+ display: block;
85
+ font-weight: 800;
86
+ margin-bottom: 0.65rem;
87
+ }
88
+
89
+ .gestation-species-grid {
90
+ display: grid;
91
+ grid-template-columns: repeat(4, minmax(0, 1fr));
92
+ gap: 0.65rem;
93
+ }
94
+
95
+ .gestation-species-grid button,
96
+ .gestation-presets button {
97
+ font: inherit;
98
+ color: var(--n-ink);
99
+ background: var(--n-card);
100
+ border: 1px solid var(--n-line);
101
+ cursor: pointer;
102
+ }
103
+
104
+ .gestation-species-grid button {
105
+ display: grid;
106
+ justify-items: start;
107
+ gap: 0.2rem;
108
+ min-height: 7rem;
109
+ padding: 0.85rem 0.9rem;
110
+ border-radius: 1.2rem;
111
+ text-align: left;
112
+ transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
113
+ }
114
+
115
+ .gestation-species-grid button:hover,
116
+ .gestation-species-grid button:focus-visible {
117
+ border-color: var(--n-moss);
118
+ transform: translateY(-2px);
119
+ }
120
+
121
+ .gestation-species-grid button svg {
122
+ margin-bottom: 0.25rem;
123
+ color: var(--n-moss);
124
+ font-size: 1.6rem;
125
+ }
126
+
127
+ .gestation-species-grid button span {
128
+ font-weight: 800;
129
+ }
130
+
131
+ .gestation-species-grid button small {
132
+ color: var(--n-muted);
133
+ font-size: 0.76rem;
134
+ }
135
+
136
+ .gestation-species-grid button.is-active {
137
+ border-color: var(--n-moss);
138
+ background: var(--n-sky);
139
+ box-shadow: inset 0 0 0 2px var(--n-moss);
140
+ transform: translateY(-2px);
141
+ }
142
+
143
+ .gestation-date-block {
144
+ display: grid;
145
+ gap: 0.45rem;
146
+ }
147
+
148
+ .gestation-date-block input {
149
+ min-height: 3.4rem;
150
+ padding: 0.75rem 1rem;
151
+ border: 1px solid var(--n-line);
152
+ border-radius: 1rem;
153
+ color: var(--n-ink);
154
+ background: var(--n-card);
155
+ font: inherit;
156
+ font-size: 1.05rem;
157
+ }
158
+
159
+ .gestation-date-block input:focus-visible {
160
+ border-color: var(--n-moss);
161
+ outline: 3px solid color-mix(in srgb, var(--n-moss) 25%, transparent);
162
+ }
163
+
164
+ .gestation-presets {
165
+ display: flex;
166
+ flex-wrap: wrap;
167
+ gap: 0.5rem;
168
+ margin-top: 0.25rem;
169
+ }
170
+
171
+ .gestation-presets button {
172
+ padding: 0.45rem 0.75rem;
173
+ border-radius: 999px;
174
+ font-size: 0.9rem;
175
+ }
176
+
177
+ .gestation-presets button:hover,
178
+ .gestation-presets button:focus-visible {
179
+ border-color: var(--n-moss);
180
+ color: var(--n-moss-deep);
181
+ }
182
+
183
+ .gestation-error {
184
+ min-height: 1.25rem;
185
+ margin: 0;
186
+ color: var(--n-alert);
187
+ }
188
+
189
+ .gestation-result {
190
+ position: relative;
191
+ display: grid;
192
+ grid-template-columns: minmax(15rem, 0.9fr) minmax(0, 1.1fr);
193
+ gap: clamp(1rem, 4vw, 3rem);
194
+ align-items: center;
195
+ margin-top: 1.5rem;
196
+ padding: clamp(1.2rem, 3vw, 2rem);
197
+ overflow: hidden;
198
+ border: 1px solid var(--n-line);
199
+ border-radius: 1.5rem;
200
+ background: var(--n-card);
201
+ }
202
+
203
+ .gestation-result-ribbon {
204
+ position: absolute;
205
+ top: 0;
206
+ right: 0;
207
+ padding: 0.45rem 0.75rem;
208
+ border-bottom-left-radius: 0.75rem;
209
+ color: var(--n-moss-deep);
210
+ background: var(--n-sky);
211
+ font-size: 0.72rem;
212
+ font-weight: 800;
213
+ letter-spacing: 0.08em;
214
+ text-transform: uppercase;
215
+ }
216
+
217
+ .gestation-scene {
218
+ min-height: 14rem;
219
+ display: grid;
220
+ place-items: center;
221
+ }
222
+
223
+ .gestation-scene-placeholder {
224
+ color: var(--n-muted);
225
+ text-align: center;
226
+ }
227
+
228
+ .gestation-nest-scene {
229
+ display: block;
230
+ width: min(100%, 22rem);
231
+ overflow: visible;
232
+ }
233
+
234
+ .scene-reed {
235
+ fill: none;
236
+ stroke: var(--n-moss);
237
+ stroke-linecap: round;
238
+ stroke-width: 4;
239
+ }
240
+
241
+ .scene-window {
242
+ fill: color-mix(in srgb, var(--n-sun) 20%, transparent);
243
+ stroke: var(--n-sun);
244
+ stroke-width: 1.5;
245
+ }
246
+
247
+ .scene-marker-ring {
248
+ fill: none;
249
+ stroke: var(--n-sun);
250
+ stroke-dasharray: 2 3;
251
+ stroke-width: 2;
252
+ }
253
+
254
+ .scene-marker {
255
+ fill: var(--n-sun);
256
+ stroke: var(--n-card);
257
+ stroke-width: 3;
258
+ }
259
+
260
+ .scene-nest {
261
+ fill: var(--n-moss-deep);
262
+ }
263
+
264
+ .scene-nest-line {
265
+ fill: none;
266
+ stroke: var(--n-sun);
267
+ stroke-linecap: round;
268
+ stroke-width: 2;
269
+ }
270
+
271
+ .scene-egg {
272
+ fill: var(--n-paper);
273
+ stroke: var(--n-sun);
274
+ stroke-width: 2;
275
+ }
276
+
277
+ .scene-label,
278
+ .scene-day,
279
+ .scene-caption {
280
+ text-anchor: middle;
281
+ fill: var(--n-ink);
282
+ }
283
+
284
+ .scene-label,
285
+ .scene-caption {
286
+ font-size: 0.58rem;
287
+ }
288
+
289
+ .scene-label {
290
+ fill: var(--n-muted);
291
+ letter-spacing: 0.08em;
292
+ }
293
+
294
+ .scene-label-accent {
295
+ fill: var(--n-moss-deep);
296
+ }
297
+
298
+ .scene-label-right {
299
+ text-anchor: start;
300
+ }
301
+
302
+ .scene-day {
303
+ font-size: 1.7rem;
304
+ font-weight: 800;
305
+ }
306
+
307
+ .scene-caption {
308
+ fill: var(--n-muted);
309
+ }
310
+
311
+ .gestation-eyebrow {
312
+ margin: 0;
313
+ color: var(--n-moss-deep);
314
+ font-size: 0.78rem;
315
+ font-weight: 800;
316
+ letter-spacing: 0.11em;
317
+ text-transform: uppercase;
318
+ }
319
+
320
+ .gestation-status {
321
+ margin: 0.45rem 0 0.15rem;
322
+ font-size: clamp(1.35rem, 4vw, 2.1rem);
323
+ font-weight: 800;
324
+ }
325
+
326
+ .gestation-status[data-tone="late"] {
327
+ color: var(--n-alert);
328
+ }
329
+
330
+ .gestation-status[data-tone="ready"] {
331
+ color: var(--n-moss-deep);
332
+ }
333
+
334
+ .gestation-status-detail {
335
+ margin: 0 0 1.25rem;
336
+ }
337
+
338
+ .gestation-facts {
339
+ display: grid;
340
+ grid-template-columns: repeat(2, minmax(0, 1fr));
341
+ gap: 0.8rem;
342
+ }
343
+
344
+ .gestation-facts div {
345
+ display: grid;
346
+ gap: 0.25rem;
347
+ padding-top: 0.65rem;
348
+ border-top: 1px solid var(--n-line);
349
+ }
350
+
351
+ .gestation-facts span {
352
+ color: var(--n-muted);
353
+ font-size: 0.78rem;
354
+ }
355
+
356
+ .gestation-facts strong {
357
+ font-size: 0.98rem;
358
+ }
359
+
360
+ .gestation-note {
361
+ display: grid;
362
+ gap: 0.25rem;
363
+ margin-top: 1.25rem;
364
+ padding: 1rem 1.15rem;
365
+ border-left: 4px solid var(--n-sun);
366
+ background: color-mix(in srgb, var(--n-sun) 12%, var(--n-card));
367
+ }
368
+
369
+ .gestation-note strong {
370
+ color: var(--n-ink);
371
+ }
372
+
373
+ .gestation-source-note {
374
+ margin: 1rem 0 0;
375
+ font-size: 0.83rem;
376
+ }
377
+
378
+ @media (max-width: 760px) {
379
+ .gestation-tool-intro,
380
+ .gestation-result {
381
+ grid-template-columns: 1fr;
382
+ }
383
+
384
+ .gestation-instant-hint {
385
+ max-width: 32rem;
386
+ }
387
+ }
388
+
389
+ @media (max-width: 520px) {
390
+ .gestation-species-grid {
391
+ grid-template-columns: repeat(2, minmax(0, 1fr));
392
+ }
393
+
394
+ .gestation-result {
395
+ padding-top: 2.5rem;
396
+ }
397
+
398
+ .gestation-scene {
399
+ min-height: 12rem;
400
+ }
401
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { petGestation } from './index';
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 petGestation.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,25 @@
1
+ import type { GestationSpecies } from './logic';
2
+
3
+ const STORAGE_KEY = 'jjlmoya-pet-gestation';
4
+
5
+ export interface GestationStorageState {
6
+ species: GestationSpecies;
7
+ matingDate: string;
8
+ }
9
+
10
+ export function loadGestationState(): Partial<GestationStorageState> {
11
+ try {
12
+ const raw = window.localStorage.getItem(STORAGE_KEY);
13
+ if (!raw) return {};
14
+ const value = JSON.parse(raw) as Partial<GestationStorageState>;
15
+ return value;
16
+ } catch {
17
+ return {};
18
+ }
19
+ }
20
+
21
+ export function saveGestationState(state: GestationStorageState): void {
22
+ try {
23
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
24
+ } catch {}
25
+ }
@@ -0,0 +1,42 @@
1
+ export interface PetGestationUI {
2
+ [key: string]: string;
3
+ journeyHint: string;
4
+ instantHint: string;
5
+ speciesLegend: string;
6
+ speciesDog: string;
7
+ speciesCat: string;
8
+ speciesRabbit: string;
9
+ speciesFerret: string;
10
+ speciesDogMeta: string;
11
+ speciesCatMeta: string;
12
+ speciesRabbitMeta: string;
13
+ speciesFerretMeta: string;
14
+ matingDateLabel: string;
15
+ matingDateHint: string;
16
+ presetToday: string;
17
+ presetWeekAgo: string;
18
+ presetFortnightAgo: string;
19
+ calculateButton: string;
20
+ resultEyebrow: string;
21
+ dueDateLabel: string;
22
+ windowLabel: string;
23
+ remainingLabel: string;
24
+ elapsedLabel: string;
25
+ daysLabel: string;
26
+ dayLabel: string;
27
+ statusFuture: string;
28
+ statusWaiting: string;
29
+ statusWindow: string;
30
+ statusLate: string;
31
+ resultEmpty: string;
32
+ sampleLabel: string;
33
+ sceneMating: string;
34
+ sceneToday: string;
35
+ sceneDue: string;
36
+ noteTitle: string;
37
+ noteText: string;
38
+ sourceTitle: string;
39
+ sourceText: string;
40
+ invalidDate: string;
41
+ futureDate: string;
42
+ }
@@ -120,7 +120,7 @@ const { ui } = Astro.props;
120
120
  <script>
121
121
  import { getFormState, setRadioValue, setMaxWeightRange, getStoredConfig, applyPresetLogic, updateDisplay } from "./handlers";
122
122
 
123
- function setupRationListeners(calculate: () => void, weightRange: HTMLInputElement, ratioRange: HTMLInputElement) {
123
+ function setupRationListeners(calculate: () => void, _weightRange: HTMLInputElement, ratioRange: HTMLInputElement) {
124
124
  document.getElementById("calcWrapper")?.addEventListener("input", calculate);
125
125
  document.getElementById("calcWrapper")?.addEventListener("change", calculate);
126
126
 
@@ -124,7 +124,7 @@ function updateStateAndUrl(state: RationState): void {
124
124
  url.searchParams.set("s", state.species);
125
125
  url.searchParams.set("st", state.stage);
126
126
  url.searchParams.set("a", state.activity);
127
- url.searchParams.set("r", document.getElementById("dietRatioRange")?.value || "0");
127
+ url.searchParams.set("r", (document.getElementById("dietRatioRange") as HTMLInputElement)?.value || "0");
128
128
  window.history.replaceState({}, "", url.toString());
129
129
  }
130
130
 
@@ -1,3 +1,4 @@
1
+ import type { ToolDefinition } from '../../types';
1
2
  import { petRation } from './entry';
2
3
  export * from './entry';
3
4
  export const PET_RATION_TOOL: ToolDefinition = {
package/src/tools.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  export { ALL_ENTRIES } from './entries';
2
2
  import { PET_AGE_TOOL } from './tool/petAge';
3
3
  import { PET_RATION_TOOL } from './tool/petRation';
4
+ import { PET_GESTATION_TOOL } from './tool/petGestation';
4
5
  import type { ToolDefinition } from './types';
5
6
 
6
7
  export const ALL_TOOLS: ToolDefinition[] = [
7
8
  PET_AGE_TOOL,
8
9
  PET_RATION_TOOL,
10
+ PET_GESTATION_TOOL,
9
11
  ];
10
12
 
11
13
  export {
12
14
  PET_AGE_TOOL,
13
15
  PET_RATION_TOOL,
16
+ PET_GESTATION_TOOL,
14
17
  };
15
-
package/src/types.ts CHANGED
@@ -5,7 +5,7 @@ export type { SEOSection };
5
5
 
6
6
  export type KnownLocale =
7
7
  | 'ar' | 'da' | 'de' | 'en' | 'es' | 'fi'
8
- | 'fr' | 'it' | 'ja' | 'ko' | 'nb' | 'nl'
8
+ | 'fr' | 'id' | 'it' | 'ja' | 'ko' | 'nb' | 'nl'
9
9
  | 'pl' | 'pt' | 'ru' | 'sv' | 'tr' | 'zh';
10
10
 
11
11
  export interface FAQItem {