Browse Source

adding dom content

content-update
Clement Denis 4 years ago committed by Clément
parent
commit
3c0b87ef69
  1. 47
      puppeteer/build-bricks-and-break.js
  2. 46
      puppeteer/fifty-shades-of-cold.js
  3. 25
      puppeteer/get-them-all.js
  4. 169
      puppeteer/get-them-all_test.js
  5. 112
      puppeteer/gossip-grid.js
  6. 25
      puppeteer/harder-bigger-bolder-stronger.js
  7. 37
      puppeteer/keycodes-symphony.js
  8. 66
      puppeteer/mouse-trap.js
  9. 9
      puppeteer/package.json
  10. 105
      puppeteer/pick-and-click.js
  11. 30
      puppeteer/pimp-my-style.js
  12. 36
      puppeteer/pimp-my-style_test.js
  13. 60
      puppeteer/test.js
  14. 72
      puppeteer/where-do-we-go.js
  15. 43
      subjects/build-brick-and-break/README.md
  16. 104
      subjects/build-brick-and-break/index.html
  17. 39
      subjects/fifty-shades-of-cold/README.md
  18. 143
      subjects/fifty-shades-of-cold/data.js
  19. 58
      subjects/fifty-shades-of-cold/index.html
  20. 30
      subjects/get-them-all/README.md
  21. 221
      subjects/get-them-all/index.html
  22. 22
      subjects/gossip-grid/README.md
  23. 19
      subjects/gossip-grid/data.js
  24. 135
      subjects/gossip-grid/index.html
  25. 17
      subjects/harder-bigger-bolder-stronger/README.md
  26. 57
      subjects/harder-bigger-bolder-stronger/index.html
  27. 20
      subjects/keycodes-symphony/README.md
  28. 57
      subjects/keycodes-symphony/index.html
  29. 22
      subjects/mouse-trap/README.md
  30. 63
      subjects/mouse-trap/index.html
  31. 24
      subjects/pick-and-click/README.md
  32. 134
      subjects/pick-and-click/index.html
  33. 49
      subjects/pimp-my-style/README.md
  34. 17
      subjects/pimp-my-style/data.js
  35. 179
      subjects/pimp-my-style/index.html
  36. 26
      subjects/where-do-we-go/README.md
  37. 107
      subjects/where-do-we-go/data.js
  38. BIN
      subjects/where-do-we-go/images/almeria.jpg
  39. BIN
      subjects/where-do-we-go/images/arlit.jpg
  40. BIN
      subjects/where-do-we-go/images/atlanta.jpg
  41. BIN
      subjects/where-do-we-go/images/black-rock-desert.jpg
  42. BIN
      subjects/where-do-we-go/images/cordoba.jpg
  43. BIN
      subjects/where-do-we-go/images/georgetown.jpg
  44. BIN
      subjects/where-do-we-go/images/killeen.jpg
  45. BIN
      subjects/where-do-we-go/images/lisse.jpg
  46. BIN
      subjects/where-do-we-go/images/los-caracoles-pass.jpg
  47. BIN
      subjects/where-do-we-go/images/marrakesh.jpg
  48. BIN
      subjects/where-do-we-go/images/moab.jpg
  49. BIN
      subjects/where-do-we-go/images/mount-fuji.jpg
  50. BIN
      subjects/where-do-we-go/images/namib-desert.jpg
  51. BIN
      subjects/where-do-we-go/images/newark.jpg
  52. BIN
      subjects/where-do-we-go/images/nishinoshima-island.jpg
  53. BIN
      subjects/where-do-we-go/images/qinhuangdao.jpg
  54. BIN
      subjects/where-do-we-go/images/rio-de-janeiro.jpg
  55. BIN
      subjects/where-do-we-go/images/shadegan-lagoon.jpg
  56. BIN
      subjects/where-do-we-go/images/skafta-river.jpg
  57. BIN
      subjects/where-do-we-go/images/tucson.jpg
  58. BIN
      subjects/where-do-we-go/images/yuanyang-county.jpg
  59. 68
      subjects/where-do-we-go/index.html

