grid_csv = FileAttachment("lcdb6_dggs_12_grid.csv").csv()
nz_outline_csv = FileAttachment("nz_outline.csv").csv()
state = FileAttachment("lcdb6_dggs_12_state.csv").csv()
change = FileAttachment("lcdb6_dggs_12_change.csv").csv()
csv_to_json = async (csv_file, data_name, colname) => {
  const data = await csv_file;
  
  return {
    type: "FeatureCollection",
    name: data_name,
    crs: {
      type: "name",
      properties: {
        name: "urn:ogc:def:crs:OGC:1.3:CRS84"
      }
    },
    features: data.map(row => {
      // Get the geo_points string from the CSV row.
      const geoPointsString = row.coordinates;

      // Optional: Fix invalid JSON (e.g., single quotes instead of double)
      const correctedJsonString = geoPointsString.replace(/'/g, '"');
      const parsedCoordinates = JSON.parse(correctedJsonString);

      // Dynamically decide if value should be number or string
      const readcol = colname === "name"
        ? row[colname]
        : Number(row[colname]);

      return {
        type: "Feature",
        properties: {
          [colname]: readcol
        },
        geometry: {
          type: "MultiPolygon",
          coordinates: parsedCoordinates
        }
      };
    })
  };
}


nz_outline = csv_to_json(nz_outline_csv, "nz_outline", "name")
grid = csv_to_json(grid_csv, "lcdb6_dggs_12_grid", "seqnum")
import { concat } from '@uwdata/arquero'
largestAbs = (a, b) => Math.max(Math.abs(a), Math.abs(b))
// rounding function
function roundToDecimal(number, decimals) {
  // Multiply the number by 10^decimals
  let factor = Math.pow(10, decimals);
  
  // Round to the nearest integer and then divide by the same factor
  return Math.round(number * factor) / factor;
}
function quantile(arr, q) {
  const nums = arr.filter(d => Number.isFinite(d)).slice();
  if (!nums.length) return undefined;
  nums.sort((a, b) => a - b);
  const pos = (nums.length - 1) * q;
  const base = Math.floor(pos);
  const rest = pos - base;
  if (nums[base + 1] !== undefined) {
    return nums[base] + rest * (nums[base + 1] - nums[base]);
  } else {
    return nums[base];
  }
}
function get_data_spread(plot_data, col_var) {

  // --- Extract numeric values, filter out invalids ---
  const numericValues = plot_data.features
    .map(f => f.properties[col_var])
    .filter(v => v != null && !isNaN(v));
  
  const sorted = numericValues.slice().sort((a,b)=>a-b);
  const min = sorted.length ? d3.quantile(sorted, 0.025) : 0;
  const max = sorted.length ? d3.quantile(sorted, 0.975) : 100;   /// this works for percentages - may need to change if using ha??

  const mid = 0;  
  const max_div = largestAbs(min, max); 
  
  return([min, max, max_div])
}
// return min and max of map datasets that have shared columns 

function getCombinedMinMax(data1, data2, column) {
  // Convert to plain arrays if they are Arquero tables
  const arr1 = typeof data1.objects === "function" ? data1.objects() : data1;
  const arr2 = typeof data2.objects === "function" ? data2.objects() : data2;

  // Combine and filter valid numeric values
  const allValues = [...arr1, ...arr2]
    .map(d => d[column])
    .filter(v => v != null && !isNaN(v));

  if (allValues.length === 0) return { min: 0, max: 0 };

  return {
    min: Math.min(...allValues),
    max: Math.max(...allValues)
  };
}
d3 = require("d3@7")  // need this for viridis

function map(plot_data, col_var, nz_outline, options = {}) {
  // defaults and merge with provided options
  const {
    min_max = null,
    legend_title = 'Landcover area (ha)',
    type = 'state',
    mapTitle = "",
    wetland = "FALSE",
    id = `map-${Math.random().toString(36).slice(2)}`  // unique map id
  } = options;

  // --- create the div and Leaflet map ---
  //const div = html`<div id="${id}" style="height:500px; width:100%; position:relative; background-color:#666363;"></div>`;
  const div = html`<div id="${id}" style="height:500px; width:100%; position:relative; background-color: #FFFFFF;"></div>`;
  const m = L.map(div, {
    zoomControl: true,
    attributionControl: false
  });

  // Add NZ outline as a dark background layer
  const nzOutlineLayer = L.geoJSON(nz_outline, {
    style: {
      fillColor: '#E8E8E8', //'#FFFFFF', // white fill //"#1D1C1C", // Dark fill // '#E8E8E8',// light grey
      fillOpacity: 1,
      color: '#E8E8E8', //"#000",         // Optional dark border
      weight: 0.5
    }
  }).addTo(m);



  // --- Title overlay ---
  if (mapTitle) {
    const titleDiv = document.createElement("div");
    titleDiv.innerText = mapTitle;
    titleDiv.style.position = "absolute";
    titleDiv.style.top = "8px";
    titleDiv.style.left = "50%";
    titleDiv.style.transform = "translateX(-50%)";
    titleDiv.style.background = "rgba(255,255,255,0.8)";
    titleDiv.style.padding = "2px 2px 2px 2px";
    titleDiv.style.font = "14px sans-serif";
    titleDiv.style.fontWeight = "bold";
    titleDiv.style.borderRadius = "4px";
    titleDiv.style.pointerEvents = "none";
    div.appendChild(titleDiv);
  }

  // --- unique gradient id for legend ---
  const gradientId = `legend-gradient-${id}`;


  // --- Extract numeric values, filter out invalids ---

  const numericValues = plot_data.features
    .map(f => f.properties[col_var])
    .filter(v => v != null && !isNaN(v));

  const sorted = numericValues.slice().sort((a,b)=>a-b);

  // Conditional logic based on input_max_div
  let min, max, max_div;

    min = sorted.length ? d3.quantile(sorted, 0.025) : 0;
    max = sorted.length ? d3.quantile(sorted, 0.975) : 100;
    max_div = largestAbs(min, max);

  const mid = 0;

  // adjust col_var to 95 quantiles for colour
  // Add an index into GeoJSON before table conversion
  plot_data.features.forEach((f, i) => f.properties.__id = i);

  const aq_plot_data = aq.from(plot_data.features)
      .derive({
        col_adjusted: aq.escape(d => {
          const v = Number(d.properties[col_var]);
          if (isNaN(v)) return null; // or keep original here if you prefer
          if (v > max) return max;
          if (v < min) return min;
          return v;
        })
      })
      
  // note this is wrapped so quarto knows it outputs a value    
  const adj_plot_data = ({
    ...plot_data,
    features: aq_plot_data.objects().map(row => {
      const i = row.properties.__id;
      return {
        ...plot_data.features[i],
        properties: {
          ...plot_data.features[i].properties,
          adj_col_var : row.col_adjusted
        }
      };
    })
  });
  

  // --- Polygon color scale ---

  // Set Bonnie's diverging colour scale (white centre in RColourBrewer Orange to Purple)
  const hexColors = [ "#7F0000", "#FF7F0E", "#F5F5F5", "#9B30FF","#3F0071"];
  // try vanimo high contrast
  //const hexColors = ["#E4B4D2", "#BC71A7", "#833C71", "#401D34", "#1F1816", "#2E351C", "#506734", "#7DA351", "#B3D189"];
  const customInterpolator = d3.piecewise(d3.interpolateRgb, hexColors);

  const baseScale = type === "state"
    ? d3.scaleSequential(d3.interpolateViridis).domain([min, max])
    : d3.scaleDiverging()
      .domain([-max_div, mid, max_div]) // Your data's range: low, neutral, high
      .interpolator(customInterpolator);

  const colorScale = v => (v == null || isNaN(v)) ? "#cccccc" : baseScale(v);

  // --- create polygons

  const polyLayer = L.geoJSON(adj_plot_data, {
    style: feature => {
      const val = feature.properties.adj_col_var;
      return {
        fillColor: val == null ? "#d3d3d3" : colorScale(val),
        color: val == null ? "#d3d3d3" : colorScale(val),  // colour outline to reduce white showing in rendering
        fillOpacity: 0.7,
        stroke: true,
        opacity: 0.7,
        weight: .5,
      };
    },
    onEachFeature: (feature, layer) => {
      layer.bindPopup(
        wetland === 'FALSE'
          ? type === 'state'
            ? `
              polygon ID: ${feature.properties.seqnum}<br>
              Landcover: ${feature.properties.landcover}<br>
              Area (ha): ${feature.properties.area_ha}<br>
              Percent area: ${feature.properties.percent_landcover}<br>
            `
            : `
              Polygon : ${feature.properties.seqnum}<br>
              Landcover: ${feature.properties.landcover}<br>
              Percent change: ${feature.properties.percent_change}<br>
              Change (ha): ${feature.properties.change_ha}<br>
            `
          : type === 'state'
            ? `
              polygon ID: ${feature.properties.seqnum}<br>
              Area (ha): ${feature.properties.wetland_area_ha}<br>
            `
            : `
              Polygon : ${feature.properties.seqnum}<br>
              Change (ha): ${feature.properties.change_ha}<br>
            `
      );
    }
  }).addTo(m);

  // --- Legend ---
  const legend = L.control({ position: "bottomright" });
  legend.onAdd = function () {
    const div = L.DomUtil.create("div", `info legend legend-${id}`);
    div.style.background = "none";
    div.style.padding = "0";
    div.style.margin = "0";
    div.style.marginBottom = "20px";
    div.style.marginRight = "10px";
    div.style.font = "11px/1.3 sans-serif";
    div.style.color = "#333";
    div.style.display = "flex";
    div.style.flexDirection = "column";
    div.style.alignItems = "flex-start";

    const barHeight = 180;

    // Title
    const title = document.createElement("div");
    title.innerHTML = legend_title;
    title.style.fontWeight = "bold";
    title.style.marginBottom = "4px";
    div.appendChild(title);

    // --- Gradient row ---
    const rowDiv = document.createElement("div");
    rowDiv.style.display = "flex";
    rowDiv.style.alignItems = "flex-start";

    // Gradient SVG
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("width", 20);
    svg.setAttribute("height", barHeight);

    const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
    const linearGrad = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
    linearGrad.setAttribute("id", gradientId);
    linearGrad.setAttribute("x1", "0%");
    linearGrad.setAttribute("y1", "100%");
    linearGrad.setAttribute("x2", "0%");
    linearGrad.setAttribute("y2", "0%");

    const nStops = 50;
    for (let i = 0; i <= nStops; i++) {
      const t = i / nStops;
      let val;
      if (type === 'state') {
        val = min + t * (max - min);
      } else {
        const scale = d3.scaleLinear().domain([0, 0.5, 1]).range([-max_div, mid, max_div]);
        val = scale(t);
      }
      const stop = document.createElementNS("http://www.w3.org/2000/svg", "stop");
      stop.setAttribute("offset", `${t*100}%`);
      stop.setAttribute("stop-color", colorScale(val));
      linearGrad.appendChild(stop);
    }
    defs.appendChild(linearGrad);
    svg.appendChild(defs);

    const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
    rect.setAttribute("x", 0);
    rect.setAttribute("y", 0);
    rect.setAttribute("width", 20);
    rect.setAttribute("height", barHeight);
    rect.setAttribute("fill", `url(#${gradientId})`);
    rect.setAttribute("stroke", "black");
    rect.setAttribute("stroke-width", "0.3");
    svg.appendChild(rect);

    const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
    line.setAttribute("x1", 0);
    line.setAttribute("y1", barHeight);
    line.setAttribute("x2", 20);
    line.setAttribute("y2", barHeight);
    line.setAttribute("stroke", "black");
    line.setAttribute("stroke-width", "0.6");
    svg.appendChild(line);

    rowDiv.appendChild(svg);

    // Labels
    const minCLabel = `< ${Math.round(-max_div)}`;
    const maxCLabel = `> ${Math.round(max_div)}`;
    const midCLabel = "0";
    const maxSLabel = `> ${Math.round(max_div)}`;

    const labelsDiv = document.createElement("div");
    labelsDiv.style.display = "flex";
    labelsDiv.style.flexDirection = "column";
    labelsDiv.style.height = `${barHeight}px`;
    labelsDiv.style.justifyContent = "space-between";
    labelsDiv.style.marginLeft = "4px";
    labelsDiv.innerHTML = type === "state"
      ? `
        <span>${maxSLabel}</span>
        <span>${Math.round((max+min)/2)}</span>
        <span>${Math.round(min)}</span>
      `
      : `
        <span>${maxCLabel}</span>
        <span>${midCLabel}</span>
        <span>${minCLabel}</span>
      `;
    rowDiv.appendChild(labelsDiv);

    div.appendChild(rowDiv);

    return div;
  };
  legend.addTo(m);

  // --- map sizing hacks ---
  
  const bounds = L.latLngBounds([
    [-46.0, 166.0],
    [-34.0, 179.0]
  ]);

  const resizeObserver = new ResizeObserver(() => {
    m.invalidateSize();
    m.fitBounds(bounds); // optional if you want to maintain framing
  });
  resizeObserver.observe(div);

  // Fit bounds once layout is ready
  setTimeout(() => {
    try {
      //m.fitBounds(polyLayer.getBounds());
      m.invalidateSize();
    } catch (err) {
      console.warn("fitBounds failed:", err);
    }
  }, 300); // allow Quarto's grid layout to finalize

  // --- cleanup on invalidation ---
  invalidation.then(() => {
    resizeObserver.disconnect();
    m.remove();
  });

  // --- Return ---
  return Object.assign(div, {
    value: m,
    invalidateSize: () => m.invalidateSize(),
    fitBounds: b => m.fitBounds(b),
    remove: () => m.remove(),
    legend,
    ready: Promise.resolve()
  });
}
// can add a swatch to the map graph to add a value for NA into the legend label etc.

swatch = function (maindiv){   const naDiv = document.createElement("div");
    naDiv.style.display = "flex";
    naDiv.style.alignItems = "center";
    naDiv.style.marginTop = "6px";

    const naBox = document.createElement("div");
    naBox.style.width = "20px";
    naBox.style.height = "20px";
    naBox.style.background = "#cccccc";
    naBox.style.border = "0.3px solid black";

    const naLabel = document.createElement("span");
    naLabel.style.fontSize = "10px";
    naLabel.style.marginLeft = "4px";
    naLabel.innerText = "NA";           // adjust label as needed

    naDiv.appendChild(naBox);
    naDiv.appendChild(naLabel);

    maindiv.appendChild(naDiv);
}
// Utility function to convert array of objects → CSV
function toCSV(data) {
  if (!data.length) return "";
  
  const headers = Object.keys(data[0]);
  const rows = data.map(row => 
    headers.map(h => JSON.stringify(row[h] ?? "")).join(",")
  );
  
  return [headers.join(","), ...rows].join("\n");
}
function joinGeoJSON(geojson, table, key = "seqnum") {
  // Build a lookup map from the table for fast joins
  let lookup = new Map(table.map(d => [String(d[key]), d]));

  return {
    type: "FeatureCollection",
    features: geojson.features
      .map(f => {
        let match = lookup.get(String(f.properties[key]));
        if (!match) return null;  // skip if no match
        return {
          ...f,
          properties: {
            ...f.properties,
            ...match
          }
        };
      })
      .filter(f => f !== null) // remove unmatched features
  };
}
// Function to sync maps
function syncMaps(mapA, mapB) {
  let syncing = false; // prevent infinite loop

  mapA.on('move', () => {
    if (syncing) return;
    syncing = true;
    const center = mapA.getCenter();
    const zoom = mapA.getZoom();
    mapB.setView(center, zoom);
    syncing = false;
  });

  mapB.on('move', () => {
    if (syncing) return;
    syncing = true;
    const center = mapB.getCenter();
    const zoom = mapB.getZoom();
    mapA.setView(center, zoom);
    syncing = false;
  });
}
// loads two maps in one container with spinner 

// --- wrapper function for your maps ---
function mapsWithLoading(maps) {
  const wrapper = html`<div style="position: relative">
    <!-- loading overlay -->
    <div class="loading-overlay" 
         style="position: absolute; inset: 0; 
                display: flex; align-items: center; justify-content: center;
                background: rgba(255,255,255,0.8); z-index: 10; font-weight: bold;">
      Loading maps...
    </div>

    <!-- grid of maps -->
    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 15px">
      <div>${maps.map1}</div>
      <div>${maps.map2}</div>
    </div>
  </div>`;

  const overlay = wrapper.querySelector(".loading-overlay");

  let loadedCount = 0;
  const totalMaps = 2; // change if you add more maps later

  // Listen for map load events
  [maps.map1, maps.map2].forEach(m => {
    m.on("load", () => {
      loadedCount++;
      if (loadedCount === totalMaps) {
        overlay.style.display = "none"; // hide overlay when all are ready
      }
    });
  });

  return wrapper;
}
from_years  = ['1996', '2001', '2008', '2012', '2018']
years = ['1996', '2001', '2008', '2012', '2018', '2023']
// App specific values for dropdown menus
regions = ["New Zealand", "Northland", "Auckland", "Waikato", "Bay of Plenty", "Gisborne", "Hawke's Bay", "Taranaki",  "Manawat\u016B-Whanganui", "Wellington", "Tasman", "Nelson", "Marlborough", "West Coast", "Canterbury", "Otago", "Southland",  "Islands outside region"]  // removed "Chatham Islands", - just using area outside region
all_medium_class = ["Indigenous total", "Exotic total", "Urban area total", "Artificial bare surfaces", "Cropping/horticulture", "Exotic forest", "Exotic grassland", "Exotic scrub/shrubland", "Indigenous forest", "Indigenous scrub/shrubland", "Natural bare/lightly-vegetated surfaces",  "Other herbaceous vegetation", "Tussock grassland", "Urban area", "Water bodies"]  // removed "Not land", and "NA"
viewof state_medium_category1 = Inputs.select(all_medium_class, {value: "Indigenous total", label: "Landclass 1: ", display: "none"});
viewof state_medium_category2 = Inputs.select(all_medium_class, {value: "Exotic total", label: "Landclass 2: ", style: "display:none"});
viewof year = Inputs.select(years, {value: '2023', label: "Year: ", style: "display:none"})
viewof change_medium_category1 = Inputs.select(all_medium_class, {value: "Indigenous total", label: "Landclass 1: "});
viewof change_medium_category2 = Inputs.select(all_medium_class, {value: "Exotic total", label: "Landclass 2: "});
//viewof detailed_category = Inputs.select(detailed_class[change_medium_category], {value: detailed_class[change_medium_category][0], label: "Detailed class: "})
detailed_category = "All" // alternative for when no detailed category supplied
viewof from_year = Inputs.select(from_years, {value: '2018', label: "From year: "});
viewof to_year = Inputs.select(years.slice(years.indexOf(from_year) + 1, 6), {value: '2023', label: "To year: "});
// for state and change
viewof sc_medium_category = Inputs.select(all_medium_class, {value: "Indigenous total", label: "Landclass: ", display: "none"});
//viewof sc_change_medium_category = Inputs.select(all_medium_class, {value: "Indigenous forest", label: "Landclass: "})
viewof sc_from_year = Inputs.select(from_years, {value: '2018', label: "From year: "});
viewof sc_to_year = Inputs.select(years.slice(years.indexOf(from_year) + 1, 6), {value: '2023', label: "To year: "});
viewof graph_type = Inputs.radio(["State", "Change", "State and change"], {value: "State", label: "Compare: "});

html`<div style="display:${graph_type === "State" ? "grid" : "none"};  grid-template-columns: 1fr 1fr; gap: 20px; row-gap: 5px; margin-bottom: 15px">
      <div>${viewof year}</div>
      <div></div>
      <div>${viewof state_medium_category1}</div>
      <div>${viewof state_medium_category2}</div>
    </div>
    <div style="display: ${graph_type === "Change" ? "grid" : "none"}; grid-template-columns: 1fr 1fr; gap: 10px; row-gap: 5px; margin-bottom: 15px">
      <div>${viewof from_year}</div>
      <div></div>
      <div>${viewof to_year}</div>
      <div></div>
      <div>${viewof change_medium_category1}</div>
      <div>${viewof change_medium_category2}</div>
    </div>
    <div style="display: ${graph_type === "State and change" ? "grid" : "none"}; grid-template-columns: 1fr 1fr; gap: 10px; row-gap: 5px; margin-bottom: 15px">
      <div></div>
      <div>${viewof sc_from_year}</div>
      <div>${viewof sc_medium_category}</div>  
      <div>${viewof sc_to_year}</div>
    </div>`
function filterState(data, medium_category, year) {

  let filtered = data.filter(d =>
    d.year === year && (medium_category.includes(d.landcover))).map(d => ({
    landcover: d.landcover,
    year: d.year,
    seqnum: d.seqnum,    
    area_ha: d.area_ha,
    percent_landcover: d.percent_area      // changed label to landcover here
    //percent_change_overall: d.percent_change_overall
  }));
    
  return filtered
}

function filterChange(data, change_medium_category, detailed_category, from_year, to_year) {
  let filtered = data.filter(d =>
    d.year_from === from_year &&
    d.year_to === to_year &&
    (
      detailed_category === "All"
        ? change_medium_category.includes(d.landcover)
        : detailed_category.includes(d.landcover)
    )
  ).map(d => ({
    landcover: d.landcover,
    from_year: d.year_from, 
    to_year: d.year_to,
    seqnum: d.seqnum,
    change_ha: d.change_ha,
    percent_change: d.percent_change,
    relative_change: d.relative_change
  }));
  return filtered;
}
state1_filtered_data = filterState(state, state_medium_category1, year)
state2_filtered_data = filterState(state, state_medium_category2, year)

plot_state1 = joinGeoJSON(grid, state1_filtered_data, "seqnum")
plot_state2 = joinGeoJSON(grid, state2_filtered_data, "seqnum")
filtered_change1 = filterChange(change, change_medium_category1, detailed_category,  from_year, to_year)
filtered_change2 = filterChange(change, change_medium_category2, detailed_category,  from_year, to_year)
// merge with polygon grid
plot_change1 = joinGeoJSON(grid, filtered_change1, "seqnum")
plot_change2 = joinGeoJSON(grid, filtered_change2, "seqnum")
sc_filtered_state = filterState(state, sc_medium_category, sc_to_year)
sc_filtered_change = filterChange(change, sc_medium_category, detailed_category,  sc_from_year, sc_to_year)
// merge with polygon grid
sc_plot_state = joinGeoJSON(grid, sc_filtered_state, "seqnum")
sc_plot_change = joinGeoJSON(grid, sc_filtered_change, "seqnum")
{
  // Build maps locally
  const localMaps = (() => {
    if (graph_type === "State") {
      return {
        map1: map(plot_state1, "area_ha", nz_outline, {
          legend_title: "Land cover (ha)",
          type: "state",
          mapTitle: `${state_medium_category1} land cover area, ${year}`
        }),
        map2: map(plot_state2, "area_ha", nz_outline, {
          legend_title: "Land cover (ha)",
          type: "state",
          mapTitle: `${state_medium_category2} land cover area, ${year}`
        })
      };
    }

    if (graph_type === "Change") {
      return {
        map1: map(plot_change1, "change_ha", nz_outline, {
          legend_title: "Land cover<br>change (ha)",
          type: "change",
          mapTitle: `Change in ${change_medium_category1.toLowerCase()} land cover area, ${from_year}\u2013${to_year}`
        }),
        map2: map(plot_change2, "change_ha", nz_outline, {
          legend_title: "Land cover<br>change (ha)",
          type: "change",
          mapTitle: `Change in ${change_medium_category2.toLowerCase()} land cover area, ${from_year}\u2013${to_year}`
        })
      };
    }

    if (graph_type === "State and change") {
      return {
        map1: map(sc_plot_state, "area_ha", nz_outline, {
          legend_title: "Land cover (ha)",
          type: "state",
          mapTitle: `${sc_medium_category} land cover area, ${sc_to_year}`
        }),
        map2: map(sc_plot_change, "change_ha", nz_outline, {
          legend_title: "Land cover<br>change (ha)",
          type: "change",
          mapTitle: `Change in ${sc_medium_category.toLowerCase()} land cover area, ${sc_from_year}\u2011${sc_to_year}`
        })
      };
    }
  })();

  // Just sync and clean up
  syncMaps(localMaps.map1.value, localMaps.map2.value);
  //syncTooltips(localMaps.map1.value, localMaps.map2.value);

  invalidation.then(() => {
    localMaps.map1.remove();
    localMaps.map2.remove();
  });

  return html`
    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 15px;">
      <div>${localMaps.map1}</div>
      <div>${localMaps.map2}</div>
    </div>`;
}
html`<p style="text-align:right; font-size:10px;">Source: Stats NZ using data from Bioeconomy Science Institute</p>`