@jjlmoya/utils-pets 1.18.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.
Files changed (73) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/data.ts +2 -0
  4. package/src/entries.ts +7 -1
  5. package/src/layouts/PreviewLayout.astro +2 -0
  6. package/src/pages/[locale]/[slug].astro +14 -5
  7. package/src/tests/diacritics_density.test.ts +1 -1
  8. package/src/tests/faq_count.test.ts +1 -1
  9. package/src/tests/inverted_punctuation.test.ts +1 -1
  10. package/src/tests/locale_completeness.test.ts +1 -1
  11. package/src/tests/script_density.test.ts +1 -1
  12. package/src/tests/title_quality.test.ts +1 -0
  13. package/src/tests/tool_validation.test.ts +5 -5
  14. package/src/tests/translation_copy.test.ts +14 -15
  15. package/src/tool/petGestation/i18n/en.ts +1 -0
  16. package/src/tool/petGestation/i18n/es.ts +1 -0
  17. package/src/tool/petToxicity/bibliography.astro +6 -0
  18. package/src/tool/petToxicity/bibliography.ts +16 -0
  19. package/src/tool/petToxicity/component.astro +53 -0
  20. package/src/tool/petToxicity/controller.ts +117 -0
  21. package/src/tool/petToxicity/dom-views.ts +61 -0
  22. package/src/tool/petToxicity/entry.ts +33 -0
  23. package/src/tool/petToxicity/evaluator.ts +23 -0
  24. package/src/tool/petToxicity/i18n/de.ts +181 -0
  25. package/src/tool/petToxicity/i18n/en.ts +175 -0
  26. package/src/tool/petToxicity/i18n/es.ts +181 -0
  27. package/src/tool/petToxicity/i18n/fr.ts +181 -0
  28. package/src/tool/petToxicity/i18n/id.ts +182 -0
  29. package/src/tool/petToxicity/i18n/it.ts +181 -0
  30. package/src/tool/petToxicity/i18n/ja.ts +181 -0
  31. package/src/tool/petToxicity/i18n/ko.ts +181 -0
  32. package/src/tool/petToxicity/i18n/nl.ts +182 -0
  33. package/src/tool/petToxicity/i18n/pl.ts +182 -0
  34. package/src/tool/petToxicity/i18n/pt.ts +181 -0
  35. package/src/tool/petToxicity/i18n/ru.ts +182 -0
  36. package/src/tool/petToxicity/i18n/sv.ts +182 -0
  37. package/src/tool/petToxicity/i18n/tr.ts +182 -0
  38. package/src/tool/petToxicity/i18n/zh.ts +181 -0
  39. package/src/tool/petToxicity/index.ts +11 -0
  40. package/src/tool/petToxicity/logic.test.ts +32 -0
  41. package/src/tool/petToxicity/logic.ts +192 -0
  42. package/src/tool/petToxicity/pet-food-toxicity-checker.css +450 -0
  43. package/src/tool/petToxicity/seo.astro +15 -0
  44. package/src/tool/petToxicity/storage.ts +37 -0
  45. package/src/tool/petToxicity/ui.ts +33 -0
  46. package/src/tool/petWaterIntake/bibliography.astro +6 -0
  47. package/src/tool/petWaterIntake/bibliography.ts +16 -0
  48. package/src/tool/petWaterIntake/component.astro +103 -0
  49. package/src/tool/petWaterIntake/controller.ts +47 -0
  50. package/src/tool/petWaterIntake/dom-views.ts +54 -0
  51. package/src/tool/petWaterIntake/entry.ts +33 -0
  52. package/src/tool/petWaterIntake/i18n/de.ts +205 -0
  53. package/src/tool/petWaterIntake/i18n/en.ts +205 -0
  54. package/src/tool/petWaterIntake/i18n/es.ts +205 -0
  55. package/src/tool/petWaterIntake/i18n/fr.ts +205 -0
  56. package/src/tool/petWaterIntake/i18n/id.ts +237 -0
  57. package/src/tool/petWaterIntake/i18n/it.ts +237 -0
  58. package/src/tool/petWaterIntake/i18n/ja.ts +237 -0
  59. package/src/tool/petWaterIntake/i18n/ko.ts +237 -0
  60. package/src/tool/petWaterIntake/i18n/nl.ts +237 -0
  61. package/src/tool/petWaterIntake/i18n/pl.ts +237 -0
  62. package/src/tool/petWaterIntake/i18n/pt.ts +237 -0
  63. package/src/tool/petWaterIntake/i18n/ru.ts +237 -0
  64. package/src/tool/petWaterIntake/i18n/sv.ts +237 -0
  65. package/src/tool/petWaterIntake/i18n/tr.ts +237 -0
  66. package/src/tool/petWaterIntake/i18n/zh.ts +237 -0
  67. package/src/tool/petWaterIntake/index.ts +11 -0
  68. package/src/tool/petWaterIntake/logic.test.ts +52 -0
  69. package/src/tool/petWaterIntake/logic.ts +84 -0
  70. package/src/tool/petWaterIntake/pet-water-intake.css +144 -0
  71. package/src/tool/petWaterIntake/seo.astro +15 -0
  72. package/src/tool/petWaterIntake/ui.ts +55 -0
  73. package/src/tools.ts +6 -0