47
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()
}
}

46
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()

25
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]
}

169
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()

112
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
}

25
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)
})
}

37
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)
}

66
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`
}
}

9
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"
}
}

105
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`
}

30
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
}

36
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())
}
})

60
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)
})

72
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 = `<div style="transform: rotate(${
scrollUp ? -90 : 90
}deg)"></div><div>${scrollUp ? 'N' : 'S'}</div>`
}
const getLocation = () => {
const { innerHeight, scrollY } = window
const index = Math.ceil((scrollY - innerHeight / 2) / innerHeight)
return places[index]
}

43
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
<div id="brick-1"></div>
```
- 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)

104
subjects/build-brick-and-break/index.html

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<title>Build brick and break</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<link rel="stylesheet" type="text/css" href="./style.css">
<style type="text/css">
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
align-content: flex-end;
height: 100vh;
color: var(--text);
padding: 10vh 36.5vw;
}
[title='brick'] {
text-align: center;
font-size: 10px;
width: 9vw;
display: inline-flex;
justify-content: center;
align-items: center;
height: 4.44vh;
background: linear-gradient(-25deg, var(--clear) 30%, var(--disabled) 90%);
}
#tools {
position: fixed;
right: 100px;
font-size: 80px;
cursor: pointer;
user-select: none;
}
[data-repaired='true'] {
color: hsl(275, 100%, 50%);
}
[data-repaired='true']:after {
content: '-repaired';
}
[data-repaired='in progress'] {
color: black;
}
[data-repaired='in progress']:after {
content: '-in progress';
}
</style>
</head>
<body>
<script type="module">
import { build, repair, destroy } from './build-bricks-and-break.js'
build()
const body = document.querySelector('body')
const tools = document.createElement('div')
tools.id = 'tools'
body.append(tools)
const dynamite = document.createElement('div')
dynamite.textContent = '🧨'
dynamite.addEventListener('click', destroy)
const hammer = document.createElement('div')
hammer.textContent = '🔨'
hammer.addEventListener('click', () => repair('brick-26', ...reparations))
tools.append(dynamite, hammer)
const random = (min, max) => {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min + 1)) + min
}
const reparations = [...Array(15).keys()].map((e) => `brick-${random(0, 54)}`)
</script>
</body>
</html>

39
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 `<style>` tag in the `<head>` tag and generate, for each color of `colors`, a class setting the `background` attribute and taking the color as value, like following:
```css
.indianred {
background: indianred;
}
```
- Create a `<div>` for each color of the `colors` array whose name contains `aqua`, `blue`, `turquoise`, `green`, `cyan`, `navy` or `purple`.\
Each `<div>` must have the corresponding generated class and display the name of the color, like following:
```html
<div class="indianred">indianred</div>
```
- The function `choseShade` is triggered when clicking on a `div`.\
Write the body of this function, which receives the shade of the clicked element as argument, and replaces all the other elements class by the chosen shade.
### Notions
- [`head`](https://developer.mozilla.org/en-US/docs/Web/API/Document/head) / [style tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
- [`className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className)
- [`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList): `contains()`, `replace()`
### Provided files
- Import the `colors` from the data file: [data.js](./data.js)
### Expected result
You can see an example of the expected result [here](https://youtu.be/a-3JDEvW-Qg)

143
subjects/fifty-shades-of-cold/data.js

@ -0,0 +1,143 @@
export const colors = [
'indianred',
'lightcoral',
'salmon',
'darksalmon',
'lightsalmon',
'crimson',
'red',
'firebrick',
'darkred',
'pink',
'lightpink',
'hotpink',
'deeppink',
'mediumvioletred',
'palevioletred',
'orange',
'coral',
'tomato',
'orangered',
'darkorange',
'yellow',
'gold',
'lightyellow',
'lemonchiffon',
'lightgoldenrodyellow',
'papayawhip',
'moccasin',
'peachpuff',
'palegoldenrod',
'khaki',
'darkkhaki',
'lavender',
'thistle',
'plum',
'violet',
'orchid',
'fuchsia',
'magenta',
'mediumorchid',
'mediumpurple',
'rebeccapurple',
'blueviolet',
'darkviolet',
'darkorchid',
'darkmagenta',
'purple',
'indigo',
'slateblue',
'darkslateblue',
'green',
'greenyellow',
'chartreuse',
'lawngreen',
'lime',
'limegreen',
'palegreen',
'lightgreen',
'mediumspringgreen',
'springgreen',
'mediumseagreen',
'seagreen',
'forestgreen',
'darkgreen',
'yellowgreen',
'olivedrab',
'olive',
'darkolivegreen',
'mediumaquamarine',
'darkseagreen',
'lightseagreen',
'darkcyan',
'teal',
'aqua',
'cyan',
'lightcyan',
'paleturquoise',
'aquamarine',
'turquoise',
'mediumturquoise',
'darkturquoise',
'cadetblue',
'steelblue',
'lightsteelblue',
'powderblue',
'lightblue',
'skyblue',
'lightskyblue',
'deepskyblue',
'dodgerblue',
'cornflowerblue',
'mediumslateblue',
'royalblue',
'blue',
'mediumblue',
'darkblue',
'navy',
'midnightblue',
'brown',
'cornsilk',
'blanchedalmond',
'bisque',
'navajowhite',
'wheat',
'burlywood',
'tan',
'rosybrown',
'sandybrown',
'goldenrod',
'darkgoldenrod',
'peru',
'chocolate',
'saddlebrown',
'sienna',
'maroon',
'white',
'snow',
'honeydew',
'mintcream',
'azure',
'aliceblue',
'ghostwhite',
'whitesmoke',
'seashell',
'beige',
'oldlace',
'floralwhite',
'ivory',
'antiquewhite',
'linen',
'lavenderblush',
'mistyrose',
'gainsboro',
'lightgray',
'silver',
'darkgray',
'gray',
'dimgray',
'lightslategray',
'slategray',
'darkslategray',
'black',
]

58
subjects/fifty-shades-of-cold/index.html

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<title>Fifty shades of cold</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
width: 100vw;
padding: 5rem;
font-size: 12px;
}
div {
cursor: pointer;
width: 150px;
height: 150px;
margin: 20px;
padding: 5px 8px;
}
</style>
</head>
<body>
<script type="module">
import { choseShade } from './fifty-shades-of-cold.js'
const divs = [...document.querySelectorAll('div')]
divs.map((d) => {
d.addEventListener('click', () => choseShade(d.textContent))
})
</script>
</body>
</html>

30
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 `<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)

