diff --git a/puppeteer/build-bricks-and-break.js b/puppeteer/build-bricks-and-break.js new file mode 100644 index 000000000..dff5778bf --- /dev/null +++ b/puppeteer/build-bricks-and-break.js @@ -0,0 +1,47 @@ +const body = document.querySelector('body') + +const create = (tag) => { + const element = document.createElement(tag) + return element +} + +export const build = (amount = 54) => { + let count = 1 + const intervalID = setInterval(() => { + const brick = create('div') + brick.title = 'brick' + brick.id = `brick-${count}` + if (count % 3 === 2) { + brick.dataset.foundation = true + } + brick.append(count) + body.append(brick) + + if (count === amount) { + window.clearInterval(intervalID) + return + } + + count++ + }, 100) +} + +export const repair = (...ids) => { + ids.forEach((id) => { + const toRepair = document.getElementById(id) + if (toRepair) { + toRepair.dataset.repaired = toRepair.hasAttribute('data-foundation') + ? 'in progress' + : true + } + }) +} + +export const destroy = () => { + const bricks = [...document.querySelectorAll('[title="brick"]')] + const toRemove = bricks[bricks.length - 1] + + if (toRemove) { + toRemove.remove() + } +} diff --git a/puppeteer/fifty-shades-of-cold.js b/puppeteer/fifty-shades-of-cold.js new file mode 100644 index 000000000..f7d545934 --- /dev/null +++ b/puppeteer/fifty-shades-of-cold.js @@ -0,0 +1,46 @@ +import { colors } from './data.js' + +export const generateClasses = () => { + document.head.append( + Object.assign(document.createElement('style'), { + type: 'text/css', + id: 'colors', + innerHTML: colors + .map((color) => `.${color} { background: ${color}; }`) + .join('\n'), + }), + ) +} + +const body = document.querySelector('body') + +const cold = ['aqua', 'blue', 'turquoise', 'green', 'purple', 'cyan', 'navy'] + +export const generateColdShades = () => { + const shades = colors.filter((color) => { + for (const c of cold) { + if (color.includes(c)) { + return true + } + } + }) + + shades.forEach((c) => { + const shade = document.createElement('div') + shade.className = c + shade.textContent = c + body.append(shade) + }) +} + +export const choseShade = (shade) => { + const all = [...document.querySelectorAll('div')] + all.forEach((a) => { + if (!a.classList.contains(shade)) { + a.classList.replace(a.className, shade) + } + }) +} + +generateClasses() +generateColdShades() diff --git a/puppeteer/get-them-all.js b/puppeteer/get-them-all.js new file mode 100644 index 000000000..11372b592 --- /dev/null +++ b/puppeteer/get-them-all.js @@ -0,0 +1,25 @@ +export const getArchitects = () => { + const architects = [...document.getElementsByTagName('a')] + const others = [...document.getElementsByTagName('span')] + return [architects, others] +} + +export const getClassical = () => { + const classicals = [...document.getElementsByClassName('classical')] + const others = [...document.querySelectorAll('a:not(.classical)')] + return [classicals, others] +} + +export const getActive = () => { + const active = [...document.querySelectorAll('.classical.active')] + const others = [...document.querySelectorAll('.classical:not(.active)')] + return [active, others] +} + +export const getBonannoPisano = () => { + const bonanno = document.getElementById('BonannoPisano') + const others = [ + ...document.querySelectorAll('a.classical.active:not(#BonannoPisano)'), + ] + return [bonanno, others] +} diff --git a/puppeteer/get-them-all_test.js b/puppeteer/get-them-all_test.js new file mode 100644 index 000000000..b4d85c7f0 --- /dev/null +++ b/puppeteer/get-them-all_test.js @@ -0,0 +1,169 @@ +import { deepStrictEqual } from 'assert' +import puppeteer from 'puppeteer-core' +import people from '../assets/data/get-them-all.js' + +const config = { + headless: false, + executablePath: + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', +} + +const browser = await puppeteer.launch(config) +const [page] = await browser.pages() +await page.goto('http://localhost:8000/dom-js/get-them-all/') + +const architects = people + .filter((p) => p.tag === 'a') + .map((e) => e.id) + .sort((a, b) => a.localeCompare(b)) + +const notArchitects = people + .filter((p) => p.tag !== 'a') + .map((e) => e.id) + .sort((a, b) => a.localeCompare(b)) + +const checkArchitects = async () => { + const btnArchitect = await page.$(`#btnArchitect`) + btnArchitect.click() + await page.waitFor(500) + + const selected = await page.$$eval('a', (nodes) => + nodes + .filter((node) => node.textContent === 'Architect') + .map((node) => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + const eliminated = await page.$$eval('span', (nodes) => + nodes + .filter((node) => node.style.opacity === '0.2') + .map((node) => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + deepStrictEqual(`architects: ${selected}`, `architects: ${architects}`) + deepStrictEqual( + `not architects: ${eliminated}`, + `not architects: ${notArchitects}`, + ) +} + +checkArchitects() +await page.waitFor(1000) + +// get classical +const classical = people + .filter((p) => p.classe === 'classical') + .map((e) => e.id) + .sort((a, b) => a.localeCompare(b)) + +const notClassical = people + .filter((p) => p.tag === 'a' && p.classe !== 'classical') + .map((e) => e.id) + .sort((a, b) => a.localeCompare(b)) + +const checkClassical = async () => { + const btnClassical = await page.$(`#btnClassical`) + btnClassical.click() + await page.waitFor(500) + + const selected = await page.$$eval('.classical', (nodes) => + nodes + .filter((node) => node.textContent === 'Classical') + .map((node) => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + const eliminated = await page.$$eval('a:not(.classical)', (nodes) => + nodes + .filter((node) => node.style.opacity === '0.2') + .map((node) => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + deepStrictEqual(`classical: ${selected}`, `classical: ${classical}`) + deepStrictEqual( + `not classical: ${eliminated}`, + `not classical: ${notClassical}`, + ) +} + +checkClassical() +await page.waitFor(1000) + +// get active +const active = people + .filter((p) => p.classe === 'classical' && p.active) + .map((e) => e.id) + .sort((a, b) => a.localeCompare(b)) + +const notActive = people + .filter( + (p) => p.tag === 'a' && p.classe === 'classical' && p.active === false, + ) + .map((e) => e.id) + .sort((a, b) => a.localeCompare(b)) + +const checkActive = async () => { + const btnActive = await page.$(`#btnActive`) + btnActive.click() + await page.waitFor(500) + + const selected = await page.$$eval('.classical.active', (nodes) => + nodes + .filter((node) => node.textContent === 'Active') + .map((node) => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + const eliminated = await page.$$eval('.classical:not(.active)', (nodes) => + nodes + .filter((node) => node.style.opacity === '0.2') + .map((node) => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + deepStrictEqual(`active: ${selected}`, `active: ${active}`) + deepStrictEqual(`not active: ${eliminated}`, `not active: ${notActive}`) +} + +checkActive() +await page.waitFor(1000) + +// get bonanno +const bonanno = people.find((p) => p.id === 'BonannoPisano').id + +const notBonanno = people + .filter( + (p) => + p.tag === 'a' && + p.classe === 'classical' && + p.active && + p.id !== 'BonannoPisano', + ) + .map((e) => e.id) + .sort((a, b) => a.localeCompare(b)) + +const checkBonanno = async () => { + const btnBonanno = await page.$(`#btnBonanno`) + btnBonanno.click() + await page.waitFor(500) + + const selected = await page.$eval('#BonannoPisano', (node) => { + if (node.textContent === 'Bonanno Pisano') return node.id + }) + + const eliminated = await page.$$eval( + 'a.classical.active:not(#BonannoPisano)', + (nodes) => + nodes + .filter((node) => node.style.opacity === '0.2') + .map((node) => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + deepStrictEqual(`bonanno: ${selected}`, `bonanno: ${bonanno}`) + deepStrictEqual(`not bonanno: ${eliminated}`, `not bonanno: ${notBonanno}`) +} + +checkBonanno() diff --git a/puppeteer/gossip-grid.js b/puppeteer/gossip-grid.js new file mode 100644 index 000000000..f8ca06932 --- /dev/null +++ b/puppeteer/gossip-grid.js @@ -0,0 +1,112 @@ +import { gossips as archived } from './data.js' + +const body = document.querySelector('body') + +const ranges = document.createElement('div') +ranges.className = 'ranges' +body.append(ranges) + +const inputs = [ + { props: ['width'], min: 200, max: 800, value: 250 }, + { props: ['fontSize', 'lineHeight'], min: 20, max: 40, value: 25 }, + { props: ['background'], min: 20, max: 75, value: 60 }, +] + +let gossips = new Proxy(archived, { + set: (target, prop, value) => { + target[prop] = value + createGossip(value, true) + return true + }, +}) + +export const grid = () => { + inputs.forEach((input) => createInput(input)) + createAddGossip() + gossips.forEach((g) => createGossip(g)) +} + +const createGossip = (g, isNew = false) => { + const gossip = document.createElement('div') + const addGossip = document.getElementById('add-gossip') + const { fontSize, lineHeight, width, background } = addGossip.style + gossip.className = 'gossip' + gossip.textContent = g + if (isNew) { + gossip.style.fontSize = fontSize + gossip.style.lineHeight = lineHeight + gossip.style.width = width + gossip.style.background = background + gossip.classList.add('fade-in') + body.insertBefore(gossip, addGossip.nextElementSibling) + } else { + body.append(gossip) + } +} + +const createAddGossip = () => { + const addGossip = document.createElement('div') + addGossip.className = 'gossip' + addGossip.id = 'add-gossip' + + const newInput = document.createElement('textarea') + newInput.autofocus = true + newInput.placeholder = 'Got a gossip to share ?' + newInput.addEventListener('keyup', (e) => addNewGossip(newInput, e)) + + const button = document.createElement('div') + button.className = 'button' + button.textContent = 'Share gossip!' + button.addEventListener('click', (e) => addNewGossip(newInput)) + + addGossip.append(newInput, button) + body.append(addGossip) +} + +const addNewGossip = (input, event) => { + const noValue = !input.value + const notEnterKey = event && event.keyCode !== 13 + if (notEnterKey || noValue) { + input.focus() + return + } + gossips[gossips.length] = input.value + input.value = '' + input.focus() +} + +const createInput = ({ props, min, max, value }) => { + const range = document.createElement('div') + range.className = 'range' + + const input = document.createElement('input') + input.type = 'range' + input.min = min + input.max = max + input.value = value + input.addEventListener('input', (e) => customize(e, ...props)) + + const propLabel = document.createElement('label') + propLabel.textContent = props[0] + + const valueLabel = document.createElement('span') + valueLabel.textContent = value + + range.append(propLabel, input, valueLabel) + ranges.append(range) +} + +const customize = ({ target }, ...props) => { + const gossips = [...document.querySelectorAll('.gossip')] + gossips.forEach((gossip) => { + props.forEach((prop) => { + const updatedValue = + (prop === 'lineHeight' && `${Number(target.value) * 1.5}px`) || + (prop === 'background' && `hsl(280, 50%, ${target.value}%)`) || + `${target.value}px` + gossip.style[prop] = updatedValue + }) + }) + const valueLabel = target.nextElementSibling + valueLabel.textContent = target.value +} diff --git a/puppeteer/harder-bigger-bolder-stronger.js b/puppeteer/harder-bigger-bolder-stronger.js new file mode 100644 index 000000000..18c0102a2 --- /dev/null +++ b/puppeteer/harder-bigger-bolder-stronger.js @@ -0,0 +1,25 @@ +const body = document.querySelector('body') + +const shapes = [...Array(100).keys()] + +const random = (min, max) => { + min = Math.ceil(min) + max = Math.floor(max) + return Math.floor(Math.random() * (max - min + 1)) + min +} + +const alphabet = 'abcdefghijklmnopqrstuvwxyz' + +export const generateLetters = () => { + shapes.forEach((c) => { + const shape = document.createElement('div') + const third = shapes.length / 3 + const firstThird = c < third + const secondThird = c > third && c < third * 2 + + shape.textContent = alphabet[random(0, alphabet.length - 1)] + shape.style.fontSize = `${c + 10 * 2}px` + shape.style.fontWeight = (firstThird && 300) || (secondThird && 400) || 600 + body.append(shape) + }) +} diff --git a/puppeteer/keycodes-symphony.js b/puppeteer/keycodes-symphony.js new file mode 100644 index 000000000..c67b299a8 --- /dev/null +++ b/puppeteer/keycodes-symphony.js @@ -0,0 +1,37 @@ +const body = document.querySelector('body') + +export const compose = () => { + document.addEventListener('keydown', (e) => handleKey(e)) + setTimeout( + () => document.removeEventListener('keydown', (e) => handleKey(e)), + 500, + ) +} + +const handleKey = (e) => { + const notes = [...document.querySelectorAll('.note')] + + if (e.key === 'Backspace') { + const last = notes[notes.length - 1] + last && last.remove() + return + } + + if (e.key === 'Escape') { + if (notes.length) { + notes.forEach((note) => note.remove()) + } + return + } + + createNote(e) +} + +const createNote = ({ key }) => { + const number = key.charCodeAt(0) * 2 - 150 + const note = document.createElement('div') + note.className = 'note' + note.textContent = key + note.style.background = `hsl(270, ${number}%, ${number}%)` + body.append(note) +} diff --git a/puppeteer/mouse-trap.js b/puppeteer/mouse-trap.js new file mode 100644 index 000000000..4a872d814 --- /dev/null +++ b/puppeteer/mouse-trap.js @@ -0,0 +1,66 @@ +const body = document.querySelector('body') + +const box = document.createElement('div') +box.className = 'box' +body.append(box) + +const { top, bottom, left, right } = box.getBoundingClientRect() + +const diameter = 50 +const radius = diameter / 2 + +const insideX = (clientX) => clientX > left + radius && clientX < right - radius +const insideY = (clientY) => clientY > top + radius && clientY < bottom - radius + +let isInside = false + +export const createCircle = () => { + document.addEventListener('click', (e) => create(e)) + setTimeout(() => document.removeEventListener('click', create), 500) +} + +const create = ({ clientX, clientY }) => { + const elem = document.createElement('div') + elem.className = 'elem' + body.append(elem) + elem.style.top = `${clientY - radius}px` + elem.style.left = `${clientX - radius}px` + const hasEntered = insideX(clientX) && insideY(clientY) + if (hasEntered) { + elem.style.background = 'var(--purple)' + } + isInside = false +} + +export const moveCircle = () => { + document.addEventListener('mousemove', (e) => move(e)) + setTimeout(() => document.removeEventListener('mousemove', move), 500) +} + +const move = (e) => { + const elems = [...document.getElementsByClassName('elem')] + const elem = elems[elems.length - 1] + if (!elem) return + position(e, elem) +} + +const position = ({ clientX, clientY }, elem) => { + const hasEntered = insideX(clientX) && insideY(clientY) + + if (hasEntered) { + isInside = true + elem.style.background = 'var(--purple)' + } + + if (isInside) { + if (insideY(clientY)) { + elem.style.top = `${clientY - radius}px` + } + if (insideX(clientX)) { + elem.style.left = `${clientX - radius}px` + } + } else { + elem.style.top = `${clientY - radius}px` + elem.style.left = `${clientX - radius}px` + } +} diff --git a/puppeteer/package.json b/puppeteer/package.json new file mode 100644 index 000000000..6f91de4f2 --- /dev/null +++ b/puppeteer/package.json @@ -0,0 +1,9 @@ +{ + "type": "module", + "scripts": { + "test": "node --harmony-top-level-await pimp-my-style/test.js" + }, + "dependencies": { + "puppeteer-core": "^3.3.0" + } +} diff --git a/puppeteer/pick-and-click.js b/puppeteer/pick-and-click.js new file mode 100644 index 000000000..01dbcf273 --- /dev/null +++ b/puppeteer/pick-and-click.js @@ -0,0 +1,105 @@ +const body = document.querySelector('body') + +const count = document.createElement('div') +count.className = 'count' +count.textContent = 'hsl(0, 50%, 0%)' + +const hueText = document.createElement('div') +hueText.className = 'text hue' +hueText.textContent = 'hue' + +const luminosityText = document.createElement('div') +luminosityText.className = 'text luminosity' +luminosityText.textContent = 'luminosity' + +const origin = document.createElement('div') +origin.className = 'text origin' + +const picked = document.createElement('div') +picked.className = 'text picked' +picked.textContent = 'Color successfully picked!' + +const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') +svg.setAttributeNS( + 'http://www.w3.org/2000/xmlns/', + 'xmlns:xlink', + 'http://www.w3.org/1999/xlink', +) +svg.setAttribute('width', window.innerWidth) +svg.setAttribute('height', window.innerHeight) +svg.setAttribute('viewBox', `0 0 ${window.innerWidth} ${window.innerHeight}`) + +const axisX = document.createElementNS('http://www.w3.org/2000/svg', 'line') +axisX.setAttribute('y1', window.innerHeight) +axisX.setAttribute('y2', 0) +svg.append(axisX) + +const axisY = document.createElementNS('http://www.w3.org/2000/svg', 'line') +axisY.setAttribute('x1', window.innerWidth) +axisY.setAttribute('x2', 0) +svg.append(axisY) + +body.append(count, hueText, luminosityText, origin, picked, svg) + +export const pick = () => { + document.addEventListener('mousemove', (e) => set(e)) + setTimeout( + () => document.removeEventListener('mousemove', (e) => set(e)), + 500, + ) + + body.addEventListener('click', click) + setTimeout(() => document.removeEventListener('click', click), 500) + + body.addEventListener('copy', copy) + setTimeout(() => document.removeEventListener('copy', copy), 500) +} + +const click = (e) => { + document.execCommand('copy') + const wave = document.createElement('div') + wave.className = 'wave' + wave.style.top = `${e.clientY - 10}px` + wave.style.left = `${e.clientX - 10}px` + body.append(wave) + setTimeout(() => wave.remove(), 150) +} + +const copy = (event) => { + event.preventDefault() + if (event.clipboardData) { + event.clipboardData.setData('text/plain', count.textContent) + picked.classList.add('fade-in') + setTimeout(() => picked.classList.remove('fade-in'), 1000) + } +} + +const calc = (number, max) => + Math.round(Math.min(max, Math.max(0, max * number))) + +const set = ({ clientX, clientY }) => { + const { innerWidth, innerHeight } = window + const padding = 100 + const mouseX = clientX - padding + const mouseY = clientY - padding + + const hue = calc(mouseX / (innerWidth - padding * 2), 360) + const luminosity = calc(mouseY / (innerHeight - padding * 2), 100) + const color = `hsl(${hue}, 50%, ${luminosity}%)` + + axisX.setAttribute('x1', clientX) + axisX.setAttribute('x2', clientX) + axisY.setAttribute('y1', clientY) + axisY.setAttribute('y2', clientY) + + axisX.setAttribute('stroke', color) + axisY.setAttribute('stroke', color) + + body.style.color = color + body.style.background = color + origin.style.background = color + count.textContent = color + + hueText.textContent = `hue\n${hue}` + luminosityText.textContent = `${luminosity}\nluminosity` +} diff --git a/puppeteer/pimp-my-style.js b/puppeteer/pimp-my-style.js new file mode 100644 index 000000000..fac3e1e08 --- /dev/null +++ b/puppeteer/pimp-my-style.js @@ -0,0 +1,30 @@ +import { styles } from './data.js' + +let count = 0 +let increment = 1 + +export const pimp = () => { + const button = document.querySelector('.button') + + const ceiling = count === styles.length - 1 + const floor = !count + + const increasing = increment > 0 + + if (increasing || (floor && !increment)) { + button.classList.add(styles[count]) + } else { + button.classList.remove(styles[count]) + } + + if (ceiling) { + increment = increment ? 0 : -1 + } + if (floor) { + increment = increment < 0 ? 0 : 1 + } + + button.classList.toggle('unpimp', increment < 0 || (!increment && ceiling)) + + count += increment +} diff --git a/puppeteer/pimp-my-style_test.js b/puppeteer/pimp-my-style_test.js new file mode 100644 index 000000000..528e04535 --- /dev/null +++ b/puppeteer/pimp-my-style_test.js @@ -0,0 +1,36 @@ +import styles from '../assets/data/pimp-my-style.js' + +export const tests = [] + +const formatClass = (limit, unpimp) => + ['button', ...styles.slice(0, limit), unpimp && 'unpimp'].filter(Boolean) + +const max = styles.length - 1 + +export const setup = async ({ page }) => { + const btn = await page.$('.button') + + return { + btn, + getClass: async () => + (await (await btn.getProperty('className')).jsonValue()).split(' '), + } +} + +tests.push(async ({ page, eq, btn, getClass }) => { + // pimp + for (const i of styles.keys()) { + console.log('pimp click', i + 1) + await btn.click() + eq(formatClass(i + 1, i === max), await getClass()) + } +}) + +tests.push(async ({ page, eq, btn, getClass }) => { + // unpimp ! + for (const i of styles.keys()) { + console.log('unpimp click', i + 1) + await btn.click() + eq(formatClass(max - i, i !== max), await getClass()) + } +}) diff --git a/puppeteer/test.js b/puppeteer/test.js new file mode 100644 index 000000000..2801d52a7 --- /dev/null +++ b/puppeteer/test.js @@ -0,0 +1,60 @@ +import http from 'http' +import fs from 'fs' +import path from 'path' +import { deepStrictEqual } from 'assert' +import puppeteer from 'puppeteer-core' + +const exercise = 'pimp-my-style' +const PORT = 9898 +const config = { + headless: false, + executablePath: + '/usr/bin/google-chrome', +// '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', +} + +const mediaTypes = { + jpg: 'image/jpeg', + png: 'image/png', + html: 'text/html', + css: 'text/css', + js: 'application/javascript', + json: 'application/json', +} + +const server = http.createServer(({ url, method }, response) => { + console.log(method + ' ' + url) + const filepath = path.join('.', url) + const ext = path.extname(filepath) + response.setHeader('Content-Type', mediaTypes[ext.slice(1)] || 'text/plain') + + const stream = fs.createReadStream(filepath) + .pipe(response) + .once('error', err => { + console.log(err) + response.statusCode = 500 // handle 404 ? + response.end('oopsie') + }) +}).listen(PORT, async (err) => { + err && (console.error(err.stack) || process.exit(1)) + const { setup, tests } = await import(`./${exercise}/test.js`) + const browser = await puppeteer.launch(config) + const [page] = await browser.pages() + await page.goto(`http://localhost:${PORT}/${exercise}/index.html`) + const context = await setup({ page }) + let code = 0 + for (const [n, test] of tests.entries()) { + try { + await test({ page, eq: deepStrictEqual, ...context }) + } catch (err) { + code = 1 + console.log(`test #${n} failed:`) + console.log(test.toString()) + console.log(err.stack) + } + } + server.close() + await browser.close() + process.exit(code) +}) + diff --git a/puppeteer/where-do-we-go.js b/puppeteer/where-do-we-go.js new file mode 100644 index 000000000..b5cf2dc89 --- /dev/null +++ b/puppeteer/where-do-we-go.js @@ -0,0 +1,72 @@ +import { places } from './data.js' + +const body = document.querySelector('body') + +export const scroll = () => { + createSections() + + const location = document.createElement('div') + location.className = 'location' + setLocation(location) + + const direction = document.createElement('div') + direction.className = 'direction' + + body.append(location, direction) + + document.addEventListener('wheel', (event) => + setLocation(location, direction, event), + ) + setTimeout(() => + document.removeEventListener('wheel', (event) => + setLocation(location, direction, event), + ), + ) +} + +const createSections = () => { + const sorted = places.sort( + (a, b) => getDegree(b.coordinates) - getDegree(a.coordinates), + ) + + sorted.map(({ name, color }) => { + const nameDashCase = name + .toLowerCase() + .split(',')[0] + .split(' ') + .join('-') + + const url = `https://raw.githubusercontent.com/MarieMalarme/dom-js/master/assets/images/${nameDashCase}.jpg` + + const section = document.createElement('section') + section.style.background = `center / cover url(${url})` + body.append(section) + }) +} + +const getDegree = (coordinates) => { + const north = coordinates.includes('N') + const degree = coordinates.split("'")[0].replace('°', '.') + return north ? degree : -degree +} + +const setLocation = (location, direction, event) => { + const { name, coordinates, color } = getLocation() + location.textContent = `${name}\n${coordinates}` + location.style.color = color + location.onclick = () => { + window.open(`https://www.google.com/maps/place/${coordinates}`, '_blank') + } + + if (!event) return + const scrollUp = event.deltaY < 0 + direction.innerHTML = `