SUSTAINABILITY.md

CO2.js Footer: Working Demo

This page demonstrates the sustainability footer pattern used on SUSTAINABILITY.md. Scroll to the bottom to see it in action. The values update once all resources have loaded.

The pattern satisfies the W3C Web Sustainability Guidelines call for WSG 5.x — Establish Sustainability Reporting Mechanisms by surfacing a live per-page-view carbon estimate to every visitor without adding a server-side dependency or a heavy third-party bundle.

1. What the footer shows

Footer metrics and their sources
Metric Source Notes
Page emissions (gCO₂e / page view) CO2.js SWD v4 model Directional estimate; not a precise measurement
Transfer size Performance Navigation Timing API Sum of all navigation and resource entries
Green hosting Green Web Foundation hosting check API Checks the current page hostname; result may vary by environment

2. Copy-paste HTML

Add the following block to your page template. The three <span> elements are updated by the script in Section 3.

<p class="footer-metrics">
  <strong>Page emissions:</strong>
  <span id="footer-co2-estimate">Calculating…</span>
  ·
  <strong>Transfer:</strong>
  <span id="footer-co2-bytes">Calculating…</span>
  ·
  <strong>Green hosting:</strong>
  <span id="footer-green-hosting">Checking…</span>
  ·
  <strong>Method:</strong>
  <a href="https://www.thegreenwebfoundation.org/news/start-calculating-digital-carbon-emissions-in-5-minutes-with-co2-js/"
  >CO2.js quickstart</a>
</p>

3. Copy-paste script

Paste this <script type="module"> block before the closing </body> tag. No build step or npm install is required — CO2.js loads from a CDN. For production use, replace @latest with a pinned version (e.g. @0.18.0) so upstream releases do not silently change your reported numbers.

<script type="module">
  import { co2 } from "https://cdn.jsdelivr.net/npm/@tgwf/co2@latest/dist/esm/index.js";
  import { check as greenHostingCheck } from "https://cdn.jsdelivr.net/npm/@tgwf/co2@latest/dist/esm/hosting.js";

  const estimateEl = document.getElementById("footer-co2-estimate");
  const bytesEl    = document.getElementById("footer-co2-bytes");
  const hostingEl  = document.getElementById("footer-green-hosting");

  const formatBytes = (bytes) => {
    if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
    const units    = ["B", "KB", "MB", "GB"];
    const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
    const value    = bytes / (1024 ** exponent);
    return `${value.toFixed(value >= 10 || exponent === 0 ? 0 : 1)} ${units[exponent]}`;
  };

  const totalTransferBytes = () => {
    const entries = [
      ...performance.getEntriesByType("navigation"),
      ...performance.getEntriesByType("resource"),
    ];
    return entries.reduce((total, entry) => {
      const transfer = Number(entry.transferSize) || 0;
      const encoded  = Number(entry.encodedBodySize) || 0;
      return total + (transfer > 0 ? transfer : encoded);
    }, 0);
  };

  const render = async () => {
    if (!estimateEl || !bytesEl || !hostingEl) return;

    // ── Emissions estimate ──────────────────────────────────────────────
    try {
      const bytes      = totalTransferBytes();
      const calculator = new co2({ model: "swd", version: 4 });
      const grams      = calculator.perVisit(bytes, false);

      bytesEl.textContent    = formatBytes(bytes);
      estimateEl.textContent = Number.isFinite(grams)
        ? `${grams.toFixed(3)} gCO2e / page view`
        : "Unavailable";
    } catch {
      bytesEl.textContent    = "Unavailable";
      estimateEl.textContent = "Unavailable";
    }

    // ── Green-hosting check ─────────────────────────────────────────────
    // Pass the hostname of the page being checked.
    // For a static site you can determine this once and hard-code it.
    try {
      const isGreen        = await greenHostingCheck(window.location.hostname);
      hostingEl.textContent = isGreen ? "Yes (Green Web Foundation)" : "No / unknown";
    } catch {
      hostingEl.textContent = "Unavailable";
    }
  };

  // Run after all resources have loaded so transfer sizes are complete.
  if (document.readyState === "complete") {
    render();
  } else {
    window.addEventListener("load", render, { once: true });
  }
</script>

4. Design trade-offs

Static vs. dynamic footer disclosure
Approach When to use Trade-offs
Static string (e.g. "≈ 0.08 gCO₂e") Most sites — update after each Lighthouse audit Zero JS runtime cost; does not vary per load
Dynamic (this demo) Dashboards or pages with variable payloads Adds CDN JS dependency (~11 KB gzipped); result varies each load; requires a network call for hosting check

For a static site, the static approach is lower-impact and equally informative. Reserve the dynamic pattern for cases where the per-load number is meaningfully different.

5. WSG references

Relevant Web Sustainability Guideline mappings
Guideline How this demo addresses it
WSG 5.x — Establish Sustainability Reporting Mechanisms Per-page-view CO₂ estimate visible to every visitor in the footer
WSG 5.28 — Include Sustainability Information in Reports Transfer size and hosting source disclosed alongside emissions estimate