@@ -0,0 +1,192 @@
1
+ export type PetSpecies = 'dog' | 'cat';
2
+ export type FoodId =
3
+ | 'chocolate'
4
+ | 'xylitol'
5
+ | 'grapes-raisins'
6
+ | 'onion-garlic'
7
+ | 'alcohol'
8
+ | 'macadamia-nuts'
9
+ | 'yeast-dough';
10
+ export type RiskLevel = 'critical' | 'high' | 'caution' | 'unknown';
11
+
12
+ export interface SpeciesAdvice {
13
+ level: RiskLevel;
14
+ summary: string;
15
+ why: string;
16
+ signs: string[];
17
+ action: string;
18
+ }
19
+
20
+ export interface FoodProfile {
21
+ id: FoodId;
22
+ name: string;
23
+ detail: string;
24
+ advice: Partial<Record<PetSpecies, SpeciesAdvice>>;
25
+ }
26
+
27
+ export interface FoodOption {
28
+ id: FoodId;
29
+ name: string;
30
+ detail: string;
31
+ }
32
+
33
+ export interface FoodEvaluation extends SpeciesAdvice {
34
+ food: FoodProfile;
35
+ species: PetSpecies;
36
+ }
37
+
38
+ export const FOOD_CATALOG: readonly FoodProfile[] = [
39
+ {
40
+ id: 'chocolate',
41
+ name: 'Chocolate and caffeine',
42
+ detail: 'Chocolate, coffee, tea, energy drinks',
43
+ advice: {
44
+ dog: {
45
+ level: 'high',
46
+ summary: 'Methylxanthines can affect the heart and nervous system.',
47
+ why: 'Cocoa products contain theobromine and caffeine. Dark chocolate, baking chocolate, cocoa powder, and concentrated drinks are more concerning than milk chocolate, but the product, amount, and body weight all matter.',
48
+ signs: ['Vomiting or diarrhea', 'Restlessness or rapid heartbeat', 'Tremors or seizures'],
49
+ action: 'Call a veterinarian or animal poison service promptly. Keep the package and note the type, amount, and time eaten.',
50
+ },
51
+ cat: {
52
+ level: 'high',
53
+ summary: 'Caffeine and cocoa compounds can disturb the heart and nervous system.',
54
+ why: 'Cats can be affected by methylxanthines in chocolate and caffeinated drinks. A cat may eat less than a dog, but a small amount is not a reason to guess at safety.',
55
+ signs: ['Vomiting or diarrhea', 'Agitation or fast breathing', 'Tremors or seizures'],
56
+ action: 'Call your veterinarian or an animal poison service promptly with the product label and estimated amount.',
57
+ },
58
+ },
59
+ },
60
+ {
61
+ id: 'xylitol',
62
+ name: 'Xylitol',
63
+ detail: 'Sugar-free gum, sweets, baked goods, some nut butters',
64
+ advice: {
65
+ dog: {
66
+ level: 'critical',
67
+ summary: 'Xylitol can cause a rapid, dangerous fall in blood sugar in dogs.',
68
+ why: 'Xylitol is used in some sugar-free foods and personal-care products. In dogs it can trigger a potent insulin release, and serious effects may appear quickly or be delayed.',
69
+ signs: ['Vomiting or marked sleepiness', 'Weakness, staggering, or collapse', 'Shaking, seizures, or bleeding problems'],
70
+ action: 'Treat this as an emergency. Call a veterinarian, emergency clinic, or animal poison service immediately and take the product packaging.',
71
+ },
72
+ cat: {
73
+ level: 'caution',
74
+ summary: 'Xylitol is not considered as dangerous to cats as it is to dogs, but a product exposure still needs context.',
75
+ why: 'Available FDA guidance describes a much clearer danger for dogs. Do not turn that difference into a safety guarantee because the product may contain other hazards and the amount may be unknown.',
76
+ signs: ['Vomiting or unusual tiredness', 'Weakness or poor coordination', 'Any sudden change after eating a product'],
77
+ action: 'Call your veterinarian with the ingredient list and product amount instead of waiting for signs.',
78
+ },
79
+ },
80
+ },
81
+ {
82
+ id: 'grapes-raisins',
83
+ name: 'Grapes and raisins',
84
+ detail: 'Fresh grapes, raisins, sultanas, baked goods',
85
+ advice: {
86
+ dog: {
87
+ level: 'high',
88
+ summary: 'Exposure can damage the kidneys, and the risky amount is not reliably predictable at home.',
89
+ why: 'Veterinary poison-control guidance links grapes and raisins with kidney injury in dogs. Individual sensitivity varies, so a seemingly small amount should not be dismissed.',
90
+ signs: ['Vomiting or diarrhea', 'Lethargy or loss of appetite', 'Changes in drinking or urination'],
91
+ action: 'Call a veterinarian or animal poison service promptly, even if your dog looks normal. Note the food, amount, and time.',
92
+ },
93
+ cat: {
94
+ level: 'caution',
95
+ summary: 'Do not offer grapes or raisins to cats, and ask a veterinarian about any known exposure.',
96
+ why: 'The clearest documented kidney-risk pattern is in dogs, while cat-specific evidence is less established. Uncertainty is not the same as proof of safety.',
97
+ signs: ['Vomiting or diarrhea', 'Unusual tiredness or loss of appetite', 'Changes in drinking or urination'],
98
+ action: 'Call your veterinarian with the food, amount, and time rather than waiting for a symptom-based answer.',
99
+ },
100
+ },
101
+ },
102
+ {
103
+ id: 'onion-garlic',
104
+ name: 'Onion, garlic and chives',
105
+ detail: 'Raw, cooked, dried, powdered, sauces and soup mixes',
106
+ advice: {
107
+ dog: {
108
+ level: 'high',
109
+ summary: 'Allium plants can damage red blood cells and cause anemia.',
110
+ why: 'Onion, garlic, chives, and leeks can be harmful in raw, cooked, dehydrated, or concentrated forms. Powdered ingredients and soup mixes can hide a meaningful exposure.',
111
+ signs: ['Vomiting or diarrhea', 'Weakness, pale gums, or fast breathing', 'Dark urine, collapse, or marked tiredness'],
112
+ action: 'Call a veterinarian for exposure guidance. Do not wait for anemia signs, which may appear days after ingestion.',
113
+ },
114
+ cat: {
115
+ level: 'critical',
116
+ summary: 'Cats are especially susceptible to red blood cell damage from allium plants.',
117
+ why: 'Veterinary toxicology references identify cats as the most susceptible common companion species. Concentrated flakes, powders, and soup mixes deserve particular caution.',
118
+ signs: ['Weakness or pale gums', 'Fast breathing or dark urine', 'Collapse or severe tiredness'],
119
+ action: 'Call a veterinarian promptly for any known exposure, even when the cat seems well. Delayed signs are possible.',
120
+ },
121
+ },
122
+ },
123
+ {
124
+ id: 'alcohol',
125
+ name: 'Alcohol',
126
+ detail: 'Drinks, extracts, desserts and fermented mixtures',
127
+ advice: {
128
+ dog: {
129
+ level: 'high',
130
+ summary: 'Alcohol can depress the nervous system and affect breathing and blood chemistry.',
131
+ why: 'Pets are much smaller than people and may ingest concentrated drinks, extracts, or alcohol-containing foods without an adult realizing how much they received.',
132
+ signs: ['Vomiting or diarrhea', 'Poor coordination or unusual sleepiness', 'Slow breathing, tremors, or collapse'],
133
+ action: 'Call a veterinarian or emergency clinic promptly and keep the container available for identification.',
134
+ },
135
+ cat: {
136
+ level: 'high',
137
+ summary: 'Alcohol can depress the nervous system and affect breathing in cats.',
138
+ why: 'Alcohol-containing drinks, extracts, and desserts are not suitable pet treats. The concentration and amount matter, and a cat can deteriorate quickly.',
139
+ signs: ['Vomiting or diarrhea', 'Poor coordination or unusual sleepiness', 'Slow breathing, tremors, or collapse'],
140
+ action: 'Call a veterinarian or emergency clinic promptly with the product and estimated amount.',
141
+ },
142
+ },
143
+ },
144
+ {
145
+ id: 'macadamia-nuts',
146
+ name: 'Macadamia nuts',
147
+ detail: 'Whole nuts, cookies, nut mixes and spreads',
148
+ advice: {
149
+ dog: {
150
+ level: 'high',
151
+ summary: 'Macadamia nuts can cause weakness, incoordination, vomiting, and tremors in dogs.',
152
+ why: 'The toxic mechanism is not fully understood, but dogs can become ill after eating macadamia nuts. Fat-rich recipes may add a separate digestive risk.',
153
+ signs: ['Weakness or difficulty walking', 'Vomiting or depression', 'Tremors or overheating'],
154
+ action: 'Call your veterinarian for exposure guidance and keep the product packaging.',
155
+ },
156
+ },
157
+ },
158
+ {
159
+ id: 'yeast-dough',
160
+ name: 'Raw yeast dough',
161
+ detail: 'Bread dough, pizza dough and rising dough',
162
+ advice: {
163
+ dog: {
164
+ level: 'high',
165
+ summary: 'Rising dough can expand in the stomach and produce alcohol.',
166
+ why: 'Raw yeast dough can continue fermenting after ingestion. Expansion and alcohol production can create a dangerous combination that needs veterinary advice.',
167
+ signs: ['A swollen or painful abdomen', 'Unsteady behavior or unusual sleepiness', 'Repeated retching or collapse'],
168
+ action: 'Call a veterinarian or emergency clinic promptly. Do not wait to see whether the dough finishes rising.',
169
+ },
170
+ cat: {
171
+ level: 'high',
172
+ summary: 'Raw yeast dough can expand and ferment inside a cat.',
173
+ why: 'Rising dough can create abdominal distension and alcohol exposure. The risk depends on the dough and amount, so a professional should assess it.',
174
+ signs: ['A swollen or painful abdomen', 'Unsteady behavior or unusual sleepiness', 'Repeated retching or collapse'],
175
+ action: 'Call a veterinarian or emergency clinic promptly and keep the recipe or packaging available.',
176
+ },
177
+ },
178
+ },
179
+ ];
180
+
181
+ export function getFoodOptions(species: PetSpecies): FoodOption[] {
182
+ return FOOD_CATALOG
183
+ .filter((food) => Boolean(food.advice[species]))
184
+ .map(({ id, name, detail }) => ({ id, name, detail }));
185
+ }
186
+
187
+ export function getFoodEvaluation(species: PetSpecies, foodId: string): FoodEvaluation | null {
188
+ const food = FOOD_CATALOG.find((candidate) => candidate.id === foodId);
189
+ const advice = food?.advice[species];
190
+ if (!food || !advice) return null;
191
+ return { ...advice, food, species };
192
+ }
@@ -0,0 +1,450 @@
1
+ :root {
2
+ --n-paper: #f8f4ea;
3
+ --n-surface: #fffdf7;
4
+ --n-ink: #22241f;
5
+ --n-muted: #686c61;
6
+ --n-line: #d8d4c8;
7
+ --n-accent: #cb3d2f;
8
+ --n-accent-dark: #8e201b;
9
+ --n-yellow: #e5aa31;
10
+ --n-green: #3b7561;
11
+ --n-shadow: #b8b1a1;
12
+ }
13
+
14
+ .theme-dark {
15
+ --n-paper: #20231f;
16
+ --n-surface: #2a2e28;
17
+ --n-ink: #f2eee3;
18
+ --n-muted: #b9b8aa;
19
+ --n-line: #52584e;
20
+ --n-accent: #f07c68;
21
+ --n-accent-dark: #ffad93;
22
+ --n-yellow: #edc35b;
23
+ --n-green: #84c8a7;
24
+ --n-shadow: #11130f;
25
+ }
26
+
27
+ .pet-toxicity {
28
+ --n-card: var(--n-surface);
29
+ --n-card-strong: var(--n-paper);
30
+ --n-danger-surface: #f7dfd6;
31
+ --n-caution-surface: #f5edcf;
32
+ --n-safe-surface: #dbece1;
33
+
34
+ background: var(--n-paper);
35
+ border: 1px solid var(--n-line);
36
+ box-sizing: border-box;
37
+ border-radius: 1.25rem;
38
+ box-shadow: 0.4rem 0.4rem 0 var(--n-shadow);
39
+ color: var(--n-ink);
40
+ max-width: 70rem;
41
+ min-width: 0;
42
+ overflow: visible;
43
+ padding: clamp(1rem, 3vw, 2rem);
44
+ }
45
+
46
+ .theme-dark .pet-toxicity {
47
+ --n-danger-surface: #512d2a;
48
+ --n-caution-surface: #4d4021;
49
+ --n-safe-surface: #244035;
50
+ }
51
+
52
+ .pet-toxicity-journey {
53
+ align-items: center;
54
+ border-bottom: 1px solid var(--n-line);
55
+ display: flex;
56
+ gap: 0.75rem;
57
+ margin-bottom: 1.25rem;
58
+ padding-bottom: 1.25rem;
59
+ }
60
+
61
+ .pet-toxicity-journey p {
62
+ margin: 0;
63
+ max-width: 52rem;
64
+ }
65
+
66
+ .pet-toxicity-journey-mark {
67
+ align-items: center;
68
+ background: var(--n-accent);
69
+ border: 2px solid var(--n-ink);
70
+ border-radius: 50%;
71
+ color: var(--n-surface);
72
+ display: inline-flex;
73
+ flex: 0 0 2rem;
74
+ font-size: 1.25rem;
75
+ font-weight: 800;
76
+ height: 2rem;
77
+ justify-content: center;
78
+ width: 2rem;
79
+ }
80
+
81
+ .pet-toxicity-workbench {
82
+ align-items: stretch;
83
+ display: grid;
84
+ gap: 1rem;
85
+ grid-template-columns: minmax(15rem, 0.7fr) minmax(20rem, 1.3fr);
86
+ }
87
+
88
+ .pet-toxicity-controls {
89
+ background: var(--n-card);
90
+ border: 1px solid var(--n-line);
91
+ border-radius: 1rem;
92
+ display: grid;
93
+ gap: 1rem;
94
+ padding: 1rem;
95
+ }
96
+
97
+ .pet-toxicity-control {
98
+ display: grid;
99
+ gap: 0.45rem;
100
+ }
101
+
102
+ .pet-toxicity-label,
103
+ .pet-toxicity-eyebrow {
104
+ color: var(--n-muted);
105
+ display: block;
106
+ font-size: 0.72rem;
107
+ font-weight: 800;
108
+ letter-spacing: 0.08em;
109
+ text-transform: uppercase;
110
+ }
111
+
112
+ .pet-toxicity-select {
113
+ position: relative;
114
+ }
115
+
116
+ .pet-toxicity-select-trigger {
117
+ align-items: center;
118
+ background: var(--n-card-strong);
119
+ border: 1px solid var(--n-line);
120
+ border-radius: 0.7rem;
121
+ color: var(--n-ink);
122
+ cursor: pointer;
123
+ display: flex;
124
+ font: inherit;
125
+ font-weight: 700;
126
+ justify-content: space-between;
127
+ min-height: 3.2rem;
128
+ padding: 0.7rem 0.8rem;
129
+ text-align: left;
130
+ width: 100%;
131
+ }
132
+
133
+ .pet-toxicity-select-trigger:focus-visible,
134
+ .pet-toxicity-menu-option:focus-visible {
135
+ outline: 3px solid var(--n-yellow);
136
+ outline-offset: 2px;
137
+ }
138
+
139
+ .pet-toxicity-chevron {
140
+ border-bottom: 2px solid currentcolor;
141
+ border-right: 2px solid currentcolor;
142
+ height: 0.55rem;
143
+ transform: rotate(45deg) translateY(-0.15rem);
144
+ width: 0.55rem;
145
+ }
146
+
147
+ .pet-toxicity-menu {
148
+ background: var(--n-card);
149
+ border: 1px solid var(--n-line);
150
+ border-radius: 0.7rem;
151
+ box-shadow: 0.25rem 0.25rem 0 var(--n-shadow);
152
+ display: grid;
153
+ gap: 0.25rem;
154
+ left: 0;
155
+ max-height: 18rem;
156
+ overflow-y: auto;
157
+ padding: 0.35rem;
158
+ position: absolute;
159
+ right: 0;
160
+ top: calc(100% + 0.4rem);
161
+ z-index: 3;
162
+ }
163
+
164
+ .pet-toxicity-menu[hidden] {
165
+ display: none;
166
+ }
167
+
168
+ .pet-toxicity-menu-option {
169
+ background: transparent;
170
+ border: 0;
171
+ border-radius: 0.5rem;
172
+ color: var(--n-ink);
173
+ cursor: pointer;
174
+ display: grid;
175
+ gap: 0.2rem;
176
+ padding: 0.65rem;
177
+ text-align: left;
178
+ }
179
+
180
+ .pet-toxicity-menu-option:hover,
181
+ .pet-toxicity-menu-option[data-selected="true"] {
182
+ background: var(--n-danger-surface);
183
+ }
184
+
185
+ .pet-toxicity-menu-option small,
186
+ .pet-toxicity-hint {
187
+ color: var(--n-muted);
188
+ font-size: 0.78rem;
189
+ }
190
+
191
+ .pet-toxicity-hint {
192
+ line-height: 1.4;
193
+ }
194
+
195
+ .pet-toxicity-visual {
196
+ min-width: 0;
197
+ }
198
+
199
+ .pet-toxicity-scene-card {
200
+ background: var(--n-card);
201
+ border: 1px solid var(--n-line);
202
+ border-radius: 1rem;
203
+ display: grid;
204
+ grid-template-rows: auto auto;
205
+ overflow: hidden;
206
+ }
207
+
208
+ .pet-toxicity-scene-copy {
209
+ display: grid;
210
+ gap: 0.3rem;
211
+ min-width: 0;
212
+ padding: 1rem 1rem 0.75rem;
213
+ }
214
+
215
+ .pet-toxicity-scene-copy strong {
216
+ font-size: clamp(1.3rem, 3vw, 2rem);
217
+ overflow-wrap: anywhere;
218
+ }
219
+
220
+ .pet-toxicity-scene-copy span:last-child {
221
+ color: var(--n-muted);
222
+ font-size: 0.85rem;
223
+ }
224
+
225
+ .pet-toxicity-scene {
226
+ display: block;
227
+ height: clamp(12rem, 23vw, 16rem);
228
+ width: 100%;
229
+ }
230
+
231
+ .pet-toxicity-scene-paper {
232
+ fill: var(--n-card-strong);
233
+ stroke: var(--n-line);
234
+ stroke-width: 1;
235
+ }
236
+
237
+ .pet-toxicity-scene-line {
238
+ fill: none;
239
+ stroke: var(--n-accent);
240
+ stroke-linecap: round;
241
+ stroke-width: 3;
242
+ }
243
+
244
+ .pet-toxicity-scene-line-thin {
245
+ opacity: 0.55;
246
+ stroke: var(--n-green);
247
+ stroke-width: 2;
248
+ }
249
+
250
+ .pet-toxicity-marker {
251
+ fill: var(--n-accent);
252
+ stroke: var(--n-card);
253
+ stroke-width: 3;
254
+ }
255
+
256
+ .pet-toxicity-marker-critical { fill: var(--n-accent-dark); }
257
+ .pet-toxicity-marker-caution { fill: var(--n-yellow); }
258
+ .pet-toxicity-marker-empty { fill: var(--n-line); }
259
+
260
+ .pet-toxicity-scene-seal {
261
+ fill: var(--n-accent);
262
+ stroke: var(--n-ink);
263
+ stroke-width: 2;
264
+ }
265
+
266
+ .pet-toxicity-scene-seal-mark {
267
+ fill: none;
268
+ stroke: var(--n-surface);
269
+ stroke-linecap: round;
270
+ stroke-width: 2;
271
+ }
272
+
273
+ .pet-toxicity-scene-seal-dot {
274
+ fill: var(--n-surface);
275
+ }
276
+
277
+ .pet-toxicity-result {
278
+ margin-top: 1rem;
279
+ }
280
+
281
+ .pet-toxicity-result-card,
282
+ .pet-toxicity-result-empty,
283
+ .pet-toxicity-emergency,
284
+ .pet-toxicity-sources {
285
+ background: var(--n-card);
286
+ border: 1px solid var(--n-line);
287
+ border-radius: 1rem;
288
+ padding: clamp(1rem, 2.5vw, 1.5rem);
289
+ }
290
+
291
+ .pet-toxicity-result-card-critical,
292
+ .pet-toxicity-result-card-high {
293
+ border-top: 0.35rem solid var(--n-accent);
294
+ }
295
+ .pet-toxicity-result-card-caution { border-top: 0.35rem solid var(--n-yellow); }
296
+
297
+ .pet-toxicity-result-head {
298
+ align-items: center;
299
+ display: flex;
300
+ gap: 0.75rem;
301
+ justify-content: space-between;
302
+ }
303
+
304
+ .pet-toxicity-risk-status {
305
+ align-items: end;
306
+ display: grid;
307
+ gap: 0.35rem;
308
+ justify-items: end;
309
+ }
310
+
311
+ .pet-toxicity-risk {
312
+ border-radius: 2rem;
313
+ font-size: 0.75rem;
314
+ font-weight: 800;
315
+ padding: 0.35rem 0.6rem;
316
+ text-transform: uppercase;
317
+ }
318
+
319
+ .pet-toxicity-risk-critical,
320
+ .pet-toxicity-risk-high {
321
+ background: var(--n-danger-surface);
322
+ color: var(--n-accent-dark);
323
+ }
324
+ .pet-toxicity-risk-caution {
325
+ background: var(--n-caution-surface);
326
+ color: var(--n-ink);
327
+ }
328
+
329
+ .pet-toxicity-risk-meter {
330
+ display: flex;
331
+ gap: 0.2rem;
332
+ }
333
+
334
+ .pet-toxicity-risk-meter span {
335
+ background: var(--n-line);
336
+ border-radius: 99px;
337
+ height: 0.28rem;
338
+ width: 1.25rem;
339
+ }
340
+
341
+ .pet-toxicity-risk-meter span.is-active {
342
+ background: var(--n-accent);
343
+ }
344
+
345
+ .pet-toxicity-result-card-caution .pet-toxicity-risk-meter span.is-active {
346
+ background: var(--n-yellow);
347
+ }
348
+
349
+ .pet-toxicity-result-card h3 {
350
+ font-size: clamp(1.4rem, 3vw, 2rem);
351
+ margin: 0.7rem 0 0.3rem;
352
+ }
353
+
354
+ .pet-toxicity-summary {
355
+ font-size: 1.05rem;
356
+ margin: 0;
357
+ }
358
+
359
+ .pet-toxicity-result-grid {
360
+ border-top: 1px solid var(--n-line);
361
+ display: grid;
362
+ gap: 1rem;
363
+ grid-template-columns: repeat(2, minmax(0, 1fr));
364
+ margin-top: 1.25rem;
365
+ padding-top: 1.25rem;
366
+ }
367
+
368
+ .pet-toxicity-result-grid p { margin: 0.45rem 0 0; }
369
+
370
+ .pet-toxicity-sign-list {
371
+ display: grid;
372
+ gap: 0.45rem;
373
+ list-style: none;
374
+ margin: 0.45rem 0 0;
375
+ padding: 0;
376
+ }
377
+
378
+ .pet-toxicity-sign-list li {
379
+ align-items: center;
380
+ background: var(--n-card-strong);
381
+ border: 1px solid var(--n-line);
382
+ border-radius: 0.65rem;
383
+ display: flex;
384
+ gap: 0.55rem;
385
+ min-height: 2.35rem;
386
+ padding: 0.4rem 0.55rem;
387
+ }
388
+
389
+ .pet-toxicity-sign-icon {
390
+ align-items: center;
391
+ background: var(--n-danger-surface);
392
+ border-radius: 50%;
393
+ color: var(--n-accent-dark);
394
+ display: inline-flex;
395
+ flex: 0 0 1.55rem;
396
+ height: 1.55rem;
397
+ justify-content: center;
398
+ width: 1.55rem;
399
+ }
400
+
401
+ .pet-toxicity-sign-icon svg {
402
+ fill: none;
403
+ height: 1rem;
404
+ stroke: currentcolor;
405
+ stroke-linecap: round;
406
+ stroke-linejoin: round;
407
+ stroke-width: 1.5;
408
+ width: 1rem;
409
+ }
410
+
411
+ .pet-toxicity-sign-icon circle {
412
+ fill: currentcolor;
413
+ stroke: none;
414
+ }
415
+ .pet-toxicity-action { grid-column: 1 / -1; }
416
+
417
+ .pet-toxicity-callout,
418
+ .pet-toxicity-emergency {
419
+ display: grid;
420
+ gap: 0.35rem;
421
+ margin-top: 1rem;
422
+ }
423
+
424
+ .pet-toxicity-callout {
425
+ background: var(--n-danger-surface);
426
+ border-radius: 0.7rem;
427
+ padding: 0.8rem;
428
+ }
429
+ .pet-toxicity-emergency {
430
+ background: var(--n-danger-surface);
431
+ border-color: var(--n-accent);
432
+ }
433
+ .pet-toxicity-emergency strong { color: var(--n-accent-dark); }
434
+
435
+ .pet-toxicity-sources { margin-top: 1rem; }
436
+ .pet-toxicity-sources p { margin: 0.45rem 0 0; }
437
+
438
+ @media (max-width: 48rem) {
439
+ .pet-toxicity {
440
+ max-width: calc(100vw - 3rem);
441
+ width: calc(100vw - 3rem);
442
+ }
443
+
444
+ .pet-toxicity-workbench,
445
+ .pet-toxicity-result-grid { grid-template-columns: 1fr; }
446
+ .pet-toxicity-action { grid-column: auto; }
447
+
448
+ .pet-toxicity-result-head { align-items: start; }
449
+ .pet-toxicity-risk-status { flex: 0 0 auto; }
450
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { petToxicity } 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 petToxicity.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,37 @@
1
+ import type { FoodId, PetSpecies } from './logic';
2
+
3
+ interface SavedState {
4
+ species: PetSpecies;
5
+ foodId: FoodId;
6
+ }
7
+
8
+ const STORAGE_KEY = 'jjlmoya-pet-toxicity-state';
9
+
10
+ function isSpecies(value: unknown): value is PetSpecies {
11
+ return value === 'dog' || value === 'cat';
12
+ }
13
+
14
+ function isSavedState(value: unknown): value is SavedState {
15
+ if (!value || typeof value !== 'object') return false;
16
+ const record = value as Record<string, unknown>;
17
+ return isSpecies(record.species) && typeof record.foodId === 'string';
18
+ }
19
+
20
+ export function loadSavedState(): SavedState | null {
21
+ try {
22
+ const raw = window.localStorage.getItem(STORAGE_KEY);
23
+ if (!raw) return null;
24
+ const parsed: unknown = JSON.parse(raw);
25
+ return isSavedState(parsed) ? parsed : null;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ export function saveState(state: SavedState): void {
32
+ try {
33
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
34
+ } catch {
35
+ return;
36
+ }
37
+ }