@jjlmoya/utils-nature 1.1.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 +60 -0
- package/src/category/i18n/en.ts +110 -0
- package/src/category/i18n/es.ts +127 -0
- package/src/category/i18n/fr.ts +110 -0
- package/src/category/index.ts +14 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +30 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/cricketThermometer/bibliography.astro +14 -0
- package/src/tool/cricketThermometer/component.astro +549 -0
- package/src/tool/cricketThermometer/i18n/en.ts +181 -0
- package/src/tool/cricketThermometer/i18n/es.ts +181 -0
- package/src/tool/cricketThermometer/i18n/fr.ts +181 -0
- package/src/tool/cricketThermometer/index.ts +34 -0
- package/src/tool/cricketThermometer/logic.ts +6 -0
- package/src/tool/cricketThermometer/seo.astro +15 -0
- package/src/tool/cricketThermometer/ui.ts +11 -0
- package/src/tool/digitalCarbon/bibliography.astro +9 -0
- package/src/tool/digitalCarbon/component.astro +582 -0
- package/src/tool/digitalCarbon/i18n/en.ts +235 -0
- package/src/tool/digitalCarbon/i18n/es.ts +235 -0
- package/src/tool/digitalCarbon/i18n/fr.ts +235 -0
- package/src/tool/digitalCarbon/index.ts +33 -0
- package/src/tool/digitalCarbon/logic.ts +107 -0
- package/src/tool/digitalCarbon/seo.astro +14 -0
- package/src/tool/digitalCarbon/ui.ts +38 -0
- package/src/tool/rainHarvester/bibliography.astro +9 -0
- package/src/tool/rainHarvester/component.astro +559 -0
- package/src/tool/rainHarvester/i18n/en.ts +185 -0
- package/src/tool/rainHarvester/i18n/es.ts +185 -0
- package/src/tool/rainHarvester/i18n/fr.ts +185 -0
- package/src/tool/rainHarvester/index.ts +33 -0
- package/src/tool/rainHarvester/logic.ts +12 -0
- package/src/tool/rainHarvester/seo.astro +14 -0
- package/src/tool/rainHarvester/ui.ts +23 -0
- package/src/tool/seedCalculator/bibliography.astro +8 -0
- package/src/tool/seedCalculator/component.astro +812 -0
- package/src/tool/seedCalculator/i18n/en.ts +213 -0
- package/src/tool/seedCalculator/i18n/es.ts +213 -0
- package/src/tool/seedCalculator/i18n/fr.ts +213 -0
- package/src/tool/seedCalculator/index.ts +34 -0
- package/src/tool/seedCalculator/logic.ts +19 -0
- package/src/tool/seedCalculator/seo.astro +9 -0
- package/src/tool/seedCalculator/ui.ts +39 -0
- package/src/tools.ts +12 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { DigitalCarbonLocaleContent } from '../index';
|
|
3
|
+
|
|
4
|
+
const slug = 'calculateur-empreinte-carbone-numerique';
|
|
5
|
+
const title = 'Calculateur d\'empreinte carbone numérique';
|
|
6
|
+
const description = 'Analysez l\'impact environnemental de toute page web. Estimez la consommation d\'énergie et les émissions de CO₂ par visite.';
|
|
7
|
+
|
|
8
|
+
const faqData = [
|
|
9
|
+
{
|
|
10
|
+
question: 'Qu\'est-ce que l\'empreinte carbone d\'une page web ?',
|
|
11
|
+
answer: 'C\'est la quantité de gaz à effet de serre, principalement le CO₂, émise dans l\'atmosphère à la suite de l\'énergie consommée par les serveurs, les réseaux de transmission et l\'appareil de l\'utilisateur pour charger et afficher une page web.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
question: 'Comment mesure-t-on l\'impact d\'un site web ?',
|
|
15
|
+
answer: 'Il est généralement mesuré en grammes de CO₂ équivalent (gCO₂e) par visite. Un site web efficace émet moins de 0,2 g de CO₂, tandis qu\'une page peu optimisée puede dépasser 2 ou 3 g par chargement.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
question: 'Pourquoi internet pollue-t-il ?',
|
|
19
|
+
answer: 'Parce que toute l\'infrastructure nécessaire (centres de données, câbles sous-marins, routeurs WiFi, smartphones) fonctionne à l\'électricité qui, dans une grande partie du monde, provient encore de la combustion du charbon ou du gaz.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
question: 'Comment réduire le CO₂ de mon site web ?',
|
|
23
|
+
answer: 'Le moyen le plus efficace est de réduire le poids de la page : optimisez les images (WebP), minimisez les fichiers CSS y JS, utilisez le chargement différé (lazy loading) y choisissez un hébergeur utilisant des énergies renouvelables.',
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const howToData = [
|
|
28
|
+
{
|
|
29
|
+
name: 'Saisir l\'URL',
|
|
30
|
+
text: 'Tapez l\'adresse web de la page que vous souhaitez analyser dans le champ de saisie.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Lancer l\'analyse',
|
|
34
|
+
text: 'Cliquez sur le bouton d\'analyse pour que notre outil estime le poids des ressources de la page.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Consulter votre évaluation',
|
|
38
|
+
text: 'Obtenez une note de A+ à F basée sur l\'efficacité énergétique du site web analysé.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Appliquer les améliorations',
|
|
42
|
+
text: 'Utilisez la liste de conseils personnalisés pour réduire le poids de votre site et son impact environnemental.',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
47
|
+
'@context': 'https://schema.org',
|
|
48
|
+
'@type': 'FAQPage',
|
|
49
|
+
mainEntity: faqData.map((item) => ({
|
|
50
|
+
'@type': 'Question',
|
|
51
|
+
name: item.question,
|
|
52
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
53
|
+
})),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const howToSchema: WithContext<HowToThing> = {
|
|
57
|
+
'@context': 'https://schema.org',
|
|
58
|
+
'@type': 'HowTo',
|
|
59
|
+
name: title,
|
|
60
|
+
description,
|
|
61
|
+
step: howToData.map((step, i) => ({
|
|
62
|
+
'@type': 'HowToStep',
|
|
63
|
+
position: i + 1,
|
|
64
|
+
name: step.name,
|
|
65
|
+
text: step.text,
|
|
66
|
+
})),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
70
|
+
'@context': 'https://schema.org',
|
|
71
|
+
'@type': 'SoftwareApplication',
|
|
72
|
+
name: title,
|
|
73
|
+
description,
|
|
74
|
+
applicationCategory: 'UtilityApplication',
|
|
75
|
+
operatingSystem: 'All',
|
|
76
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
77
|
+
inLanguage: 'fr',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const content: DigitalCarbonLocaleContent = {
|
|
81
|
+
slug,
|
|
82
|
+
title,
|
|
83
|
+
description,
|
|
84
|
+
ui: {
|
|
85
|
+
headInputs: 'Analyseur d\'URL',
|
|
86
|
+
headResults: 'Résultat de l\'impact',
|
|
87
|
+
headTips: 'Conseils d\'optimisation',
|
|
88
|
+
labelUrl: 'URL de la page',
|
|
89
|
+
btnAnalyze: 'Analyser le site',
|
|
90
|
+
btnAnalyzing: 'Analyse en cours...',
|
|
91
|
+
placeholderUrl: 'https://exemple.fr',
|
|
92
|
+
errorInvalidUrl: 'Veuillez saisir une URL valide.',
|
|
93
|
+
errorFetchFailed: 'Impossible d\'analyser le site. Essayez une autre URL.',
|
|
94
|
+
|
|
95
|
+
resultTitle: 'Empreinte carbone numérique',
|
|
96
|
+
resultSubtitle: 'Efficacité estimée de la page',
|
|
97
|
+
co2PerVisit: 'CO₂ par visite',
|
|
98
|
+
energyPerVisit: 'Énergie consommée',
|
|
99
|
+
co2Annual: 'CO₂ annuel (100k visites)',
|
|
100
|
+
impactTitle: 'Impact réel annuel',
|
|
101
|
+
impactTrees: 'Arbres nécessaires',
|
|
102
|
+
impactKm: 'Km en voiture (approx.)',
|
|
103
|
+
treesLabel: 'Arbres',
|
|
104
|
+
kmLabel: 'Kilomètres',
|
|
105
|
+
|
|
106
|
+
ratingExcellent: 'Excellent. Cette page consomme très peu d\'énergie par rapport à la moyenne mondiale.',
|
|
107
|
+
ratingVeryGood: 'Très bonne efficacité. La page est bien optimisée et a un impact minimal.',
|
|
108
|
+
ratingGood: 'Efficacité acceptable. Il y a de la marge pour s\'améliorer mais c\'est en dessous de la moyenne.',
|
|
109
|
+
ratingAverage: 'Dans la moyenne. La page consomme ce qui est attendu sur le web actuel.',
|
|
110
|
+
ratingPoor: 'Au-dessus de la moyenne. Pensez à optimiser les images, les scripts et les polices.',
|
|
111
|
+
ratingVeryPoor: 'Impact élevé. Le poids de la page est significatif et devrait être réduit.',
|
|
112
|
+
ratingCritical: 'Impact très élevé. Il y a une grande opportunité de réduire la taille de la page.',
|
|
113
|
+
|
|
114
|
+
tipImages: 'Convertissez les images en WebP ou AVIF pour réduire leur poids jusqu\'à 70%.',
|
|
115
|
+
tipCompression: 'Activez Brotli ou GZIP sur le serveur pour compresser les ressources.',
|
|
116
|
+
tipLazyLoading: 'Utilisez le lazy loading pour les images et vidéos hors champ.',
|
|
117
|
+
tipHosting: 'Choisissez un hébergement avec énergie renouvelable (Cloudflare, Netlify, Vercel).',
|
|
118
|
+
tipThirdParty: 'Supprimez les scripts d\'analyse et les widgets tiers inutiles.',
|
|
119
|
+
tipFonts: 'Ne chargez que les graisses typographiques que vous utilisez réellement.',
|
|
120
|
+
tipCache: 'Configurez les en-têtes de cache HTTP pour éviter les re-téléchargements.',
|
|
121
|
+
},
|
|
122
|
+
faqTitle: 'Questions Fréquemment Posées',
|
|
123
|
+
faq: faqData,
|
|
124
|
+
howTo: howToData,
|
|
125
|
+
bibliographyTitle: 'Références sur la durabilité du web',
|
|
126
|
+
seo: [
|
|
127
|
+
{
|
|
128
|
+
type: 'title',
|
|
129
|
+
text: 'Calculateur d\'empreinte carbone numérique : Quel est le CO₂ généré par votre site',
|
|
130
|
+
level: 2,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'paragraph',
|
|
134
|
+
html: 'Découvrez l\'impact environnemental réel de n\'importe quelle page web. Analysez son poids, estimez les grammes de CO₂ par visite et apprenez à réduire la pollution numérique de vos projets.',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
type: 'title',
|
|
138
|
+
text: 'Qu\'est-ce que l\'empreinte carbone numérique d\'un site web ?',
|
|
139
|
+
level: 2,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'paragraph',
|
|
143
|
+
html: 'Chaque fois que vous ouvrez une page web, votre appareil, votre routeur, les câbles sous-marins et les serveurs à l\'autre bout du monde consomment de l\'électricité. Cette électricité est encore largement générée par la combustion d\'énergies fossiles. Résultat : une quantité réelle de <strong>CO₂ émise dans l\'atmosphère à chaque visite</strong>.',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: 'paragraph',
|
|
147
|
+
html: 'L\'<strong>empreinte carbone numérique</strong> d\'un site web se mesure en grammes de CO₂ équivalent (gCO₂e) par visite. Un site web moyen génère environ 0,5 g de CO₂ par chargement. Bien que cela semble insignifiant, un site avec 100 000 visites mensuelles peut émettre plus de 600 kg de CO₂ par an, l\'équivalent de conduire une voiture à essence sur plus de 3 000 km.',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: 'title',
|
|
151
|
+
text: 'Comment est calculé le CO₂ d\'une page web ?',
|
|
152
|
+
level: 2,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: 'paragraph',
|
|
156
|
+
html: 'Le modèle de calcul utilisé est basé sur les standards du <strong>Sustainable Web Design Model</strong>, qui divise la consommation d\'énergie en quatre segments principaux :',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: 'card',
|
|
160
|
+
title: 'Transfert de données',
|
|
161
|
+
html: 'Le poids total de la page détermine le nombre de gigaoctets transférés. Le standard considère <code>0,81 kWh/Go</code> pour l\'infrastructure réseau.',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
type: 'card',
|
|
165
|
+
title: 'Appareil utilisateur',
|
|
166
|
+
html: 'L\'ordinateur ou le mobile recevant la page consomme de l\'énergie. Elle est estimée à <code>0,52 kWh/Go</code> de données traitées.',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: 'card',
|
|
170
|
+
title: 'Intensité carbone',
|
|
171
|
+
html: 'On utilise la valeur de référence mondiale de <code>442 gCO₂/kWh</code> pour convertir la consommation d\'énergie en émissions réelles.',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: 'card',
|
|
175
|
+
title: 'Facteur de cache',
|
|
176
|
+
html: 'Le modèle applique un facteur de <code>0,75</code> en supposant que 25 % des utilisateurs ont déjà des ressources en cache.',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
type: 'title',
|
|
180
|
+
text: 'Que signifie l\'évaluation de l\'efficacité ?',
|
|
181
|
+
level: 2,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: 'list',
|
|
185
|
+
items: [
|
|
186
|
+
'<strong>A+ et A :</strong> Moins de 0,2 g de CO₂. Sites très légers et optimisés.',
|
|
187
|
+
'<strong>B :</strong> Entre 0,2 et 0,5 g. En dessous de la moyenne mondiale.',
|
|
188
|
+
'<strong>C :</strong> Entre 0,5 et 1 g. La moyenne du web actuel.',
|
|
189
|
+
'<strong>D et E :</strong> Entre 1 et 4 g. Pages lourdes avec un impact important.',
|
|
190
|
+
'<strong>F :</strong> Plus de 4 g par visite. Impact très élevé.',
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: 'title',
|
|
195
|
+
text: 'Comment réduire l\'empreinte carbone de votre site',
|
|
196
|
+
level: 2,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'card',
|
|
200
|
+
title: 'Optimisation des images',
|
|
201
|
+
html: 'Utilisez des formats comme <strong>WebP ou AVIF</strong> pour réduire la taille jusqu\'à 80 % sans perte de qualité visible.',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: 'card',
|
|
205
|
+
title: 'Compression du serveur',
|
|
206
|
+
html: 'L\'activation de <strong>Brotli ou GZIP</strong> réduit la taille des fichiers texte jusqu\'à 70 %.',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: 'card',
|
|
210
|
+
title: 'Hébergement durable',
|
|
211
|
+
html: 'Choisir un fournisseur avec des <strong>énergies renouvelables certifiées</strong> peut réduire l\'impact de près de 100 %.',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
type: 'card',
|
|
215
|
+
title: 'Cache et CDN',
|
|
216
|
+
html: 'Configurez des <strong>en-têtes de cache longs</strong> pour éviter les téléchargements inutiles.',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: 'title',
|
|
220
|
+
text: 'L\'impact réel d\'Internet sur le climat',
|
|
221
|
+
level: 2,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
type: 'paragraph',
|
|
225
|
+
html: 'Internet représente entre <strong>2% et 4% des émissions mondiales de CO₂</strong>, un chiffre comparable à l\'industrie aéronautique. Chaque kilo-octet éliminé ne rend pas seulement votre site plus rapide : il réduit de manière mesurable la pollution numérique.',
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
bibliography: [
|
|
229
|
+
{ name: 'Sustainable Web Design Model', url: 'https://sustainablewebdesign.org/' },
|
|
230
|
+
{ name: 'Website Carbon Calculator', url: 'https://www.websitecarbon.com/' },
|
|
231
|
+
{ name: 'The Green Web Foundation', url: 'https://www.thegreenwebfoundation.org/' },
|
|
232
|
+
{ name: 'W3C - Web Sustainability Guidelines', url: 'https://w3c.github.io/sustyweb/' },
|
|
233
|
+
],
|
|
234
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
235
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { NatureToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
|
|
2
|
+
import DigitalCarbonComponent from './component.astro';
|
|
3
|
+
import DigitalCarbonSEO from './seo.astro';
|
|
4
|
+
import DigitalCarbonBibliography from './bibliography.astro';
|
|
5
|
+
import type { DigitalCarbonUI } from './ui';
|
|
6
|
+
|
|
7
|
+
export type DigitalCarbonLocaleContent = ToolLocaleContent<DigitalCarbonUI>;
|
|
8
|
+
|
|
9
|
+
import { content as es } from './i18n/es';
|
|
10
|
+
import { content as en } from './i18n/en';
|
|
11
|
+
import { content as fr } from './i18n/fr';
|
|
12
|
+
|
|
13
|
+
export const digitalCarbon: NatureToolEntry<DigitalCarbonUI> = {
|
|
14
|
+
id: 'digital-carbon',
|
|
15
|
+
icons: {
|
|
16
|
+
bg: 'mdi:leaf',
|
|
17
|
+
fg: 'mdi:monitor',
|
|
18
|
+
},
|
|
19
|
+
i18n: {
|
|
20
|
+
es: async () => es,
|
|
21
|
+
en: async () => en,
|
|
22
|
+
fr: async () => fr,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { DigitalCarbonComponent, DigitalCarbonSEO, DigitalCarbonBibliography };
|
|
27
|
+
|
|
28
|
+
export const DIGITAL_CARBON_TOOL: ToolDefinition = {
|
|
29
|
+
entry: digitalCarbon,
|
|
30
|
+
Component: DigitalCarbonComponent,
|
|
31
|
+
SEOComponent: DigitalCarbonSEO,
|
|
32
|
+
BibliographyComponent: DigitalCarbonBibliography,
|
|
33
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export interface DCFRating {
|
|
2
|
+
label: string;
|
|
3
|
+
color: string;
|
|
4
|
+
pct: number;
|
|
5
|
+
descKey: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface DCFResult {
|
|
9
|
+
url: string;
|
|
10
|
+
bytes: number;
|
|
11
|
+
co2g: number;
|
|
12
|
+
energyWh: number;
|
|
13
|
+
co2Year: number;
|
|
14
|
+
trees: string;
|
|
15
|
+
km: string;
|
|
16
|
+
pageSize: string;
|
|
17
|
+
rating: DCFRating;
|
|
18
|
+
tipKeys: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const NET_KWH_PER_GB = 0.81;
|
|
22
|
+
const DEVICE_KWH_PER_GB = 0.52;
|
|
23
|
+
const CARBON_G_PER_KWH = 442;
|
|
24
|
+
const RETURN_RATIO = 0.75;
|
|
25
|
+
|
|
26
|
+
export function formatBytes(bytes: number): string {
|
|
27
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
28
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
29
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function calcCO2(bytes: number): { co2g: number; energyWh: number } {
|
|
33
|
+
const gb = (bytes / 1e9) * RETURN_RATIO;
|
|
34
|
+
const energyKwh = (NET_KWH_PER_GB + DEVICE_KWH_PER_GB) * gb;
|
|
35
|
+
return { co2g: energyKwh * CARBON_G_PER_KWH, energyWh: energyKwh * 1000 };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getRating(co2g: number): DCFRating {
|
|
39
|
+
if (co2g < 0.1) return { label: 'A+', color: '#10b981', pct: 5, descKey: 'ratingExcellent' };
|
|
40
|
+
if (co2g < 0.2) return { label: 'A', color: '#22c55e', pct: 15, descKey: 'ratingVeryGood' };
|
|
41
|
+
if (co2g < 0.5) return { label: 'B', color: '#84cc16', pct: 35, descKey: 'ratingGood' };
|
|
42
|
+
if (co2g < 1.0) return { label: 'C', color: '#f59e0b', pct: 58, descKey: 'ratingAverage' };
|
|
43
|
+
if (co2g < 2.0) return { label: 'D', color: '#f97316', pct: 72, descKey: 'ratingPoor' };
|
|
44
|
+
if (co2g < 4.0) return { label: 'E', color: '#ef4444', pct: 88, descKey: 'ratingVeryPoor' };
|
|
45
|
+
return { label: 'F', color: '#dc2626', pct: 98, descKey: 'ratingCritical' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getTipKeys(bytes: number): string[] {
|
|
49
|
+
const keys: string[] = [];
|
|
50
|
+
const kb = bytes / 1024;
|
|
51
|
+
if (kb > 500) keys.push('tipImages');
|
|
52
|
+
if (kb > 200) keys.push('tipCompression');
|
|
53
|
+
if (kb > 300) keys.push('tipLazyLoading');
|
|
54
|
+
keys.push('tipHosting');
|
|
55
|
+
keys.push('tipThirdParty');
|
|
56
|
+
if (kb > 1000) keys.push('tipFonts');
|
|
57
|
+
keys.push('tipCache');
|
|
58
|
+
return keys;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function extractHtml(text: string): string {
|
|
62
|
+
try {
|
|
63
|
+
const json = JSON.parse(text) as Record<string, unknown>;
|
|
64
|
+
return (json.contents ?? json.body ?? text) as string;
|
|
65
|
+
} catch {
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function estimatePageSize(url: string): Promise<number> {
|
|
71
|
+
const encoded = encodeURIComponent(url);
|
|
72
|
+
const proxies = [
|
|
73
|
+
`https://api.allorigins.win/get?url=${encoded}`,
|
|
74
|
+
`https://corsproxy.io/?${encoded}`,
|
|
75
|
+
`https://api.codetabs.com/v1/proxy/?quest=${encoded}`,
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
for (const proxy of proxies) {
|
|
79
|
+
try {
|
|
80
|
+
const resp = await fetch(proxy, { signal: AbortSignal.timeout(10000) });
|
|
81
|
+
if (!resp.ok) continue;
|
|
82
|
+
const html = extractHtml(await resp.text());
|
|
83
|
+
if (!html || html.length < 100) continue;
|
|
84
|
+
return Math.round(new TextEncoder().encode(html).length * 5.5);
|
|
85
|
+
} catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw new Error('fetch_failed');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildResult(url: string, bytes: number): DCFResult {
|
|
93
|
+
const { co2g, energyWh } = calcCO2(bytes);
|
|
94
|
+
const co2Year = (co2g * 100000) / 1000;
|
|
95
|
+
return {
|
|
96
|
+
url,
|
|
97
|
+
bytes,
|
|
98
|
+
co2g,
|
|
99
|
+
energyWh,
|
|
100
|
+
co2Year,
|
|
101
|
+
trees: (co2Year / 21).toFixed(1),
|
|
102
|
+
km: (co2Year / 0.21).toFixed(0),
|
|
103
|
+
pageSize: formatBytes(bytes),
|
|
104
|
+
rating: getRating(co2g),
|
|
105
|
+
tipKeys: getTipKeys(bytes),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { digitalCarbon } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale } = Astro.props;
|
|
11
|
+
const content = await digitalCarbon.i18n[locale]!();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<SEORenderer content={{ locale: locale as string, sections: content.seo }} />
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface DigitalCarbonUI extends Record<string, string> {
|
|
2
|
+
headInputs: string;
|
|
3
|
+
headResults: string;
|
|
4
|
+
headTips: string;
|
|
5
|
+
labelUrl: string;
|
|
6
|
+
btnAnalyze: string;
|
|
7
|
+
btnAnalyzing: string;
|
|
8
|
+
placeholderUrl: string;
|
|
9
|
+
errorInvalidUrl: string;
|
|
10
|
+
errorFetchFailed: string;
|
|
11
|
+
|
|
12
|
+
resultTitle: string;
|
|
13
|
+
resultSubtitle: string;
|
|
14
|
+
co2PerVisit: string;
|
|
15
|
+
energyPerVisit: string;
|
|
16
|
+
co2Annual: string;
|
|
17
|
+
impactTitle: string;
|
|
18
|
+
impactTrees: string;
|
|
19
|
+
impactKm: string;
|
|
20
|
+
treesLabel: string;
|
|
21
|
+
kmLabel: string;
|
|
22
|
+
|
|
23
|
+
ratingExcellent: string;
|
|
24
|
+
ratingVeryGood: string;
|
|
25
|
+
ratingGood: string;
|
|
26
|
+
ratingAverage: string;
|
|
27
|
+
ratingPoor: string;
|
|
28
|
+
ratingVeryPoor: string;
|
|
29
|
+
ratingCritical: string;
|
|
30
|
+
|
|
31
|
+
tipImages: string;
|
|
32
|
+
tipCompression: string;
|
|
33
|
+
tipLazyLoading: string;
|
|
34
|
+
tipHosting: string;
|
|
35
|
+
tipThirdParty: string;
|
|
36
|
+
tipFonts: string;
|
|
37
|
+
tipCache: string;
|
|
38
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { rainHarvester } from './index';
|
|
4
|
+
|
|
5
|
+
const { locale = 'es' } = Astro.props;
|
|
6
|
+
const content = await rainHarvester.i18n[locale as keyof typeof rainHarvester.i18n]?.();
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
{content && <SharedBibliography links={content.bibliography} />}
|