@jjlmoya/utils-aquarium 1.59.0 → 1.61.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 (65) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +4 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/i18n_coverage.test.ts +1 -1
  6. package/src/tests/registry_contract.test.ts +67 -0
  7. package/src/tests/tool_validation.test.ts +4 -3
  8. package/src/tool/aquariumEvaporationTopOffPlanner/aquarium-evaporation-top-off-planner.css +490 -0
  9. package/src/tool/aquariumEvaporationTopOffPlanner/bibliography.astro +6 -0
  10. package/src/tool/aquariumEvaporationTopOffPlanner/bibliography.ts +10 -0
  11. package/src/tool/aquariumEvaporationTopOffPlanner/component.astro +61 -0
  12. package/src/tool/aquariumEvaporationTopOffPlanner/controller.ts +232 -0
  13. package/src/tool/aquariumEvaporationTopOffPlanner/dom-views.ts +39 -0
  14. package/src/tool/aquariumEvaporationTopOffPlanner/entry.ts +27 -0
  15. package/src/tool/aquariumEvaporationTopOffPlanner/evaluator.ts +9 -0
  16. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/de.ts +3 -0
  17. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/en.ts +179 -0
  18. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/es.ts +3 -0
  19. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/fr.ts +3 -0
  20. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/id.ts +3 -0
  21. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/it.ts +3 -0
  22. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/ja.ts +3 -0
  23. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/ko.ts +3 -0
  24. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/nl.ts +3 -0
  25. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/pl.ts +3 -0
  26. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/pt.ts +3 -0
  27. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/ru.ts +3 -0
  28. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/shared.ts +79 -0
  29. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/sv.ts +3 -0
  30. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/tr.ts +3 -0
  31. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/translations.ts +509 -0
  32. package/src/tool/aquariumEvaporationTopOffPlanner/i18n/zh.ts +3 -0
  33. package/src/tool/aquariumEvaporationTopOffPlanner/index.ts +11 -0
  34. package/src/tool/aquariumEvaporationTopOffPlanner/logic.test.ts +61 -0
  35. package/src/tool/aquariumEvaporationTopOffPlanner/logic.ts +102 -0
  36. package/src/tool/aquariumEvaporationTopOffPlanner/seo.astro +13 -0
  37. package/src/tool/aquariumEvaporationTopOffPlanner/storage.ts +28 -0
  38. package/src/tool/aquariumEvaporationTopOffPlanner/ui.ts +49 -0
  39. package/src/tool/aquariumLightingPhotoperiodPlanner/aquarium-lighting-photoperiod-planner.css +9 -9
  40. package/src/tool/aquariumLightingPhotoperiodPlanner/component.astro +3 -1
  41. package/src/tool/aquariumLightingPhotoperiodPlanner/controller.ts +21 -9
  42. package/src/tool/aquariumLightingPhotoperiodPlanner/dom-views.test.ts +2 -1
  43. package/src/tool/aquariumLightingPhotoperiodPlanner/dom-views.ts +20 -8
  44. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/de.ts +2 -0
  45. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/en.ts +2 -0
  46. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/es.ts +2 -0
  47. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/fr.ts +2 -0
  48. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/id.ts +2 -0
  49. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/it.ts +2 -0
  50. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/ja.ts +2 -0
  51. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/ko.ts +2 -0
  52. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/nl.ts +2 -0
  53. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/pl.ts +2 -0
  54. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/pt.ts +2 -0
  55. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/ru.ts +2 -0
  56. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/sv.ts +2 -0
  57. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/tr.ts +2 -0
  58. package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/zh.ts +4 -2
  59. package/src/tool/aquariumLightingPhotoperiodPlanner/logic.test.ts +10 -0
  60. package/src/tool/aquariumLightingPhotoperiodPlanner/logic.ts +10 -0
  61. package/src/tool/aquariumLightingPhotoperiodPlanner/storage.ts +12 -8
  62. package/src/tool/aquariumLightingPhotoperiodPlanner/ui.ts +2 -0
  63. package/src/tool/aquariumTankVolumeWaterChangeCalculator/i18n/fr.ts +1 -1
  64. package/src/tool/aquariumTankVolumeWaterChangeCalculator/i18n/it.ts +1 -1
  65. package/src/tools.ts +6 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-aquarium",