221
subjects/get-them-all/index.html

@ -0,0 +1,221 @@
<!DOCTYPE html>
<html>
<head>
<title>Get them all</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
padding: 100px;
justify-content: center;
align-items: center;
font-size: 15px;
margin-top: 150px;
}
#buttons {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
top: 0;
height: 150px;
background: var(--background);
box-shadow: 0 0 50px black;
}
#buttons * {
margin: 0 20px;
padding: 10px 20px;
background: var(--clear);
border-radius: 20px;
cursor: pointer;
user-select: none;
width: 200px;
text-align: center;
}
.disabled {
pointer-events: none;
opacity: 0.3;
}
a,
span {
min-width: 110px;
min-height: 110px;
width: 5vw;
height: 5vw;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
border: solid 1px var(--clear);
line-height: 22px;
padding: 10px;
color: var(--clear);
margin: 30px;
}
.found {
box-shadow: 8px 8px 15px rgba(0, 0, 0, 0.6),
-10px -10px 15px rgba(255, 255, 255, 0.074);
border: none;
background: var(--purple);
color: var(--background);
}
</style>
</head>
<body>
<script type="module">
import {
getBonannoPisano,
getActive,
getArchitects,
getClassical,
} from './get-them-all.js'
const people = [
{ id: 'LolaDunam', tag: 'span', classe: 'modern', active: false },
{ id: 'LeeMarley', tag: 'span', classe: 'baroque', active: false },
{ id: 'JeanDujardin', tag: 'a', classe: 'classical', active: true },
{ id: 'MarloStanfield', tag: 'span', classe: 'modern', active: false },
{ id: 'GeorgesDrumond', tag: 'span', classe: 'baroque', active: true },
{ id: 'JuliaWhite', tag: 'span', classe: 'modern', active: true },
{ id: 'BarneyLeberre', tag: 'span', classe: 'modern', active: true },
{ id: 'DavidCarretta', tag: 'a', classe: 'classical', active: false },
{ id: 'AugustoCesar', tag: 'span', classe: 'modern', active: true },
{ id: 'DavidGuetta', tag: 'a', classe: 'modern', active: false },
{ id: 'MarlonBrando', tag: 'a', classe: 'classical', active: false },
{ id: 'BonannoPisano', tag: 'a', classe: 'classical', active: true },
{ id: 'AvonBarksdale', tag: 'span', classe: 'baroque', active: true },
{ id: 'BarackObama', tag: 'span', classe: 'baroque', active: false },
{ id: 'MarcDupont', tag: 'span', classe: 'modern', active: false },
{ id: 'BillieElliott', tag: 'a', classe: 'baroque', active: true },
{ id: 'MariaCallas', tag: 'a', classe: 'baroque', active: false },
{ id: 'SteveJobbs', tag: 'a', classe: 'classical', active: false },
{ id: 'JoeLee', tag: 'span', classe: 'baroque', active: false },
{ id: 'AnthonyGrant', tag: 'span', classe: 'baroque', active: false },
{ id: 'ShakimaGreggs', tag: 'a', classe: 'modern', active: true },
{ id: 'RoyDeere', tag: 'span', classe: 'baroque', active: true },
{ id: 'BobTurner', tag: 'a', classe: 'classical', active: true },
{ id: 'AngeloCapri', tag: 'span', classe: 'modern', active: false },
{ id: 'SamMcDonald', tag: 'span', classe: 'baroque', active: true },
{ id: 'FannyLelouche', tag: 'span', classe: 'baroque', active: true },
{ id: 'ClarkLoister', tag: 'a', classe: 'classical', active: false },
{ id: 'FinanObrien', tag: 'span', classe: 'modern', active: false },
{ id: 'ClariceSterling', tag: 'a', classe: 'modern', active: true },
{ id: 'JayHernan', tag: 'span', classe: 'baroque', active: true },
{ id: 'HelenMirren', tag: 'a', classe: 'classical', active: false },
{ id: 'SarahForestier', tag: 'a', classe: 'modern', active: false },
{ id: 'JacquesChirac', tag: 'a', classe: 'classical', active: true },
{ id: 'MartinWealer', tag: 'a', classe: 'baroque', active: true },
{ id: 'JodieFoster', tag: 'span', classe: 'baroque', active: true },
{ id: 'JeanJacques', tag: 'span', classe: 'modern', active: false },
{ id: 'MollyHeart', tag: 'a', classe: 'baroque', active: false },
{ id: 'FabioSalso', tag: 'a', classe: 'classical', active: true },
{ id: 'CarlosSanchez', tag: 'span', classe: 'baroque', active: true },
{ id: 'RussellBell', tag: 'a', classe: 'classical', active: false },
{ id: 'JackDoe', tag: 'span', classe: 'baroque', active: true },
{ id: 'EricCarver', tag: 'a', classe: 'classical', active: false },
{ id: 'LouisDeschamps', tag: 'span', classe: 'baroque', active: true },
{ id: 'HoracioCane', tag: 'a', classe: 'baroque', active: true },
{ id: 'HenryBright', tag: 'a', classe: 'baroque', active: true },
]
const body = document.querySelector('body')
const shuffle = array => {
const test = array.length - 1
for (let i = test; i > 0; i--) {
const j = Math.floor(Math.random() * i)
const temp = array[i]
array[i] = array[j]
array[j] = temp
}
return array
}
shuffle(people).map(({ id, classe, address, plans, tag, active }) => {
const people = document.createElement(tag)
people.id = id
people.textContent = 'Someone'
people.className = `${classe} ${active ? 'active' : ''}`
body.append(people)
})
const buttonsContainer = document.createElement('div')
buttonsContainer.id = 'buttons'
body.append(buttonsContainer)
const buttons = [
{ name: 'Architect', action: getArchitects },
{ name: 'Classical', action: getClassical },
{ name: 'Active', action: getActive },
{ name: 'Bonanno', action: getBonannoPisano },
]
buttons.forEach(({ name, action }, i) => {
const btn = document.createElement('div')
btn.id = `btn${name}`
btn.textContent = `Get ${name}${i === 0 ? 's' : ''}`
if (i > 0) {
btn.className = 'disabled'
}
btn.addEventListener('click', () => {
const [targetted, others] = action()
if (name === 'Bonanno') {
targetted.textContent = targetted.id.replace('P', ' P')
targetted.classList.add('found')
} else {
targetted.forEach(t => {
t.textContent = name
})
}
others.forEach(o => {
o.style.opacity = 0.2
})
btn.className = 'disabled'
const last = i + 1 === buttons.length
if (last) return
const next = document.getElementById(`btn${buttons[i + 1].name}`)
next.classList.remove('disabled')
})
buttonsContainer.append(btn)
})
</script>
</body>
</html>

