Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Toggle map marker details when clicked #23

Merged
merged 18 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 169 additions & 21 deletions src/alpaca-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ export default class AlpacaMap extends LitElement {
/* From me */
--almost-black: #333333;
--grey: #666666;
--brown: #83580b;
--green: #7a9a01; /* Pantone 377 C - from my chair */

color: var(--almost-black);
--private-farm: var(--brown);
--public-farm: var(--green);
}

.web-component-container {
Expand Down Expand Up @@ -182,6 +185,98 @@ export default class AlpacaMap extends LitElement {
width: auto;
background-color: var(--pale-blue);
}

/********* Farm styles in unhighlighted state *********/
/* Ref: https://developers.google.com/maps/documentation/javascript/advanced-markers/html-markers#maps_advanced_markers_html-css */

.farm {
display: flex;
align-items: center;
justify-content: center;

background-color: white;
border-radius: 1rem;
box-shadow: 10px 10px 5px #0003;
color: var(--almost-black);

/* Override google map font to avoid flicker when load */
font:
400 1.5em Poppins,
Arial,
sans-serif;
padding: 0.75rem;

width: auto;
max-width: 15rem;
}

.farm::after {
border-left: 9px solid transparent;
border-right: 9px solid transparent;
content: "";
height: 0;
left: 50%;
position: absolute;
top: 100%;
transform: translate(-50%);
width: 0;
z-index: 1;
}

.farm .summary {
display: flex;
align-items: center;
justify-content: center;

font-size: 1.5rem;
gap: 0.5rem;
}

.farm .details {
display: none;
flex-direction: column;
flex: 1;
}

/********* Farm styles in highlighted state *********/

/* .farm.highlight {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 10px 10px 5px rgba(0, 0, 0, 0.2);
height: 80px;
padding: 8px 15px;
width: auto;
} */

.farm.highlight .details {
display: flex;
}

/********* Farm category colours *********/
.farm.private {
border: 0.2em solid var(--private-farm);
}

.farm.public {
border: 0.2em solid var(--public-farm);
}

.farm.private::after {
border-top: 9px solid var(--private-farm);
}

.farm.public::after {
border-top: 9px solid var(--public-farm);
}

.farm.private .icon svg {
fill: var(--private-farm);
}

.farm.public .icon svg {
fill: var(--public-farm);
}
`,
];

Expand Down Expand Up @@ -290,25 +385,53 @@ export default class AlpacaMap extends LitElement {
this.map.mapTypes.set("styled_map", styledMapType);
this.map.setMapTypeId("styled_map");

const infoWindow = new google.maps.InfoWindow({
content: "",
disableAutoPan: true,
});

// Add markers to the map
function toggleHighlight(markerView, farm) {
if (markerView.content.classList.contains("highlight")) {
markerView.content.classList.remove("highlight");
markerView.zIndex = null;
} else {
markerView.content.classList.add("highlight");
markerView.zIndex = 1;
}
}

function buildContent(farm) {
const content = document.createElement("div");
content.classList.add("farm");
content.classList.add(farm?.category?.private ? "private" : "public");

content.innerHTML = `
<div class="summary">
<div class="icon">${iconHouseFlag().svgString}</div>
<div class="count">${farm?.count?.alpacas?.status?.active} 🦙</div>
</div>

<div class="details">
<h4>${farm?.name}</h4>
${farm?.city}
<address>${farm?.location?.google?.formatted_address}</address>
<address>
<a href="${farm?.location?.google?.directions_url_href}" target="_blank" rel="noreferrer" title="Google directions">Directions</a>
</address>
</div>
`;

return content;
}

const markers = this.farms.map((farm) => {
const marker = new google.maps.marker.AdvancedMarkerElement({
const marker = new AdvancedMarkerElement({
content: buildContent(farm),
position: farm.location.lat_lng,
title: farm?.name,
});

// markers can only be keyboard focusable when they have click listeners
// open info window when marker is clicked

// toggle marker summary/details when marker is clicked
marker.addListener("click", () => {
infoWindow.setContent(
farm.location.lat_lng.lat + ", " + farm.location.lat_lng.lng
);
infoWindow.open(this.map, marker);
toggleHighlight(marker, farm);
});

farm._marker = marker;
Expand All @@ -317,7 +440,20 @@ export default class AlpacaMap extends LitElement {
});

// Add a marker clusterer to manage the markers.
this.cluster = new MarkerClusterer({ map: this.map });
this.cluster = new MarkerClusterer({
map: this.map,
algorithmOptions: {
radius: 100,
/*
Ref: https://www.npmjs.com/package/supercluster
minPoints: 10,
minZoom: 4,
maxZoom: 16,
radius: 100,
maxZoom: 16,
*/
},
});
this.cluster.addMarkers(markers);
}

Expand Down Expand Up @@ -372,17 +508,23 @@ export default class AlpacaMap extends LitElement {
<div class="toggle-group">
<span class="toggle">
<input type="checkbox" id="public" name="public" checked />
<label for="public"> ${iconHouseFlag()}Public farms</label>
<label for="public">
${iconHouseFlag().htmlObject}Public farms</label
>
</span>

<span class="toggle">
<input type="checkbox" id="private" name="private" checked />
<label for="private">${iconKey()}Private farms</label>
<label for="private"
>${iconKey().htmlObject}Private farms</label
>
</span>

<span class="toggle">
<input type="checkbox" id="alpacaSales" name="alpacaSales" />
<label for="alpacaSales">${iconHandshake()}Alpaca sales</label>
<label for="alpacaSales"
>${iconHandshake().htmlObject}Alpaca sales</label
>
</span>

<span class="toggle">
Expand All @@ -392,18 +534,20 @@ export default class AlpacaMap extends LitElement {
name="alpacaWalking"
/>
<label for="alpacaWalking"
>${iconPersonHiking()}Alpaca walking</label
>${iconPersonHiking().htmlObject}Alpaca walking</label
>
</span>

<span class="toggle">
<input type="checkbox" id="bookable" name="bookable" />
<label for="bookable">${iconCalendarCheck()}Bookable</label>
<label for="bookable"
>${iconCalendarCheck().htmlObject}Bookable</label
>
</span>

<span class="toggle">
<input type="checkbox" id="shop" name="shop" />
<label for="shop">${iconStore()}Shop</label>
<label for="shop">${iconStore().htmlObject}Shop</label>
</span>

<span class="toggle">
Expand All @@ -412,12 +556,16 @@ export default class AlpacaMap extends LitElement {
id="overnightStay"
name="overnightStay"
/>
<label for="overnightStay">${iconBed()}Overnight stay</label>
<label for="overnightStay"
>${iconBed().htmlObject}Overnight stay</label
>
</span>

<span class="toggle">
<input type="checkbox" id="studServices" name="studServices" />
<label for="studServices">${iconMars()}Stud services</label>
<label for="studServices"
>${iconMars().htmlObject}Stud services</label
>
</span>
</div>
</form>
Expand Down
81 changes: 65 additions & 16 deletions src/svg-icons.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { css, svg, html } from "lit";
import { unsafeSVG } from "lit/directives/unsafe-svg.js"; /* This is safe since this is duplicating internal known markup, not markup from user */

export const iconStyles = [
css`
Expand All @@ -11,49 +12,97 @@ export const iconStyles = [
];

export const iconBed = () => {
const svgIcon = svg`<path d="M32 32c17.7 0 32 14.3 32 32V320H288V160c0-17.7 14.3-32 32-32H544c53 0 96 43 96 96V448c0 17.7-14.3 32-32 32s-32-14.3-32-32V416H352 320 64v32c0 17.7-14.3 32-32 32s-32-14.3-32-32V64C0 46.3 14.3 32 32 32zm144 96a80 80 0 1 1 0 160 80 80 0 1 1 0-160z"/>`;
const path = `<path d="M32 32c17.7 0 32 14.3 32 32V320H288V160c0-17.7 14.3-32 32-32H544c53 0 96 43 96 96V448c0 17.7-14.3 32-32 32s-32-14.3-32-32V416H352 320 64v32c0 17.7-14.3 32-32 32s-32-14.3-32-32V64C0 46.3 14.3 32 32 32zm144 96a80 80 0 1 1 0 160 80 80 0 1 1 0-160z"/>`;

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
const svgFragment = unsafeSVG(path);

return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconCalendarCheck = () => {
const svgIcon = svg`<path d="M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192zM329 305c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-95 95-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L329 305z"/>`;
const path = `<path d="M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192zM329 305c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-95 95-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L329 305z"/>`;

const svgFragment = unsafeSVG(path);

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconHandshake = () => {
const svgIcon = svg`<path d="M323.4 85.2l-96.8 78.4c-16.1 13-19.2 36.4-7 53.1c12.9 17.8 38 21.3 55.3 7.8l99.3-77.2c7-5.4 17-4.2 22.5 2.8s4.2 17-2.8 22.5l-20.9 16.2L512 316.8V128h-.7l-3.9-2.5L434.8 79c-15.3-9.8-33.2-15-51.4-15c-21.8 0-43 7.5-60 21.2zm22.8 124.4l-51.7 40.2C263 274.4 217.3 268 193.7 235.6c-22.2-30.5-16.6-73.1 12.7-96.8l83.2-67.3c-11.6-4.9-24.1-7.4-36.8-7.4C234 64 215.7 69.6 200 80l-72 48V352h28.2l91.4 83.4c19.6 17.9 49.9 16.5 67.8-3.1c5.5-6.1 9.2-13.2 11.1-20.6l17 15.6c19.5 17.9 49.9 16.6 67.8-2.9c4.5-4.9 7.8-10.6 9.9-16.5c19.4 13 45.8 10.3 62.1-7.5c17.9-19.5 16.6-49.9-2.9-67.8l-134.2-123zM16 128c-8.8 0-16 7.2-16 16V352c0 17.7 14.3 32 32 32H64c17.7 0 32-14.3 32-32V128H16zM48 320a16 16 0 1 1 0 32 16 16 0 1 1 0-32zM544 128V352c0 17.7 14.3 32 32 32h32c17.7 0 32-14.3 32-32V144c0-8.8-7.2-16-16-16H544zm32 208a16 16 0 1 1 32 0 16 16 0 1 1 -32 0z"/>`;
const path = `<path d="M323.4 85.2l-96.8 78.4c-16.1 13-19.2 36.4-7 53.1c12.9 17.8 38 21.3 55.3 7.8l99.3-77.2c7-5.4 17-4.2 22.5 2.8s4.2 17-2.8 22.5l-20.9 16.2L512 316.8V128h-.7l-3.9-2.5L434.8 79c-15.3-9.8-33.2-15-51.4-15c-21.8 0-43 7.5-60 21.2zm22.8 124.4l-51.7 40.2C263 274.4 217.3 268 193.7 235.6c-22.2-30.5-16.6-73.1 12.7-96.8l83.2-67.3c-11.6-4.9-24.1-7.4-36.8-7.4C234 64 215.7 69.6 200 80l-72 48V352h28.2l91.4 83.4c19.6 17.9 49.9 16.5 67.8-3.1c5.5-6.1 9.2-13.2 11.1-20.6l17 15.6c19.5 17.9 49.9 16.6 67.8-2.9c4.5-4.9 7.8-10.6 9.9-16.5c19.4 13 45.8 10.3 62.1-7.5c17.9-19.5 16.6-49.9-2.9-67.8l-134.2-123zM16 128c-8.8 0-16 7.2-16 16V352c0 17.7 14.3 32 32 32H64c17.7 0 32-14.3 32-32V128H16zM48 320a16 16 0 1 1 0 32 16 16 0 1 1 0-32zM544 128V352c0 17.7 14.3 32 32 32h32c17.7 0 32-14.3 32-32V144c0-8.8-7.2-16-16-16H544zm32 208a16 16 0 1 1 32 0 16 16 0 1 1 -32 0z"/>`;

const svgFragment = unsafeSVG(path);

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconHouseFlag = () => {
const svgIcon = svg`<path d="M480 0c-17.7 0-32 14.3-32 32V192 512h64V192H624c8.8 0 16-7.2 16-16V48c0-8.8-7.2-16-16-16H512c0-17.7-14.3-32-32-32zM416 159L276.8 39.7c-12-10.3-29.7-10.3-41.7 0l-224 192C1 240.4-2.7 254.5 2 267.1S18.6 288 32 288H64V480c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V384c0-17.7 14.3-32 32-32h64c17.7 0 32 14.3 32 32v96c0 17.7 14.3 32 32 32h64.7l.2 0h-1V159z"/>`;
const path = `<path d="M480 0c-17.7 0-32 14.3-32 32V192 512h64V192H624c8.8 0 16-7.2 16-16V48c0-8.8-7.2-16-16-16H512c0-17.7-14.3-32-32-32zM416 159L276.8 39.7c-12-10.3-29.7-10.3-41.7 0l-224 192C1 240.4-2.7 254.5 2 267.1S18.6 288 32 288H64V480c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V384c0-17.7 14.3-32 32-32h64c17.7 0 32 14.3 32 32v96c0 17.7 14.3 32 32 32h64.7l.2 0h-1V159z" />`;

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
const svgFragment = unsafeSVG(path);

return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconKey = () => {
const svgIcon = svg`<path d="M336 352c97.2 0 176-78.8 176-176S433.2 0 336 0S160 78.8 160 176c0 18.7 2.9 36.8 8.3 53.7L7 391c-4.5 4.5-7 10.6-7 17v80c0 13.3 10.7 24 24 24h80c13.3 0 24-10.7 24-24V448h40c13.3 0 24-10.7 24-24V384h40c6.4 0 12.5-2.5 17-7l33.3-33.3c16.9 5.4 35 8.3 53.7 8.3zM376 96a40 40 0 1 1 0 80 40 40 0 1 1 0-80z"/>`;
const path = `<path d="M336 352c97.2 0 176-78.8 176-176S433.2 0 336 0S160 78.8 160 176c0 18.7 2.9 36.8 8.3 53.7L7 391c-4.5 4.5-7 10.6-7 17v80c0 13.3 10.7 24 24 24h80c13.3 0 24-10.7 24-24V448h40c13.3 0 24-10.7 24-24V384h40c6.4 0 12.5-2.5 17-7l33.3-33.3c16.9 5.4 35 8.3 53.7 8.3zM376 96a40 40 0 1 1 0 80 40 40 0 1 1 0-80z"/>`;

const svgFragment = unsafeSVG(path);

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconMars = () => {
const svgIcon = svg`<path d="M289.8 46.8c3.7-9 12.5-14.8 22.2-14.8H424c13.3 0 24 10.7 24 24V168c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-33.4-33.4L321 204.2c19.5 28.4 31 62.7 31 99.8c0 97.2-78.8 176-176 176S0 401.2 0 304s78.8-176 176-176c37 0 71.4 11.4 99.8 31l52.6-52.6L295 73c-6.9-6.9-8.9-17.2-5.2-26.2zM400 80l0 0h0v0zM176 416a112 112 0 1 0 0-224 112 112 0 1 0 0 224z"/>`;
const path = `<path d="M289.8 46.8c3.7-9 12.5-14.8 22.2-14.8H424c13.3 0 24 10.7 24 24V168c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-33.4-33.4L321 204.2c19.5 28.4 31 62.7 31 99.8c0 97.2-78.8 176-176 176S0 401.2 0 304s78.8-176 176-176c37 0 71.4 11.4 99.8 31l52.6-52.6L295 73c-6.9-6.9-8.9-17.2-5.2-26.2zM400 80l0 0h0v0zM176 416a112 112 0 1 0 0-224 112 112 0 1 0 0 224z"/>`;

const svgFragment = unsafeSVG(path);

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconPersonHiking = () => {
const svgIcon = svg`<path d="M192 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm51.3 182.7L224.2 307l49.7 49.7c9 9 14.1 21.2 14.1 33.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V397.3l-73.9-73.9c-15.8-15.8-22.2-38.6-16.9-60.3l20.4-84c8.3-34.1 42.7-54.9 76.7-46.4c19 4.8 35.6 16.4 46.4 32.7L305.1 208H336V184c0-13.3 10.7-24 24-24s24 10.7 24 24v55.8c0 .1 0 .2 0 .2s0 .2 0 .2V488c0 13.3-10.7 24-24 24s-24-10.7-24-24V272H296.6c-16 0-31-8-39.9-21.4l-13.3-20zM81.1 471.9L117.3 334c3 4.2 6.4 8.2 10.1 11.9l41.9 41.9L142.9 488.1c-4.5 17.1-22 27.3-39.1 22.8s-27.3-22-22.8-39.1zm55.5-346L101.4 266.5c-3 12.1-14.9 19.9-27.2 17.9l-47.9-8c-14-2.3-22.9-16.3-19.2-30L31.9 155c9.5-34.8 41.1-59 77.2-59h4.2c15.6 0 27.1 14.7 23.3 29.8z"/>`;
const path = `<path d="M192 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm51.3 182.7L224.2 307l49.7 49.7c9 9 14.1 21.2 14.1 33.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V397.3l-73.9-73.9c-15.8-15.8-22.2-38.6-16.9-60.3l20.4-84c8.3-34.1 42.7-54.9 76.7-46.4c19 4.8 35.6 16.4 46.4 32.7L305.1 208H336V184c0-13.3 10.7-24 24-24s24 10.7 24 24v55.8c0 .1 0 .2 0 .2s0 .2 0 .2V488c0 13.3-10.7 24-24 24s-24-10.7-24-24V272H296.6c-16 0-31-8-39.9-21.4l-13.3-20zM81.1 471.9L117.3 334c3 4.2 6.4 8.2 10.1 11.9l41.9 41.9L142.9 488.1c-4.5 17.1-22 27.3-39.1 22.8s-27.3-22-22.8-39.1zm55.5-346L101.4 266.5c-3 12.1-14.9 19.9-27.2 17.9l-47.9-8c-14-2.3-22.9-16.3-19.2-30L31.9 155c9.5-34.8 41.1-59 77.2-59h4.2c15.6 0 27.1 14.7 23.3 29.8z"/>`;

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
const svgFragment = unsafeSVG(path);

return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const iconStore = () => {
const svgIcon = svg`<path d="M547.6 103.8L490.3 13.1C485.2 5 476.1 0 466.4 0H109.6C99.9 0 90.8 5 85.7 13.1L28.3 103.8c-29.6 46.8-3.4 111.9 51.9 119.4c4 .5 8.1 .8 12.1 .8c26.1 0 49.3-11.4 65.2-29c15.9 17.6 39.1 29 65.2 29c26.1 0 49.3-11.4 65.2-29c15.9 17.6 39.1 29 65.2 29c26.2 0 49.3-11.4 65.2-29c16 17.6 39.1 29 65.2 29c4.1 0 8.1-.3 12.1-.8c55.5-7.4 81.8-72.5 52.1-119.4zM499.7 254.9l-.1 0c-5.3 .7-10.7 1.1-16.2 1.1c-12.4 0-24.3-1.9-35.4-5.3V384H128V250.6c-11.2 3.5-23.2 5.4-35.6 5.4c-5.5 0-11-.4-16.3-1.1l-.1 0c-4.1-.6-8.1-1.3-12-2.3V384v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V384 252.6c-4 1-8 1.8-12.3 2.3z"/>`;
const path = `<path d="M547.6 103.8L490.3 13.1C485.2 5 476.1 0 466.4 0H109.6C99.9 0 90.8 5 85.7 13.1L28.3 103.8c-29.6 46.8-3.4 111.9 51.9 119.4c4 .5 8.1 .8 12.1 .8c26.1 0 49.3-11.4 65.2-29c15.9 17.6 39.1 29 65.2 29c26.1 0 49.3-11.4 65.2-29c15.9 17.6 39.1 29 65.2 29c26.2 0 49.3-11.4 65.2-29c16 17.6 39.1 29 65.2 29c4.1 0 8.1-.3 12.1-.8c55.5-7.4 81.8-72.5 52.1-119.4zM499.7 254.9l-.1 0c-5.3 .7-10.7 1.1-16.2 1.1c-12.4 0-24.3-1.9-35.4-5.3V384H128V250.6c-11.2 3.5-23.2 5.4-35.6 5.4c-5.5 0-11-.4-16.3-1.1l-.1 0c-4.1-.6-8.1-1.3-12-2.3V384v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V384 252.6c-4 1-8 1.8-12.3 2.3z"/>`;

const svgFragment = unsafeSVG(path);

return {
svgString: toSVGString(path),
htmlObject: toHTMLObject(svgFragment),
};
};

export const toSVGString = (path) => {
return `<svg class="icon" viewBox="0 0 640 512">${path}</svg>`;
};

return html`<svg class="icon" viewBox="0 0 640 512">${svgIcon}</svg>`;
export const toHTMLObject = (svgFragment) => {
return html`<svg class="icon" viewBox="0 0 640 512">${svgFragment}</svg>`;
};