3
- "version": "1.59.0",
3
+ "version": "1.61.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -2,10 +2,11 @@ import type { AquariumCategoryEntry } from '../types';
2
2
  import { aquariumTankVolumeWaterChangeCalculator } from '../tool/aquariumTankVolumeWaterChangeCalculator/entry';
3
3
  import { aquariumFilterTurnoverStockingPlanner } from '../tool/aquariumFilterTurnoverStockingPlanner/entry';
4
4
  import { aquariumLightingPhotoperiodPlanner } from '../tool/aquariumLightingPhotoperiodPlanner/entry';
5
+ import { aquariumEvaporationTopOffPlanner } from '../tool/aquariumEvaporationTopOffPlanner/entry';
5
6
 
6
7
  export const aquariumCategory: AquariumCategoryEntry = {
7
8
  icon: 'mdi:fishbowl-outline',
8
- tools: [aquariumTankVolumeWaterChangeCalculator, aquariumFilterTurnoverStockingPlanner, aquariumLightingPhotoperiodPlanner],
9
+ tools: [aquariumTankVolumeWaterChangeCalculator, aquariumFilterTurnoverStockingPlanner, aquariumLightingPhotoperiodPlanner, aquariumEvaporationTopOffPlanner],
9
10
  i18n: {
10
11
  en: () => import('./i18n/en').then((m) => m.content),
11
12
  es: () => import('./i18n/es').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -4,10 +4,13 @@ export { aquariumFilterTurnoverStockingPlanner } from './tool/aquariumFilterTurn
4
4
  export type { AquariumFilterTurnoverStockingPlannerLocaleContent } from './tool/aquariumFilterTurnoverStockingPlanner/entry';
5
5
  export { aquariumLightingPhotoperiodPlanner } from './tool/aquariumLightingPhotoperiodPlanner/entry';
6
6
  export type { AquariumLightingPhotoperiodPlannerLocaleContent } from './tool/aquariumLightingPhotoperiodPlanner/entry';
7
+ export { aquariumEvaporationTopOffPlanner } from './tool/aquariumEvaporationTopOffPlanner/entry';
8
+ export type { AquariumEvaporationTopOffPlannerLocaleContent } from './tool/aquariumEvaporationTopOffPlanner/entry';
7
9
  export { aquariumCategory } from './category';
8
10
 
9
11
  import { aquariumTankVolumeWaterChangeCalculator } from './tool/aquariumTankVolumeWaterChangeCalculator/entry';
10
12
  import { aquariumFilterTurnoverStockingPlanner } from './tool/aquariumFilterTurnoverStockingPlanner/entry';
11
13
  import { aquariumLightingPhotoperiodPlanner } from './tool/aquariumLightingPhotoperiodPlanner/entry';
14
+ import { aquariumEvaporationTopOffPlanner } from './tool/aquariumEvaporationTopOffPlanner/entry';
12
15
 
13
- export const ALL_ENTRIES = [aquariumTankVolumeWaterChangeCalculator, aquariumFilterTurnoverStockingPlanner, aquariumLightingPhotoperiodPlanner];
16
+ export const ALL_ENTRIES = [aquariumTankVolumeWaterChangeCalculator, aquariumFilterTurnoverStockingPlanner, aquariumLightingPhotoperiodPlanner, aquariumEvaporationTopOffPlanner];
package/src/index.ts CHANGED
@@ -20,3 +20,5 @@ export { AQUARIUM_TANK_VOLUME_WATER_CHANGE_CALCULATOR_TOOL, aquariumTankVolumeWa
20
20
  export type { AquariumTankVolumeWaterChangeCalculatorUI, AquariumTankVolumeWaterChangeCalculatorLocaleContent } from './tool/aquariumTankVolumeWaterChangeCalculator/index';
21
21
  export { AQUARIUM_FILTER_TURNOVER_STOCKING_PLANNER_TOOL, aquariumFilterTurnoverStockingPlanner } from './tool/aquariumFilterTurnoverStockingPlanner/index';
22
22
  export type { AquariumFilterTurnoverStockingPlannerUI, AquariumFilterTurnoverStockingPlannerLocaleContent } from './tool/aquariumFilterTurnoverStockingPlanner/index';
23
+ export { AQUARIUM_EVAPORATION_TOP_OFF_PLANNER_TOOL, aquariumEvaporationTopOffPlanner } from './tool/aquariumEvaporationTopOffPlanner/index';
24
+ export type { AquariumEvaporationTopOffPlannerUI, AquariumEvaporationTopOffPlannerLocaleContent } from './tool/aquariumEvaporationTopOffPlanner/index';
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
  import { ALL_TOOLS } from '../tools';
3
3
 
4
4
  const EXPECTED_LOCALES = ['de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'];
5
- const ENGLISH_FIRST_TOOL_IDS = new Set<string>([]);
5
+ const ENGLISH_FIRST_TOOL_IDS = new Set<string>();
6
6
 
7
7
  describe('I18n Coverage Validation', () => {
8
8
  ALL_TOOLS.forEach(({ entry }) => {
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { existsSync, readdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { pathToFileURL } from 'url';
5
+ import { ALL_ENTRIES } from '../entries';
6
+ import { ALL_TOOLS } from '../tools';
7
+ import type { ToolDefinition } from '../types';
8
+
9
+ function isToolDefinition(value: unknown): value is ToolDefinition {
10
+ if (!value || typeof value !== 'object') return false;
11
+
12
+ const candidate = value as Record<string, unknown>;
13
+ return Boolean(
14
+ candidate.entry &&
15
+ typeof candidate.entry === 'object' &&
16
+ typeof candidate.Component === 'function' &&
17
+ typeof candidate.SEOComponent === 'function' &&
18
+ typeof candidate.BibliographyComponent === 'function',
19
+ );
20
+ }
21
+
22
+ const toolRoot = join(process.cwd(), 'src', 'tool');
23
+ const toolDirectories = readdirSync(toolRoot, { withFileTypes: true })
24
+ .filter((entry) => entry.isDirectory() && existsSync(join(toolRoot, entry.name, 'index.ts')))
25
+ .map((entry) => entry.name)
26
+ .sort();
27
+
28
+ describe('Library registry contract', () => {
29
+ it('keeps ALL_ENTRIES and ALL_TOOLS synchronized by id', () => {
30
+ const entryIds = ALL_ENTRIES.map((entry) => entry.id);
31
+ const toolIds = ALL_TOOLS.map((tool) => tool.entry.id);
32
+
33
+ expect(new Set(entryIds).size).toBe(entryIds.length);
34
+ expect(new Set(toolIds).size).toBe(toolIds.length);
35
+ expect([...toolIds].sort()).toEqual([...entryIds].sort());
36
+ });
37
+
38
+ it('registers every tool runtime index in both public registries', async () => {
39
+ const failures: string[] = [];
40
+
41
+ for (const directory of toolDirectories) {
42
+ const runtimeModule = await import(
43
+ pathToFileURL(join(toolRoot, directory, 'index.ts')).href,
44
+ );
45
+ const definitions = [...new Set(Object.values(runtimeModule).filter(isToolDefinition))];
46
+
47
+ if (definitions.length !== 1) {
48
+ failures.push(`${directory}: expected exactly one ToolDefinition export, found ${definitions.length}`);
49
+ continue;
50
+ }
51
+
52
+ const [definition] = definitions;
53
+ if (!definition) {
54
+ failures.push(`${directory}: runtime definition is undefined`);
55
+ continue;
56
+ }
57
+ if (!ALL_TOOLS.some((tool) => tool.entry.id === definition.entry.id)) {
58
+ failures.push(`${directory}: runtime definition is missing from ALL_TOOLS`);
59
+ }
60
+ if (!ALL_ENTRIES.some((entry) => entry.id === definition.entry.id)) {
61
+ failures.push(`${directory}: runtime entry is missing from ALL_ENTRIES`);
62
+ }
63
+ }
64
+
65
+ expect(failures).toEqual([]);
66
+ });
67
+ });
@@ -3,13 +3,14 @@ import { ALL_TOOLS } from '../tools';
3
3
  import { aquariumCategory } from '../data';
4
4
 
5
5
  describe('Tool Validation Suite', () => {
6
- it('registers exactly three aquarium production tools', () => {
7
- expect(ALL_TOOLS.length).toBe(3);
8
- expect(aquariumCategory.tools).toHaveLength(3);
6
+ it('registers exactly four aquarium production tools', () => {
7
+ expect(ALL_TOOLS.length).toBe(4);
8
+ expect(aquariumCategory.tools).toHaveLength(4);
9
9
  expect(aquariumCategory.tools.map((tool) => tool.id)).toEqual([
10
10
  'aquarium-tank-volume-water-change-calculator',
11
11
  'aquarium-filter-turnover-stocking-planner',
12
12
  'aquarium-lighting-photoperiod-schedule-planner',
13
+ 'aquarium-evaporation-top-off-planner',
13
14
  ]);
14
15
  });
15
16
  });
@@ -0,0 +1,490 @@
1
+ .n-evaporation {
2
+ --n-paper: #eef5f2;
3
+ --n-panel: #fff;
4
+ --n-ink: #12383a;
5
+ --n-muted: #607979;
6
+ --n-line: #c8dcda;
7
+ --n-teal: #147d79;
8
+ --n-teal-dark: #0b5655;
9
+ --n-water: #56b9b5;
10
+ --n-sand: #e7b75e;
11
+ --n-loss: #f3dfbb;
12
+ --n-good: #dcefe5;
13
+ --n-alert: #9a5d12;
14
+ --n-shadow: rgba(19, 62, 62, 0.12);
15
+
16
+ display: grid;
17
+ grid-template-columns: minmax(320px, 0.95fr) minmax(360px, 1.05fr);
18
+ max-width: 1080px;
19
+ margin: 0 auto;
20
+ overflow: hidden;
21
+ color: var(--n-ink);
22
+ background: var(--n-paper);
23
+ border: 1px solid var(--n-line);
24
+ border-radius: 24px;
25
+ box-shadow: 0 14px 36px var(--n-shadow);
26
+ }
27
+
28
+ .theme-dark .n-evaporation {
29
+ --n-paper: #111b1c;
30
+ --n-panel: #1c292a;
31
+ --n-ink: #edf5f2;
32
+ --n-muted: #afc7c4;
33
+ --n-line: #355355;
34
+ --n-teal: #72d4cb;
35
+ --n-teal-dark: #a2e9e0;
36
+ --n-water: #318e93;
37
+ --n-sand: #edc66c;
38
+ --n-loss: #493c29;
39
+ --n-good: #234239;
40
+ --n-alert: #f2bf68;
41
+ --n-shadow: rgba(0, 0, 0, 0.34);
42
+ }
43
+
44
+ .n-evaporation *,
45
+ .n-evaporation *::before,
46
+ .n-evaporation *::after {
47
+ box-sizing: border-box;
48
+ }
49
+
50
+ .n-evaporation h2,
51
+ .n-evaporation h3,
52
+ .n-evaporation p {
53
+ margin: 0;
54
+ }
55
+
56
+ .n-output,
57
+ .n-controls {
58
+ min-width: 0;
59
+ align-self: start;
60
+ padding: clamp(22px, 3vw, 34px);
61
+ }
62
+
63
+ .n-controls {
64
+ background: var(--n-panel);
65
+ border-right: 1px solid var(--n-line);
66
+ grid-column: 1;
67
+ grid-row: 1;
68
+ }
69
+
70
+ .n-output {
71
+ grid-column: 2;
72
+ grid-row: 1;
73
+ }
74
+
75
+ .n-output-head,
76
+ .n-controls-head,
77
+ .n-schedule-head {
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: space-between;
81
+ gap: 16px;
82
+ }
83
+
84
+ .n-main-result {
85
+ display: grid;
86
+ gap: 4px;
87
+ max-width: 420px;
88
+ color: var(--n-ink);
89
+ letter-spacing: -0.04em;
90
+ line-height: 1;
91
+ }
92
+
93
+ .n-answer-label {
94
+ display: block;
95
+ color: var(--n-muted);
96
+ font-size: 0.72rem;
97
+ font-weight: 800;
98
+ letter-spacing: 0.04em;
99
+ text-transform: uppercase;
100
+ }
101
+
102
+ .n-main-result strong {
103
+ color: var(--n-teal-dark);
104
+ font-size: clamp(2.25rem, 4vw, 3.25rem);
105
+ font-weight: 850;
106
+ }
107
+
108
+ .n-main-result span {
109
+ color: var(--n-ink);
110
+ font-size: clamp(1rem, 2vw, 1.35rem);
111
+ font-weight: 750;
112
+ letter-spacing: -0.02em;
113
+ }
114
+
115
+ .n-status {
116
+ flex: 0 0 auto;
117
+ width: 11px;
118
+ height: 11px;
119
+ overflow: hidden;
120
+ color: transparent;
121
+ background: var(--n-teal);
122
+ border-radius: 50%;
123
+ }
124
+
125
+ .n-scene {
126
+ margin-top: 22px;
127
+ padding: 8px;
128
+ background: var(--n-panel);
129
+ border: 1px solid var(--n-line);
130
+ border-radius: 18px;
131
+ }
132
+
133
+ .n-evaporation-scene {
134
+ display: block;
135
+ width: 100%;
136
+ height: 220px;
137
+ }
138
+
139
+ .n-tank-frame {
140
+ fill: var(--n-panel);
141
+ stroke: var(--n-teal-dark);
142
+ stroke-width: 3;
143
+ }
144
+
145
+ .n-water {
146
+ fill: var(--n-water);
147
+ }
148
+
149
+ .n-loss-zone {
150
+ fill: var(--n-loss);
151
+ opacity: 0.78;
152
+ }
153
+
154
+ .n-waterline,
155
+ .n-mark {
156
+ fill: none;
157
+ stroke: var(--n-sand);
158
+ stroke-width: 3;
159
+ }
160
+
161
+ .n-mark-label,
162
+ .n-scene-label,
163
+ .n-scene-caption {
164
+ fill: var(--n-muted);
165
+ text-anchor: middle;
166
+ }
167
+
168
+ .n-mark-label {
169
+ font-size: 13px;
170
+ text-anchor: end;
171
+ }
172
+
173
+ .n-scene-label {
174
+ fill: var(--n-teal-dark);
175
+ font-size: 21px;
176
+ font-weight: 850;
177
+ }
178
+
179
+ .n-scene-caption {
180
+ font-size: 13px;
181
+ }
182
+
183
+ .n-stat-grid {
184
+ display: grid;
185
+ grid-template-columns: repeat(4, minmax(0, 1fr));
186
+ gap: 1px;
187
+ margin-top: 18px;
188
+ overflow: hidden;
189
+ background: var(--n-line);
190
+ border: 1px solid var(--n-line);
191
+ border-radius: 12px;
192
+ }
193
+
194
+ .n-stat {
195
+ display: grid;
196
+ min-height: 70px;
197
+ gap: 6px;
198
+ padding: 12px;
199
+ background: var(--n-panel);
200
+ }
201
+
202
+ .n-stat-feature {
203
+ background: var(--n-good);
204
+ }
205
+
206
+ .n-stat span,
207
+ .n-schedule-head span {
208
+ color: var(--n-muted);
209
+ font-size: 0.68rem;
210
+ }
211
+
212
+ .n-stat strong {
213
+ align-self: end;
214
+ color: var(--n-ink);
215
+ font-size: 1rem;
216
+ line-height: 1;
217
+ }
218
+
219
+ .n-stat-feature strong {
220
+ color: var(--n-teal-dark);
221
+ font-size: 1.12rem;
222
+ }
223
+
224
+ .n-alert {
225
+ margin-top: 12px;
226
+ padding: 10px 12px;
227
+ color: var(--n-alert);
228
+ background: var(--n-loss);
229
+ border: 1px solid var(--n-sand);
230
+ border-radius: 9px;
231
+ font-size: 0.72rem;
232
+ font-weight: 750;
233
+ line-height: 1.35;
234
+ }
235
+
236
+ .n-alert[hidden],
237
+ .n-field[hidden],
238
+ .n-mini-toggle button[hidden] {
239
+ display: none;
240
+ }
241
+
242
+ .n-schedule-head {
243
+ align-items: baseline;
244
+ margin-top: 22px;
245
+ padding-bottom: 9px;
246
+ border-bottom: 1px solid var(--n-line);
247
+ }
248
+
249
+ .n-schedule-head h3,
250
+ .n-controls-head h2 {
251
+ color: var(--n-ink);
252
+ font-size: 0.92rem;
253
+ }
254
+
255
+ .n-calendar {
256
+ display: grid;
257
+ grid-template-columns: repeat(7, minmax(0, 1fr));
258
+ gap: 5px;
259
+ margin-top: 11px;
260
+ }
261
+
262
+ .n-calendar-item {
263
+ display: grid;
264
+ min-height: 48px;
265
+ gap: 4px;
266
+ padding: 7px 6px;
267
+ color: var(--n-muted);
268
+ background: var(--n-panel);
269
+ border: 1px solid var(--n-line);
270
+ border-radius: 8px;
271
+ font-size: 0.68rem;
272
+ }
273
+
274
+ .n-calendar-item span {
275
+ color: var(--n-ink);
276
+ font-weight: 850;
277
+ }
278
+
279
+ .n-calendar-item small {
280
+ font-size: 0.61rem;
281
+ }
282
+
283
+ .n-calendar-item strong {
284
+ color: var(--n-teal-dark);
285
+ font-size: 0.61rem;
286
+ }
287
+
288
+ .n-calendar-item.is-refill {
289
+ background: var(--n-good);
290
+ border-color: var(--n-teal);
291
+ }
292
+
293
+ .n-controls-head {
294
+ padding-bottom: 20px;
295
+ border-bottom: 1px solid var(--n-line);
296
+ }
297
+
298
+ .n-unit-toggle {
299
+ display: flex;
300
+ flex: 0 0 auto;
301
+ padding: 3px;
302
+ background: var(--n-paper);
303
+ border: 1px solid var(--n-line);
304
+ border-radius: 9px;
305
+ }
306
+
307
+ .n-unit-toggle button {
308
+ padding: 7px 8px;
309
+ color: var(--n-muted);
310
+ background: transparent;
311
+ border: 0;
312
+ border-radius: 6px;
313
+ cursor: pointer;
314
+ font: inherit;
315
+ font-size: 0.67rem;
316
+ font-weight: 800;
317
+ }
318
+
319
+ .n-unit-toggle button.is-active,
320
+ .n-unit-toggle button:hover {
321
+ color: var(--n-teal-dark);
322
+ background: var(--n-good);
323
+ }
324
+
325
+ .n-fields {
326
+ display: grid;
327
+ grid-template-columns: repeat(2, minmax(0, 1fr));
328
+ gap: 18px 12px;
329
+ padding-top: 22px;
330
+ }
331
+
332
+ .n-field {
333
+ display: grid;
334
+ gap: 8px;
335
+ }
336
+
337
+ .n-field-head {
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: space-between;
341
+ gap: 7px;
342
+ }
343
+
344
+ .n-field-head label,
345
+ .n-water-type > span {
346
+ color: var(--n-ink);
347
+ font-size: 0.72rem;
348
+ font-weight: 800;
349
+ line-height: 1.2;
350
+ }
351
+
352
+ .n-field > span {
353
+ color: var(--n-ink);
354
+ font-size: 0.72rem;
355
+ font-weight: 800;
356
+ line-height: 1.2;
357
+ }
358
+
359
+ .n-field small {
360
+ color: var(--n-muted);
361
+ font-size: 0.65rem;
362
+ font-weight: 600;
363
+ }
364
+
365
+ .n-field input {
366
+ width: 100%;
367
+ min-height: 43px;
368
+ padding: 8px 10px;
369
+ color: var(--n-ink);
370
+ background: var(--n-paper);
371
+ border: 1px solid var(--n-line);
372
+ border-radius: 8px;
373
+ font: inherit;
374
+ font-size: 0.86rem;
375
+ }
376
+
377
+ .n-mini-toggle {
378
+ display: inline-flex;
379
+ flex: 0 0 auto;
380
+ padding: 2px;
381
+ background: var(--n-paper);
382
+ border: 1px solid var(--n-line);
383
+ border-radius: 7px;
384
+ }
385
+
386
+ .n-mini-toggle button {
387
+ padding: 4px 5px;
388
+ color: var(--n-muted);
389
+ background: transparent;
390
+ border: 0;
391
+ border-radius: 5px;
392
+ cursor: pointer;
393
+ font: inherit;
394
+ font-size: 0.59rem;
395
+ font-weight: 800;
396
+ }
397
+
398
+ .n-mini-toggle button.is-active,
399
+ .n-mini-toggle button:hover {
400
+ color: var(--n-teal-dark);
401
+ background: var(--n-good);
402
+ }
403
+
404
+ .n-advanced {
405
+ margin-top: 22px;
406
+ padding-top: 17px;
407
+ border-top: 1px solid var(--n-line);
408
+ }
409
+
410
+ .n-advanced summary {
411
+ color: var(--n-teal-dark);
412
+ cursor: pointer;
413
+ font-size: 0.72rem;
414
+ font-weight: 850;
415
+ }
416
+
417
+ .n-advanced-grid {
418
+ display: grid;
419
+ gap: 18px;
420
+ padding-top: 17px;
421
+ }
422
+
423
+ .n-water-type {
424
+ display: grid;
425
+ gap: 8px;
426
+ }
427
+
428
+ .n-unit-toggle button:focus-visible,
429
+ .n-field input:focus-visible {
430
+ outline: 3px solid var(--n-sand);
431
+ outline-offset: 3px;
432
+ }
433
+
434
+ @media (max-width: 760px) {
435
+ .n-evaporation {
436
+ grid-template-columns: 1fr;
437
+ }
438
+
439
+ .n-output,
440
+ .n-controls {
441
+ grid-column: 1;
442
+ grid-row: auto;
443
+ }
444
+
445
+ .n-controls {
446
+ order: 1;
447
+ border-top: 1px solid var(--n-line);
448
+ border-left: 0;
449
+ border-right: 0;
450
+ }
451
+
452
+ .n-output {
453
+ order: 2;
454
+ }
455
+
456
+ }
457
+
458
+ @media (max-width: 480px) {
459
+ .n-output,
460
+ .n-controls {
461
+ padding: 20px 16px;
462
+ }
463
+
464
+ .n-output-head,
465
+ .n-controls-head {
466
+ align-items: flex-start;
467
+ flex-direction: column;
468
+ }
469
+
470
+ .n-status {
471
+ display: none;
472
+ }
473
+
474
+ .n-stat {
475
+ min-height: 66px;
476
+ padding: 9px;
477
+ }
478
+
479
+ .n-stat-grid {
480
+ grid-template-columns: repeat(2, minmax(0, 1fr));
481
+ }
482
+
483
+ .n-calendar {
484
+ gap: 4px;
485
+ }
486
+
487
+ .n-calendar-item {
488
+ padding: 6px 4px;
489
+ }
490
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { bibliography } from './bibliography';
4
+ ---
5
+
6
+ <SharedBibliography links={bibliography} />
@@ -0,0 +1,10 @@
1
+ export const bibliography = [
2
+ {
3
+ name: 'FAO, Generalidades sobre las necesidades de agua para un estanque piscícola',
4
+ url: 'https://www.fao.org/fishery/static/FAO_Training/FAO_Training/General/x6705s/x6705s01.htm',
5
+ },
6
+ {
7
+ name: 'University of Florida IFAS, Marine Aquarium Project Book',
8
+ url: 'https://ask.ifas.ufl.edu/publication/4H433',
9
+ },
10
+ ];