22
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)

19
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'`,
]

135
subjects/gossip-grid/index.html

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<title>Gossip grid</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
justify-content: center;
width: 100vw;
height: 100vh;
padding: 10rem;
align-items: flex-start;
}
.gossip {
background: hsl(280, 50%, 50%);
margin: 20px;
width: 250px;
padding: 20px 25px;
line-height: 30px;
font-size: 20px;
word-break: break-word;
color: white;
display: flex;
justify-content: space-between;
flex-direction: column;
}
.gossip:first-letter {
text-transform: uppercase;
}
.ranges {
position: fixed;
top: 5rem;
display: flex;
}
.range {
display: flex;
justify-content: center;
align-items: center;
color: var(--clear);
font-family: monospace;
}
.range label,
.range span {
width: 100px;
}
.range label {
text-align: right;
}
input {
margin: 20px 25px;
}
textarea {
height: 100%;
width: 100%;
max-width: 100%;
outline: none;
border: none;
font-family: inherit;
font-size: inherit;
line-height: inherit;
letter-spacing: inherit;
color: inherit;
background-color: transparent;
padding: 0;
margin: 0;
resize: none;
}
textarea::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.button {
margin-top: 20px;
text-align: right;
border: solid 1px white;
align-self: flex-end;
padding: 5px 15px;
cursor: pointer;
}
.fade-in {
animation: fade-in 0.75s;
}
@keyframes fade-in {
from {
opacity: 0%;
}
to {
opacity: 100%;
}
}
</style>
</head>
<body>
<script type="module">
import { grid } from './gossip-grid.js'
grid()
</script>
</body>
</html>

