@jjlmoya/utils-cooking 1.2.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 (130) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +24 -0
  3. package/src/category/i18n/es.ts +208 -0
  4. package/src/category/i18n/fr.ts +24 -0
  5. package/src/category/index.ts +37 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +32 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/i18n-titles.test.ts +66 -0
  18. package/src/tests/locale_completeness.test.ts +42 -0
  19. package/src/tests/mocks/astro_mock.js +2 -0
  20. package/src/tests/no_h1_in_components.test.ts +48 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/tool_validation.test.ts +17 -0
  23. package/src/tool/american-kitchen-converter/AmericanKitchenEngine.ts +259 -0
  24. package/src/tool/american-kitchen-converter/bibliography.astro +6 -0
  25. package/src/tool/american-kitchen-converter/component.astro +838 -0
  26. package/src/tool/american-kitchen-converter/i18n/en.ts +282 -0
  27. package/src/tool/american-kitchen-converter/i18n/es.ts +281 -0
  28. package/src/tool/american-kitchen-converter/i18n/fr.ts +292 -0
  29. package/src/tool/american-kitchen-converter/index.ts +24 -0
  30. package/src/tool/american-kitchen-converter/seo.astro +8 -0
  31. package/src/tool/banana-ripeness/BananaCare.css +587 -0
  32. package/src/tool/banana-ripeness/BananaEngine.ts +79 -0
  33. package/src/tool/banana-ripeness/bibliography.astro +6 -0
  34. package/src/tool/banana-ripeness/component.astro +285 -0
  35. package/src/tool/banana-ripeness/i18n/en.ts +177 -0
  36. package/src/tool/banana-ripeness/i18n/es.ts +177 -0
  37. package/src/tool/banana-ripeness/i18n/fr.ts +177 -0
  38. package/src/tool/banana-ripeness/index.ts +24 -0
  39. package/src/tool/banana-ripeness/seo.astro +8 -0
  40. package/src/tool/brine/bibliography.astro +6 -0
  41. package/src/tool/brine/component.astro +884 -0
  42. package/src/tool/brine/i18n/en.ts +221 -0
  43. package/src/tool/brine/i18n/es.ts +222 -0
  44. package/src/tool/brine/i18n/fr.ts +221 -0
  45. package/src/tool/brine/index.ts +26 -0
  46. package/src/tool/brine/seo.astro +8 -0
  47. package/src/tool/cookware-guide/CookwareGuide.css +487 -0
  48. package/src/tool/cookware-guide/bibliography.astro +6 -0
  49. package/src/tool/cookware-guide/component.astro +164 -0
  50. package/src/tool/cookware-guide/i18n/en.ts +163 -0
  51. package/src/tool/cookware-guide/i18n/es.ts +163 -0
  52. package/src/tool/cookware-guide/i18n/fr.ts +164 -0
  53. package/src/tool/cookware-guide/index.ts +24 -0
  54. package/src/tool/cookware-guide/init.ts +174 -0
  55. package/src/tool/cookware-guide/seo.astro +8 -0
  56. package/src/tool/egg-timer/EggTimer.css +503 -0
  57. package/src/tool/egg-timer/bibliography.astro +14 -0
  58. package/src/tool/egg-timer/component.astro +281 -0
  59. package/src/tool/egg-timer/i18n/en.ts +230 -0
  60. package/src/tool/egg-timer/i18n/es.ts +222 -0
  61. package/src/tool/egg-timer/i18n/fr.ts +121 -0
  62. package/src/tool/egg-timer/index.ts +27 -0
  63. package/src/tool/egg-timer/seo.astro +39 -0
  64. package/src/tool/ingredient-rescaler/IngredientRescaler.css +308 -0
  65. package/src/tool/ingredient-rescaler/bibliography.astro +6 -0
  66. package/src/tool/ingredient-rescaler/component.astro +107 -0
  67. package/src/tool/ingredient-rescaler/i18n/en.ts +265 -0
  68. package/src/tool/ingredient-rescaler/i18n/es.ts +268 -0
  69. package/src/tool/ingredient-rescaler/i18n/fr.ts +207 -0
  70. package/src/tool/ingredient-rescaler/index.ts +24 -0
  71. package/src/tool/ingredient-rescaler/init.ts +200 -0
  72. package/src/tool/ingredient-rescaler/seo.astro +8 -0
  73. package/src/tool/kitchen-timer/KitchenTimer.css +325 -0
  74. package/src/tool/kitchen-timer/bibliography.astro +6 -0
  75. package/src/tool/kitchen-timer/component.astro +341 -0
  76. package/src/tool/kitchen-timer/i18n/en.ts +154 -0
  77. package/src/tool/kitchen-timer/i18n/es.ts +154 -0
  78. package/src/tool/kitchen-timer/i18n/fr.ts +154 -0
  79. package/src/tool/kitchen-timer/index.ts +26 -0
  80. package/src/tool/kitchen-timer/init.ts +55 -0
  81. package/src/tool/kitchen-timer/lib/AudioHelper.ts +27 -0
  82. package/src/tool/kitchen-timer/lib/DockManager.ts +97 -0
  83. package/src/tool/kitchen-timer/lib/KitchenTimer.ts +264 -0
  84. package/src/tool/kitchen-timer/seo.astro +8 -0
  85. package/src/tool/meringue-peak/MeringueCalculator.css +298 -0
  86. package/src/tool/meringue-peak/bibliography.astro +6 -0
  87. package/src/tool/meringue-peak/component.astro +169 -0
  88. package/src/tool/meringue-peak/i18n/en.ts +257 -0
  89. package/src/tool/meringue-peak/i18n/es.ts +234 -0
  90. package/src/tool/meringue-peak/i18n/fr.ts +234 -0
  91. package/src/tool/meringue-peak/index.ts +24 -0
  92. package/src/tool/meringue-peak/seo.astro +8 -0
  93. package/src/tool/mold-scaler/MoldScaler.css +406 -0
  94. package/src/tool/mold-scaler/bibliography.astro +6 -0
  95. package/src/tool/mold-scaler/component.astro +126 -0
  96. package/src/tool/mold-scaler/i18n/en.ts +268 -0
  97. package/src/tool/mold-scaler/i18n/es.ts +269 -0
  98. package/src/tool/mold-scaler/i18n/fr.ts +276 -0
  99. package/src/tool/mold-scaler/index.ts +26 -0
  100. package/src/tool/mold-scaler/init.ts +264 -0
  101. package/src/tool/mold-scaler/seo.astro +8 -0
  102. package/src/tool/pizza/Pizza.css +569 -0
  103. package/src/tool/pizza/bibliography.astro +6 -0
  104. package/src/tool/pizza/calculator.ts +143 -0
  105. package/src/tool/pizza/component.astro +237 -0
  106. package/src/tool/pizza/i18n/en.ts +288 -0
  107. package/src/tool/pizza/i18n/es.ts +289 -0
  108. package/src/tool/pizza/i18n/fr.ts +288 -0
  109. package/src/tool/pizza/index.ts +27 -0
  110. package/src/tool/pizza/seo.astro +8 -0
  111. package/src/tool/roux-guide/RouxGuide.css +483 -0
  112. package/src/tool/roux-guide/bibliography.astro +6 -0
  113. package/src/tool/roux-guide/component.astro +194 -0
  114. package/src/tool/roux-guide/i18n/en.ts +233 -0
  115. package/src/tool/roux-guide/i18n/es.ts +225 -0
  116. package/src/tool/roux-guide/i18n/fr.ts +225 -0
  117. package/src/tool/roux-guide/index.ts +24 -0
  118. package/src/tool/roux-guide/init.ts +187 -0
  119. package/src/tool/roux-guide/seo.astro +8 -0
  120. package/src/tool/sourdough-calculator/SourdoughCalculator.css +369 -0
  121. package/src/tool/sourdough-calculator/bibliography.astro +6 -0
  122. package/src/tool/sourdough-calculator/component.astro +198 -0
  123. package/src/tool/sourdough-calculator/i18n/en.ts +242 -0
  124. package/src/tool/sourdough-calculator/i18n/es.ts +243 -0
  125. package/src/tool/sourdough-calculator/i18n/fr.ts +248 -0
  126. package/src/tool/sourdough-calculator/index.ts +24 -0
  127. package/src/tool/sourdough-calculator/init.ts +131 -0
  128. package/src/tool/sourdough-calculator/seo.astro +8 -0
  129. package/src/tools.ts +29 -0
  130. package/src/types.ts +73 -0
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as DATA from '../data';
3
+
4
+ const ENTRIES = [
5
+ { id: 'cookingCategory', i18n: DATA.cookingCategory.i18n },
6
+ ];
7
+
8
+ describe('SEO Content Length Validation', () => {
9
+ ENTRIES.forEach((entry) => {
10
+ describe(`Tool: ${entry.id}`, () => {
11
+ Object.keys(entry.i18n).forEach((locale) => {
12
+ it(`${locale}: SEO section should exist`, async () => {
13
+ const loader = (entry.i18n as Record<string, () => Promise<{ seo?: unknown[] }>>)[locale];
14
+ const content = await loader();
15
+ if (!content.seo) return;
16
+ expect(Array.isArray(content.seo)).toBe(true);
17
+ });
18
+ });
19
+ });
20
+ });
21
+ });
22
+
@@ -0,0 +1,17 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import { cookingCategory } from '../data';
4
+
5
+ describe('Tool Validation Suite', () => {
6
+ describe('Library Registration', () => {
7
+ it('should have 12 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(12);
9
+ });
10
+
11
+ it('cookingCategory should be defined', () => {
12
+ expect(cookingCategory).toBeDefined();
13
+ expect(cookingCategory.i18n).toBeDefined();
14
+ });
15
+ });
16
+ });
17
+
@@ -0,0 +1,259 @@
1
+ export class AmericanKitchenEngine {
2
+ static initTabs() {
3
+ const tabs = document.querySelectorAll(".akc-tab-btn");
4
+ const contents = document.querySelectorAll(".akc-tab-content");
5
+ tabs.forEach((tab) => {
6
+ tab.addEventListener("click", () => {
7
+ const targetId = tab.getAttribute("data-tab");
8
+ if (!targetId) return;
9
+ tabs.forEach((t) => t.classList.remove("active"));
10
+ tab.classList.add("active");
11
+ contents.forEach((c) => {
12
+ if (c.id === `content-${targetId}`) {
13
+ c.classList.add("active");
14
+ } else {
15
+ c.classList.remove("active");
16
+ }
17
+ });
18
+ });
19
+ });
20
+ }
21
+ private static createCupUpdater(
22
+ cupInput: HTMLInputElement,
23
+ cupResult: HTMLElement,
24
+ density: { value: number }
25
+ ) {
26
+ return () => {
27
+ let val = parseFloat(cupInput.value);
28
+ if (isNaN(val) || val < 0) val = 0;
29
+ cupResult.textContent = Math.round(val * density.value).toString();
30
+ };
31
+ }
32
+ private static setupCupItems(
33
+ cupItems: NodeListOf<Element>,
34
+ density: { value: number },
35
+ updateFn: () => void
36
+ ) {
37
+ cupItems.forEach((item) => {
38
+ item.addEventListener("click", () => {
39
+ cupItems.forEach((i) => i.classList.remove("active"));
40
+ item.classList.add("active");
41
+ const densityVal = item.getAttribute("data-density");
42
+ if (densityVal) {
43
+ density.value = parseFloat(densityVal);
44
+ updateFn();
45
+ }
46
+ });
47
+ });
48
+ }
49
+ private static setupCupButtons(
50
+ cupInput: HTMLInputElement | null,
51
+ cupPlus: HTMLElement | null,
52
+ cupMinus: HTMLElement | null,
53
+ updateFn: () => void
54
+ ) {
55
+ if (cupPlus && cupInput) {
56
+ cupPlus.addEventListener("click", () => {
57
+ cupInput.value = (parseFloat(cupInput.value) + 0.25).toString();
58
+ updateFn();
59
+ });
60
+ }
61
+
62
+ if (cupMinus && cupInput) {
63
+ cupMinus.addEventListener("click", () => {
64
+ const newVal = parseFloat(cupInput.value) - 0.25;
65
+ if (newVal >= 0) {
66
+ cupInput.value = newVal.toString();
67
+ updateFn();
68
+ }
69
+ });
70
+ }
71
+ }
72
+ private static setupCupQuicks(
73
+ cupQuicks: NodeListOf<Element>,
74
+ cupInput: HTMLInputElement | null,
75
+ updateFn: () => void
76
+ ) {
77
+ cupQuicks.forEach((btn) => {
78
+ btn.addEventListener("click", () => {
79
+ const val = btn.getAttribute("data-val");
80
+ if (val && cupInput) {
81
+ cupInput.value = val;
82
+ updateFn();
83
+ }
84
+ });
85
+ });
86
+ }
87
+ static initCups() {
88
+ const cupInput = document.getElementById("cup-input") as HTMLInputElement | null;
89
+ const cupResult = document.getElementById("cup-result");
90
+ if (!cupInput || !cupResult) return;
91
+
92
+ const density = { value: 120 };
93
+ const updateCups = this.createCupUpdater(cupInput, cupResult, density);
94
+
95
+ cupInput.addEventListener("input", updateCups);
96
+ this.setupCupItems(document.querySelectorAll(".cup-item"), density, updateCups);
97
+ this.setupCupButtons(
98
+ cupInput,
99
+ document.getElementById("cup-plus"),
100
+ document.getElementById("cup-minus"),
101
+ updateCups
102
+ );
103
+ this.setupCupQuicks(document.querySelectorAll(".cup-quick"), cupInput, updateCups);
104
+ }
105
+ private static createSpoonUpdater(
106
+ spoonInput: HTMLInputElement,
107
+ spoonResult: HTMLElement,
108
+ state: { density: number; multiplier: number }
109
+ ) {
110
+ return () => {
111
+ let val = parseFloat(spoonInput.value);
112
+ if (isNaN(val) || val < 0) val = 0;
113
+ const result =
114
+ state.multiplier === 0.3333
115
+ ? ((val * state.density) / 3).toFixed(1)
116
+ : (val * state.density).toFixed(1);
117
+ spoonResult.textContent = result.replace(".0", "");
118
+ };
119
+ }
120
+ private static setupSpoonItems(
121
+ spoonItems: NodeListOf<Element>,
122
+ state: { density: number; multiplier: number },
123
+ updateFn: () => void
124
+ ) {
125
+ spoonItems.forEach((item) => {
126
+ item.addEventListener("click", () => {
127
+ spoonItems.forEach((i) => i.classList.remove("active"));
128
+ item.classList.add("active");
129
+ const density = item.getAttribute("data-density");
130
+ if (density) {
131
+ state.density = parseFloat(density);
132
+ updateFn();
133
+ }
134
+ });
135
+ });
136
+ }
137
+ private static setupSpoonToggles(
138
+ spoonToggles: NodeListOf<Element>,
139
+ state: { density: number; multiplier: number },
140
+ updateFn: () => void
141
+ ) {
142
+ spoonToggles.forEach((toggle) => {
143
+ toggle.addEventListener("click", () => {
144
+ spoonToggles.forEach((t) => t.classList.remove("active"));
145
+ toggle.classList.add("active");
146
+ const mult = toggle.getAttribute("data-multiplier");
147
+ if (mult) {
148
+ state.multiplier = parseFloat(mult);
149
+ updateFn();
150
+ }
151
+ });
152
+ });
153
+ }
154
+ private static setupSpoonButtons(
155
+ spoonInput: HTMLInputElement | null,
156
+ spoonPlus: HTMLElement | null,
157
+ spoonMinus: HTMLElement | null,
158
+ updateFn: () => void
159
+ ) {
160
+ if (spoonPlus && spoonInput) {
161
+ spoonPlus.addEventListener("click", () => {
162
+ spoonInput.value = (parseFloat(spoonInput.value) + 0.25).toString();
163
+ updateFn();
164
+ });
165
+ }
166
+
167
+ if (spoonMinus && spoonInput) {
168
+ spoonMinus.addEventListener("click", () => {
169
+ const newVal = parseFloat(spoonInput.value) - 0.25;
170
+ if (newVal >= 0) {
171
+ spoonInput.value = newVal.toString();
172
+ updateFn();
173
+ }
174
+ });
175
+ }
176
+ }
177
+ private static setupSpoonQuicks(
178
+ spoonQuicks: NodeListOf<Element>,
179
+ spoonInput: HTMLInputElement | null,
180
+ updateFn: () => void
181
+ ) {
182
+ spoonQuicks.forEach((btn) => {
183
+ btn.addEventListener("click", () => {
184
+ const val = btn.getAttribute("data-val");
185
+ if (val && spoonInput) {
186
+ spoonInput.value = val;
187
+ updateFn();
188
+ }
189
+ });
190
+ });
191
+ }
192
+ static initSpoons() {
193
+ const spoonInput = document.getElementById("spoon-input") as HTMLInputElement | null;
194
+ const spoonResult = document.getElementById("spoon-result");
195
+ if (!spoonInput || !spoonResult) return;
196
+
197
+ const state = { density: 15, multiplier: 1 };
198
+ const updateSpoons = this.createSpoonUpdater(spoonInput, spoonResult, state);
199
+
200
+ spoonInput.addEventListener("input", updateSpoons);
201
+ this.setupSpoonItems(document.querySelectorAll(".spoon-item"), state, updateSpoons);
202
+ this.setupSpoonToggles(document.querySelectorAll(".spoon-toggle"), state, updateSpoons);
203
+ this.setupSpoonButtons(
204
+ spoonInput,
205
+ document.getElementById("spoon-plus"),
206
+ document.getElementById("spoon-minus"),
207
+ updateSpoons
208
+ );
209
+ this.setupSpoonQuicks(document.querySelectorAll(".spoon-quick"), spoonInput, updateSpoons);
210
+ }
211
+ private static setupTempConversion(
212
+ tempF: HTMLInputElement,
213
+ tempC: HTMLInputElement
214
+ ) {
215
+ const updateF = () => {
216
+ const c = parseFloat(tempC.value);
217
+ if (isNaN(c)) {
218
+ tempF.value = "";
219
+ } else {
220
+ tempF.value = Math.round((c * 9) / 5 + 32).toString();
221
+ }
222
+ };
223
+
224
+ const updateC = () => {
225
+ const f = parseFloat(tempF.value);
226
+ if (isNaN(f)) {
227
+ tempC.value = "";
228
+ } else {
229
+ tempC.value = Math.round(((f - 32) * 5) / 9).toString();
230
+ }
231
+ };
232
+
233
+ tempF.addEventListener("input", updateC);
234
+ tempC.addEventListener("input", updateF);
235
+ }
236
+ private static setupTempPresets(
237
+ tempF: HTMLInputElement,
238
+ tempC: HTMLInputElement
239
+ ) {
240
+ const tempPresets = document.querySelectorAll(".temp-preset");
241
+ tempPresets.forEach((preset) => {
242
+ preset.addEventListener("click", () => {
243
+ const f = preset.getAttribute("data-f");
244
+ if (f) {
245
+ tempF.value = f;
246
+ const c = Math.round(((parseFloat(f) - 32) * 5) / 9);
247
+ tempC.value = c.toString();
248
+ }
249
+ });
250
+ });
251
+ }
252
+ static initTemperatures() {
253
+ const tempF = document.getElementById("temp-f-input") as HTMLInputElement | null;
254
+ const tempC = document.getElementById("temp-c-input") as HTMLInputElement | null;
255
+ if (!tempF || !tempC) return;
256
+
257
+ this.setupTempConversion(tempF, tempC);
258
+ this.setupTempPresets(tempF, tempC);
259
+ }}
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography } from "@jjlmoya/utils-shared";
3
+ import { content } from "./i18n/es";
4
+ ---
5
+
6
+ <Bibliography links={content.bibliography} />