@jjlmoya/utils-aquarium 1.60.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 (42) 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/i18n/zh.ts +2 -2
  40. package/src/tool/aquariumTankVolumeWaterChangeCalculator/i18n/fr.ts +1 -1
  41. package/src/tool/aquariumTankVolumeWaterChangeCalculator/i18n/it.ts +1 -1
  42. 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.60.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
+ ];