17
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)

57
subjects/harder-bigger-bolder-stronger/index.html

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>Harder, bigger, bolder, stronger</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
width: 100vw;
padding: 5rem;
font-size: 12px;
}
div {
display: flex;
justify-content: center;
align-items: center;
margin: 5px;
width: 200px;
height: 200px;
color: white;
}
</style>
</head>
<body>
<script type="module">
import { generateLetters } from './harder-bigger-bolder-stronger.js'
generateLetters()
</script>
</body>
</html>

20
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)

57
subjects/keycodes-symphony/index.html

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>Keycodes symphony</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
width: 100vw;
height: 100vh;
}
.note {
width: 100%;
flex: 1 1 80px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 15px;
font-size: 40px;
color: white;
transition: all 0.2s ease-in-out;
}
</style>
</head>
<body>
<script type="module">
import { compose } from './keycodes-symphony.js'
compose()
</script>
</body>
</html>

22
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)

63
subjects/mouse-trap/index.html

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<title>Mouse trap</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
width: 100vw;
height: 100vh;
padding: 5rem;
font-size: 12px;
}
.elem {
width: 50px;
height: 50px;
border-radius: 50%;
background: var(--clear);
position: absolute;
opacity: 0.75;
}
.box {
width: 25vw;
height: 25vh;
border: solid 1px var(--clear);
}
</style>
</head>
<body>
<script type="module">
import { createCircle, moveCircle } from './mouse-trap.js'
createCircle()
moveCircle()
</script>
</body>
</html>

