adding dom content

This commit is contained in:
Clement Denis 2020-06-15 23:33:28 +02:00 committed by Clément
parent a9adac2911
commit 3c0b87ef69
59 changed files with 2493 additions and 0 deletions

View File

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

View File

@ -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 Normal file
View File

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

View File

@ -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 Normal file
View File

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

View File

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

View File

@ -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 Normal file
View File

@ -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 Normal file
View File

@ -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 Normal file
View File

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

View File

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

View File

@ -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 Normal file
View File

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

View File

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

View File

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

View File

@ -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>

View File

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

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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 Scotts 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 shouldnt 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'`,
]

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -0,0 +1,17 @@
export const styles = [
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten',
'eleven',
'twelve',
'thirteen',
'fourteen',
'fifteen',
]

View File

@ -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>

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

View File

@ -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>