feat(PublicPages): add email and phone obfucations

This commit is contained in:
Matthieu Bessat 2020-08-14 16:30:08 +02:00
parent 9ae135f816
commit 4d46ed86f4
4 changed files with 139 additions and 29 deletions

View file

@ -123,3 +123,51 @@ let closeModal = () => {
video.pause() video.pause()
} }
} }
/**
* Decode things
*/
let formatPhone = (phone) => {
if (phone.indexOf('+33') === 0) {
phone = '0' + phone.substr(3)
}
let phoneSplit = ''
let partEnd = false
for (var i = 0; i < phone.length; i++) {
phoneSplit += phone.charAt(i)
if (partEnd === true) {
phoneSplit += ' '
}
partEnd = !partEnd
}
return ["+33" + phone.substr(1), phoneSplit]
}
/**
* Decode email and phone data
*/
document.querySelectorAll('.offuscated').forEach(node => {
let data = node.getAttribute('data-source')
let type = node.getAttribute('data-source-type')
data = data.replace(/'/gm, '"').replace(/%_/gm, '')
data = data.replace(/\$/gm, 'A')
data = data.replace(/£/gm, 'c')
data = data.replace(/ù/gm, 'b')
data = data.replace(/#/gm, 'd')
data = data.replace(/µ/gm, 'e')
data = data.replace(/§/gm, 'z')
data = data.replace(/à/gm, 'i')
data = data.replace(/\|/gm, 'f')
data = JSON.parse(data).join('')
data = atob(data)
if (type === 'phone') {
data = formatPhone(data)
node.href = node.href.replace('data', data[0])
data = data[1]
} else {
node.href = node.href.replace('data', data)
}
node.textContent = data
})

View file

@ -1 +1 @@
document.querySelectorAll(".schedule-category").forEach(e=>{let t=!1,i=e.querySelector(".schedule-category-collapse-icon"),o=e.querySelector(".schedule-category-table");e.querySelector(".schedule-category-header").onclick=()=>{t?(i.style.transform="rotate(180deg)",o.style.maxHeight=null):(i.style.transform="rotate(0deg)",o.style.maxHeight=o.scrollHeight+"px"),t=!t}});let description=document.querySelector(".description-cutted"),descriptionActions=document.querySelector(".description-actions-container"),descriptionOpened=!1,defaultMaxHeight="";if(null!==description){let e=document.querySelector(".description-btn");e.onclick=()=>{descriptionOpened?(descriptionActions.className+=" closed",description.style.maxHeight=defaultMaxHeight,e.textContent="Ouvrir la description"):(descriptionActions.className=descriptionActions.className.replace(" closed",""),defaultMaxHeight=description.style.maxHeight,description.style.maxHeight=description.scrollHeight+"px",e.textContent="Fermer la description"),descriptionOpened=!descriptionOpened}}let mediaModal=document.querySelector("#media-modal"),mediaModalContent=document.querySelector("#media-modal-content"),openModal=(e,t)=>{mediaModal.style.visibility="visible",mediaModal.style.opacity=1,mediaModalContent.innerHTML="";let i=document.createAttribute("src");i.value=e;let o=null;if(t){o=document.createElement("video"),o.setAttribute("controls",""),o.setAttribute("autoplay",""),o.setAttribute("name","media");let t=document.createElement("source");t.setAttribute("src",e),t.setAttribute("type","video/mp4"),o.appendChild(t)}else o=document.createElement("img"),o.attributes.setNamedItem(i);mediaModalContent.appendChild(o),document.body.style.overflow="hidden",document.body.style.touchAction="none",setTimeout(()=>{const e=t=>{!mediaModalContent.contains(t.target)&&isVisible(mediaModalContent)&&(closeModal(),document.removeEventListener("click",e))};document.addEventListener("click",e)},100)};const isVisible=e=>!!e&&!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length);let closeModal=()=>{mediaModal.style.visibility="hidden",mediaModal.style.opacity=0,document.body.style.overflow="initial",document.body.style.height="initial";let e=document.querySelector("#media-modal video");null!==e&&e.pause()}; document.querySelectorAll(".schedule-category").forEach(e=>{let t=!1,o=e.querySelector(".schedule-category-collapse-icon"),i=e.querySelector(".schedule-category-table");e.querySelector(".schedule-category-header").onclick=()=>{t?(o.style.transform="rotate(180deg)",i.style.maxHeight=null):(o.style.transform="rotate(0deg)",i.style.maxHeight=i.scrollHeight+"px"),t=!t}});let description=document.querySelector(".description-cutted"),descriptionActions=document.querySelector(".description-actions-container"),descriptionOpened=!1,defaultMaxHeight="";if(null!==description){let e=document.querySelector(".description-btn");e.onclick=()=>{descriptionOpened?(descriptionActions.className+=" closed",description.style.maxHeight=defaultMaxHeight,e.textContent="Ouvrir la description"):(descriptionActions.className=descriptionActions.className.replace(" closed",""),defaultMaxHeight=description.style.maxHeight,description.style.maxHeight=description.scrollHeight+"px",e.textContent="Fermer la description"),descriptionOpened=!descriptionOpened}}let mediaModal=document.querySelector("#media-modal"),mediaModalContent=document.querySelector("#media-modal-content"),openModal=(e,t)=>{mediaModal.style.visibility="visible",mediaModal.style.opacity=1,mediaModalContent.innerHTML="";let o=document.createAttribute("src");o.value=e;let i=null;if(t){i=document.createElement("video"),i.setAttribute("controls",""),i.setAttribute("autoplay",""),i.setAttribute("name","media");let t=document.createElement("source");t.setAttribute("src",e),t.setAttribute("type","video/mp4"),i.appendChild(t)}else i=document.createElement("img"),i.attributes.setNamedItem(o);mediaModalContent.appendChild(i),document.body.style.overflow="hidden",document.body.style.touchAction="none",setTimeout(()=>{const e=t=>{!mediaModalContent.contains(t.target)&&isVisible(mediaModalContent)&&(closeModal(),document.removeEventListener("click",e))};document.addEventListener("click",e)},100)};const isVisible=e=>!!e&&!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length);let closeModal=()=>{mediaModal.style.visibility="hidden",mediaModal.style.opacity=0,document.body.style.overflow="initial",document.body.style.height="initial",document.body.style.touchAction="initial";let e=document.querySelector("#media-modal video");null!==e&&e.pause()},formatPhone=e=>{0===e.indexOf("+33")&&(e="0"+e.substr(3));let t="",o=!1;for(var i=0;i<e.length;i++)t+=e.charAt(i),!0===o&&(t+=" "),o=!o;return["+33"+e.substr(1),t]};document.querySelectorAll(".offuscated").forEach(e=>{let t=e.getAttribute("data-source"),o=e.getAttribute("data-source-type");t=t.replace(/'/gm,'"').replace(/%_/gm,""),t=t.replace(/\$/gm,"A"),t=t.replace(/£/gm,"c"),t=t.replace(/ù/gm,"b"),t=t.replace(/#/gm,"d"),t=t.replace(/µ/gm,"e"),t=t.replace(/§/gm,"z"),t=t.replace(/à/gm,"i"),t=t.replace(/\|/gm,"f"),t=JSON.parse(t).join(""),t=atob(t),"phone"===o?(t=formatPhone(t),e.href=e.href.replace("data",t[0]),t=t[1]):e.href=e.href.replace("data",t),e.textContent=t});

View file

@ -99,39 +99,73 @@ export default class PublicController {
version.cover = { location: "https://fva-condorcet.s3.fr-par.scw.cloud/default-cover-plz-get-you-a-real-image.jpg" } version.cover = { location: "https://fva-condorcet.s3.fr-par.scw.cloud/default-cover-plz-get-you-a-real-image.jpg" }
} }
let formatPhone = (phone: string) => { let offuscate = (original: string) => {
if (phone.indexOf('+33') === 0) { if (original.length === 0) { return original }
phone = '0' + phone.substr(3) let cipher: string[] = []
let data: string = Buffer.from(original, 'utf-8').toString('base64')
data = data.replace(/A/gm, '$')
data = data.replace(/c/gm, '£')
data = data.replace(/b/gm, 'ù')
data = data.replace(/d/gm, '#')
data = data.replace(/e/gm, 'µ')
data = data.replace(/z/gm, '§')
data = data.replace(/i/gm, 'à')
data = data.replace(/f/gm, '|')
let remain = data
let take: number = 0
while (remain.length != 0) {
take = remain.length
if (take > 5) { take = 5 }
take = Math.floor(Math.random() * (take + 1))
if (take === 0) { take = 2 }
if (take > remain.length) {
take = remain.length
} }
let phoneSplit = '' cipher.push('%_' + remain.substr(0, take) + '%_')
let partEnd = false remain = remain.substr(take)
for (var i = 0; i < phone.length; i++) {
phoneSplit += phone.charAt(i)
if (partEnd === true) {
phoneSplit += ' '
}
partEnd = !partEnd
}
return ["+33" + phone.substr(1), phoneSplit]
} }
return JSON.stringify(cipher).replace(/"/gm, "'")
}
/**
* Parse contact
*/
if (Utils.isUsable(version.contacts)) { if (Utils.isUsable(version.contacts)) {
if (Utils.isStrUsable(version.contacts, 'email')) {
version.contacts.email = offuscate(version.contacts.email)
}
if (Utils.isStrUsable(version.contacts, 'address')) { if (Utils.isStrUsable(version.contacts, 'address')) {
version.contacts.address = version.contacts.address.split('\n') version.contacts.address = version.contacts.address.split('\n')
} }
if (Utils.isStrUsable(version.contacts, 'phone')) { if (Utils.isStrUsable(version.contacts, 'phone')) {
let formated: any = formatPhone(version.contacts.phone) //let formated: any = formatPhone(version.contacts.phone)
version.contacts.phoneInt = formated[0] version.contacts.phone = offuscate(version.contacts.phone)
version.contacts.phoneSplit = formated[1]
} }
if (Utils.isUsable(version.contacts, 'peoples') && Array.isArray(version.contacts.peoples)) { if (Utils.isUsable(version.contacts, 'peoples') && Array.isArray(version.contacts.peoples)) {
version.contacts.peoples = version.contacts.peoples.map((p: any) => { version.contacts.peoples = version.contacts.peoples.map((p: any) => {
let formated: any = formatPhone(p.phone) // let formated: any = formatPhone(p.phone)
p.phoneInt = formated[0] // p.phoneInt = formated[0]
p.phoneSplit = formated[1] // p.phoneSplit = formated[1]
p.email = offuscate(p.email)
p.phone = offuscate(p.phone)
return p return p
}) })
} }
if (Utils.isStrUsable(version.contacts, 'website')) {
if (version.contacts.website.indexOf('https://') === -1 || version.contacts.website.indexOf('https://') === -1) {
version.contacts.website = 'http://' + version.contacts.website
}
try {
let url = new URL(version.contacts.website)
version.contacts.websiteLabel = url.hostname + (url.pathname === '/' ? '' : url.pathname)
} catch (err) {
console.error(err)
version.contacts.website = version.contacts.websiteLabel = "OULA, c'est pas bon :("
}
}
if (Utils.isStrUsable(version.contacts, 'facebook')) { if (Utils.isStrUsable(version.contacts, 'facebook')) {
try { try {
version.contacts.facebookLabel = new URL(version.contacts.facebook).pathname.replace('/', '') version.contacts.facebookLabel = new URL(version.contacts.facebook).pathname.replace('/', '')
@ -157,12 +191,20 @@ export default class PublicController {
} }
} }
} }
/**
* Parse gallery
*/
if (Array.isArray(version.gallery)) { if (Array.isArray(version.gallery)) {
version.gallery = version.gallery.slice(0, 5).map((media: any) => { version.gallery = version.gallery.slice(0, 5).map((media: any) => {
media.isVideo = media.contentType.indexOf('video/') !== -1 media.isVideo = media.contentType.indexOf('video/') !== -1
return media return media
}) })
} }
/**
* Parse schedule
*/
if (Utils.isUsable(version, 'schedule')) { if (Utils.isUsable(version, 'schedule')) {
// TODO: Rassembler les horaires qui sont dans le même jour, uniquement pour le front // TODO: Rassembler les horaires qui sont dans le même jour, uniquement pour le front
version.schedule = version.schedule.map((s: any) => { version.schedule = version.schedule.map((s: any) => {
@ -193,6 +235,12 @@ export default class PublicController {
// if (hour.charAt(1) === ':') { // if (hour.charAt(1) === ':') {
// hour = '0' + hour // hour = '0' + hour
// } // }
/**
* Obfuscate emails and phone
*/
res.render('organization.twig', { res.render('organization.twig', {
layout: 'standalone', layout: 'standalone',
data: version, data: version,

View file

@ -202,17 +202,23 @@
<svg class="people-contact-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg class="people-contact-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 8C118.941 8 8 118.919 8 256c0 137.059 110.919 248 248 248 48.154 0 95.342-14.14 135.408-40.223 12.005-7.815 14.625-24.288 5.552-35.372l-10.177-12.433c-7.671-9.371-21.179-11.667-31.373-5.129C325.92 429.757 291.314 440 256 440c-101.458 0-184-82.542-184-184S154.542 72 256 72c100.139 0 184 57.619 184 160 0 38.786-21.093 79.742-58.17 83.693-17.349-.454-16.91-12.857-13.476-30.024l23.433-121.11C394.653 149.75 383.308 136 368.225 136h-44.981a13.518 13.518 0 0 0-13.432 11.993l-.01.092c-14.697-17.901-40.448-21.775-59.971-21.775-74.58 0-137.831 62.234-137.831 151.46 0 65.303 36.785 105.87 96 105.87 26.984 0 57.369-15.637 74.991-38.333 9.522 34.104 40.613 34.103 70.71 34.103C462.609 379.41 504 307.798 504 232 504 95.653 394.023 8 256 8zm-21.68 304.43c-22.249 0-36.07-15.623-36.07-40.771 0-44.993 30.779-72.729 58.63-72.729 22.292 0 35.601 15.241 35.601 40.77 0 45.061-33.875 72.73-58.161 72.73z"></path> <path fill="currentColor" d="M256 8C118.941 8 8 118.919 8 256c0 137.059 110.919 248 248 248 48.154 0 95.342-14.14 135.408-40.223 12.005-7.815 14.625-24.288 5.552-35.372l-10.177-12.433c-7.671-9.371-21.179-11.667-31.373-5.129C325.92 429.757 291.314 440 256 440c-101.458 0-184-82.542-184-184S154.542 72 256 72c100.139 0 184 57.619 184 160 0 38.786-21.093 79.742-58.17 83.693-17.349-.454-16.91-12.857-13.476-30.024l23.433-121.11C394.653 149.75 383.308 136 368.225 136h-44.981a13.518 13.518 0 0 0-13.432 11.993l-.01.092c-14.697-17.901-40.448-21.775-59.971-21.775-74.58 0-137.831 62.234-137.831 151.46 0 65.303 36.785 105.87 96 105.87 26.984 0 57.369-15.637 74.991-38.333 9.522 34.104 40.613 34.103 70.71 34.103C462.609 379.41 504 307.798 504 232 504 95.653 394.023 8 256 8zm-21.68 304.43c-22.249 0-36.07-15.623-36.07-40.771 0-44.993 30.779-72.729 58.63-72.729 22.292 0 35.601 15.241 35.601 40.77 0 45.061-33.875 72.73-58.161 72.73z"></path>
</svg> </svg>
<a href="mailto:{{ data.contacts.email|e }}" class="people-email-content"> <a
{{ data.contacts.email|e }} href="mailto:data"
class="people-email-content offuscated"
data-source-type="email"
data-source="{{ data.contacts.email }}">
</a> </a>
</div> </div>
{% if data.contacts.phoneInt is not empty %} {% if data.contacts.phone is not empty %}
<div class="people-contact phone"> <div class="people-contact phone">
<svg class="people-contact-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg class="people-contact-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M493.4 24.6l-104-24c-11.3-2.6-22.9 3.3-27.5 13.9l-48 112c-4.2 9.8-1.4 21.3 6.9 28l60.6 49.6c-36 76.7-98.9 140.5-177.2 177.2l-49.6-60.6c-6.8-8.3-18.2-11.1-28-6.9l-112 48C3.9 366.5-2 378.1.6 389.4l24 104C27.1 504.2 36.7 512 48 512c256.1 0 464-207.5 464-464 0-11.2-7.7-20.9-18.6-23.4z"></path> <path fill="currentColor" d="M493.4 24.6l-104-24c-11.3-2.6-22.9 3.3-27.5 13.9l-48 112c-4.2 9.8-1.4 21.3 6.9 28l60.6 49.6c-36 76.7-98.9 140.5-177.2 177.2l-49.6-60.6c-6.8-8.3-18.2-11.1-28-6.9l-112 48C3.9 366.5-2 378.1.6 389.4l24 104C27.1 504.2 36.7 512 48 512c256.1 0 464-207.5 464-464 0-11.2-7.7-20.9-18.6-23.4z"></path>
</svg> </svg>
<a href="telto:{{ data.contacts.phoneInt|e }}" class="people-email-content"> <a
{{ data.contacts.phoneSplit|e }} href="telto:data"
class="people-email-content offuscated"
data-source-type="phone"
data-source="{{ data.contacts.phone }}">
</a> </a>
</div> </div>
{% endif %} {% endif %}
@ -233,18 +239,26 @@
<svg class="people-contact-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg class="people-contact-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 8C118.941 8 8 118.919 8 256c0 137.059 110.919 248 248 248 48.154 0 95.342-14.14 135.408-40.223 12.005-7.815 14.625-24.288 5.552-35.372l-10.177-12.433c-7.671-9.371-21.179-11.667-31.373-5.129C325.92 429.757 291.314 440 256 440c-101.458 0-184-82.542-184-184S154.542 72 256 72c100.139 0 184 57.619 184 160 0 38.786-21.093 79.742-58.17 83.693-17.349-.454-16.91-12.857-13.476-30.024l23.433-121.11C394.653 149.75 383.308 136 368.225 136h-44.981a13.518 13.518 0 0 0-13.432 11.993l-.01.092c-14.697-17.901-40.448-21.775-59.971-21.775-74.58 0-137.831 62.234-137.831 151.46 0 65.303 36.785 105.87 96 105.87 26.984 0 57.369-15.637 74.991-38.333 9.522 34.104 40.613 34.103 70.71 34.103C462.609 379.41 504 307.798 504 232 504 95.653 394.023 8 256 8zm-21.68 304.43c-22.249 0-36.07-15.623-36.07-40.771 0-44.993 30.779-72.729 58.63-72.729 22.292 0 35.601 15.241 35.601 40.77 0 45.061-33.875 72.73-58.161 72.73z"></path> <path fill="currentColor" d="M256 8C118.941 8 8 118.919 8 256c0 137.059 110.919 248 248 248 48.154 0 95.342-14.14 135.408-40.223 12.005-7.815 14.625-24.288 5.552-35.372l-10.177-12.433c-7.671-9.371-21.179-11.667-31.373-5.129C325.92 429.757 291.314 440 256 440c-101.458 0-184-82.542-184-184S154.542 72 256 72c100.139 0 184 57.619 184 160 0 38.786-21.093 79.742-58.17 83.693-17.349-.454-16.91-12.857-13.476-30.024l23.433-121.11C394.653 149.75 383.308 136 368.225 136h-44.981a13.518 13.518 0 0 0-13.432 11.993l-.01.092c-14.697-17.901-40.448-21.775-59.971-21.775-74.58 0-137.831 62.234-137.831 151.46 0 65.303 36.785 105.87 96 105.87 26.984 0 57.369-15.637 74.991-38.333 9.522 34.104 40.613 34.103 70.71 34.103C462.609 379.41 504 307.798 504 232 504 95.653 394.023 8 256 8zm-21.68 304.43c-22.249 0-36.07-15.623-36.07-40.771 0-44.993 30.779-72.729 58.63-72.729 22.292 0 35.601 15.241 35.601 40.77 0 45.061-33.875 72.73-58.161 72.73z"></path>
</svg> </svg>
<a href="mailto:{{ people.email|e }}" class="people-email-content"> <a
{{ people.email|e }} href="mailto:data"
class="people-email-content offuscated"
data-source-type="email"
data-source="{{ people.email }}">
</a> </a>
</div> </div>
{% if people.phone is not empty %}
<div class="people-contact phone"> <div class="people-contact phone">
<svg class="people-contact-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg class="people-contact-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M493.4 24.6l-104-24c-11.3-2.6-22.9 3.3-27.5 13.9l-48 112c-4.2 9.8-1.4 21.3 6.9 28l60.6 49.6c-36 76.7-98.9 140.5-177.2 177.2l-49.6-60.6c-6.8-8.3-18.2-11.1-28-6.9l-112 48C3.9 366.5-2 378.1.6 389.4l24 104C27.1 504.2 36.7 512 48 512c256.1 0 464-207.5 464-464 0-11.2-7.7-20.9-18.6-23.4z"></path> <path fill="currentColor" d="M493.4 24.6l-104-24c-11.3-2.6-22.9 3.3-27.5 13.9l-48 112c-4.2 9.8-1.4 21.3 6.9 28l60.6 49.6c-36 76.7-98.9 140.5-177.2 177.2l-49.6-60.6c-6.8-8.3-18.2-11.1-28-6.9l-112 48C3.9 366.5-2 378.1.6 389.4l24 104C27.1 504.2 36.7 512 48 512c256.1 0 464-207.5 464-464 0-11.2-7.7-20.9-18.6-23.4z"></path>
</svg> </svg>
<a href="telto:{{ people.phoneInt|e }}" class="people-email-content"> <a
{{ people.phoneSplit|e }} href="telto:data"
class="people-email-content offuscate"
data-source-type="phone"
data-source="{{ people.phone }}">
</a> </a>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
@ -310,7 +324,7 @@
</div> </div>
<div class="contact-content"> <div class="contact-content">
<a href="{{ data.contacts.website|e }}"> <a href="{{ data.contacts.website|e }}">
{{ data.contacts.website|e }} {{ data.contacts.websiteLabel|e }}
<svg class="external-link" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"> <svg class="external-link" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path fill="currentColor" d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"></path> <path fill="currentColor" d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"></path>
</svg> </svg>