24
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)

134
subjects/pick-and-click/index.html

@ -0,0 +1,134 @@
<!DOCTYPE html>
<html>
<head>
<title>Pick & click</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
width: 100vw;
height: 100vh;
padding: 5rem;
cursor: crosshair;
}
svg {
filter: invert(100%);
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
}
svg line {
stroke-width: 0.6px;
}
.count {
filter: invert(100%);
font-size: 17px;
}
.text {
position: fixed;
filter: invert(100%);
font-size: 50px;
cursor: pointer;
white-space: pre-wrap;
}
.hue {
top: 100px;
right: 100px;
text-align: right;
}
.luminosity {
bottom: 100px;
left: 100px;
}
.origin {
width: 40px;
height: 40px;
top: 80px;
left: 80px;
border-radius: 50%;
background: black;
}
.picked {
opacity: 0;
bottom: 100px;
right: 100px;
}
.wave {
background: white;
width: 20px;
height: 20px;
border-radius: 50%;
position: absolute;
animation: wave 0.15s ease-out;
opacity: 0;
}
.fade-in {
animation: fade-in 1s;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes wave {
from {
opacity: 0;
transform: scale(1);
}
to {
opacity: 0.4;
transform: scale(2);
}
}
</style>
</head>
<body>
<script type="module">
import { pick } from './pick-and-click.js'
pick()
</script>
</body>
</html>

49
subjects/pimp-my-style/README.md

@ -0,0 +1,49 @@
## Pimp my style
### Instructions
Check out that button on the HTML page:
```html
<div class="button">pimp my style</div>
```
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 --> <div class="button"></div>
...adding
Click 1 --> <div class="button one"></div>
Click 2 --> <div class="button one two"></div>
...toggling `unpimp`
Click 3 --> <div class="button one two three unpimp"></div>
...and removing backwards
Click 4 --> <div class="button one two unpimp"></div>
Click 5 --> <div class="button one unpimp"></div>
Click 6 --> <div class="button"></div>
```
### 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)

17
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',
]

179
subjects/pimp-my-style/index.html

@ -0,0 +1,179 @@
<!DOCTYPE html>
<html>
<head>
<title>Pimp my style</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
width: 100vw;
height: 100vh;
color: var(--text);
padding: 2.5rem 0;
}
div {
user-select: none;
letter-spacing: 0;
}
.button:first-letter {
text-transform: uppercase;
}
.button.unpimp:before {
content: 'Un';
}
.button {
background: var(--background);
font-family: serif;
cursor: pointer;
width: 70%;
text-align: center;
}
.one {
font-size: 75px;
}
.two {
font-family: sans-serif;
}
.three {
letter-spacing: 15px;
}
.four {
padding: 20px 40px;
border: solid 1px var(--clear);
}
.five {
border-radius: 100px;
}
.six {
border: none;
box-shadow: 8px 8px 15px rgba(255, 255, 255, 0.075),
-10px -10px 15px rgba(0, 0, 0, 0.3);
}
.seven {
color: var(--purple);
}
.eight {
border: solid 0.5px var(--purple);
}
.nine {
background: var(--purple);
color: white;
position: relative;
box-shadow: 0px 0px 35px rgba(0, 0, 0, 0.8);
}
.ten:after {
position: absolute;
content: '';
width: 100%;
height: 100%;
padding: 15px;
top: -16px;
left: -16px;
border-radius: 100px;
border: solid 1px var(--clear);
}
.eleven:after {
position: absolute;
content: '';
padding: 30px;
top: -31px;
left: -31px;
background: white;
z-index: -1;
}
.twelve {
color: var(--background);
}
.thirteen {
text-decoration: underline;
}
.fourteen {
animation: animation 1.5s linear infinite;
background: linear-gradient(
to right,
var(--purple) 0%,
white 48%,
white 52%,
var(--purple) 100%
);
background-size: 500px 640px;
position: relative;
}
@keyframes animation {
0% {
background-position: 0 0;
}
100% {
background-position: 500px 0;
}
}
.fifteen {
height: 100vh;
width: 100vw;
border-radius: 0;
display: flex;
justify-content: center;
align-items: center;
}
</style>
</head>
<body>
<script type="module">
import { pimp } from './pimp-my-style.js'
const body = document.querySelector('body')
const button = document.createElement('div')
button.className = 'button'
button.textContent = 'pimp my style'
body.append(button)
button.addEventListener('click', pimp)
</script>
</body>
</html>

26
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)

107
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`,
},
]

