@jjlmoya/utils-nature 1.20.0 → 1.22.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 -2
- package/src/entries.ts +5 -1
- package/src/index.ts +4 -0
- package/src/pages/[locale]/[slug].astro +5 -4
- package/src/tests/diacritics_density.test.ts +1 -1
- package/src/tests/inverted_punctuation.test.ts +1 -1
- package/src/tests/script_density.test.ts +1 -1
- package/src/tests/seo_length.test.ts +1 -0
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/title_quality.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +2 -3
- package/src/tests/translation_copy.test.ts +2 -2
- package/src/tool/compostBinVolumeRatioCalculator/bibliography.astro +18 -0
- package/src/tool/compostBinVolumeRatioCalculator/bibliography.ts +12 -0
- package/src/tool/compostBinVolumeRatioCalculator/component.astro +77 -0
- package/src/tool/compostBinVolumeRatioCalculator/compost-bin-volume-ratio-calculator.css +509 -0
- package/src/tool/compostBinVolumeRatioCalculator/controller.ts +189 -0
- package/src/tool/compostBinVolumeRatioCalculator/dom-views.ts +162 -0
- package/src/tool/compostBinVolumeRatioCalculator/entry.ts +28 -0
- package/src/tool/compostBinVolumeRatioCalculator/evaluator.ts +26 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/de.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/en.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/es.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/fr.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/id.ts +185 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/it.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/ja.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/ko.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/nl.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/pl.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/pt.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/ru.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/sv.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/tr.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/zh.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/index.ts +11 -0
- package/src/tool/compostBinVolumeRatioCalculator/logic.test.ts +55 -0
- package/src/tool/compostBinVolumeRatioCalculator/logic.ts +165 -0
- package/src/tool/compostBinVolumeRatioCalculator/seo.astro +23 -0
- package/src/tool/compostBinVolumeRatioCalculator/storage.ts +25 -0
- package/src/tool/compostBinVolumeRatioCalculator/ui.ts +54 -0
- package/src/tool/cricketThermometer/component.astro +1 -1
- package/src/tool/cricketThermometer/i18n/de.ts +2 -2
- package/src/tool/cricketThermometer/i18n/en.ts +2 -2
- package/src/tool/cricketThermometer/i18n/es.ts +2 -2
- package/src/tool/cricketThermometer/i18n/fr.ts +2 -2
- package/src/tool/cricketThermometer/i18n/id.ts +2 -2
- package/src/tool/cricketThermometer/i18n/it.ts +2 -2
- package/src/tool/cricketThermometer/i18n/ja.ts +2 -2
- package/src/tool/cricketThermometer/i18n/ko.ts +2 -2
- package/src/tool/cricketThermometer/i18n/nl.ts +2 -2
- package/src/tool/cricketThermometer/i18n/pl.ts +2 -2
- package/src/tool/cricketThermometer/i18n/pt.ts +2 -2
- package/src/tool/cricketThermometer/i18n/ru.ts +2 -2
- package/src/tool/cricketThermometer/i18n/sv.ts +2 -2
- package/src/tool/cricketThermometer/i18n/tr.ts +2 -2
- package/src/tool/cricketThermometer/i18n/zh.ts +2 -2
- package/src/tool/cricketThermometer/index.ts +1 -0
- package/src/tool/digitalCarbon/component.astro +3 -3
- package/src/tool/digitalCarbon/i18n/de.ts +2 -2
- package/src/tool/digitalCarbon/i18n/en.ts +2 -2
- package/src/tool/digitalCarbon/i18n/es.ts +2 -2
- package/src/tool/digitalCarbon/i18n/fr.ts +2 -2
- package/src/tool/digitalCarbon/i18n/id.ts +2 -2
- package/src/tool/digitalCarbon/i18n/it.ts +2 -2
- package/src/tool/digitalCarbon/i18n/ja.ts +2 -2
- package/src/tool/digitalCarbon/i18n/ko.ts +2 -2
- package/src/tool/digitalCarbon/i18n/nl.ts +2 -2
- package/src/tool/digitalCarbon/i18n/pl.ts +2 -2
- package/src/tool/digitalCarbon/i18n/pt.ts +2 -2
- package/src/tool/digitalCarbon/i18n/ru.ts +2 -2
- package/src/tool/digitalCarbon/i18n/sv.ts +2 -2
- package/src/tool/digitalCarbon/i18n/tr.ts +2 -2
- package/src/tool/digitalCarbon/i18n/zh.ts +2 -2
- package/src/tool/digitalCarbon/index.ts +1 -0
- package/src/tool/rainHarvester/i18n/de.ts +2 -2
- package/src/tool/rainHarvester/i18n/en.ts +2 -2
- package/src/tool/rainHarvester/i18n/es.ts +2 -2
- package/src/tool/rainHarvester/i18n/fr.ts +2 -2
- package/src/tool/rainHarvester/i18n/id.ts +2 -2
- package/src/tool/rainHarvester/i18n/it.ts +2 -2
- package/src/tool/rainHarvester/i18n/ja.ts +2 -2
- package/src/tool/rainHarvester/i18n/ko.ts +2 -2
- package/src/tool/rainHarvester/i18n/nl.ts +2 -2
- package/src/tool/rainHarvester/i18n/pl.ts +2 -2
- package/src/tool/rainHarvester/i18n/pt.ts +2 -2
- package/src/tool/rainHarvester/i18n/ru.ts +2 -2
- package/src/tool/rainHarvester/i18n/sv.ts +2 -2
- package/src/tool/rainHarvester/i18n/tr.ts +2 -2
- package/src/tool/rainHarvester/i18n/zh.ts +2 -2
- package/src/tool/rainHarvester/index.ts +1 -0
- package/src/tool/seedCalculator/i18n/de.ts +2 -2
- package/src/tool/seedCalculator/i18n/en.ts +2 -2
- package/src/tool/seedCalculator/i18n/es.ts +2 -2
- package/src/tool/seedCalculator/i18n/fr.ts +2 -2
- package/src/tool/seedCalculator/i18n/id.ts +2 -2
- package/src/tool/seedCalculator/i18n/it.ts +2 -2
- package/src/tool/seedCalculator/i18n/ja.ts +2 -2
- package/src/tool/seedCalculator/i18n/ko.ts +2 -2
- package/src/tool/seedCalculator/i18n/nl.ts +2 -2
- package/src/tool/seedCalculator/i18n/pl.ts +2 -2
- package/src/tool/seedCalculator/i18n/pt.ts +2 -2
- package/src/tool/seedCalculator/i18n/ru.ts +2 -2
- package/src/tool/seedCalculator/i18n/sv.ts +2 -2
- package/src/tool/seedCalculator/i18n/tr.ts +2 -2
- package/src/tool/seedCalculator/i18n/zh.ts +2 -2
- package/src/tool/seedCalculator/index.ts +1 -0
- package/src/tool/urbanGardenPlanner/bibliography.astro +9 -0
- package/src/tool/urbanGardenPlanner/bibliography.ts +16 -0
- package/src/tool/urbanGardenPlanner/component.astro +131 -0
- package/src/tool/urbanGardenPlanner/controller.ts +88 -0
- package/src/tool/urbanGardenPlanner/dom-views.ts +84 -0
- package/src/tool/urbanGardenPlanner/entry.ts +29 -0
- package/src/tool/urbanGardenPlanner/evaluator.ts +27 -0
- package/src/tool/urbanGardenPlanner/i18n/de.ts +50 -0
- package/src/tool/urbanGardenPlanner/i18n/en.ts +179 -0
- package/src/tool/urbanGardenPlanner/i18n/es.ts +50 -0
- package/src/tool/urbanGardenPlanner/i18n/fr.ts +50 -0
- package/src/tool/urbanGardenPlanner/i18n/id.ts +50 -0
- package/src/tool/urbanGardenPlanner/i18n/it.ts +45 -0
- package/src/tool/urbanGardenPlanner/i18n/ja.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/ko.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/nl.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/pl.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/pt.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/ru.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/sv.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/tr.ts +104 -0
- package/src/tool/urbanGardenPlanner/i18n/zh.ts +104 -0
- package/src/tool/urbanGardenPlanner/index.ts +11 -0
- package/src/tool/urbanGardenPlanner/logic.test.ts +45 -0
- package/src/tool/urbanGardenPlanner/logic.ts +55 -0
- package/src/tool/urbanGardenPlanner/seo.astro +15 -0
- package/src/tool/urbanGardenPlanner/storage.ts +25 -0
- package/src/tool/urbanGardenPlanner/ui.ts +38 -0
- package/src/tool/urbanGardenPlanner/urban-garden-rainwater-and-soil-planner.css +531 -0
- package/src/tools.ts +5 -2
- package/src/types.ts +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WithContext, FAQPage,
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import { bibliography } from '../bibliography';
|
|
3
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
4
4
|
import type { SeedCalculatorUI } from '../ui';
|
|
@@ -60,7 +60,7 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
60
60
|
})),
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const howToSchema: WithContext<
|
|
63
|
+
const howToSchema: WithContext<HowTo> = {
|
|
64
64
|
'@context': 'https://schema.org',
|
|
65
65
|
'@type': 'HowTo',
|
|
66
66
|
name: title,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { urbanGardenPlanner } from './index';
|
|
4
|
+
|
|
5
|
+
const { locale = 'en' } = Astro.props;
|
|
6
|
+
const content = await urbanGardenPlanner.i18n[locale as keyof typeof urbanGardenPlanner.i18n]?.();
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
{content && <SharedBibliography links={content.bibliography} />}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BibliographyEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const bibliography: BibliographyEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
name: 'RHS Urban gardening',
|
|
6
|
+
url: 'https://www.rhs.org.uk/advice/urban-gardening',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'US EPA Rain barrels',
|
|
10
|
+
url: 'https://www.epa.gov/soakuptherain/soak-rain-rain-barrels',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'FAO Water harvesting and soil moisture management',
|
|
14
|
+
url: 'https://www.fao.org/4/y4690e/y4690e00.htm',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Icon } from 'astro-icon/components';
|
|
3
|
+
import { urbanGardenPlanner } from './index';
|
|
4
|
+
import { DEFAULT_INPUTS } from './logic';
|
|
5
|
+
|
|
6
|
+
const { ui: propUi, locale = 'en' } = Astro.props;
|
|
7
|
+
let ui = propUi;
|
|
8
|
+
|
|
9
|
+
if (!ui) {
|
|
10
|
+
const content = await urbanGardenPlanner.i18n[locale as keyof typeof urbanGardenPlanner.i18n]?.();
|
|
11
|
+
ui = content?.ui;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!ui) return null;
|
|
15
|
+
|
|
16
|
+
const config = JSON.stringify({ ui, defaults: DEFAULT_INPUTS });
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<urban-garden-planner class="ugrp-root">
|
|
20
|
+
<div class="ugrp-shell">
|
|
21
|
+
<section class="ugrp-input-panel" aria-labelledby="ugrp-input-heading">
|
|
22
|
+
<div class="ugrp-kicker">{ui.eyebrow}</div>
|
|
23
|
+
<h2 id="ugrp-input-heading" class="ugrp-heading">
|
|
24
|
+
<span class="ugrp-step">1</span>
|
|
25
|
+
{ui.headInputs}
|
|
26
|
+
</h2>
|
|
27
|
+
|
|
28
|
+
<div class="ugrp-fields">
|
|
29
|
+
<label class="ugrp-field" for="gardenArea">
|
|
30
|
+
<span class="ugrp-label"><Icon name="mdi:flower-outline" aria-hidden="true" />{ui.labelGardenArea}</span>
|
|
31
|
+
<span class="ugrp-input-line">
|
|
32
|
+
<input id="gardenArea" type="number" min="0" step="0.5" value={DEFAULT_INPUTS.gardenAreaM2} />
|
|
33
|
+
<span>{ui.unitSquareMeters}</span>
|
|
34
|
+
</span>
|
|
35
|
+
<span class="ugrp-help">{ui.helpGardenArea}</span>
|
|
36
|
+
</label>
|
|
37
|
+
|
|
38
|
+
<label class="ugrp-field" for="substrateDepth">
|
|
39
|
+
<span class="ugrp-label"><Icon name="mdi:layers-outline" aria-hidden="true" />{ui.labelDepth}</span>
|
|
40
|
+
<span class="ugrp-input-line">
|
|
41
|
+
<input id="substrateDepth" type="number" min="0" step="1" value={DEFAULT_INPUTS.substrateDepthCm} />
|
|
42
|
+
<span>{ui.unitCentimeters}</span>
|
|
43
|
+
</span>
|
|
44
|
+
<span class="ugrp-help">{ui.helpDepth}</span>
|
|
45
|
+
</label>
|
|
46
|
+
|
|
47
|
+
<label class="ugrp-field" for="collectionArea">
|
|
48
|
+
<span class="ugrp-label"><Icon name="mdi:home-roof" aria-hidden="true" />{ui.labelCollectionArea}</span>
|
|
49
|
+
<span class="ugrp-input-line">
|
|
50
|
+
<input id="collectionArea" type="number" min="0" step="0.5" value={DEFAULT_INPUTS.collectionAreaM2} />
|
|
51
|
+
<span>{ui.unitSquareMeters}</span>
|
|
52
|
+
</span>
|
|
53
|
+
<span class="ugrp-help">{ui.helpCollectionArea}</span>
|
|
54
|
+
</label>
|
|
55
|
+
|
|
56
|
+
<label class="ugrp-field" for="rainfall">
|
|
57
|
+
<span class="ugrp-label"><Icon name="mdi:weather-rainy" aria-hidden="true" />{ui.labelRainfall}</span>
|
|
58
|
+
<span class="ugrp-input-line">
|
|
59
|
+
<input id="rainfall" type="number" min="0" step="1" value={DEFAULT_INPUTS.rainfallMm} />
|
|
60
|
+
<span>{ui.unitMillimeters}</span>
|
|
61
|
+
</span>
|
|
62
|
+
<span class="ugrp-help">{ui.helpRainfall}</span>
|
|
63
|
+
</label>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="ugrp-presets">
|
|
67
|
+
<div class="ugrp-subheading">{ui.presetsTitle}</div>
|
|
68
|
+
<div class="ugrp-preset-row">
|
|
69
|
+
<button type="button" class="ugrp-preset" data-preset="4,20,8,15">{ui.presetBalcony}</button>
|
|
70
|
+
<button type="button" class="ugrp-preset" data-preset="12,30,25,20">{ui.presetRaisedBed}</button>
|
|
71
|
+
<button type="button" class="ugrp-preset" data-preset="24,35,50,25">{ui.presetCommunity}</button>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="ugrp-note ugrp-note-assumption">
|
|
76
|
+
<Icon name="mdi:calculator-variant-outline" aria-hidden="true" />
|
|
77
|
+
<div><strong>{ui.assumptionTitle}</strong><span>{ui.assumptionText}</span></div>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<section class="ugrp-result-panel" aria-labelledby="ugrp-scene-heading">
|
|
82
|
+
<div class="ugrp-result-topline">
|
|
83
|
+
<div>
|
|
84
|
+
<div class="ugrp-kicker">{ui.headScene}</div>
|
|
85
|
+
<h2 id="ugrp-scene-heading" class="ugrp-heading"><span class="ugrp-step ugrp-step-light">2</span>{ui.sceneGardenBed}</h2>
|
|
86
|
+
</div>
|
|
87
|
+
<div id="planStatus" class="ugrp-status" aria-live="polite">{ui.statusBalanced}</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="ugrp-landscape" aria-label={ui.headScene} role="img">
|
|
91
|
+
<div id="rainDots" class="ugrp-rain-dots" aria-hidden="true"></div>
|
|
92
|
+
<div class="ugrp-cloud"><Icon name="mdi:weather-cloudy" aria-hidden="true" /></div>
|
|
93
|
+
<div class="ugrp-catchment"><span>{ui.sceneCatchment}</span><i></i></div>
|
|
94
|
+
<div class="ugrp-pipe"><i></i><b></b></div>
|
|
95
|
+
<div class="ugrp-bed">
|
|
96
|
+
<div class="ugrp-bed-frame"></div>
|
|
97
|
+
<div id="soilFill" class="ugrp-soil-fill" role="progressbar" aria-valuemin="0" aria-valuemax="100"></div>
|
|
98
|
+
<div class="ugrp-leaves"><Icon name="mdi:sprout" aria-hidden="true" /><Icon name="mdi:sprout" aria-hidden="true" /><Icon name="mdi:sprout" aria-hidden="true" /></div>
|
|
99
|
+
<span>{ui.sceneSoil}</span>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="ugrp-water-vessel"><div id="waterFill" role="progressbar" aria-valuemin="0" aria-valuemax="100"></div><Icon name="mdi:water-outline" aria-hidden="true" /><span>{ui.sceneWater}</span></div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<p id="planStatusNote" class="ugrp-status-note">{ui.statusNote}</p>
|
|
105
|
+
|
|
106
|
+
<div class="ugrp-metrics" aria-live="polite">
|
|
107
|
+
<div class="ugrp-metric ugrp-metric-main"><span>{ui.labelWaterCaptured}</span><strong id="waterValue">0 L</strong></div>
|
|
108
|
+
<div class="ugrp-metric"><span>{ui.labelSoilNeeded}</span><strong id="soilValue">0 L</strong></div>
|
|
109
|
+
<div class="ugrp-metric"><span>{ui.labelGardenDepth}</span><strong id="depthValue">0 mm</strong></div>
|
|
110
|
+
<div class="ugrp-metric"><span>{ui.labelWateringArea}</span><strong id="areaValue">0 m²</strong></div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div class="ugrp-note ugrp-note-limit">
|
|
114
|
+
<Icon name="mdi:leaf-circle-outline" aria-hidden="true" />
|
|
115
|
+
<div><strong>{ui.limitationTitle}</strong><span>{ui.limitationText}</span></div>
|
|
116
|
+
</div>
|
|
117
|
+
</section>
|
|
118
|
+
</div>
|
|
119
|
+
</urban-garden-planner>
|
|
120
|
+
|
|
121
|
+
<script is:inline type="application/json" id="ugrp-config" set:html={config}></script>
|
|
122
|
+
|
|
123
|
+
<script>
|
|
124
|
+
import { mountUrbanGardenPlanner } from './controller';
|
|
125
|
+
const root = document.querySelector<HTMLElement>('urban-garden-planner');
|
|
126
|
+
const configNode = document.getElementById('ugrp-config');
|
|
127
|
+
if (root && configNode) {
|
|
128
|
+
const config = JSON.parse(configNode.textContent || '{}');
|
|
129
|
+
mountUrbanGardenPlanner(root, config);
|
|
130
|
+
}
|
|
131
|
+
</script>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { evaluateGardenPlan } from './evaluator';
|
|
2
|
+
import { calculateGardenPlan, DEFAULT_INPUTS, type GardenPlanInput } from './logic';
|
|
3
|
+
import { findElements, renderGardenPlan, type GardenPlannerElements } from './dom-views';
|
|
4
|
+
import { loadGardenPlan, saveGardenPlan } from './storage';
|
|
5
|
+
import type { UrbanGardenPlannerUI } from './ui';
|
|
6
|
+
|
|
7
|
+
export interface UrbanGardenPlannerConfig {
|
|
8
|
+
ui: UrbanGardenPlannerUI;
|
|
9
|
+
defaults: GardenPlanInput;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const inputKeys: (keyof GardenPlanInput)[] = [
|
|
13
|
+
'gardenAreaM2',
|
|
14
|
+
'substrateDepthCm',
|
|
15
|
+
'collectionAreaM2',
|
|
16
|
+
'rainfallMm',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
class UrbanGardenPlannerController {
|
|
20
|
+
private readonly elements: GardenPlannerElements;
|
|
21
|
+
private readonly config: UrbanGardenPlannerConfig;
|
|
22
|
+
|
|
23
|
+
constructor(private readonly root: HTMLElement, config: UrbanGardenPlannerConfig) {
|
|
24
|
+
this.elements = findElements(root);
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
mount(): void {
|
|
29
|
+
this.restoreInputs();
|
|
30
|
+
this.bindInputs();
|
|
31
|
+
this.bindPresets();
|
|
32
|
+
this.calculate();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private restoreInputs(): void {
|
|
36
|
+
const saved = loadGardenPlan();
|
|
37
|
+
const values = { ...DEFAULT_INPUTS, ...this.config.defaults, ...saved };
|
|
38
|
+
inputKeys.forEach((key) => {
|
|
39
|
+
const element = this.elements.inputs[key];
|
|
40
|
+
if (element) element.value = String(values[key]);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private bindInputs(): void {
|
|
45
|
+
inputKeys.forEach((key) => {
|
|
46
|
+
this.elements.inputs[key]?.addEventListener('input', () => this.calculate());
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private bindPresets(): void {
|
|
51
|
+
this.root.querySelectorAll<HTMLButtonElement>('[data-preset]').forEach((button) => {
|
|
52
|
+
button.addEventListener('click', () => {
|
|
53
|
+
const values = button.dataset.preset?.split(',').map(Number) ?? [];
|
|
54
|
+
inputKeys.forEach((key, index) => {
|
|
55
|
+
const element = this.elements.inputs[key];
|
|
56
|
+
if (element && Number.isFinite(values[index])) element.value = String(values[index]);
|
|
57
|
+
});
|
|
58
|
+
this.calculate();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private readInputs(): GardenPlanInput {
|
|
64
|
+
return {
|
|
65
|
+
gardenAreaM2: Number(this.elements.inputs.gardenAreaM2?.value),
|
|
66
|
+
substrateDepthCm: Number(this.elements.inputs.substrateDepthCm?.value),
|
|
67
|
+
collectionAreaM2: Number(this.elements.inputs.collectionAreaM2?.value),
|
|
68
|
+
rainfallMm: Number(this.elements.inputs.rainfallMm?.value),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private calculate(): void {
|
|
73
|
+
const input = this.readInputs();
|
|
74
|
+
const result = calculateGardenPlan(input);
|
|
75
|
+
const assessment = evaluateGardenPlan(input, result);
|
|
76
|
+
saveGardenPlan(input);
|
|
77
|
+
renderGardenPlan(this.elements, result, assessment, this.config.ui);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function mountUrbanGardenPlanner(
|
|
82
|
+
root: HTMLElement,
|
|
83
|
+
config: UrbanGardenPlannerConfig,
|
|
84
|
+
): void {
|
|
85
|
+
if (root.dataset.mounted === 'true') return;
|
|
86
|
+
root.dataset.mounted = 'true';
|
|
87
|
+
new UrbanGardenPlannerController(root, config).mount();
|
|
88
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { GardenPlanAssessment } from './evaluator';
|
|
2
|
+
import type { GardenPlanResult } from './logic';
|
|
3
|
+
import type { UrbanGardenPlannerUI } from './ui';
|
|
4
|
+
|
|
5
|
+
export interface GardenPlannerElements {
|
|
6
|
+
root: HTMLElement;
|
|
7
|
+
inputs: Record<keyof GardenPlanResult, HTMLInputElement | null>;
|
|
8
|
+
soilValue: HTMLElement | null;
|
|
9
|
+
waterValue: HTMLElement | null;
|
|
10
|
+
depthValue: HTMLElement | null;
|
|
11
|
+
areaValue: HTMLElement | null;
|
|
12
|
+
status: HTMLElement | null;
|
|
13
|
+
statusNote: HTMLElement | null;
|
|
14
|
+
soilFill: HTMLElement | null;
|
|
15
|
+
waterFill: HTMLElement | null;
|
|
16
|
+
rainDots: HTMLElement | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function findElements(root: HTMLElement): GardenPlannerElements {
|
|
20
|
+
const input = (id: string) => root.querySelector<HTMLInputElement>(id);
|
|
21
|
+
const output = (id: string) => root.querySelector<HTMLElement>(id);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
root,
|
|
25
|
+
inputs: {
|
|
26
|
+
gardenAreaM2: input('#gardenArea'),
|
|
27
|
+
substrateDepthCm: input('#substrateDepth'),
|
|
28
|
+
collectionAreaM2: input('#collectionArea'),
|
|
29
|
+
rainfallMm: input('#rainfall'),
|
|
30
|
+
soilVolumeLiters: null,
|
|
31
|
+
grossRainwaterLiters: null,
|
|
32
|
+
harvestedWaterLiters: null,
|
|
33
|
+
gardenWaterDepthMm: null,
|
|
34
|
+
wateringAreaAt10Mm: null,
|
|
35
|
+
},
|
|
36
|
+
soilValue: output('#soilValue'),
|
|
37
|
+
waterValue: output('#waterValue'),
|
|
38
|
+
depthValue: output('#depthValue'),
|
|
39
|
+
areaValue: output('#areaValue'),
|
|
40
|
+
status: output('#planStatus'),
|
|
41
|
+
statusNote: output('#planStatusNote'),
|
|
42
|
+
soilFill: output('#soilFill'),
|
|
43
|
+
waterFill: output('#waterFill'),
|
|
44
|
+
rainDots: output('#rainDots'),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function formatNumber(value: number, maximumFractionDigits = 0): string {
|
|
49
|
+
return new Intl.NumberFormat('en', { maximumFractionDigits }).format(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function setText(element: HTMLElement | null, text: string): void {
|
|
53
|
+
if (element) element.textContent = text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function statusLabel(ui: UrbanGardenPlannerUI, assessment: GardenPlanAssessment): string {
|
|
57
|
+
const labels = {
|
|
58
|
+
balanced: ui.statusBalanced,
|
|
59
|
+
lightCapture: ui.statusLightCapture,
|
|
60
|
+
deepBed: ui.statusDeepBed,
|
|
61
|
+
noRain: ui.statusNoRain,
|
|
62
|
+
};
|
|
63
|
+
return labels[assessment.status];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function renderGardenPlan(
|
|
67
|
+
elements: GardenPlannerElements,
|
|
68
|
+
result: GardenPlanResult,
|
|
69
|
+
assessment: GardenPlanAssessment,
|
|
70
|
+
ui: UrbanGardenPlannerUI,
|
|
71
|
+
): void {
|
|
72
|
+
setText(elements.soilValue, `${formatNumber(result.soilVolumeLiters)} L`);
|
|
73
|
+
setText(elements.waterValue, `${formatNumber(result.harvestedWaterLiters, 1)} L`);
|
|
74
|
+
setText(elements.depthValue, `${formatNumber(result.gardenWaterDepthMm, 1)} mm`);
|
|
75
|
+
setText(elements.areaValue, `${formatNumber(result.wateringAreaAt10Mm, 1)} m²`);
|
|
76
|
+
setText(elements.status, statusLabel(ui, assessment));
|
|
77
|
+
setText(elements.statusNote, ui.statusNote);
|
|
78
|
+
elements.root.dataset.status = assessment.status;
|
|
79
|
+
elements.root.style.setProperty('--ugrp-water-progress', `${assessment.progress}%`);
|
|
80
|
+
elements.root.style.setProperty('--ugrp-soil-progress', `${Math.min(92, Math.max(18, result.substrateDepthCm * 1.7))}%`);
|
|
81
|
+
elements.root.style.setProperty('--ugrp-rain-progress', `${Math.min(100, Math.max(8, result.rainfallMm * 2.2))}%`);
|
|
82
|
+
if (elements.soilFill) elements.soilFill.setAttribute('aria-valuenow', String(result.substrateDepthCm));
|
|
83
|
+
if (elements.waterFill) elements.waterFill.setAttribute('aria-valuenow', String(result.harvestedWaterLiters));
|
|
84
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { NatureToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
+
import type { UrbanGardenPlannerUI } from './ui';
|
|
3
|
+
|
|
4
|
+
export type UrbanGardenPlannerLocaleContent = ToolLocaleContent<UrbanGardenPlannerUI>;
|
|
5
|
+
|
|
6
|
+
export const urbanGardenPlanner: NatureToolEntry<UrbanGardenPlannerUI> = {
|
|
7
|
+
id: 'urban-garden-rainwater-soil-planner',
|
|
8
|
+
icons: {
|
|
9
|
+
bg: 'mdi:water-outline',
|
|
10
|
+
fg: 'mdi:sprout',
|
|
11
|
+
},
|
|
12
|
+
i18n: {
|
|
13
|
+
en: async () => (await import('./i18n/en')).content,
|
|
14
|
+
de: async () => (await import('./i18n/de')).content,
|
|
15
|
+
es: async () => (await import('./i18n/es')).content,
|
|
16
|
+
fr: async () => (await import('./i18n/fr')).content,
|
|
17
|
+
id: async () => (await import('./i18n/id')).content,
|
|
18
|
+
it: async () => (await import('./i18n/it')).content,
|
|
19
|
+
ja: async () => (await import('./i18n/ja')).content,
|
|
20
|
+
ko: async () => (await import('./i18n/ko')).content,
|
|
21
|
+
nl: async () => (await import('./i18n/nl')).content,
|
|
22
|
+
pl: async () => (await import('./i18n/pl')).content,
|
|
23
|
+
pt: async () => (await import('./i18n/pt')).content,
|
|
24
|
+
ru: async () => (await import('./i18n/ru')).content,
|
|
25
|
+
sv: async () => (await import('./i18n/sv')).content,
|
|
26
|
+
tr: async () => (await import('./i18n/tr')).content,
|
|
27
|
+
zh: async () => (await import('./i18n/zh')).content,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GardenPlanInput, GardenPlanResult } from './logic';
|
|
2
|
+
|
|
3
|
+
export type GardenPlanStatus = 'balanced' | 'lightCapture' | 'deepBed' | 'noRain';
|
|
4
|
+
|
|
5
|
+
export interface GardenPlanAssessment {
|
|
6
|
+
status: GardenPlanStatus;
|
|
7
|
+
progress: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function evaluateGardenPlan(
|
|
11
|
+
input: GardenPlanInput,
|
|
12
|
+
result: GardenPlanResult,
|
|
13
|
+
): GardenPlanAssessment {
|
|
14
|
+
if (result.rainfallMm === 0 || result.collectionAreaM2 === 0) {
|
|
15
|
+
return { status: 'noRain', progress: 8 };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (result.gardenWaterDepthMm < 5) {
|
|
19
|
+
return { status: 'lightCapture', progress: Math.max(12, result.gardenWaterDepthMm * 8) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (input.substrateDepthCm > 45) {
|
|
23
|
+
return { status: 'deepBed', progress: 86 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { status: 'balanced', progress: Math.min(92, Math.max(18, result.gardenWaterDepthMm * 3)) };
|
|
27
|
+
}
|