@jjlmoya/utils-pets 1.17.0 → 1.19.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 +3 -1
- package/src/data.ts +4 -0
- package/src/entries.ts +5 -1
- package/src/index.ts +3 -2
- package/src/layouts/PreviewLayout.astro +2 -0
- package/src/pages/[locale]/[slug].astro +14 -5
- package/src/tests/diacritics_density.test.ts +1 -1
- package/src/tests/faq_count.test.ts +1 -1
- package/src/tests/inverted_punctuation.test.ts +1 -1
- package/src/tests/locale_completeness.test.ts +1 -2
- package/src/tests/script_density.test.ts +1 -1
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/title_quality.test.ts +1 -0
- package/src/tests/tool_validation.test.ts +4 -4
- package/src/tests/translation_copy.test.ts +14 -15
- package/src/tool/petAge/index.ts +1 -0
- package/src/tool/petGestation/bibliography.astro +6 -0
- package/src/tool/petGestation/bibliography.ts +12 -0
- package/src/tool/petGestation/component.astro +68 -0
- package/src/tool/petGestation/controller.ts +69 -0
- package/src/tool/petGestation/dom-views.ts +60 -0
- package/src/tool/petGestation/entry.ts +31 -0
- package/src/tool/petGestation/evaluator.ts +28 -0
- package/src/tool/petGestation/i18n/de.ts +209 -0
- package/src/tool/petGestation/i18n/en.ts +198 -0
- package/src/tool/petGestation/i18n/es.ts +198 -0
- package/src/tool/petGestation/i18n/fr.ts +209 -0
- package/src/tool/petGestation/i18n/id.ts +209 -0
- package/src/tool/petGestation/i18n/it.ts +209 -0
- package/src/tool/petGestation/i18n/ja.ts +209 -0
- package/src/tool/petGestation/i18n/ko.ts +209 -0
- package/src/tool/petGestation/i18n/nl.ts +209 -0
- package/src/tool/petGestation/i18n/pl.ts +209 -0
- package/src/tool/petGestation/i18n/pt.ts +209 -0
- package/src/tool/petGestation/i18n/ru.ts +209 -0
- package/src/tool/petGestation/i18n/sv.ts +209 -0
- package/src/tool/petGestation/i18n/tr.ts +209 -0
- package/src/tool/petGestation/i18n/zh.ts +209 -0
- package/src/tool/petGestation/index.ts +11 -0
- package/src/tool/petGestation/logic.test.ts +44 -0
- package/src/tool/petGestation/logic.ts +103 -0
- package/src/tool/petGestation/pet-gestation-calculator.css +401 -0
- package/src/tool/petGestation/seo.astro +15 -0
- package/src/tool/petGestation/storage.ts +25 -0
- package/src/tool/petGestation/ui.ts +42 -0
- package/src/tool/petRation/component.astro +1 -1
- package/src/tool/petRation/handlers.ts +1 -1
- package/src/tool/petRation/index.ts +1 -0
- package/src/tool/petToxicity/bibliography.astro +6 -0
- package/src/tool/petToxicity/bibliography.ts +16 -0
- package/src/tool/petToxicity/component.astro +53 -0
- package/src/tool/petToxicity/controller.ts +117 -0
- package/src/tool/petToxicity/dom-views.ts +61 -0
- package/src/tool/petToxicity/entry.ts +33 -0
- package/src/tool/petToxicity/evaluator.ts +23 -0
- package/src/tool/petToxicity/i18n/de.ts +181 -0
- package/src/tool/petToxicity/i18n/en.ts +175 -0
- package/src/tool/petToxicity/i18n/es.ts +181 -0
- package/src/tool/petToxicity/i18n/fr.ts +181 -0
- package/src/tool/petToxicity/i18n/id.ts +182 -0
- package/src/tool/petToxicity/i18n/it.ts +181 -0
- package/src/tool/petToxicity/i18n/ja.ts +181 -0
- package/src/tool/petToxicity/i18n/ko.ts +181 -0
- package/src/tool/petToxicity/i18n/nl.ts +182 -0
- package/src/tool/petToxicity/i18n/pl.ts +182 -0
- package/src/tool/petToxicity/i18n/pt.ts +181 -0
- package/src/tool/petToxicity/i18n/ru.ts +182 -0
- package/src/tool/petToxicity/i18n/sv.ts +182 -0
- package/src/tool/petToxicity/i18n/tr.ts +182 -0
- package/src/tool/petToxicity/i18n/zh.ts +181 -0
- package/src/tool/petToxicity/index.ts +11 -0
- package/src/tool/petToxicity/logic.test.ts +32 -0
- package/src/tool/petToxicity/logic.ts +192 -0
- package/src/tool/petToxicity/pet-food-toxicity-checker.css +450 -0
- package/src/tool/petToxicity/seo.astro +15 -0
- package/src/tool/petToxicity/storage.ts +37 -0
- package/src/tool/petToxicity/ui.ts +33 -0
- package/src/tools.ts +6 -1
- package/src/types.ts +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
import './pet-food-toxicity-checker.css';
|
|
3
|
+
import { getFoodEvaluation } from './logic';
|
|
4
|
+
import { renderFoodMenu, renderResult, renderScene, renderSpeciesMenu } from './dom-views';
|
|
5
|
+
import { petToxicity } from './entry';
|
|
6
|
+
|
|
7
|
+
const content = await petToxicity.i18n.en?.();
|
|
8
|
+
if (!content) return null;
|
|
9
|
+
const { ui } = content;
|
|
10
|
+
const defaultSpecies = 'dog';
|
|
11
|
+
const defaultFood = 'chocolate';
|
|
12
|
+
const evaluation = getFoodEvaluation(defaultSpecies, defaultFood);
|
|
13
|
+
const config = { ui, defaultSpecies, defaultFood };
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<section class="pet-toxicity" data-pet-toxicity-root aria-label={content.title}>
|
|
17
|
+
<div class="pet-toxicity-journey">
|
|
18
|
+
<span class="pet-toxicity-journey-mark" aria-hidden="true">!</span>
|
|
19
|
+
<p>{ui.journeyHint}</p>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="pet-toxicity-workbench">
|
|
22
|
+
<div class="pet-toxicity-controls">
|
|
23
|
+
<div class="pet-toxicity-control">
|
|
24
|
+
<span class="pet-toxicity-label">{ui.speciesLabel}</span>
|
|
25
|
+
<div class="pet-toxicity-select">
|
|
26
|
+
<button class="pet-toxicity-select-trigger" type="button" data-menu-trigger data-species-trigger aria-haspopup="listbox" aria-expanded="false"><span data-trigger-value>{ui.speciesDog}</span><span class="pet-toxicity-chevron" aria-hidden="true"></span></button>
|
|
27
|
+
<div class="pet-toxicity-menu" data-menu data-species-menu role="listbox" hidden>{renderSpeciesMenu(defaultSpecies, ui)}</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="pet-toxicity-control">
|
|
31
|
+
<span class="pet-toxicity-label">{ui.foodLabel}</span>
|
|
32
|
+
<div class="pet-toxicity-select">
|
|
33
|
+
<button class="pet-toxicity-select-trigger" type="button" data-menu-trigger data-food-trigger aria-haspopup="listbox" aria-expanded="false"><span data-trigger-value>{getFoodEvaluation(defaultSpecies, defaultFood)?.food.name ?? ui.foodPlaceholder}</span><span class="pet-toxicity-chevron" aria-hidden="true"></span></button>
|
|
34
|
+
<div class="pet-toxicity-menu" data-menu data-food-menu role="listbox" hidden>{renderFoodMenu([{ id: 'chocolate', name: 'Chocolate and caffeine', detail: 'Chocolate, coffee, tea, energy drinks' }], defaultFood)}</div>
|
|
35
|
+
</div>
|
|
36
|
+
<small class="pet-toxicity-hint">{ui.foodMenuHint}</small>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="pet-toxicity-visual" data-scene>{renderScene(defaultSpecies, evaluation, ui)}</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="pet-toxicity-result" data-result aria-live="polite">{renderResult(evaluation, ui)}</div>
|
|
42
|
+
<div class="pet-toxicity-emergency"><strong>{ui.emergencyTitle}</strong><span>{ui.emergencyText}</span></div>
|
|
43
|
+
<div class="pet-toxicity-sources"><span class="pet-toxicity-label">{ui.sourceLabel}</span><p>{ui.sourceText}</p></div>
|
|
44
|
+
</section>
|
|
45
|
+
|
|
46
|
+
<script is:inline type="application/json" data-pet-toxicity-config set:html={JSON.stringify(config)}></script>
|
|
47
|
+
<script>
|
|
48
|
+
import { mountPetToxicity } from './controller';
|
|
49
|
+
|
|
50
|
+
const root = document.querySelector<HTMLElement>('[data-pet-toxicity-root]');
|
|
51
|
+
const configElement = document.querySelector<HTMLScriptElement>('[data-pet-toxicity-config]');
|
|
52
|
+
if (root && configElement?.textContent) mountPetToxicity(root, JSON.parse(configElement.textContent));
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { getFoodEvaluation, getFoodOptions } from './logic';
|
|
2
|
+
import type { FoodId, PetSpecies } from './logic';
|
|
3
|
+
import { renderFoodMenu, renderResult, renderScene, renderSpeciesMenu } from './dom-views';
|
|
4
|
+
import { loadSavedState, saveState } from './storage';
|
|
5
|
+
import type { PetToxicityUI } from './ui';
|
|
6
|
+
|
|
7
|
+
interface MountPayload {
|
|
8
|
+
ui: PetToxicityUI;
|
|
9
|
+
defaultSpecies: PetSpecies;
|
|
10
|
+
defaultFood: FoodId;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ToolState {
|
|
14
|
+
species: PetSpecies;
|
|
15
|
+
foodId: FoodId;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function findElement<T extends Element>(root: HTMLElement, selector: string): T {
|
|
19
|
+
const element = root.querySelector<T>(selector);
|
|
20
|
+
if (!element) throw new Error(`Missing pet toxicity element: ${selector}`);
|
|
21
|
+
return element;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function closeMenus(root: HTMLElement): void {
|
|
25
|
+
root.querySelectorAll<HTMLElement>('[data-menu]').forEach((menu) => { menu.hidden = true; });
|
|
26
|
+
root.querySelectorAll<HTMLElement>('[data-menu-trigger]').forEach((trigger) => { trigger.setAttribute('aria-expanded', 'false'); });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function toggleMenu(root: HTMLElement, menu: HTMLElement, trigger: HTMLElement): void {
|
|
30
|
+
const shouldOpen = menu.hidden;
|
|
31
|
+
closeMenus(root);
|
|
32
|
+
menu.hidden = !shouldOpen;
|
|
33
|
+
trigger.setAttribute('aria-expanded', String(shouldOpen));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function labelForSpecies(species: PetSpecies, ui: PetToxicityUI): string {
|
|
37
|
+
return species === 'dog' ? ui.speciesDog : ui.speciesCat;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setupKeyboard(root: HTMLElement): void {
|
|
41
|
+
root.querySelectorAll<HTMLElement>('[data-menu-trigger]').forEach((trigger) => {
|
|
42
|
+
trigger.addEventListener('keydown', (event) => {
|
|
43
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
trigger.click();
|
|
46
|
+
}
|
|
47
|
+
if (event.key === 'Escape') closeMenus(root);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ViewElements {
|
|
53
|
+
speciesTrigger: HTMLButtonElement;
|
|
54
|
+
speciesMenu: HTMLElement;
|
|
55
|
+
foodTrigger: HTMLButtonElement;
|
|
56
|
+
foodMenu: HTMLElement;
|
|
57
|
+
scene: HTMLElement;
|
|
58
|
+
result: HTMLElement;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderState(elements: ViewElements, state: ToolState, payload: MountPayload): void {
|
|
62
|
+
const options = getFoodOptions(state.species);
|
|
63
|
+
if (!options.some((option) => option.id === state.foodId)) state.foodId = options[0]?.id ?? payload.defaultFood;
|
|
64
|
+
const evaluation = getFoodEvaluation(state.species, state.foodId);
|
|
65
|
+
const selectedFood = options.find((option) => option.id === state.foodId);
|
|
66
|
+
elements.speciesTrigger.querySelector('[data-trigger-value]')!.textContent = labelForSpecies(state.species, payload.ui);
|
|
67
|
+
elements.foodTrigger.querySelector('[data-trigger-value]')!.textContent = selectedFood?.name ?? payload.ui.foodPlaceholder;
|
|
68
|
+
elements.speciesMenu.innerHTML = renderSpeciesMenu(state.species, payload.ui);
|
|
69
|
+
elements.foodMenu.innerHTML = renderFoodMenu(options, state.foodId);
|
|
70
|
+
elements.scene.innerHTML = renderScene(state.species, evaluation, payload.ui);
|
|
71
|
+
elements.result.innerHTML = renderResult(evaluation, payload.ui);
|
|
72
|
+
saveState(state);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function bindSelection(root: HTMLElement, elements: ViewElements, state: ToolState, render: () => void): void {
|
|
76
|
+
elements.speciesTrigger.addEventListener('click', () => toggleMenu(root, elements.speciesMenu, elements.speciesTrigger));
|
|
77
|
+
elements.foodTrigger.addEventListener('click', () => toggleMenu(root, elements.foodMenu, elements.foodTrigger));
|
|
78
|
+
root.addEventListener('click', (event) => {
|
|
79
|
+
const target = event.target as HTMLElement;
|
|
80
|
+
const speciesOption = target.closest<HTMLElement>('[data-species-id]');
|
|
81
|
+
const foodOption = target.closest<HTMLElement>('[data-food-id]');
|
|
82
|
+
if (speciesOption) selectSpecies(speciesOption, root, state, render);
|
|
83
|
+
if (foodOption) selectFood(foodOption, root, state, render);
|
|
84
|
+
});
|
|
85
|
+
document.addEventListener('click', (event) => {
|
|
86
|
+
if (!root.contains(event.target as Node)) closeMenus(root);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function selectSpecies(option: HTMLElement, root: HTMLElement, state: ToolState, render: () => void): void {
|
|
91
|
+
state.species = option.dataset.speciesId as PetSpecies;
|
|
92
|
+
closeMenus(root);
|
|
93
|
+
render();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function selectFood(option: HTMLElement, root: HTMLElement, state: ToolState, render: () => void): void {
|
|
97
|
+
state.foodId = option.dataset.foodId as FoodId;
|
|
98
|
+
closeMenus(root);
|
|
99
|
+
render();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function mountPetToxicity(root: HTMLElement, payload: MountPayload): void {
|
|
103
|
+
const saved = loadSavedState();
|
|
104
|
+
const state: ToolState = { species: saved?.species ?? payload.defaultSpecies, foodId: saved?.foodId ?? payload.defaultFood };
|
|
105
|
+
const elements: ViewElements = {
|
|
106
|
+
speciesTrigger: findElement<HTMLButtonElement>(root, '[data-species-trigger]'),
|
|
107
|
+
speciesMenu: findElement<HTMLElement>(root, '[data-species-menu]'),
|
|
108
|
+
foodTrigger: findElement<HTMLButtonElement>(root, '[data-food-trigger]'),
|
|
109
|
+
foodMenu: findElement<HTMLElement>(root, '[data-food-menu]'),
|
|
110
|
+
scene: findElement<HTMLElement>(root, '[data-scene]'),
|
|
111
|
+
result: findElement<HTMLElement>(root, '[data-result]'),
|
|
112
|
+
};
|
|
113
|
+
const render = () => renderState(elements, state, payload);
|
|
114
|
+
bindSelection(root, elements, state, render);
|
|
115
|
+
setupKeyboard(root);
|
|
116
|
+
render();
|
|
117
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getRiskPresentation } from './evaluator';
|
|
2
|
+
import type { RiskTone } from './evaluator';
|
|
3
|
+
import type { FoodEvaluation, FoodOption, PetSpecies } from './logic';
|
|
4
|
+
import type { PetToxicityUI } from './ui';
|
|
5
|
+
|
|
6
|
+
function escapeHtml(value: string): string {
|
|
7
|
+
return value.replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character] ?? character);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function selectedAttribute(selected: boolean): string {
|
|
11
|
+
return selected ? ' aria-selected="true" data-selected="true"' : ' aria-selected="false"';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function renderSpeciesMenu(species: PetSpecies, ui: PetToxicityUI): string {
|
|
15
|
+
const options = [
|
|
16
|
+
{ id: 'dog', name: ui.speciesDog, detail: ui.speciesDogMeta },
|
|
17
|
+
{ id: 'cat', name: ui.speciesCat, detail: ui.speciesCatMeta },
|
|
18
|
+
];
|
|
19
|
+
return options.map((option) => `<button class="pet-toxicity-menu-option" type="button" role="option" data-species-id="${option.id}"${selectedAttribute(option.id === species)}><span>${escapeHtml(option.name)}</span><small>${escapeHtml(option.detail)}</small></button>`).join('');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function renderFoodMenu(options: FoodOption[], selectedId: string): string {
|
|
23
|
+
return options.map((option) => `<button class="pet-toxicity-menu-option" type="button" role="option" data-food-id="${option.id}"${selectedAttribute(option.id === selectedId)}><span>${escapeHtml(option.name)}</span><small>${escapeHtml(option.detail)}</small></button>`).join('');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderMarkers(evaluation: FoodEvaluation | null): string {
|
|
27
|
+
const tone = evaluation?.level ?? 'empty';
|
|
28
|
+
const markerClass = evaluation ? `pet-toxicity-marker pet-toxicity-marker-${tone}` : 'pet-toxicity-marker pet-toxicity-marker-empty';
|
|
29
|
+
return [0, 1, 2, 3, 4].map((index) => `<circle class="${markerClass}" cx="${74 + index * 47}" cy="${82 + (index % 2) * 34}" r="${evaluation ? 7 + index : 5}" />`).join('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function renderScene(species: PetSpecies, evaluation: FoodEvaluation | null, ui: PetToxicityUI): string {
|
|
33
|
+
const speciesName = species === 'dog' ? ui.speciesDog : ui.speciesCat;
|
|
34
|
+
const selectedName = evaluation?.food.name ?? ui.sceneReady;
|
|
35
|
+
const stateText = evaluation ? ui.sceneSelected : ui.resultEmpty;
|
|
36
|
+
return `<div class="pet-toxicity-scene-card"><div class="pet-toxicity-scene-copy"><span class="pet-toxicity-eyebrow">${escapeHtml(ui.sceneLabel)}</span><strong>${escapeHtml(selectedName)}</strong><span>${escapeHtml(speciesName)} · ${escapeHtml(stateText)}</span></div><svg class="pet-toxicity-scene" viewBox="0 0 320 180" role="img" aria-label="${escapeHtml(selectedName)} risk scene"><rect class="pet-toxicity-scene-paper" x="14" y="14" width="292" height="152" rx="18" /><path class="pet-toxicity-scene-line" d="M38 122 C80 46 124 146 166 66 S246 50 282 118" /><path class="pet-toxicity-scene-line-thin" d="M42 138 C92 84 132 138 174 98 S240 82 280 136" />${renderMarkers(evaluation)}<circle class="pet-toxicity-scene-seal" cx="258" cy="52" r="20" /><path class="pet-toxicity-scene-seal-mark" d="M258 43v12" /><circle class="pet-toxicity-scene-seal-dot" cx="258" cy="62" r="1.7" /></svg></div>`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function renderSignIcon(index: number): string {
|
|
40
|
+
const paths = [
|
|
41
|
+
'<circle cx="8" cy="8" r="6" /><path d="M8 4.5v4" /><circle cx="8" cy="11.5" r=".7" />',
|
|
42
|
+
'<path d="M1.5 9h2.5l1.5-4 2.5 6 1.6-3h2.3" />',
|
|
43
|
+
'<path d="m8 1.5 6 11H2l6-11Z" /><path d="M8 5v3" /><circle cx="8" cy="10.5" r=".7" />',
|
|
44
|
+
];
|
|
45
|
+
return `<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false">${paths[index % paths.length]}</svg>`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderList(items: string[]): string {
|
|
49
|
+
return `<ul class="pet-toxicity-sign-list">${items.map((item, index) => `<li><span class="pet-toxicity-sign-icon">${renderSignIcon(index)}</span><span>${escapeHtml(item)}</span></li>`).join('')}</ul>`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function renderRiskMeter(tone: RiskTone, label: string): string {
|
|
53
|
+
const activeCount = { critical: 3, high: 2, caution: 1, unknown: 0 }[tone];
|
|
54
|
+
return `<span class="pet-toxicity-risk-meter" role="img" aria-label="${escapeHtml(label)}"><span class="${activeCount >= 1 ? 'is-active' : ''}"></span><span class="${activeCount >= 2 ? 'is-active' : ''}"></span><span class="${activeCount >= 3 ? 'is-active' : ''}"></span></span>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function renderResult(evaluation: FoodEvaluation | null, ui: PetToxicityUI): string {
|
|
58
|
+
if (!evaluation) return `<div class="pet-toxicity-result-empty">${escapeHtml(ui.resultEmpty)}</div>`;
|
|
59
|
+
const risk = getRiskPresentation(evaluation.level, ui);
|
|
60
|
+
return `<article class="pet-toxicity-result-card pet-toxicity-result-card-${risk.tone}"><div class="pet-toxicity-result-head"><span class="pet-toxicity-eyebrow">${escapeHtml(ui.resultEyebrow)}</span><span class="pet-toxicity-risk-status"><span class="pet-toxicity-risk pet-toxicity-risk-${risk.tone}">${escapeHtml(risk.label)}</span>${renderRiskMeter(risk.tone, `${ui.riskLabel}: ${risk.label}`)}</span></div><h3>${escapeHtml(evaluation.food.name)}</h3><p class="pet-toxicity-summary">${escapeHtml(evaluation.summary)}</p><div class="pet-toxicity-result-grid"><div><span class="pet-toxicity-label">${escapeHtml(ui.whyLabel)}</span><p>${escapeHtml(evaluation.why)}</p></div><div><span class="pet-toxicity-label">${escapeHtml(ui.signsLabel)}</span>${renderList(evaluation.signs)}</div><div class="pet-toxicity-action"><span class="pet-toxicity-label">${escapeHtml(ui.actionLabel)}</span><p>${escapeHtml(evaluation.action)}</p></div></div><div class="pet-toxicity-callout"><strong>${escapeHtml(ui.callVetLabel)}</strong><span>${escapeHtml(ui.callVetText)}</span></div></article>`;
|
|
61
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PetToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
+
import type { PetToxicityUI } from './ui';
|
|
3
|
+
|
|
4
|
+
export type { PetToxicityUI } from './ui';
|
|
5
|
+
|
|
6
|
+
export type PetToxicityLocaleContent = ToolLocaleContent<PetToxicityUI>;
|
|
7
|
+
|
|
8
|
+
export const petToxicity: PetToolEntry<PetToxicityUI> = {
|
|
9
|
+
id: 'pet-toxicity',
|
|
10
|
+
icons: {
|
|
11
|
+
bg: 'mdi:paw',
|
|
12
|
+
fg: 'mdi:alert-octagon',
|
|
13
|
+
},
|
|
14
|
+
i18n: {
|
|
15
|
+
de: () => import('./i18n/de').then((module) => module.content),
|
|
16
|
+
en: () => import('./i18n/en').then((module) => module.content),
|
|
17
|
+
es: () => import('./i18n/es').then((module) => module.content),
|
|
18
|
+
fr: () => import('./i18n/fr').then((module) => module.content),
|
|
19
|
+
id: () => import('./i18n/id').then((module) => module.content),
|
|
20
|
+
it: () => import('./i18n/it').then((module) => module.content),
|
|
21
|
+
ja: () => import('./i18n/ja').then((module) => module.content),
|
|
22
|
+
ko: () => import('./i18n/ko').then((module) => module.content),
|
|
23
|
+
nl: () => import('./i18n/nl').then((module) => module.content),
|
|
24
|
+
pl: () => import('./i18n/pl').then((module) => module.content),
|
|
25
|
+
pt: () => import('./i18n/pt').then((module) => module.content),
|
|
26
|
+
ru: () => import('./i18n/ru').then((module) => module.content),
|
|
27
|
+
sv: () => import('./i18n/sv').then((module) => module.content),
|
|
28
|
+
tr: () => import('./i18n/tr').then((module) => module.content),
|
|
29
|
+
zh: () => import('./i18n/zh').then((module) => module.content),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { bibliography } from './bibliography';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PetToxicityUI } from './ui';
|
|
2
|
+
import type { FoodEvaluation, RiskLevel } from './logic';
|
|
3
|
+
|
|
4
|
+
export type RiskTone = 'critical' | 'high' | 'caution' | 'unknown';
|
|
5
|
+
|
|
6
|
+
export interface RiskPresentation {
|
|
7
|
+
label: string;
|
|
8
|
+
tone: RiskTone;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getRiskPresentation(level: RiskLevel, ui: PetToxicityUI): RiskPresentation {
|
|
12
|
+
const labels: Record<RiskLevel, string> = {
|
|
13
|
+
critical: ui.riskCritical,
|
|
14
|
+
high: ui.riskHigh,
|
|
15
|
+
caution: ui.riskCaution,
|
|
16
|
+
unknown: ui.riskUnknown,
|
|
17
|
+
};
|
|
18
|
+
return { label: labels[level], tone: level };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getResultTitle(evaluation: FoodEvaluation | null, ui: PetToxicityUI): string {
|
|
22
|
+
return evaluation ? evaluation.food.name : ui.resultEmpty;
|
|
23
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
3
|
+
import type { PetToxicityLocaleContent, PetToxicityUI } from '../entry';
|
|
4
|
+
|
|
5
|
+
const slug = 'gefahrliche-lebensmittel-hunde-katzen-prufer';
|
|
6
|
+
const title = 'Prüfer für gefährliche Lebensmittel bei Hunden und Katzen';
|
|
7
|
+
const description = 'Prüfen Sie häufige Futtermittelbelastungen bei Hunden und Katzen, verstehen Sie Warnzeichen und wissen Sie, wann ein Tierarzt zu kontaktieren ist.';
|
|
8
|
+
|
|
9
|
+
const ui: PetToxicityUI = {
|
|
10
|
+
"journeyHint": "Wählen Sie das Tier, öffnen Sie den Lebensmittelatlas und lesen Sie die artspezifische Warnung vor der weiteren Entscheidung.",
|
|
11
|
+
"speciesLabel": "Tierart",
|
|
12
|
+
"speciesDog": "Hund",
|
|
13
|
+
"speciesCat": "Katze",
|
|
14
|
+
"speciesDogMeta": "Hundeprofil",
|
|
15
|
+
"speciesCatMeta": "Katzenprofil",
|
|
16
|
+
"foodLabel": "Lebensmittel oder Zutat",
|
|
17
|
+
"foodPlaceholder": "Wählen Sie ein Lebensmittel",
|
|
18
|
+
"foodMenuHint": "Die Liste ändert sich mit der Tierart, da das Risiko artspezifisch ist.",
|
|
19
|
+
"resultEyebrow": "Der Lebensmittelatlas zeigt",
|
|
20
|
+
"resultEmpty": "Wählen Sie ein Lebensmittel, um die Sicherheitskarte zu öffnen.",
|
|
21
|
+
"sceneLabel": "Expositionskarte",
|
|
22
|
+
"sceneReady": "Wählen Sie ein Lebensmittel",
|
|
23
|
+
"sceneSelected": "Artspezifische Karte geöffnet",
|
|
24
|
+
"riskCritical": "Dringendes Bedenken",
|
|
25
|
+
"riskHigh": "Hohes Bedenken",
|
|
26
|
+
"riskCaution": "Vorsicht",
|
|
27
|
+
"riskUnknown": "Nicht genügend Daten",
|
|
28
|
+
"riskLabel": "Risikostufe",
|
|
29
|
+
"whyLabel": "Warum es wichtig ist",
|
|
30
|
+
"signsLabel": "Zu beobachtende Zeichen",
|
|
31
|
+
"actionLabel": "Was zu tun ist",
|
|
32
|
+
"callVetLabel": "Der sicherste nächste Schritt ist professioneller Rat.",
|
|
33
|
+
"callVetText": "Ein Rechner kann keine toxische Dosis schätzen, eine Exposition bestätigen oder eine Untersuchung ersetzen.",
|
|
34
|
+
"sourceLabel": "Verwendete Nachweise",
|
|
35
|
+
"sourceText": "Die Lebensmittelprofile basieren auf Richtlinien der Giftnotrufzentralen und der Tiertoxikologie von ASPCA, FDA und MSD Veterinary Manual.",
|
|
36
|
+
"emergencyTitle": "Wenn Ihr Haustier etwas Bedenkliches gefressen hat",
|
|
37
|
+
"emergencyText": "Rufen Sie sofort Ihren Tierarzt, eine Notfallklinik oder den Giftnotruf an. Halten Sie Verpackung, Zutatenliste, geschätzte Menge und Zeitpunkt bereit. Leiten Sie kein Erbrechen ein.",
|
|
38
|
+
"openMenu": "Optionen öffnen",
|
|
39
|
+
"closeMenu": "Optionen schließen"
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const faq: PetToxicityLocaleContent['faq'] = [
|
|
43
|
+
{
|
|
44
|
+
"question": "Kann dieser Prüfer mir sagen, ob mein Haustier sicher ist?",
|
|
45
|
+
"answer": "Nein. Es ist ein pädagogischer Leitfaden für häufige Lebensmittelgefahren, keine Diagnose oder Dosisberechnung. Das Risiko hängt von Tierart, Produkt, Menge, Körpergewicht und Zeit seit der Aufnahme ab. Kontaktieren Sie bei Verdacht stets einen Tierarzt."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"question": "Welche Lebensmittel sind für Hunde besonders gefährlich?",
|
|
49
|
+
"answer": "Zu den schwerwiegenden Gefahren gehören Xylit, Schokolade und Koffein, Weintrauben und Rosinen, Zwiebeln und Knoblauch, Alkohol, Makadamianüsse und roher Hefeteig. Ein Produktetikett hilft dem Tierarzt bei der Beurteilung."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"question": "Sind Zwiebeln und Knoblauch für Katzen gefährlich?",
|
|
53
|
+
"answer": "Ja. Katzen reagieren besonders empfindlich auf Schäden an den roten Blutkörperchen durch Allium-Pflanzen wie Zwiebeln, Knoblauch und Schnittlauch. Rohe, gekochte, getrocknete und pulverisierte Formen sind relevant."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"question": "Warum zeigt Xylit für Katzen und Hunde unterschiedliche Warnungen?",
|
|
57
|
+
"answer": "Xylit führt bei Hunden zu einem raschen, lebensbedrohlichen Abfall des Blutzuckers. Bei Katzen ist diese Gefahr in Leitlinien weniger klar belegt, was jedoch kein Sicherheitsversprechen ist."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"question": "Was soll ich tun, wenn das gefressene Lebensmittel nicht gelistet ist?",
|
|
61
|
+
"answer": "Ein fehlender Eintrag ist kein Beweis für Sicherheit. Sichern Sie die Verpackung, notieren Sie Menge sowie Zeitpunkt und kontaktieren Sie umgehend einen Tierarzt oder Giftnotruf."
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const howTo: PetToxicityLocaleContent['howTo'] = [
|
|
66
|
+
{
|
|
67
|
+
"name": "Tierart auswählen",
|
|
68
|
+
"text": "Wählen Sie Hund oder Katze, damit der Lebensmittelatlas die relevanten artspezifischen Nachweise anzeigt."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "Lebensmittel auswählen",
|
|
72
|
+
"text": "Öffnen Sie das Menü und wählen Sie die passende Zutat. Beachten Sie konzentrierte oder getrocknete Produkte."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "Sicherheitskarte lesen",
|
|
76
|
+
"text": "Überprüfen Sie, warum das Lebensmittel wichtig ist, welche Anzeichen auftreten können und welcher Schritt empfohlen wird."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "Bei Bedarf Fachpersonal kontaktieren",
|
|
80
|
+
"text": "Rufen Sie bei einer Exposition den Tierarzt an und halten Sie Angaben zu Produkt, Menge und Tier bereit."
|
|
81
|
+
}
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const seo: PetToxicityLocaleContent['seo'] = [
|
|
85
|
+
{
|
|
86
|
+
type: 'summary',
|
|
87
|
+
title: "Ein schneller Lebensmittelsicherheitscheck für Hunde und Katzen",
|
|
88
|
+
items: [
|
|
89
|
+
"Wählen Sie zuerst die Tierart, da dieselbe Zutat bei Hunden und Katzen unterschiedliche Bedenken auslöst.",
|
|
90
|
+
"Nutzen Sie die Lebensmittelkarten, um Gefahr, Warnzeichen und Dringlichkeit zu verstehen.",
|
|
91
|
+
"Behandeln Sie eine Xylit-Aufnahme bei Hunden als dringenden Notfall.",
|
|
92
|
+
"Verständigen Sie bei einer echten Exposition sofort den Tierarzt."
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: 'title',
|
|
97
|
+
text: "Wie Sie den Prüfer für gefährliche Lebensmittel verantwortungsvoll nutzen",
|
|
98
|
+
level: 2,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'paragraph',
|
|
102
|
+
html: "Dieser Prüfer für gefährliche Lebensmittel wurde für die entscheidende erste Minute entwickelt, nachdem ein Hund oder eine Katze etwas Unerlaubtes gefressen hat. Wählen Sie das Tier, öffnen Sie das passende Profil und nutzen Sie die Karte zur Vorbereitung des Telefonats mit der Tierarztpraxis. Die Struktur trennt den Grund der Gefahr von beobachtbaren Symptomen und empfohlenen Handlungen. Das erleichtert die Übersicht in einer anstrengenden Situation und spart wertvolle Minuten vor der Abfahrt in die Praxis.",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'paragraph',
|
|
106
|
+
html: "Die Wahl der Tierart ist essenziell. Hunde und Katzen verstoffwechseln Substanzen unterschiedlich. Xylit verursacht bei Hunden einen schnellen Abfall des Blutzuckers, während bei Katzen Zwiebeln und Knoblauch durch eine höhere Empfindlichkeit der roten Blutkörperchen auffallen. Eine genaue Einordnung hilft dabei, Fehlinformationen in Foren vorzubeugen und die richtigen Maßnahmen zu ergreifen.",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: 'title',
|
|
110
|
+
text: "Häufige Gefahren und wichtige Details",
|
|
111
|
+
level: 2,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'table',
|
|
115
|
+
headers: ["Lebensmittel oder Zutat","Hauptbedenken","Wichtiger Kontext"],
|
|
116
|
+
rows: [["Schokolade und Koffein","Herz- und Nervensystemwirkungen","Kakaogehalt, Produktart, Menge und Körpergewicht sind entscheidend. Die Dosis bestimmt die Schwere der Vergiftung."],["Xylit","Schneller Blutzuckerabfall bei Hunden","Zuckerfreie Kaugummis, Süßwaren und Gebäck prüfen. Bereits geringe Mengen können lebensbedrohlich sein."],["Weintrauben und Rosinen","Mögliche Nierenschädigung bei Hunden","Auch kleine Mengen nicht als harmlos einstufen. Das Nierenversagen kann verzögert eintreten."],["Zwiebel und Knoblauch","Schädigung roter Blutkörperchen","Gekochte, getrocknete und pulverisierte Formen beachten. Die Anämie entwickelt sich oft erst nach Tagen."],["Roher Hefeteig","Magenausdehnung und Alkoholbildung","Aufgehender Teig erzeugt Druck und Gärungsprodukte. Es droht eine gefährliche Magendrehung."]],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'paragraph',
|
|
120
|
+
html: "Eine Exposition kann auch aus mechanischen oder physikalischen Gründen gefährlich sein. Hefeteig geht im Magen auf, fettige Speisen können eine Bauchspeicheldrüsenentzündung begünstigen und Knochen bergen Erstickungs- sowie Verletzungsgefahren. Eine sorgfältige Beobachtung ist unerlässlich.",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: 'title',
|
|
124
|
+
text: "Maßnahmen nach einer möglichen Exposition",
|
|
125
|
+
level: 2,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: 'list',
|
|
129
|
+
items: [
|
|
130
|
+
"Reste entfernen und Verpackung sichern.",
|
|
131
|
+
"Tierart, Gewicht, Produkt, Menge und Zeitpunkt notieren.",
|
|
132
|
+
"Tierarzt oder Giftnotruf anrufen.",
|
|
133
|
+
"Kein Erbrechen ohne tierärztliche Anweisung einleiten.",
|
|
134
|
+
"Bei Kollaps, Atemnot oder Krämpfen sofort die Notfallklinik aufsuchen."
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'tip',
|
|
139
|
+
title: "Das Wohl des Tieres steht an erster Stelle",
|
|
140
|
+
html: "Symptome können verzögert auftreten. Suchen Sie bei Vergiftungsverdacht stets professionelle Hilfe auf.",
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
const schemas: PetToxicityLocaleContent['schemas'] = [
|
|
145
|
+
{
|
|
146
|
+
'@context': 'https://schema.org',
|
|
147
|
+
'@type': 'SoftwareApplication',
|
|
148
|
+
name: title,
|
|
149
|
+
description,
|
|
150
|
+
applicationCategory: 'HealthApplication',
|
|
151
|
+
operatingSystem: 'Web',
|
|
152
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
153
|
+
} as WithContext<SoftwareApplication>,
|
|
154
|
+
{
|
|
155
|
+
'@context': 'https://schema.org',
|
|
156
|
+
'@type': 'FAQPage',
|
|
157
|
+
mainEntity: faq.map((item) => ({
|
|
158
|
+
'@type': 'Question',
|
|
159
|
+
name: item.question,
|
|
160
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
161
|
+
})),
|
|
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: PetToxicityLocaleContent = {
|
|
172
|
+
slug,
|
|
173
|
+
title,
|
|
174
|
+
description,
|
|
175
|
+
ui,
|
|
176
|
+
seo,
|
|
177
|
+
faq,
|
|
178
|
+
bibliography,
|
|
179
|
+
howTo,
|
|
180
|
+
schemas,
|
|
181
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
3
|
+
import type { PetToxicityLocaleContent, PetToxicityUI } from '../entry';
|
|
4
|
+
|
|
5
|
+
const slug = 'pet-food-toxicity-checker';
|
|
6
|
+
const title = 'Pet Food Toxicity Checker';
|
|
7
|
+
const description = 'Check common food exposures for dogs and cats, understand the main warning signs, and know when to contact a veterinarian or animal poison service.';
|
|
8
|
+
|
|
9
|
+
const ui: PetToxicityUI = {
|
|
10
|
+
journeyHint: 'Choose the animal, open the food atlas, and read the species-specific warning before deciding what to do next.',
|
|
11
|
+
speciesLabel: 'Animal',
|
|
12
|
+
speciesDog: 'Dog',
|
|
13
|
+
speciesCat: 'Cat',
|
|
14
|
+
speciesDogMeta: 'Dog profile',
|
|
15
|
+
speciesCatMeta: 'Cat profile',
|
|
16
|
+
foodLabel: 'Food or ingredient',
|
|
17
|
+
foodPlaceholder: 'Choose a food',
|
|
18
|
+
foodMenuHint: 'The list changes with the animal because risk is species-specific.',
|
|
19
|
+
resultEyebrow: 'The food atlas says',
|
|
20
|
+
resultEmpty: 'Choose a food to open its safety card.',
|
|
21
|
+
sceneLabel: 'Exposure map',
|
|
22
|
+
sceneReady: 'Choose a food',
|
|
23
|
+
sceneSelected: 'species-specific card open',
|
|
24
|
+
riskCritical: 'Urgent concern',
|
|
25
|
+
riskHigh: 'High concern',
|
|
26
|
+
riskCaution: 'Caution',
|
|
27
|
+
riskUnknown: 'Not enough data',
|
|
28
|
+
riskLabel: 'Risk level',
|
|
29
|
+
whyLabel: 'Why it matters',
|
|
30
|
+
signsLabel: 'Signs to watch',
|
|
31
|
+
actionLabel: 'What to do',
|
|
32
|
+
callVetLabel: 'The safest next step is professional advice.',
|
|
33
|
+
callVetText: 'A calculator cannot estimate a toxic dose, confirm an exposure, or replace an examination.',
|
|
34
|
+
sourceLabel: 'Evidence used',
|
|
35
|
+
sourceText: 'The food profiles are based on poison control and veterinary toxicology guidance from the ASPCA, FDA, and Merck Veterinary Manual. The guide is educational and does not calculate treatments or safe doses.',
|
|
36
|
+
emergencyTitle: 'If your pet has eaten something concerning',
|
|
37
|
+
emergencyText: 'Call your veterinarian, an emergency animal clinic, or an animal poison service now. Keep the package, ingredient list, estimated amount, and time of exposure. Do not induce vomiting or give a home remedy unless a veterinary professional tells you to.',
|
|
38
|
+
openMenu: 'Open options',
|
|
39
|
+
closeMenu: 'Close options',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const faq: PetToxicityLocaleContent['faq'] = [
|
|
43
|
+
{
|
|
44
|
+
question: 'Can this pet food toxicity checker tell me whether my pet is safe?',
|
|
45
|
+
answer: 'No. It is an educational guide to common food hazards, not a diagnosis or dose calculator. The risk depends on the species, product, amount, body size, health history, and time since exposure. If your pet has eaten a concerning food, contact a veterinarian or animal poison service even when the animal looks normal.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
question: 'What foods are especially dangerous for dogs?',
|
|
49
|
+
answer: 'Common serious concerns include xylitol, chocolate and caffeine, grapes and raisins, onions and garlic, alcohol, macadamia nuts, and raw yeast dough. The list is not complete. A product label and an accurate estimate of the amount help a veterinary professional assess the situation.',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
question: 'Are onions and garlic dangerous for cats?',
|
|
53
|
+
answer: 'Yes. Veterinary toxicology guidance identifies cats as especially susceptible to red blood cell damage from allium plants such as onions, garlic, chives, and leeks. Raw, cooked, dried, powdered, and concentrated forms can matter. Delayed signs are possible, so contact a veterinarian after a known exposure instead of waiting for weakness or pale gums.',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
question: 'Why does xylitol show a different warning for cats and dogs?',
|
|
57
|
+
answer: 'Xylitol is associated with a rapid and potentially life-threatening fall in blood sugar in dogs. FDA guidance describes a much clearer danger for dogs than for cats, but that difference is not a promise that every product is safe for a cat. Check the ingredient list and ask a veterinarian about any exposure.',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
question: 'What should I do if my pet ate a food that is not listed?',
|
|
61
|
+
answer: 'Do not treat an absent entry as proof of safety. Save the packaging, identify the ingredients, note the amount and time, and contact a veterinarian or animal poison service. The guide intentionally keeps its profiles narrow where species-specific evidence is important.',
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const howTo: PetToxicityLocaleContent['howTo'] = [
|
|
66
|
+
{ name: 'Choose the animal', text: 'Select Dog or Cat so the food atlas shows the relevant species-specific evidence and warning.' },
|
|
67
|
+
{ name: 'Choose the food', text: 'Open the food or ingredient menu and choose the closest match. Read the detail line because concentrated, cooked, powdered, and mixed products can change the context.' },
|
|
68
|
+
{ name: 'Read the safety card', text: 'Review why the food matters, which general signs may appear, and the recommended next step. A caution label is not a safe-dose calculation.' },
|
|
69
|
+
{ name: 'Contact a professional when needed', text: 'If exposure happened, contact a veterinarian, emergency animal clinic, or animal poison service with the product, amount, time, and pet details. Do not use a home remedy unless instructed.' },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const seo: PetToxicityLocaleContent['seo'] = [
|
|
73
|
+
{
|
|
74
|
+
type: 'summary',
|
|
75
|
+
title: 'A quick food safety check for dogs and cats',
|
|
76
|
+
items: [
|
|
77
|
+
'Choose the species first because the same ingredient can create different concerns for dogs and cats.',
|
|
78
|
+
'Use the food cards to understand the hazard, common warning signs, and the appropriate urgency.',
|
|
79
|
+
'Treat xylitol exposure in dogs as an urgent concern and do not wait for symptoms.',
|
|
80
|
+
'Contact a veterinarian or animal poison service for a real exposure. The checker does not calculate doses or treatments.',
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'title',
|
|
85
|
+
text: 'How to use a pet food toxicity checker responsibly',
|
|
86
|
+
level: 2,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'paragraph',
|
|
90
|
+
html: 'This pet food toxicity checker is built for the anxious first minute after a dog or cat finds something it should not eat. Choose the animal, open the closest food profile, and use the card to organize the next conversation with a veterinary professional. The result separates the reason a food matters from the signs an owner might notice and the action that makes sense. That structure is more useful than a single green or red label because food exposures are not determined by the name of an ingredient alone.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'paragraph',
|
|
94
|
+
html: 'The species selector is essential. Dogs and cats do not process every substance in the same way, and a difference in evidence must not be mistaken for a guarantee of safety. Xylitol is a clear example: it can cause a rapid and dangerous blood sugar problem in dogs, while available public guidance describes a less established risk for cats. Onions and garlic show the opposite kind of nuance because cats are especially susceptible to red blood cell damage. The food list therefore changes with the animal rather than pretending that one universal table is enough.',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'title',
|
|
98
|
+
text: 'Common food hazards and the details that change the answer',
|
|
99
|
+
level: 2,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: 'table',
|
|
103
|
+
headers: ['Food or ingredient', 'Main concern', 'Important context'],
|
|
104
|
+
rows: [
|
|
105
|
+
['Chocolate and caffeine', 'Heart and nervous system effects', 'Cocoa concentration, product type, amount, and body size matter.'],
|
|
106
|
+
['Xylitol', 'Rapid low blood sugar in dogs', 'Check sugar-free products, gum, sweets, baked goods, and some nut butters.'],
|
|
107
|
+
['Grapes and raisins', 'Possible kidney injury in dogs', 'Do not assume a small amount is harmless because sensitivity varies.'],
|
|
108
|
+
['Onion and garlic', 'Red blood cell damage and anemia', 'Raw, cooked, dried, powdered, and concentrated forms can matter.'],
|
|
109
|
+
['Raw yeast dough', 'Expansion and alcohol production', 'A rising dough can create both pressure and fermentation risk.'],
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: 'paragraph',
|
|
114
|
+
html: 'A food exposure can also be dangerous for a reason that is not classic poisoning. Raw dough may expand and ferment. Fatty foods can trigger digestive upset or pancreatitis risk in some animals. Bones, pits, and corn cobs can obstruct the digestive tract. If the item is not in this checker, that is a signal to ask a professional, not permission to wait. Keep the original packaging because brand recipes, sweetener names, cocoa concentration, and serving size can change the assessment.',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: 'title',
|
|
118
|
+
text: 'What to do after a possible exposure',
|
|
119
|
+
level: 2,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'list',
|
|
123
|
+
items: [
|
|
124
|
+
'Move the remaining food away and keep the package or ingredient list.',
|
|
125
|
+
'Write down the pet species, approximate weight, food, amount, and time of exposure.',
|
|
126
|
+
'Call a veterinarian, emergency animal clinic, or animal poison service for case-specific advice.',
|
|
127
|
+
'Do not induce vomiting, give milk, give charcoal, or use another home remedy unless a professional directs you.',
|
|
128
|
+
'Seek urgent help for collapse, seizures, breathing trouble, repeated vomiting, pale gums, severe weakness, or a swollen painful abdomen.',
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'tip',
|
|
133
|
+
title: 'The animal comes before the calendar',
|
|
134
|
+
html: 'Symptoms can be delayed, absent at first, or caused by an ingredient that the checker does not cover. If something happened in real life, professional advice is the result you need. This tool helps you prepare useful information for that call; it does not replace the call.',
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const schemas: PetToxicityLocaleContent['schemas'] = [
|
|
139
|
+
{
|
|
140
|
+
'@context': 'https://schema.org',
|
|
141
|
+
'@type': 'SoftwareApplication',
|
|
142
|
+
name: title,
|
|
143
|
+
description,
|
|
144
|
+
applicationCategory: 'HealthApplication',
|
|
145
|
+
operatingSystem: 'Web',
|
|
146
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
147
|
+
} as WithContext<SoftwareApplication>,
|
|
148
|
+
{
|
|
149
|
+
'@context': 'https://schema.org',
|
|
150
|
+
'@type': 'FAQPage',
|
|
151
|
+
mainEntity: faq.map((item) => ({
|
|
152
|
+
'@type': 'Question',
|
|
153
|
+
name: item.question,
|
|
154
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
155
|
+
})),
|
|
156
|
+
} as WithContext<FAQPage>,
|
|
157
|
+
{
|
|
158
|
+
'@context': 'https://schema.org',
|
|
159
|
+
'@type': 'HowTo',
|
|
160
|
+
name: title,
|
|
161
|
+
step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })),
|
|
162
|
+
} as WithContext<HowTo>,
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
export const content: PetToxicityLocaleContent = {
|
|
166
|
+
slug,
|
|
167
|
+
title,
|
|
168
|
+
description,
|
|
169
|
+
ui,
|
|
170
|
+
seo,
|
|
171
|
+
faq,
|
|
172
|
+
bibliography,
|
|
173
|
+
howTo,
|
|
174
|
+
schemas,
|
|
175
|
+
};
|