BIN
subjects/where-do-we-go/images/almeria.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 206 KiB

BIN
subjects/where-do-we-go/images/arlit.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 155 KiB

BIN
subjects/where-do-we-go/images/atlanta.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 218 KiB

BIN
subjects/where-do-we-go/images/black-rock-desert.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 244 KiB

BIN
subjects/where-do-we-go/images/cordoba.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 610 KiB

BIN
subjects/where-do-we-go/images/georgetown.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 248 KiB

BIN
subjects/where-do-we-go/images/killeen.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 296 KiB

BIN
subjects/where-do-we-go/images/lisse.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 166 KiB

BIN
subjects/where-do-we-go/images/los-caracoles-pass.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 209 KiB

BIN
subjects/where-do-we-go/images/marrakesh.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 444 KiB

BIN
subjects/where-do-we-go/images/moab.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 223 KiB

BIN
subjects/where-do-we-go/images/mount-fuji.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 133 KiB

BIN
subjects/where-do-we-go/images/namib-desert.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 164 KiB

BIN
subjects/where-do-we-go/images/newark.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 227 KiB

BIN
subjects/where-do-we-go/images/nishinoshima-island.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 222 KiB

BIN
subjects/where-do-we-go/images/qinhuangdao.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 126 KiB

BIN
subjects/where-do-we-go/images/rio-de-janeiro.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 282 KiB

BIN
subjects/where-do-we-go/images/shadegan-lagoon.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 78 KiB

BIN
subjects/where-do-we-go/images/skafta-river.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 163 KiB

BIN
subjects/where-do-we-go/images/tucson.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 168 KiB

BIN
subjects/where-do-we-go/images/yuanyang-county.jpg

diff.bin_not_shown

After

Width:  |  Height:  |  Size: 197 KiB

68
subjects/where-do-we-go/index.html

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<title>Where do we go?</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link id="fav" rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<style>
:root {
--background: hsl(0, 0%, 12%);
--text: hsl(0, 0%, 80%);
--clear: hsl(0, 0%, 65%);
--disabled: hsl(0, 0%, 35%);
--purple: #bb73e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
letter-spacing: 1.5px;
background: var(--background);
}
section {
height: 100vh;
width: 100vw;
}
.location {
position: fixed;
top: 0;
color: white;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 100px;
line-height: 130px;
white-space: pre-wrap;
text-align: center;
cursor: pointer;
}
.direction {
position: fixed;
right: 100px;
top: 100px;
text-align: center;
font-size: 40px;
line-height: 60px;
color: white;
white-space: pre-wrap;
}
</style>
</head>
<body>
<script type="module">
import { scroll } from './where-do-we-go.js'
scroll()
</script>
</body>
</html>
Loading…
Cancel
Save