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 = `
${scrollUp ? 'N' : 'S'}
` +} + +const getLocation = () => { + const { innerHeight, scrollY } = window + const index = Math.ceil((scrollY - innerHeight / 2) / innerHeight) + return places[index] +} diff --git a/subjects/build-brick-and-break/README.md b/subjects/build-brick-and-break/README.md new file mode 100644 index 000000000..46c555c59 --- /dev/null +++ b/subjects/build-brick-and-break/README.md @@ -0,0 +1,43 @@ +## Build brick and break + +### Instructions + +Today, your mission is to build a 3-column brick tower, maintain it and finally break it! + +Create a function `build` which will create and display the given amount of bricks passed as argument: + +- each brick has to be created and added to the page at a regular interval of time (at least 50ms), +- each brick will receive a unique `id` property, like following: + +```html +
+``` + +- each brick in the middle column has to be set with the custom attribute `foundation` receiving the value `true` + +Each one of the two emojis in the top-right corner fires a function on click: + +- 🔨 triggers the function `repair` +- 🧨 triggers the function `destroy` + +Write the body of the `repair` function, which receives any number of `ids`, and for each `id`, retrieves the HTML element and set a custom attribute `repaired` set to `in progress` if it is a brick situated in the middle column, and `true` if not. + +Write the body of the `destroy` function, which removes the current last brick in the tower. + +### Notions + +- [`createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) +- [`append()`](https://developer.mozilla.org/fr/docs/Web/API/ParentNode/append) +- [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) +- [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) / [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval) +- [`hasAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute) +- [dataset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/dataset) +- [`remove()`](https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove) + +### Provided files + +- Use this CSS file: [style.css](./style.css) + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/OjSP_7u9CZ4) diff --git a/subjects/build-brick-and-break/index.html b/subjects/build-brick-and-break/index.html new file mode 100644 index 000000000..988e2835b --- /dev/null +++ b/subjects/build-brick-and-break/index.html @@ -0,0 +1,104 @@ + + + + Build brick and break + + + + + + + + + \ No newline at end of file diff --git a/subjects/fifty-shades-of-cold/README.md b/subjects/fifty-shades-of-cold/README.md new file mode 100644 index 000000000..b0c855ff1 --- /dev/null +++ b/subjects/fifty-shades-of-cold/README.md @@ -0,0 +1,39 @@ +## Fifty shades of cold + +### Instructions + +You've been asked to freshen a webpage atmosphere by displaying shades of cold colors. + +Check the `colors` array provided in the data file below. + +- Create a ` + + + + + \ No newline at end of file diff --git a/subjects/get-them-all/README.md b/subjects/get-them-all/README.md new file mode 100644 index 000000000..8e72d9e77 --- /dev/null +++ b/subjects/get-them-all/README.md @@ -0,0 +1,30 @@ +## Get them all + +### Instructions + +You've been attributed the task to find the main architect of the Tower of Pisa before he achieves his plans, avoiding us nowadays all those lame pictures of people pretending to stop it from falling. + +You arrived at the architects' chamber to find him, but all you have in front of you is a bunch of unknown people.\ +Step by step, with the little information you have, gather information and figure out by elimination who he is. + +On top of the webpage, each of the four buttons fires a function which has to return an array containing 2 entries: the targetted people, and the others eliminated at that step (the ones previously eliminated mustn't be included). + +- Write the body of the `getArchitects` function, which targets the architects, all corresponding to a `` tag. + +- Write the body of the `getClassical` function, which targets the architects belonging to the `classical` class. + +- Write the body of the `getActive` function, which targets the classical architects who are `active` in their class. + +- Write the body of the `getBonannoPisano` function, which targets the architect you're looking for, whose `id` is `BonannoPisano`. + +### Notions + +- [`getElementsByTagName()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByTagName) +- [`getElementsByClassName()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName) +- [`getElementById()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) +- [`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) / [`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) + +### Provided files + +- Use this CSS file: [style.css](./style.css) +- You can take a look at the data: [data.js](./data.js) diff --git a/subjects/get-them-all/index.html b/subjects/get-them-all/index.html new file mode 100644 index 000000000..a1134f565 --- /dev/null +++ b/subjects/get-them-all/index.html @@ -0,0 +1,221 @@ + + + + Get them all + + + + + + + + \ No newline at end of file diff --git a/subjects/gossip-grid/README.md b/subjects/gossip-grid/README.md new file mode 100644 index 000000000..3a8902592 --- /dev/null +++ b/subjects/gossip-grid/README.md @@ -0,0 +1,22 @@ +## Gossip grid + +### Instructions + +Good information is the pillar of society, that's why you've decided to dedicate your time to reveal the powerful truth to the world and deliver essential and strong news: you're launching a gossip grid. + +Create the function `grid` which displays all the `gossips`, provided in the data file below, as cards on a grid, and allows the user to: + +- add a new gossip to the list +- customize the width, font size and background of each card with `range` inputs. + +### Notions + +- [Inputs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input): [`text`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text), [`range`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range) + +### Provided files + +- Import the `gossips` from the data file: [data.js](./data.js) + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/nbR2eHBqTxU) diff --git a/subjects/gossip-grid/data.js b/subjects/gossip-grid/data.js new file mode 100644 index 000000000..ebecd9b4d --- /dev/null +++ b/subjects/gossip-grid/data.js @@ -0,0 +1,19 @@ +export const gossips = [ + `Oasis star Noel Gallagher used to gorge on Greggs pastries and donuts every day`, + `Lea Michele's lookalike Monica Moskatow says Glee star called her ugly`, + `WE PAY FOR JUICY INFO!`, + `Trainer to Hollywood's biggest stars reveals how to get an A-list body`, + `Ed Sheeran comes out of music retirement to write brand new song`, + `Kylie Jenner & Travis Scott’s breakup timeline`, + `Quiet on the set: temper tantrums stars hope you forget`, + `The style & grace of Chloë Grace Moretz: her top 20 red carpet looks`, + `Paulina Porizkova feels betrayed after being cut out of husband Ric Ocasek's will`, + `From too hot to not: Paris Hilton and Chris Zylka's relationship history`, + `No bite in the big apple? Celine Dion looks scary skinny in New York`, + `Jennifer Aniston and Brad Pitt relationship timeline`, + `They shouldn’t have said that: 10 celebrity rants heard around the world`, + `The most intense celebrity fights on set`, + `The 18 most bitter real housewives feuds`, + `Tristan Thompson's remarkable transformation from skinny teen to hulking NBA ace`, + `Kim Kardashian 'considers leaving home' with Kanye West to 'save marriage'`, +] diff --git a/subjects/gossip-grid/index.html b/subjects/gossip-grid/index.html new file mode 100644 index 000000000..fa06ea5d5 --- /dev/null +++ b/subjects/gossip-grid/index.html @@ -0,0 +1,135 @@ + + + + Gossip grid + + + + + + + + \ No newline at end of file diff --git a/subjects/harder-bigger-bolder-stronger/README.md b/subjects/harder-bigger-bolder-stronger/README.md new file mode 100644 index 000000000..3f3d90138 --- /dev/null +++ b/subjects/harder-bigger-bolder-stronger/README.md @@ -0,0 +1,17 @@ +## Harder, bigger, bolder, stronger + +### Instructions + +Being stuck at home, bored, desperate and coming up with a lot of weird ideas, a friend asks you to develop a tool to measure his ocular skills: one of those [Monoyer charts](https://en.wikipedia.org/wiki/Monoyer_chart) that ophthalmologists use. + +Generate a board where each new letter is harder, bigger, bolder and stronger! + +Create the function `generateLetters` which creates 100 `div`, each containing a letter randomly picked through the alphabet, and whose style properties have to be increased: + +- `font-size` has to grow from `20` to at least `100` pixels +- `font-weigth` has to be `300` for the first third of the letters, `400` for the second third, and `600` for the last third + +### Notions + +- [`style`](https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style) +- [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) diff --git a/subjects/harder-bigger-bolder-stronger/index.html b/subjects/harder-bigger-bolder-stronger/index.html new file mode 100644 index 000000000..f771c1eb3 --- /dev/null +++ b/subjects/harder-bigger-bolder-stronger/index.html @@ -0,0 +1,57 @@ + + + + Harder, bigger, bolder, stronger + + + + + + + + \ No newline at end of file diff --git a/subjects/keycodes-symphony/README.md b/subjects/keycodes-symphony/README.md new file mode 100644 index 000000000..13c1239ba --- /dev/null +++ b/subjects/keycodes-symphony/README.md @@ -0,0 +1,20 @@ +## Keycodes symphony + +### Instructions + +Like an inspired Beethoven who's going to write his Moonlight Sonata, you're about to compose a colourful symphony of letters with your keyboard. + +Export the function `compose`: + +- Make it fire every time a key is pressed +- Create a new `note`, which has a background color generated using its `keyCode`, and displays the corresponding letter pressed +- If the pressed key is the `Delete` one, delete the last note +- If the pressed key is the `Escape` one, clear all the notes + +### Notions + +- [Keyboard event](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent): [`keydown`](https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event), [`key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/5DdijwBnpAk) diff --git a/subjects/keycodes-symphony/index.html b/subjects/keycodes-symphony/index.html new file mode 100644 index 000000000..ffc423f72 --- /dev/null +++ b/subjects/keycodes-symphony/index.html @@ -0,0 +1,57 @@ + + + + Keycodes symphony + + + + + + + + \ No newline at end of file diff --git a/subjects/mouse-trap/README.md b/subjects/mouse-trap/README.md new file mode 100644 index 000000000..8ecd538c4 --- /dev/null +++ b/subjects/mouse-trap/README.md @@ -0,0 +1,22 @@ +## Mouse trap + +### Instructions + +Develop a trap to capture the elements when the mouse is getting too close to the center of the page! + +- Create a function `createCircle`: make it fire on every click on the page, and create a white circle at the position of the mouse on the screen + +- Create a function `moveCircle`: make it fire when the mouse moves, and get the last circle created and makes it move along with the mouse + +- Set a box in the center of the page ; when a circle is inside that box, it has to be purple ; once a circle enters the box, it is trapped inside and cannot go out of it anymore. + +### Notions + +- [`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener): `click`, `mousemove` +- [`removeEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) +- [Mouse event](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent): [`click`](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event), [`mousemove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event) / [`clientX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX), [`clientY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY) +- [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/qF843P-V2Yw) diff --git a/subjects/mouse-trap/index.html b/subjects/mouse-trap/index.html new file mode 100644 index 000000000..c59f1185a --- /dev/null +++ b/subjects/mouse-trap/index.html @@ -0,0 +1,63 @@ + + + + Mouse trap + + + + + + + + \ No newline at end of file diff --git a/subjects/pick-and-click/README.md b/subjects/pick-and-click/README.md new file mode 100644 index 000000000..36bdc7f77 --- /dev/null +++ b/subjects/pick-and-click/README.md @@ -0,0 +1,24 @@ +## Pick & click + +### Instructions + +Today, you're gonna create your own color picker. + +Write the function `pick` which creates a `hsl` color picker varying the `hue` and `luminosity` according to the position of the mouse, which: + +- displays the `hue` value in text +- displays the `luminosity` value in text +- displays the full `hsl` value in text +- copies that value in the clipboard on click +- displays two lines, for X and Y axis, following the cursor + +### Notions + +- [Copy event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event) +- [Mouse move event](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event) +- [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg): [`createElementNS`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS), [`setAttribute`](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute) + +### Expected result + +You can see an example of the expected result: +[![video](https://img.youtube.com/vi/eE4eE9_eKZI/0.jpg)](https://www.youtube.com/watch?v=eE4eE9_eKZI) diff --git a/subjects/pick-and-click/index.html b/subjects/pick-and-click/index.html new file mode 100644 index 000000000..36d223741 --- /dev/null +++ b/subjects/pick-and-click/index.html @@ -0,0 +1,134 @@ + + + + Pick & click + + + + + + + + \ No newline at end of file diff --git a/subjects/pimp-my-style/README.md b/subjects/pimp-my-style/README.md new file mode 100644 index 000000000..e050acc62 --- /dev/null +++ b/subjects/pimp-my-style/README.md @@ -0,0 +1,49 @@ +## Pimp my style + +### Instructions + +Check out that button on the HTML page: + +```html +
pimp my style
+``` + +For now, it's only a lonely, basic and sad element ; let's pimp it up! + +On each click on the page, a function `pimp` is triggered. +Write the body of that function so that the button's class is altered: + +- Add in order the next class of the `styles` array provided in the data file below +- When the end of the array is reached, remove backwards each class +- Toggle the class 'unpimp' when removing classes + +``` +Example for a `styles` array with only 3 classes: + +Page load -->
+ +...adding +Click 1 -->
+Click 2 -->
+ +...toggling `unpimp` +Click 3 -->
+ +...and removing backwards +Click 4 -->
+Click 5 -->
+Click 6 -->
+``` + +### Notions + +- [`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList): `add()`, `remove()`, `toggle()` + +### Provided files + +- Use this CSS file: [https://mariemalarme.github.io/dom-js/assets/style/pimp-my-style.css](https://mariemalarme.github.io/dom-js/assets/style/pimp-my-style.css) +- Import the `styles` from the data file: [https://mariemalarme.github.io/dom-js/assets/data/pimp-my-style.js](https://mariemalarme.github.io/dom-js/assets/data/pimp-my-style.js) + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/VIRf3TBDTN4) diff --git a/subjects/pimp-my-style/data.js b/subjects/pimp-my-style/data.js new file mode 100644 index 000000000..23f66eb4b --- /dev/null +++ b/subjects/pimp-my-style/data.js @@ -0,0 +1,17 @@ +export const styles = [ + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'ten', + 'eleven', + 'twelve', + 'thirteen', + 'fourteen', + 'fifteen', +] diff --git a/subjects/pimp-my-style/index.html b/subjects/pimp-my-style/index.html new file mode 100644 index 000000000..3a78c0497 --- /dev/null +++ b/subjects/pimp-my-style/index.html @@ -0,0 +1,179 @@ + + + + Pimp my style + + + + + + + + diff --git a/subjects/where-do-we-go/README.md b/subjects/where-do-we-go/README.md new file mode 100644 index 000000000..79846ed45 --- /dev/null +++ b/subjects/where-do-we-go/README.md @@ -0,0 +1,26 @@ +## Where do we go? + +### Instructions + +Tired of staying home for too long, you decide to develop a page to index ideas for your next travel destinations, so that next time you'll ask yourself 'Where do we go?', you won't need to get lost for 3 hours! + +Create a page which displays the list of `places` provided in the data file below: + +- sort the `places` from the Northest to the Southest +- display a fullscreen-size image for each place ; use the images hosted here: `https://github.com/MarieMalarme/dom-js/tree/master/assets/images`, also available as Github Pages here `https://mariemalarme.github.io/dom-js/assets/images/locationName.jpg` +- display a location indicator, displaying the `name` and the `coordinates` of the current place featured in the image, using the corresponding `color` as text color, which updates on scroll when another image is reached +- display a compass indicating the latitude direction ; North if the user is scrolling up, South if he's scrolling down +- when clicking on the page, open a link redirecting to the Google Maps' coordinates of the place currently displayed. + +### Notions + +- [Wheel event](https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event): [`deltaY`](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaY) +- [`window`](https://developer.mozilla.org/en-US/docs/Web/API/Window): [`innerHeight`](https://developer.mozilla.org/en-US/docs/Web/API/Window/innerHeight), [`scrollY`](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY), [`open()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) + +### Provided files + +- Import the `places` from the data file: [https://mariemalarme.github.io/dom-js/assets/data/where-do-we-go.js](https://mariemalarme.github.io/dom-js/assets/data/where-do-we-go.js) + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/BLxNi1WH6_0) diff --git a/subjects/where-do-we-go/data.js b/subjects/where-do-we-go/data.js new file mode 100644 index 000000000..65d57a445 --- /dev/null +++ b/subjects/where-do-we-go/data.js @@ -0,0 +1,107 @@ +export const places = [ + { + name: 'Cordoba, Spain', + color: 'deeppink', + coordinates: `37°53'17.43"N 4°46'45.78"W`, + }, + { + name: 'Yuanyang County, China', + color: 'cyan', + coordinates: `23°09'32.30"N 102°44'41.46"E`, + }, + { + name: 'Namib Desert, Namibia', + color: 'lime', + coordinates: `24°45'4.19"S 15°16'21.00"E`, + }, + { + name: 'Newark, New Jersey, USA', + color: 'yellow', + coordinates: `40°44'8.37"N 74°10'20.52"W`, + }, + { + name: 'Nishinoshima Island, Japan', + color: 'lightcoral', + coordinates: `27°14'50.84"N 140°52'46.04"E`, + }, + { + name: 'Lisse, The Netherlands', + color: 'cornflowerblue', + coordinates: `52°15'28.55"N 4°33'26.94"E`, + }, + { + name: 'Shadegan Lagoon, Iran', + color: 'firebrick', + coordinates: `30°39'16.55"N 48°39'14.14"E`, + }, + { + name: 'Qinhuangdao, China', + color: 'seashell', + coordinates: `39°56'7.3"N 119°36'1.88"E`, + }, + { + name: 'Marrakesh, Morocco', + color: 'orange', + coordinates: `31°37'46.1"N 7°58'51.9"W`, + }, + { + name: 'Los Caracoles Pass, Chile', + color: 'violet', + coordinates: `32°49'51.6"S 70°05'22.9"W`, + }, + { + name: 'Tucson, Arizona, USA', + color: 'springgreen', + coordinates: `32°13'21.38"N 110°58'28.96"W`, + }, + { + name: 'Arlit, Niger', + color: 'blue', + coordinates: `18°44'20.41"N 7°23'22.12"E`, + }, + { + name: 'Black Rock Desert, Nevada, USA', + color: 'crimson', + coordinates: `40°54'35.0"N 119°03'26.5"W`, + }, + { + name: 'Mount Fuji, Japan', + color: 'darkviolet', + coordinates: `35°21'37.0"N 138°43'38.1"E`, + }, + { + name: 'Moab, Utah, USA', + color: 'gold', + coordinates: `38°34'23.94"N 109°32'59.42"W`, + }, + { + name: 'Rio de Janeiro, Brasil', + color: 'hotpink', + coordinates: `22°59'13.4"S 43°12'15.9"W`, + }, + { + name: 'Killeen, Texas, USA', + color: 'greenyellow', + coordinates: `31°07'1.63"N 97°43'40.07"W`, + }, + { + name: 'Skafta River, Iceland', + color: 'mistyrose', + coordinates: `63°39'47.7"N 17°47'57.9"W`, + }, + { + name: 'Almeria, Spain', + color: 'mediumturquoise', + coordinates: `36°50'08.7"N 2°27'44.8"W`, + }, + { + name: 'Atlanta, Georgia, USA', + color: 'white', + coordinates: `33°45'39.0"N 84°23'50.1"W`, + }, + { + name: 'Georgetown, California, USA', + color: 'sandybrown', + coordinates: `38°54'22.4"N 120°50'23.9"W`, + }, +] diff --git a/subjects/where-do-we-go/images/almeria.jpg b/subjects/where-do-we-go/images/almeria.jpg new file mode 100644 index 000000000..7364bfa35 Binary files /dev/null and b/subjects/where-do-we-go/images/almeria.jpg differ diff --git a/subjects/where-do-we-go/images/arlit.jpg b/subjects/where-do-we-go/images/arlit.jpg new file mode 100644 index 000000000..b7b324a77 Binary files /dev/null and b/subjects/where-do-we-go/images/arlit.jpg differ diff --git a/subjects/where-do-we-go/images/atlanta.jpg b/subjects/where-do-we-go/images/atlanta.jpg new file mode 100644 index 000000000..e0e9882c2 Binary files /dev/null and b/subjects/where-do-we-go/images/atlanta.jpg differ diff --git a/subjects/where-do-we-go/images/black-rock-desert.jpg b/subjects/where-do-we-go/images/black-rock-desert.jpg new file mode 100644 index 000000000..3adb6d782 Binary files /dev/null and b/subjects/where-do-we-go/images/black-rock-desert.jpg differ diff --git a/subjects/where-do-we-go/images/cordoba.jpg b/subjects/where-do-we-go/images/cordoba.jpg new file mode 100644 index 000000000..f212ac158 Binary files /dev/null and b/subjects/where-do-we-go/images/cordoba.jpg differ diff --git a/subjects/where-do-we-go/images/georgetown.jpg b/subjects/where-do-we-go/images/georgetown.jpg new file mode 100644 index 000000000..807c95137 Binary files /dev/null and b/subjects/where-do-we-go/images/georgetown.jpg differ diff --git a/subjects/where-do-we-go/images/killeen.jpg b/subjects/where-do-we-go/images/killeen.jpg new file mode 100644 index 000000000..8ebcd7f34 Binary files /dev/null and b/subjects/where-do-we-go/images/killeen.jpg differ diff --git a/subjects/where-do-we-go/images/lisse.jpg b/subjects/where-do-we-go/images/lisse.jpg new file mode 100644 index 000000000..7563d88b6 Binary files /dev/null and b/subjects/where-do-we-go/images/lisse.jpg differ diff --git a/subjects/where-do-we-go/images/los-caracoles-pass.jpg b/subjects/where-do-we-go/images/los-caracoles-pass.jpg new file mode 100644 index 000000000..e30d2efd3 Binary files /dev/null and b/subjects/where-do-we-go/images/los-caracoles-pass.jpg differ diff --git a/subjects/where-do-we-go/images/marrakesh.jpg b/subjects/where-do-we-go/images/marrakesh.jpg new file mode 100644 index 000000000..3eda43e79 Binary files /dev/null and b/subjects/where-do-we-go/images/marrakesh.jpg differ diff --git a/subjects/where-do-we-go/images/moab.jpg b/subjects/where-do-we-go/images/moab.jpg new file mode 100644 index 000000000..ebcd2477e Binary files /dev/null and b/subjects/where-do-we-go/images/moab.jpg differ diff --git a/subjects/where-do-we-go/images/mount-fuji.jpg b/subjects/where-do-we-go/images/mount-fuji.jpg new file mode 100644 index 000000000..9a9efa28d Binary files /dev/null and b/subjects/where-do-we-go/images/mount-fuji.jpg differ diff --git a/subjects/where-do-we-go/images/namib-desert.jpg b/subjects/where-do-we-go/images/namib-desert.jpg new file mode 100644 index 000000000..ef63c37cf Binary files /dev/null and b/subjects/where-do-we-go/images/namib-desert.jpg differ diff --git a/subjects/where-do-we-go/images/newark.jpg b/subjects/where-do-we-go/images/newark.jpg new file mode 100644 index 000000000..bcc2110de Binary files /dev/null and b/subjects/where-do-we-go/images/newark.jpg differ diff --git a/subjects/where-do-we-go/images/nishinoshima-island.jpg b/subjects/where-do-we-go/images/nishinoshima-island.jpg new file mode 100644 index 000000000..f47e6f397 Binary files /dev/null and b/subjects/where-do-we-go/images/nishinoshima-island.jpg differ diff --git a/subjects/where-do-we-go/images/qinhuangdao.jpg b/subjects/where-do-we-go/images/qinhuangdao.jpg new file mode 100644 index 000000000..e0308110d Binary files /dev/null and b/subjects/where-do-we-go/images/qinhuangdao.jpg differ diff --git a/subjects/where-do-we-go/images/rio-de-janeiro.jpg b/subjects/where-do-we-go/images/rio-de-janeiro.jpg new file mode 100644 index 000000000..c452b1fd5 Binary files /dev/null and b/subjects/where-do-we-go/images/rio-de-janeiro.jpg differ diff --git a/subjects/where-do-we-go/images/shadegan-lagoon.jpg b/subjects/where-do-we-go/images/shadegan-lagoon.jpg new file mode 100644 index 000000000..231c6e209 Binary files /dev/null and b/subjects/where-do-we-go/images/shadegan-lagoon.jpg differ diff --git a/subjects/where-do-we-go/images/skafta-river.jpg b/subjects/where-do-we-go/images/skafta-river.jpg new file mode 100644 index 000000000..b0091eee4 Binary files /dev/null and b/subjects/where-do-we-go/images/skafta-river.jpg differ diff --git a/subjects/where-do-we-go/images/tucson.jpg b/subjects/where-do-we-go/images/tucson.jpg new file mode 100644 index 000000000..c71cadf83 Binary files /dev/null and b/subjects/where-do-we-go/images/tucson.jpg differ diff --git a/subjects/where-do-we-go/images/yuanyang-county.jpg b/subjects/where-do-we-go/images/yuanyang-county.jpg new file mode 100644 index 000000000..b79061f25 Binary files /dev/null and b/subjects/where-do-we-go/images/yuanyang-county.jpg differ diff --git a/subjects/where-do-we-go/index.html b/subjects/where-do-we-go/index.html new file mode 100644 index 000000000..298a0ded2 --- /dev/null +++ b/subjects/where-do-we-go/index.html @@ -0,0 +1,68 @@ + + + + Where do we go? + + + + + + + + \ No newline at end of file