From 8573f049f2f30a02ca54441fd7b22ce396771be4 Mon Sep 17 00:00:00 2001 From: Clement Denis Date: Sun, 10 Jul 2022 23:51:42 +0100 Subject: [PATCH] Create a copy of the dom tests to js image --- js/tests/Dockerfile | 19 +- js/tests/action-reaction-dom_test.mjs | 45 +++++ js/tests/bring-it-to-life-dom_test.mjs | 43 +++++ js/tests/build-brick-and-break-dom_test.mjs | 109 +++++++++++ js/tests/class-that-dom_test.mjs | 36 ++++ js/tests/fifty-shades-of-cold-dom_test.mjs | 71 +++++++ js/tests/first-words-dom_test.mjs | 46 +++++ js/tests/get-them-all-dom_test.mjs | 127 ++++++++++++ js/tests/gossip-grid-dom_test.mjs | 123 ++++++++++++ ...harder-bigger-bolder-stronger-dom_test.mjs | 67 +++++++ js/tests/hello-there_test.js | 23 --- js/tests/keycodes-symphony-dom_test.mjs | 55 ++++++ js/tests/mouse-trap-dom_test.mjs | 122 ++++++++++++ js/tests/nesting-organs-dom_test.mjs | 90 +++++++++ js/tests/pick-and-click-dom_test.mjs | 110 +++++++++++ js/tests/pimp-my-style-dom_test.mjs | 36 ++++ js/tests/select-and-style-dom_test.mjs | 49 +++++ js/tests/skeleton-dom_test.mjs | 25 +++ js/tests/test.mjs | 164 +++++++++++++--- js/tests/the-calling-dom_test.mjs | 19 ++ js/tests/where-do-we-go-dom_test.mjs | 174 +++++++++++++++++ subjects/action-reaction-dom/README.md | 81 ++++++++ subjects/bring-it-to-life-dom/README.md | 50 +++++ .../bring-it-to-life-dom/bring-it-to-life.png | Bin 0 -> 49625 bytes subjects/build-brick-and-break-dom/README.md | 44 +++++ .../build-brick-and-break.html | 108 +++++++++++ subjects/class-that-dom/README.md | 57 ++++++ subjects/class-that-dom/class-that.png | Bin 0 -> 50413 bytes subjects/fifty-shades-of-cold-dom/README.md | 46 +++++ .../fifty-shades-of-cold.data.js | 143 ++++++++++++++ .../fifty-shades-of-cold.html | 61 ++++++ subjects/first-words-dom/README.md | 72 +++++++ subjects/get-them-all-dom/README.md | 53 +++++ .../get-them-all-dom/get-them-all.data.js | 47 +++++ subjects/get-them-all-dom/get-them-all.html | 179 +++++++++++++++++ subjects/gossip-grid-dom/README.md | 38 ++++ subjects/gossip-grid-dom/gossip-grid.data.js | 19 ++ subjects/gossip-grid-dom/gossip-grid.html | 135 +++++++++++++ .../README.md | 28 +++ .../harder-bigger-bolder-stronger.html | 57 ++++++ subjects/keycodes-symphony-dom/README.md | 29 +++ .../keycodes-symphony.html | 57 ++++++ subjects/mouse-trap-dom/README.md | 35 ++++ subjects/mouse-trap-dom/mouse-trap.html | 63 ++++++ subjects/nesting-organs-dom/README.md | 79 ++++++++ .../nesting-organs-dom/nesting-organs.png | Bin 0 -> 44500 bytes subjects/pick-and-click-dom/README.md | 44 +++++ .../pick-and-click-dom/pick-and-click.html | 86 +++++++++ subjects/pimp-my-style-dom/README.md | 55 ++++++ .../pimp-my-style-dom/pimp-my-style.data.js | 17 ++ subjects/pimp-my-style-dom/pimp-my-style.html | 181 ++++++++++++++++++ subjects/select-and-style-dom/README.md | 81 ++++++++ .../select-and-style-dom/select-and-style.png | Bin 0 -> 37461 bytes subjects/skeleton-dom/README.md | 81 ++++++++ subjects/the-calling-dom/README.md | 40 ++++ subjects/where-do-we-go-dom/README.md | 43 +++++ .../where-do-we-go-dom/where-do-we-go.data.js | 107 +++++++++++ .../where-do-we-go-dom/where-do-we-go.html | 72 +++++++ .../where-do-we-go_images/almeria.jpg | Bin 0 -> 211080 bytes .../where-do-we-go_images/arlit.jpg | Bin 0 -> 158784 bytes .../where-do-we-go_images/atlanta.jpg | Bin 0 -> 223305 bytes .../black-rock-desert.jpg | Bin 0 -> 249516 bytes .../where-do-we-go_images/cordoba.jpg | Bin 0 -> 625179 bytes .../where-do-we-go_images/georgetown.jpg | Bin 0 -> 253447 bytes .../where-do-we-go_images/killeen.jpg | Bin 0 -> 303542 bytes .../where-do-we-go_images/lisse.jpg | Bin 0 -> 170418 bytes .../los-caracoles-pass.jpg | Bin 0 -> 214449 bytes .../where-do-we-go_images/marrakesh.jpg | Bin 0 -> 454889 bytes .../where-do-we-go_images/moab.jpg | Bin 0 -> 228023 bytes .../where-do-we-go_images/mount-fuji.jpg | Bin 0 -> 136429 bytes .../where-do-we-go_images/namib-desert.jpg | Bin 0 -> 168253 bytes .../where-do-we-go_images/newark.jpg | Bin 0 -> 232627 bytes .../nishinoshima-island.jpg | Bin 0 -> 227628 bytes .../where-do-we-go_images/qinhuangdao.jpg | Bin 0 -> 129577 bytes .../where-do-we-go_images/rio-de-janeiro.jpg | Bin 0 -> 288661 bytes .../where-do-we-go_images/shadegan-lagoon.jpg | Bin 0 -> 80206 bytes .../where-do-we-go_images/skafta-river.jpg | Bin 0 -> 167341 bytes .../where-do-we-go_images/tucson.jpg | Bin 0 -> 172553 bytes .../where-do-we-go_images/yuanyang-county.jpg | Bin 0 -> 201338 bytes 79 files changed, 3787 insertions(+), 54 deletions(-) create mode 100644 js/tests/action-reaction-dom_test.mjs create mode 100644 js/tests/bring-it-to-life-dom_test.mjs create mode 100644 js/tests/build-brick-and-break-dom_test.mjs create mode 100644 js/tests/class-that-dom_test.mjs create mode 100644 js/tests/fifty-shades-of-cold-dom_test.mjs create mode 100644 js/tests/first-words-dom_test.mjs create mode 100644 js/tests/get-them-all-dom_test.mjs create mode 100644 js/tests/gossip-grid-dom_test.mjs create mode 100644 js/tests/harder-bigger-bolder-stronger-dom_test.mjs delete mode 100644 js/tests/hello-there_test.js create mode 100644 js/tests/keycodes-symphony-dom_test.mjs create mode 100644 js/tests/mouse-trap-dom_test.mjs create mode 100644 js/tests/nesting-organs-dom_test.mjs create mode 100644 js/tests/pick-and-click-dom_test.mjs create mode 100644 js/tests/pimp-my-style-dom_test.mjs create mode 100644 js/tests/select-and-style-dom_test.mjs create mode 100644 js/tests/skeleton-dom_test.mjs create mode 100644 js/tests/the-calling-dom_test.mjs create mode 100644 js/tests/where-do-we-go-dom_test.mjs create mode 100644 subjects/action-reaction-dom/README.md create mode 100644 subjects/bring-it-to-life-dom/README.md create mode 100644 subjects/bring-it-to-life-dom/bring-it-to-life.png create mode 100644 subjects/build-brick-and-break-dom/README.md create mode 100644 subjects/build-brick-and-break-dom/build-brick-and-break.html create mode 100644 subjects/class-that-dom/README.md create mode 100644 subjects/class-that-dom/class-that.png create mode 100644 subjects/fifty-shades-of-cold-dom/README.md create mode 100644 subjects/fifty-shades-of-cold-dom/fifty-shades-of-cold.data.js create mode 100644 subjects/fifty-shades-of-cold-dom/fifty-shades-of-cold.html create mode 100644 subjects/first-words-dom/README.md create mode 100644 subjects/get-them-all-dom/README.md create mode 100644 subjects/get-them-all-dom/get-them-all.data.js create mode 100644 subjects/get-them-all-dom/get-them-all.html create mode 100644 subjects/gossip-grid-dom/README.md create mode 100644 subjects/gossip-grid-dom/gossip-grid.data.js create mode 100644 subjects/gossip-grid-dom/gossip-grid.html create mode 100644 subjects/harder-bigger-bolder-stronger-dom/README.md create mode 100644 subjects/harder-bigger-bolder-stronger-dom/harder-bigger-bolder-stronger.html create mode 100644 subjects/keycodes-symphony-dom/README.md create mode 100644 subjects/keycodes-symphony-dom/keycodes-symphony.html create mode 100644 subjects/mouse-trap-dom/README.md create mode 100644 subjects/mouse-trap-dom/mouse-trap.html create mode 100644 subjects/nesting-organs-dom/README.md create mode 100644 subjects/nesting-organs-dom/nesting-organs.png create mode 100644 subjects/pick-and-click-dom/README.md create mode 100644 subjects/pick-and-click-dom/pick-and-click.html create mode 100644 subjects/pimp-my-style-dom/README.md create mode 100644 subjects/pimp-my-style-dom/pimp-my-style.data.js create mode 100644 subjects/pimp-my-style-dom/pimp-my-style.html create mode 100644 subjects/select-and-style-dom/README.md create mode 100644 subjects/select-and-style-dom/select-and-style.png create mode 100644 subjects/skeleton-dom/README.md create mode 100644 subjects/the-calling-dom/README.md create mode 100644 subjects/where-do-we-go-dom/README.md create mode 100644 subjects/where-do-we-go-dom/where-do-we-go.data.js create mode 100644 subjects/where-do-we-go-dom/where-do-we-go.html create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/almeria.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/arlit.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/atlanta.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/black-rock-desert.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/cordoba.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/georgetown.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/killeen.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/lisse.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/los-caracoles-pass.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/marrakesh.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/moab.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/mount-fuji.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/namib-desert.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/newark.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/nishinoshima-island.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/qinhuangdao.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/rio-de-janeiro.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/shadegan-lagoon.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/skafta-river.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/tucson.jpg create mode 100644 subjects/where-do-we-go-dom/where-do-we-go_images/yuanyang-county.jpg diff --git a/js/tests/Dockerfile b/js/tests/Dockerfile index 225688c7..fa4f1270 100644 --- a/js/tests/Dockerfile +++ b/js/tests/Dockerfile @@ -1,4 +1,21 @@ -FROM node:14.16.0-alpine3.12 +FROM alpine:3.16.0 + +# Installs latest Chromium package. +RUN apk add --no-cache \ + chromium \ + nss \ + freetype \ + harfbuzz \ + ca-certificates \ + ttf-freefont \ + nodejs \ + yarn + +# Tell Puppeteer to skip installing Chrome. We'll be using the installed package. +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \ + PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser + +RUN yarn add puppeteer@15.3.2 WORKDIR /app COPY . . diff --git a/js/tests/action-reaction-dom_test.mjs b/js/tests/action-reaction-dom_test.mjs new file mode 100644 index 00000000..e1827782 --- /dev/null +++ b/js/tests/action-reaction-dom_test.mjs @@ -0,0 +1,45 @@ +export const tests = [] + +tests.push(async ({ eq, page }) => { + // check the initial class name of the eye left + const eyeLeft = await page.$eval('#eye-left', (node) => node.className) + eq(eyeLeft, 'eye') + + // check that the text of the button says 'close' + const buttonText = await page.$eval('button', (node) => node.textContent) + eq(buttonText, 'Click to close the left eye') +}) + +tests.push(async ({ eq, page }) => { + // click the button to close the left eye + const button = await page.$('button') + button.click() + + // check that the class has been added + await page.waitForSelector('#eye-left.eye.eye-closed', { timeout: 150 }) + + // check the background color has changed + await eq.$('#eye-left.eye.eye-closed', { + style: { backgroundColor: 'black' }, + }) + + // check that the text of the button changed to 'open' + await eq.$('button', { textContent: 'Click to open the left eye' }) +}) + +tests.push(async ({ eq, page }) => { + // click the button a second time to open the left eye + const button = await page.$('button') + button.click() + + // check that the class has been removed + await page.waitForSelector('#eye-left.eye:not(.eye-closed)', { timeout: 150 }) + + // check the background color has changed + await eq.$('#eye-left.eye:not(.eye-closed)', { + style: { backgroundColor: 'red' }, + }) + + // check that the text of the button changed to 'close' + await eq.$('button', { textContent: 'Click to close the left eye' }) +}) diff --git a/js/tests/bring-it-to-life-dom_test.mjs b/js/tests/bring-it-to-life-dom_test.mjs new file mode 100644 index 00000000..a9e25ffb --- /dev/null +++ b/js/tests/bring-it-to-life-dom_test.mjs @@ -0,0 +1,43 @@ +export const tests = [] + +tests.push(async ({ eq, page }) => { + // check the JS script has been linked + await eq.$('script', { type: 'module' }) + + // check the JS script has a valid src + const source = await page.$eval( + 'script', + (node) => node.src.includes('.js') && node.src, + ) + if (!source.length) throw Error('missing script src') +}) + +tests.push(async ({ eq, page }) => { + // check the class 'eye-closed' has been added in the CSS + eq.css('.eye-closed', { + height: '4px', + padding: '0px 5px', + borderRadius: '10px', + }) +}) + +tests.push(async ({ eq, page }) => { + // check the class of left eye before the JS is loaded + await page.setJavaScriptEnabled(false) + await page.reload() + await eq.$('p#eye-left', { className: 'eye' }) +}) + +tests.push(async ({ eq, page }) => { + // check the class of left eye has been updated after the JS is loaded + await page.setJavaScriptEnabled(true) + await page.reload() + await eq.$('p#eye-left', { className: 'eye eye-closed' }) + + // check the background color of left eye has changed after the JS is loaded + const eyeLeftBg = await page.$eval( + '#eye-left', + (node) => node.style.backgroundColor, + ) + eq(eyeLeftBg, 'black') +}) diff --git a/js/tests/build-brick-and-break-dom_test.mjs b/js/tests/build-brick-and-break-dom_test.mjs new file mode 100644 index 00000000..658cda3f --- /dev/null +++ b/js/tests/build-brick-and-break-dom_test.mjs @@ -0,0 +1,109 @@ +export const tests = [] + +export const setup = async ({ page }) => ({ + getBricksIds: async () => + await page.$$eval('div', (nodes) => + nodes.filter((node) => node.id.includes('brick')).map((n) => n.id), + ), +}) + +const between = (expected, min, max) => expected >= min && expected <= max + +tests.push(async ({ page, eq }) => { + // check that the brick divs are built at a regular interval of 100ms + // the average of the divs built every 100ms must be close to 10 + let repeat = 0 + let buildSteps = [] + + while (repeat < 3) { + const divs = await page.$$eval('div', (nodes) => nodes.length) + console.log(`step ${repeat + 1}: ${divs} bricks`) + buildSteps.push(divs) + await page.waitForTimeout(1000) + repeat++ + } + + const diff1 = buildSteps[1] - buildSteps[0] + const diff2 = buildSteps[2] - buildSteps[1] + const average = Math.round((diff1 + diff2) / 2) + + if (average < 9) { + console.log('average too low --> new bricks built / sec:', average) + } else if (average > 11) { + console.log('average too high --> new bricks built / sec:', average) + } else { + console.log('good average of new bricks built / sec') + } + + eq(between(average, 9, 11), between(10, 9, 11)) +}) + +const allBricksIds = [...Array(54).keys()].map((i) => `brick-${i + 1}`) + +tests.push(async ({ page, eq, ctx }) => { + // check that all the bricks are here and have the correct id + await page.waitForTimeout(3000) + const bricksIds = await ctx.getBricksIds() + eq(bricksIds, allBricksIds) +}) + +tests.push(async ({ page, eq }) => { + // check that the middle column bricks have the `foundation` attribute to `true` + const expectedIds = allBricksIds.filter( + (b) => b.replace('brick-', '') % 3 === 2, + ) + const middleBricksIds = await page.$$eval('div', (nodes) => + nodes + .filter((node) => node.id.includes('brick') && node.dataset.foundation) + .map((n) => n.id), + ) + eq(middleBricksIds, expectedIds) +}) + +tests.push(async ({ page, eq }) => { + // check that the bricks to repair have the right repaired attribute + const hammer = await page.$('#hammer') + await hammer.click() + + const expectedRepairedIds = await page.$eval('body', (body) => { + const getIdInt = (str) => str.replace('brick-', '') + return body.dataset.reparations + .split(',') + .sort((a, b) => getIdInt(b) - getIdInt(a)) + .map((id) => { + const isMiddleBrick = getIdInt(id) % 3 === 2 + const status = isMiddleBrick ? 'in progress' : 'repaired' + return `${id}_${status}` + }) + }) + + const repairedIds = await page.$$eval('div', (nodes) => { + const getIdInt = (str) => str.replace('brick-', '') + return nodes + .filter( + (node) => + node.dataset.repaired === 'true' || + node.dataset.repaired === 'in progress', + ) + .sort((a, b) => getIdInt(b.id) - getIdInt(a.id)) + .map(({ id }) => { + const isMiddleBrick = getIdInt(id) % 3 === 2 + const status = isMiddleBrick ? 'in progress' : 'repaired' + return `${id}_${status}` + }) + }) + + eq(repairedIds, expectedRepairedIds) +}) + +tests.push(async ({ page, eq, getBricksIds }) => { + // check that the last brick is removed on each dynamite click + const dynamite = await page.$('#dynamite') + + for (const i of allBricksIds.keys()) { + await dynamite.click() + const { length } = allBricksIds + const expectedRemainingBricks = allBricksIds.slice(0, length - (i + 1)) + eq(await getBricksIds(), expectedRemainingBricks) + } +}) diff --git a/js/tests/class-that-dom_test.mjs b/js/tests/class-that-dom_test.mjs new file mode 100644 index 00000000..f84a4459 --- /dev/null +++ b/js/tests/class-that-dom_test.mjs @@ -0,0 +1,36 @@ +export const tests = [] + +tests.push(async ({ page, eq }) => { + // check the class 'eye' has been declared properly in the CSS + eq.css('.eye', { + width: '60px', + height: '60px', + backgroundColor: 'red', + borderRadius: '50%', + }) +}) + +tests.push(async ({ page, eq }) => { + // check the class 'arm' has been declared properly in the CSS + eq.css('.arm', { backgroundColor: 'aquamarine' }) +}) + +tests.push(async ({ page, eq }) => { + // check the class 'leg' has been declared properly in the CSS + eq.css('.leg', { backgroundColor: 'dodgerblue' }) +}) + +tests.push(async ({ page, eq }) => { + // check the class 'body-member' has been declared properly in the CSS + eq.css('.body-member', { width: '50px', margin: '30px' }) +}) + +tests.push(async ({ page, eq }) => { + // check that the targetted elements have the correct class names + await eq.$('p#eye-left', { className: 'eye' }) + await eq.$('p#eye-right', { className: 'eye' }) + await eq.$('div#arm-left', { className: 'arm body-member' }) + await eq.$('div#arm-right', { className: 'arm body-member' }) + await eq.$('div#leg-left', { className: 'leg body-member' }) + await eq.$('div#leg-right', { className: 'leg body-member' }) +}) diff --git a/js/tests/fifty-shades-of-cold-dom_test.mjs b/js/tests/fifty-shades-of-cold-dom_test.mjs new file mode 100644 index 00000000..a946885d --- /dev/null +++ b/js/tests/fifty-shades-of-cold-dom_test.mjs @@ -0,0 +1,71 @@ +import { colors } from '../subjects/fifty-shades-of-cold/fifty-shades-of-cold.data.js' + +colors.sort() + +const cold = ['aqua', 'blue', 'turquoise', 'green', 'purple', 'cyan', 'navy'] + +const isCold = color => cold.some(coldColor => color.includes(coldColor)) +const toClass = (a, b) => `.${a} { background: ${b} }` +const toDiv = (a, b) => `
${b}
` + +export const tests = [] + +tests.push(async ({ page, eq }) => { + // check that the css is properly generated + + const style = await page.$$eval('style', nodes => nodes[1].innerHTML) + const classes = style + .split('}') + .map(s => s.replace(/(\.|{|:|;|\s+)/g, '')) + .filter(Boolean) + .sort() + + for (const [i, c] of colors.entries()) { + if (!classes[i]) throw Error(`Not enough class (expected: ${c})`) + const [a, b] = classes[i].split('background') + eq(toClass(a, b), toClass(c, c)) + } +}) + +tests.push(async ({ page, eq }) => { + // check that the divs are properly generated + + const values = await page.$$eval('div', nodes => + nodes.map(n => [n.className, n.textContent]), + ) + const divs = values.map(v => toDiv(...v)).sort() + + let skipped = 0 + for (const [i, c] of colors.entries()) { + if (isCold(c)) { + if (!values[i - skipped]) throw Error(`Not enough div (expected: ${c})`) + eq(divs[i - skipped], toDiv(c, c)) + continue + } + skipped++ + if (divs.includes(toDiv(c, c))) { + throw Error(`div ${toDiv(c, c)} is not very cold`) + } + } +}) + +tests.push(async ({ page, eq }) => { + // test that clicking update the color accordingly + + const coldColors = colors.filter(isCold) + for (const c of coldColors) { + await page.$$eval( + 'div', + (nodes, c) => nodes.find(n => n.textContent === c).click(), + c, + ) + + const count = await page.$$eval( + 'div', + (nodes, c) => nodes.filter(n => n.className === c).length, + c, + ) + + eq(count, coldColors.length) + } +}) diff --git a/js/tests/first-words-dom_test.mjs b/js/tests/first-words-dom_test.mjs new file mode 100644 index 00000000..c0895bc0 --- /dev/null +++ b/js/tests/first-words-dom_test.mjs @@ -0,0 +1,46 @@ +export const tests = [] + +tests.push(async ({ eq, page }) => { + // check the class words has been added in the CSS + await eq.css('.words', { textAlign: 'center', fontFamily: 'sans-serif' }) +}) + +tests.push(async ({ eq, page }) => { + // check the torso element is initially empty + const isEmpty = await page.$eval('#torso', (node) => !node.children.length) + eq(isEmpty, true) +}) + +tests.push(async ({ eq, page }) => { + // click on the button + const button = await page.$('button#speak-button') + await button.click() + + // check a new text element is added in the torso + const torsoChildren = await page.$eval('#torso', (node) => + [...node.children].map((child) => ({ + tag: child.tagName, + text: child.textContent, + class: child.className, + })), + ) + eq(torsoChildren, [textNode]) +}) + +tests.push(async ({ eq, page }) => { + // click a second time on the button + const button = await page.$('button#speak-button') + await button.click() + + // check a second new text element is added in the torso + const torsoChildren = await page.$eval('#torso', (node) => + [...node.children].map((child) => ({ + tag: child.tagName, + text: child.textContent, + class: child.className, + })), + ) + eq(torsoChildren, [textNode, textNode]) +}) + +const textNode = { tag: 'DIV', text: 'Hello there!', class: 'words' } diff --git a/js/tests/get-them-all-dom_test.mjs b/js/tests/get-them-all-dom_test.mjs new file mode 100644 index 00000000..5071e57d --- /dev/null +++ b/js/tests/get-them-all-dom_test.mjs @@ -0,0 +1,127 @@ +import { people } from './subjects/get-them-all/get-them-all.data.js' + +const getIds = predicate => + people + .filter(predicate) + .map(e => e.id) + .sort((a, b) => a.localeCompare(b)) + +const architects = getIds(p => p.tag === 'a') +const notArchitects = getIds(p => p.tag !== 'a') + +const classical = getIds(p => p.classe === 'classical') +const notClassical = getIds(p => p.tag === 'a' && p.classe !== 'classical') + +const active = getIds(p => p.classe === 'classical' && p.active) +const notActive = getIds( + p => p.tag === 'a' && p.classe === 'classical' && p.active === false, +) + +const bonanno = people.find(p => p.id === 'BonannoPisano').id +const notBonanno = getIds( + p => + p.tag === 'a' && + p.classe === 'classical' && + p.active && + p.id !== 'BonannoPisano', +) + +export const tests = [] + +tests.push(async ({ eq, page }) => { + // get architects + const btnArchitect = await page.$(`#btnArchitect`) + btnArchitect.click() + await page.waitForTimeout(150) + + const selected = await page.$$eval('a', nodes => + nodes + .filter(node => node.textContent === 'Architect') + .map(node => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + eq(selected, architects) + + 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)), + ) + + eq(eliminated, notArchitects) +}) + +tests.push(async ({ page, eq }) => { + // get classical + const btnClassical = await page.$(`#btnClassical`) + btnClassical.click() + await page.waitForTimeout(150) + + const selected = await page.$$eval('.classical', nodes => + nodes + .filter(node => node.textContent === 'Classical') + .map(node => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + + eq(selected, classical) + + 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)), + ) + + eq(eliminated, notClassical) +}) + +tests.push(async ({ page, eq }) => { + // check active + const btnActive = await page.$(`#btnActive`) + btnActive.click() + await page.waitForTimeout(150) + + const selected = await page.$$eval('.classical.active', nodes => + nodes + .filter(node => node.textContent === 'Active') + .map(node => node.id) + .sort((a, b) => a.localeCompare(b)), + ) + eq(selected, active) + + 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)), + ) + + eq(eliminated, notActive) +}) + +tests.push(async ({ page, eq }) => { + // get bonanno + const btnBonanno = await page.$(`#btnBonanno`) + btnBonanno.click() + await page.waitForTimeout(150) + + const selected = await page.$eval('#BonannoPisano', node => { + if (node.textContent === 'Bonanno Pisano') return node.id + }) + + eq(`bonanno: ${selected}`, `bonanno: ${bonanno}`) + + 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)), + ) + + eq(eliminated, notBonanno) +}) diff --git a/js/tests/gossip-grid-dom_test.mjs b/js/tests/gossip-grid-dom_test.mjs new file mode 100644 index 00000000..5d6639c1 --- /dev/null +++ b/js/tests/gossip-grid-dom_test.mjs @@ -0,0 +1,123 @@ +import { gossips } from './subjects/gossip-grid/gossip-grid.data.js' + +export const tests = [] + +tests.push(async ({ page, eq }) => { + // test that the grid is properly generated + + const content = await page.$$eval('.gossip', nodes => nodes.map(n => n.textContent)) + + eq(content, ['Share gossip!', ...gossips]) +}) + + +tests.push(async ({ page, eq }) => { + // test that you can add a gossip + + const rand = Math.random().toString(36).slice(2) + await page.type('textarea', `coucou ${rand}`) + await page.click('.gossip button') + const content = await page.$eval('div.gossip', n => n.textContent) + + eq(content, `coucou ${rand}`) +}) + + +const getStyle = (nodes, key) => nodes.map(n => n.style[key]) +tests.push(async ({ page, eq }) => { + // test that you can change the width to the min + + const min = await page.$eval('#width', n => { + n.value = n.min + n.dispatchEvent(new Event('input')) + return Number(n.min) + }) + + eq(min, 200) + + const values = await page.$$eval('div.gossip', getStyle, 'width') + + eq(Array(gossips.length + 1).fill(`${min}px`), values) +}) + +tests.push(async ({ page, eq }) => { + // test that you can change the width to the max + + const max = await page.$eval('#width', n => { + n.value = n.max + n.dispatchEvent(new Event('input')) + return Number(n.max) + }) + + eq(max, 800) + + const values = await page.$$eval('div.gossip', getStyle, 'width') + + eq(Array(gossips.length + 1).fill(`${max}px`), values) +}) + +tests.push(async ({ page, eq }) => { + // test that you can change the font-size to the min + + const min = await page.$eval('#fontSize', n => { + n.value = n.min + n.dispatchEvent(new Event('input')) + return Number(n.min) + }) + + eq(min, 20) + + const values = await page.$$eval('div.gossip', getStyle, 'fontSize') + + eq(Array(gossips.length + 1).fill(`${min}px`), values) +}) + +tests.push(async ({ page, eq }) => { + // test that you can change the font-size to the max + + const max = await page.$eval('#fontSize', n => { + n.value = n.max + n.dispatchEvent(new Event('input')) + return Number(n.max) + }) + + eq(max, 40) + + const values = await page.$$eval('div.gossip', getStyle, 'fontSize') + + eq(Array(gossips.length + 1).fill(`${max}px`), values) +}) + +const getBackground = (nodes, key) => nodes.map(n => n.style.background || n.style.backgroundColor) +tests.push(async ({ page, eq, rgbToHsl }) => { + // test that you can change the background to the darkest + + const min = await page.$eval('#background', n => { + n.value = n.min + n.dispatchEvent(new Event('input')) + return Number(n.min) + }) + + eq(min, 20) + + const values = await page.$$eval('div.gossip', getBackground) + const lightness = values.map(rgbToHsl).map(([h,s,l]) => l) + eq(Array(gossips.length + 1).fill(min), lightness) +}) + +tests.push(async ({ page, eq, rgbToHsl }) => { + // test that you can change the background to the darkest + + const max = await page.$eval('#background', n => { + n.value = n.max + n.dispatchEvent(new Event('input')) + return Number(n.max) + }) + + eq(max, 75) + + const values = await page.$$eval('div.gossip', getBackground) + const lightness = values.map(rgbToHsl).map(([h,s,l]) => Math.round(l)) + + eq(Array(gossips.length + 1).fill(max), lightness) +}) diff --git a/js/tests/harder-bigger-bolder-stronger-dom_test.mjs b/js/tests/harder-bigger-bolder-stronger-dom_test.mjs new file mode 100644 index 00000000..266d40f1 --- /dev/null +++ b/js/tests/harder-bigger-bolder-stronger-dom_test.mjs @@ -0,0 +1,67 @@ +export const tests = [] + +export const setup = async ({ page }) => ({ + content: await page.$$eval('div', nodes => nodes.map(n => ({ + text: n.textContent, + size: Number((n.style.fontSize || '').slice(0, -2)), + weight: Number(n.style.fontWeight), + }))) +}) + +tests.push(({ eq, ctx }) => { + // should contain 120 items + eq(ctx.content.length, 120) +}) + +tests.push(({ eq, ctx }) => { + // ctx.content should only be one letter long + eq(ctx.content.reduce((total, { text }) => total + text.length, 0), 120) +}) + +tests.push(({ eq, ctx }) => { + // we expect random to yield at least 10 different letters + eq(new Set(ctx.content).size > 10, true) +}) + +tests.push(({ eq, ctx }) => { + // only letters from 'A' to 'Z' + eq(ctx.content.every(({ text }) => text >= 'A' && text <= 'Z'), true) +}) + +tests.push(({ eq, ctx }) => { + // letter size should grow + + // first should be 11 + eq(ctx.content[0].size, 11) + + // last should be 120 + eq(ctx.content[119].size, 130) + + // each letter should be bigger than the previous + let prev = 0 + for (const { size } of ctx.content) { + if (prev >= size) { + throw Error('Letters should grow') + } + } +}) + +tests.push(({ eq, ctx }) => { + // letter weight should increase in thirds + const third = n => ({ weight }) => weight === n + + // first third should be 300 + eq(ctx.content[0].weight, 300) + eq(ctx.content[39].weight, 300) + eq(ctx.content.slice(0, 40).every(third(300)), true) + + // second third should be 400 + eq(ctx.content[40].weight, 400) + eq(ctx.content[79].weight, 400) + eq(ctx.content.slice(40, 80).every(third(400)), true) + + // last third should be 600 + eq(ctx.content[80].weight, 600) + eq(ctx.content[119].weight, 600) + eq(ctx.content.slice(80).every(third(600)), true) +}) diff --git a/js/tests/hello-there_test.js b/js/tests/hello-there_test.js deleted file mode 100644 index fce22c8e..00000000 --- a/js/tests/hello-there_test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { readFileSync as read } from 'fs' - -// /*/ // ⚡ -export const tests = [ - ({ eq, path }) => // code must use console.log - read(path, 'utf8').trim().includes('console.log'), - - async ({ eq, code }) => { - // console.log must have been called with the right value - const log = console.log.bind(console) - const args = [] - console.log = (..._args) => { - args.push(..._args) - log(..._args) - } - - const b64 = Buffer.from(code).toString("base64") - const url = `data:text/javascript;base64,${b64}` - await import(url) - console.log = log - return eq(args.join(' ').trim(), 'Hello There') - }, -] diff --git a/js/tests/keycodes-symphony-dom_test.mjs b/js/tests/keycodes-symphony-dom_test.mjs new file mode 100644 index 00000000..36b2abe2 --- /dev/null +++ b/js/tests/keycodes-symphony-dom_test.mjs @@ -0,0 +1,55 @@ +export const tests = [] + +export const setup = async ({ page }) => ({ + getNotes: async () => + await page.$$eval('.note', (nodes) => { + return nodes.map((note) => note.textContent) + }), +}) + +const characters = `didyouhandlethekeydowneventcorrectly` + +tests.push(async ({ page, eq, ctx }) => { + // check that a note is created and matches the right letter when a key is pressed + for (const [i, character] of characters.split('').entries()) { + await page.keyboard.down(character) + const typed = characters.slice(0, i + 1).split('') + eq(await ctx.getNotes(), typed) + } +}) + +tests.push(async ({ page, eq, ctx }) => { + // check that the last note is removed when Backspace key is pressed + let step = 1 + while (step < 10) { + await page.keyboard.down('Backspace') + const typed = characters.slice(0, characters.length - step).split('') + eq(await ctx.getNotes(), typed) + step++ + } +}) + +tests.push(async ({ page, eq, ctx }) => { + // check that all the notes are cleared when Escape key is pressed + await page.keyboard.down('Escape') + const cleared = (await ctx.getNotes()).length === 0 + eq(await cleared, true) +}) + +tests.push(async ({ page, eq }) => { + // check that notes have different background colors + const test = 'abcdefghijklmnopqrstuvwxyz' + let step = 0 + while (step < test.length) { + await page.keyboard.down(test[step]) + step++ + } + + const getNotesBg = await page.$$eval('.note', (nodes) => { + return nodes.map((note) => note.style.backgroundColor) + }) + + const colors = [...new Set(getNotesBg)] + const allDifferent = colors.length === test.length + eq(allDifferent, true) +}) diff --git a/js/tests/mouse-trap-dom_test.mjs b/js/tests/mouse-trap-dom_test.mjs new file mode 100644 index 00000000..9588fb44 --- /dev/null +++ b/js/tests/mouse-trap-dom_test.mjs @@ -0,0 +1,122 @@ +export const tests = [] + +export const setup = async ({ page }) => ({ + getCirclesPos: () => + page.$$eval('.circle', nodes => { + const circleRadius = 25 + const formatPos = pos => Number(pos.replace('px', '')) + circleRadius + return nodes.map(node => [ + formatPos(node.style.left), + formatPos(node.style.top), + ]) + }), +}) + +tests.push(async ({ page, eq, ctx }) => { + // check that a circle is created on click at the mouse position + const { width, height } = await page.evaluate(() => ({ + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight, + })) + + const clicks = [...Array(10).keys()].map(e => [random(width), random(height)]) + + for (const [i, click] of clicks.entries()) { + const [posX, posY] = click + await page.mouse.click(posX, posY) + const currentCircle = (await ctx.getCirclesPos())[i] + eq(currentCircle, click) + } +}) + +tests.push(async ({ page, eq, ctx }) => { + // check that the last created circle moves along the mouse + let move = 0 + while (move < 100) { + move++ + const x = move + const y = move * 2 + await page.mouse.move(x, y) + const circles = await ctx.getCirclesPos() + const currentCirclePos = circles[circles.length - 1] + eq(currentCirclePos, [x, y]) + } +}) + +tests.push(async ({ page, eq, ctx }) => { + // check that a circle is trapped and purple when inside the box + const box = await page.$eval('.box', box => ({ + top: box.getBoundingClientRect().top, + right: box.getBoundingClientRect().right, + left: box.getBoundingClientRect().left, + bottom: box.getBoundingClientRect().bottom, + })) + + await page.mouse.click(200, 200) + + let move = 200 + let hasEntered = false + + while (move < 500) { + const x = move + 50 + const y = move + await page.mouse.move(x, y) + + const circles = await ctx.getCirclesPos() + const currentCircle = circles[circles.length - 1] + + const circleRadius = 25 + + const bg = await page.$$eval( + '.circle', + nodes => nodes[nodes.length - 1].style.background, + ) + + const insideX = x > box.left + circleRadius && x < box.right - circleRadius + const insideY = y > box.top + circleRadius && y < box.bottom - circleRadius + const isInside = insideX && insideY + + // check that the background is set to the right color + if (isInside) { + hasEntered = true + eq(bg, 'var(--purple)') + } else { + eq(bg, hasEntered ? 'var(--purple)' : 'white') + } + + // check that the mouse is trapped inside the box + if (hasEntered) { + if (insideY) { + eq(currentCircle[1], y) + } else { + const maxY = + currentCircle[1] === box.top + circleRadius + 1 || + currentCircle[1] === box.top + circleRadius || + currentCircle[1] === box.bottom - circleRadius || + currentCircle[1] === box.bottom - circleRadius - 1 + eq(maxY, true) + } + if (insideX) { + eq(currentCircle[0], x) + } else { + const maxX = + currentCircle[0] === box.left + circleRadius || + currentCircle[0] === box.left + circleRadius + 1 || + currentCircle[0] === box.right - circleRadius || + currentCircle[0] === box.right - circleRadius - 1 + eq(maxX, true) + } + } + move++ + } +}) + +const random = (min, max) => { + if (!max) { + max = min + min = 0 + } + min = Math.ceil(min) + max = Math.floor(max) + return Math.floor(Math.random() * (max - min + 1)) + min +} diff --git a/js/tests/nesting-organs-dom_test.mjs b/js/tests/nesting-organs-dom_test.mjs new file mode 100644 index 00000000..c92a9ecb --- /dev/null +++ b/js/tests/nesting-organs-dom_test.mjs @@ -0,0 +1,90 @@ +export const tests = [] + +tests.push(async ({ page, eq }) => { + // check that the HTML structure is correct & elements are nested properly + const elements = await page.$$eval('body', (nodes) => { + const toNode = (el) => { + const node = {} + node.tag = el.tagName.toLowerCase() + node.id = el.id + if (el.children.length) { + node.children = [...el.children].map(toNode) + } + return node + } + return [...nodes[0].children].map(toNode) + }) + eq(expectedStructure, elements) +}) + +tests.push(async ({ page, eq }) => { + // check the section selector style has been updated properly + eq.css('section', { + display: 'flex', + justifyContent: 'center', + }) +}) + +tests.push(async ({ page, eq }) => { + // check if the provided CSS has been correctly copy pasted + eq.css('div, p', { + border: '1px solid black', + padding: '10px', + margin: '0px', + borderRadius: '30px', + }) + + eq.css('#face', { alignItems: 'center' }) + + eq.css('#eyes', { + display: 'flex', + backgroundColor: 'yellow', + justifyContent: 'space-between', + alignItems: 'center', + borderRadius: '50px', + width: '200px', + }) + + eq.css('#torso', { + width: '200px', + backgroundColor: 'violet', + }) +}) + +const expectedStructure = [ + { + tag: 'section', + + id: 'face', + children: [ + { + tag: 'div', + + id: 'eyes', + children: [ + { tag: 'p', id: 'eye-left' }, + { tag: 'p', id: 'eye-right' }, + ], + }, + ], + }, + { + tag: 'section', + + id: 'upper-body', + children: [ + { tag: 'div', id: 'arm-left' }, + { tag: 'div', id: 'torso' }, + { tag: 'div', id: 'arm-right' }, + ], + }, + { + tag: 'section', + + id: 'lower-body', + children: [ + { tag: 'div', id: 'leg-left' }, + { tag: 'div', id: 'leg-right' }, + ], + }, +] diff --git a/js/tests/pick-and-click-dom_test.mjs b/js/tests/pick-and-click-dom_test.mjs new file mode 100644 index 00000000..be25d2d3 --- /dev/null +++ b/js/tests/pick-and-click-dom_test.mjs @@ -0,0 +1,110 @@ +export const tests = [] + +const between = (expected, min, max) => expected >= min && expected <= max + +export const setup = async ({ page, rgbToHsl }) => ({ + bodyBgRgb: async () => + rgbToHsl(await page.$eval('body', (body) => body.style.background)), +}) + +tests.push(async ({ page, eq }) => { + // check that the background color is changing on mouse move + // by simulating 20 moves, so there must be 20 different background colors + let move = 50 + let bgs = [] + while (move < 250) { + move += 10 + const x = move + const y = move * 2 + await page.mouse.move(x, y) + const bodyBg = await page.$eval('body', (body) => body.style.background) + const validColor = bodyBg.includes('rgb') + if (!validColor) continue + bgs.push(bodyBg) + } + const differentBgs = [...new Set(bgs)].length + eq(differentBgs, 20) +}) + +const near = (a, b) => a < b + 2.5 && a > b - 2.5 +const numVal = (n) => n.textContent.replace(/[^0-9,]/g, '') +const generateCoords = (random) => [ + [random(125, 500), random(125, 400)], + [random(125, 500), random(125, 400)], + [random(125, 500), random(125, 400)], +] + +tests.push(async ({ page, eq, bodyBgRgb, random }) => { + // check that the hsl value displayed matches the current background color + for (const move of generateCoords(random)) { + await page.mouse.move(...move) + const a = await bodyBgRgb() + const b = (await page.$eval('.hsl', numVal)).split(',') + if (a.every((v, i) => near(v, Number(b[i])))) continue + throw Error(`hsl(${a.map(Math.round)}) to far from hsl(${b})`) + } +}) + +tests.push(async ({ page, eq, bodyBgRgb, random }) => { + // check that the hue value displayed matches the current background color + for (const move of generateCoords(random)) { + await page.mouse.move(...move) + const [h] = await bodyBgRgb() + const hue = await page.$eval('.hue', numVal) + + if (!near(h, Number(hue))) { + console.log({ h, hue, c: near(h, Number(hue)) }) + throw Error(`hue ${Math.round(h)} to far from ${hue}`) + } + } +}) + +tests.push(async ({ page, eq, bodyBgRgb, random }) => { + // check that the luminosity value displayed matches the current background color + for (const move of generateCoords(random)) { + await page.mouse.move(...move) + const [, , l] = await bodyBgRgb() + const lum = await page.$eval('.luminosity', numVal) + + if (!near(l, Number(lum))) { + throw Error(`luminosity ${Math.round(l)} to far from ${lum}`) + } + } +}) + +tests.push(async ({ page, eq, bodyBgRgb, random }) => { + // check that the hsl value is copied in the clipboard on click + // Override readText if writeText is used due to a puppeteer bug + await page.evaluate(() => { + window.navigator.clipboard.writeText = async (text) => { + window.navigator.clipboard.readText = async () => text + } + }) + for (const move of generateCoords(random)) { + await page.mouse.click(...move) + const clipboard = await page.evaluate(() => + window.navigator.clipboard.readText() + ) + const hslValue = await page.$eval('.hsl', (hsl) => hsl.textContent) + eq(hslValue, clipboard) + } +}) + +tests.push(async ({ page, eq, bodyBgRgb, random }) => { + // check that each svg axis is following the mouse + const [[mouseX, mouseY]] = generateCoords(random) + await page.mouse.move(mouseX, mouseY) + const axisX = await page.$eval('#axisX', (line) => [ + line.getAttribute('x1'), + line.getAttribute('x2'), + ]) + const axisY = await page.$eval('#axisY', (line) => [ + line.getAttribute('y1'), + line.getAttribute('y2'), + ]) + + const checkAxisCoords = (coords) => Number([...new Set(coords)].join()) + + eq(checkAxisCoords(axisX), mouseX) + eq(checkAxisCoords(axisY), mouseY) +}) diff --git a/js/tests/pimp-my-style-dom_test.mjs b/js/tests/pimp-my-style-dom_test.mjs new file mode 100644 index 00000000..ffd17da6 --- /dev/null +++ b/js/tests/pimp-my-style-dom_test.mjs @@ -0,0 +1,36 @@ +import { styles } from './subjects/pimp-my-style/pimp-my-style.data.js' + +export const tests = [] + +const formatClass = (limit, unpimp) => + ['button', ...styles.slice(0, limit), unpimp && 'unpimp'].filter(Boolean) + +const max = styles.length - 1 + +export const setup = async ({ page }) => { + const btn = await page.$('.button') + + return { + btn, + getClass: async () => + (await (await btn.getProperty('className')).jsonValue()).split(' '), + } +} + +tests.push(async ({ page, eq, btn, getClass }) => { + // pimp + for (const i of styles.keys()) { + console.log('pimp click', i + 1) + await btn.click() + eq(formatClass(i + 1, i === max), await getClass()) + } +}) + +tests.push(async ({ page, eq, btn, getClass }) => { + // unpimp ! + for (const i of styles.keys()) { + console.log('unpimp click', i + 1) + await btn.click() + eq(formatClass(max - i, i !== max), await getClass()) + } +}) diff --git a/js/tests/select-and-style-dom_test.mjs b/js/tests/select-and-style-dom_test.mjs new file mode 100644 index 00000000..a2198073 --- /dev/null +++ b/js/tests/select-and-style-dom_test.mjs @@ -0,0 +1,49 @@ + +export const tests = [] + +// this test is commented out because it will not work for editor mode +// tests.push(async ({ eq }) => { +// // check the CSS stylesheet is linked in the head tag +// +// await eq.$('head link', { +// rel: 'stylesheet', +// href: 'http://localhost:9898/select-and-style/select-and-style.css', +// }) +// }) + +tests.push(async ({ eq }) => { + // check the universal selector has been declared properly + + await eq.css('*', { + margin: '0px', + opacity: '0.85', + boxSizing: 'border-box', + }) +}) + + +tests.push(async ({ eq }) => { + // check that the body was styled + + await eq.css('body', { height: '100vh' }) +}) + + +tests.push(async ({ eq }) => { + // check that sections elements are styled + + await eq.css('section', { + padding: '20px', + width: '100%', + height: 'calc(33.3333%)', + }) +}) + + +tests.push(async ({ eq }) => { + // check that the individual sections are styled + + await eq.css('#face', { backgroundColor: 'cyan' }) + await eq.css('#upper-body', { backgroundColor: 'blueviolet' }) + await eq.css('#lower-body', { backgroundColor: 'lightsalmon' }) +}) diff --git a/js/tests/skeleton-dom_test.mjs b/js/tests/skeleton-dom_test.mjs new file mode 100644 index 00000000..832ee039 --- /dev/null +++ b/js/tests/skeleton-dom_test.mjs @@ -0,0 +1,25 @@ +export const tests = [] + +tests.push(async ({ page, eq }) => { + // check that the title tag is present & is set with some text + const title = await page.$eval('title', (node) => node.textContent) + if (!title.length) throw Error('missing title') +}) + +tests.push(async ({ page, eq }) => { + // check the face + + return eq.$('section:nth-child(1)', { textContent: 'face' }) +}) + +tests.push(async ({ page, eq }) => { + // check the upper-body + + return eq.$('section:nth-child(2)', { textContent: 'upper-body' }) +}) + +tests.push(async ({ page, eq }) => { + // check the lower-body, my favorite part + + return eq.$('section:nth-child(3)', { textContent: 'lower-body' }) +}) diff --git a/js/tests/test.mjs b/js/tests/test.mjs index d4ddf774..85ef5646 100644 --- a/js/tests/test.mjs +++ b/js/tests/test.mjs @@ -1,8 +1,10 @@ +import puppeteer from 'puppeteer' import { join as joinPath, dirname, extname } from 'path' -import { fileURLToPath } from 'url' +import { readFile, writeFile } from 'fs/promises' import { deepStrictEqual } from 'assert' -import * as fs from 'fs' -const { readFile, writeFile } = fs.promises +import { fileURLToPath } from 'url' +import http from 'http' +import fs from 'fs' global.window = global global.fetch = url => { @@ -21,6 +23,12 @@ global.fetch = url => { const wait = delay => new Promise(s => setTimeout(s, delay)) const fail = fn => { try { fn() } catch (err) { return true } } +const upperFirst = (str) => str[0].toUpperCase() + str.slice(1) +const randStr = (n = 7) => Math.random().toString(36).slice(2, n) +const between = (min, max) => { + max || (max = min, min = 0) + return Math.floor(Math.random() * (max - min) + min) +} const props = [String,Array] .flatMap(({ prototype }) => @@ -38,9 +46,16 @@ const eq = (a, b) => { } const [solutionPath, name] = process.argv.slice(2) + +const tools = { eq, fail, wait, randStr, between, upperFirst } +const cleanup = (exitCode = 0) => { + if (!tools.browser) process.exit(exitCode) + tools.server.close() + return tools.browser.close().finally(() => process.exit(exitCode)) +} const fatal = (...args) => { console.error(...args) - process.exit(1) + return cleanup(1) } solutionPath || fatal('missing solution-path, usage:\nnode test solution-path exercise-name') @@ -75,7 +90,7 @@ const any = arr => f(firstError) }) -const testNode = async ({ name }) => { +const testNode = async () => { const path = `${solutionPath}/${name}.mjs` return { path, @@ -84,7 +99,7 @@ const testNode = async ({ name }) => { } } -const runInlineTests = async ({ json, name }) => { +const runInlineTests = async ({ json }) => { const restore = new Set() const equal = deepStrictEqual const saveArguments = (src, key) => { @@ -103,12 +118,11 @@ const runInlineTests = async ({ json, name }) => { const logs = [] console.log = (...args) => logs.push(args) const die = (...args) => { - logs.forEach((args) => console.info(...args)) - console.error(...args) - process.exit(1) + logs.forEach((logArgs) => console.info(...logArgs)) + return fatal(...args) } - const solution = await loadAndSanitizeSolution(name) + const solution = await loadAndSanitizeSolution() for (const { description, code } of JSON.parse(json)) { logs.length = 0 const [provided, tests] = code.includes('// Your code') @@ -130,14 +144,15 @@ ${tests.trim()}`.trim() console.info(`${description}:`, 'PASS') } catch (err) { console.info(`${description}:`, 'FAIL') - console.info(`\n======= Code ======= \n${fullCode}`) console.info('\n======= Error ======') - die(' ->', err.message) + console.info(' ->', err.message, '\n') + console.info('\n======= Code =======') + die(fullCode) } } } -const loadAndSanitizeSolution = async name => { +const loadAndSanitizeSolution = async () => { const path = `${solutionPath}/${name}.js` const rawCode = await read(path, "student solution") @@ -151,18 +166,15 @@ const loadAndSanitizeSolution = async name => { const runTests = async ({ url, path, code }) => { const { setup, tests } = await import(url).catch(err => - fatal(`Unable to execute ${name} solution, error:\n${stackFmt(err, url)}`), + fatal(`Unable to execute ${name}, error:\n${stackFmt(err, url)}`), ) - const randStr = (n = 7) => Math.random().toString(36).slice(2, n) - const between = (min, max) => { - max || (max = min, min = 0) - return Math.floor(Math.random() * (max - min) + min) - } - const upperFirst = (str) => str[0].toUpperCase() + str.slice(1) - - const tools = { eq, fail, wait, code, path, randStr, between, upperFirst } + Object.assign(tools, { code, path }) tools.ctx = (await (setup && setup(tools))) || {} + const isDOM = name.endsWith('-dom') + if (isDOM) { + Object.assign(tools, await prepareForDOM({ code })) + } let timeout for (const [i, t] of tests.entries()) { try { @@ -172,19 +184,108 @@ const runTests = async ({ url, path, code }) => { timeout = setTimeout(f, 60000, Error('Time limit reached (1min)')) }), ]) - if (!(await waitWithTimeout)) { + if (!(await waitWithTimeout) && !isDOM) { throw Error('Test failed') } } catch (err) { - console.log(`test #${i+1} failed:\n${t.toString()}\n\nError:`) - fatal(stackFmt(err, url)) + console.info(`test #${i+1} failed:\n${t.toString()}\n`) + return fatal(stackFmt(err, url)) } finally { clearTimeout(timeout) } } - console.log(`${name} passed (${tests.length} tests)`) + cleanup(0) + console.info(`${name} passed (${tests.length} tests)`) } +// add puppeteer tests as JS language: +const PORT = 9898 +const config = { + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + + // This will write shared memory files into /tmp instead of /dev/shm, + // because Docker’s default for /dev/shm is 64MB + '--disable-dev-shm-usage', + ], + headless: !process.env.DEBUG_PUPPETTEER, +} + +// LEGACY random, use between instead (only used by dom exercise, to be replaced) +const random = (min, max = min) => { + max === min && (min = 0) + min = Math.ceil(min) + return Math.floor(Math.random() * (Math.floor(max) - min + 1)) + min +} + +const rgbToHsl = rgbStr => { + const [r, g, b] = rgbStr.slice(4, -1).split(',').map(Number) + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + const l = (max + min) / ((0xff * 2) / 100) + + if (max === min) return [0, 0, l] + + const d = max - min + const s = (d / (l > 50 ? 0xff * 2 - max - min : max + min)) * 100 + if (max === r) return [((g - b) / d + (g < b && 6)) * 60, s, l] + return max === g + ? [((b - r) / d + 2) * 60, s, l] + : [((r - g) / d + 4) * 60, s, l] +} + +const prepareForDOM = ({ code }, server) => new Promise((s, f) => (server = http + .createServer(({ url, method }, response) => { + console.info(method + ' ' + url) + // Loading either the `index.html` or the js code (student solution) + response.setHeader('Content-Type', 'text/html') + return response.end(``) + })) + .listen(PORT, async listenErr => { + if (listenErr) return f(listenErr) + try { + const browser = await puppeteer.launch(config) + const [page] = await browser.pages() + await page.goto(`http://localhost:${PORT}/index.html`) + deepStrictEqual.$ = async (selector, props) => { + const keys = Object.keys(props) + const extractProps = (node, props) => { + const fromProps = (a, b) => Object.fromEntries(Object.keys(b).map(k => [ + k, + typeof b[k] === 'object' ? fromProps(a[k], b[k]) : a[k], + ])) + return fromProps(node, props) + } + const domProps = await page.$eval(selector, extractProps, props) + return deepStrictEqual(props, domProps) + } + + deepStrictEqual.css = async (selector, props) => { + const cssProps = await page.evaluate((selector, props) => { + const styles = Object.fromEntries([...document.styleSheets] + .flatMap(({ cssRules }) => [...cssRules].map(r => [r.selectorText, r.style]))) + + if (!styles[selector]) { + throw Error(`css ${selector} did not match any declarations`) + } + + return Object.fromEntries(Object.keys(props).map(k => [k, styles[selector][k]])) + }, selector, props) + + return deepStrictEqual(props, cssProps) + } + + browser + .defaultBrowserContext() + .overridePermissions(`http://localhost:${PORT}`, ['clipboard-read']) + + s({ page, browser, random, rgbToHsl, eq: deepStrictEqual, server }) + } catch (err) { + f(err) + } + })) + const main = async () => { const { test, mode } = await any([ readTest(joinPath(root, `${name}.json`)), @@ -192,19 +293,22 @@ const main = async () => { readTest(joinPath(root, `${name}_test.mjs`)), ]).catch(ifNoEnt((err) => fatal(`Missing test for ${name}`))) - if (mode === "node") return runTests(await testNode({ test, name })) - if (mode === "inline") return runInlineTests({ json: test, name }) + if (mode === "node") return runTests(await testNode()) + if (mode === "inline") return runInlineTests({ json: test }) - const { rawCode, code, path } = await loadAndSanitizeSolution(name) + const { rawCode, code, path } = await loadAndSanitizeSolution() const parts = test.split("// /*/ // ⚡") const [inject, testCode] = parts.length < 2 ? ["", test] : parts const combined = `${inject.trim()}\n${rawCode .replace(inject.trim(), "") .trim()}\n${testCode.trim()}\n` + // write to file and read file instead ? const b64 = Buffer.from(combined).toString("base64") const url = `data:text/javascript;base64,${b64}` return runTests({ path, code, url }) } -main().catch(err => fatal(err.stack)) +main().catch(err => { + fatal(err?.stack || Error('').stack) +}) diff --git a/js/tests/the-calling-dom_test.mjs b/js/tests/the-calling-dom_test.mjs new file mode 100644 index 00000000..e6aaea82 --- /dev/null +++ b/js/tests/the-calling-dom_test.mjs @@ -0,0 +1,19 @@ +export const tests = [] + +tests.push(async ({ page, eq }) => { + // check the face + + return eq.$('section#face', { textContent: '' }) +}) + +tests.push(async ({ page, eq }) => { + // check the upper-body + + return eq.$('section#upper-body', { textContent: '' }) +}) + +tests.push(async ({ page, eq }) => { + // check the lower-body, my favorite part + + return eq.$('section#lower-body', { textContent: '' }) +}) diff --git a/js/tests/where-do-we-go-dom_test.mjs b/js/tests/where-do-we-go-dom_test.mjs new file mode 100644 index 00000000..ec28cca1 --- /dev/null +++ b/js/tests/where-do-we-go-dom_test.mjs @@ -0,0 +1,174 @@ +import { places } from './subjects/where-do-we-go/where-do-we-go.data.js' + +export const tests = [] + +const random = (min, max) => { + if (!max) { + max = min + min = 0 + } + min = Math.ceil(min) + max = Math.floor(max) + return Math.floor(Math.random() * (max - min + 1)) + min +} + +const getDegree = coordinates => { + const north = coordinates.includes('N') + const degree = coordinates.split("'")[0].replace('°', '.') + return north ? degree : -degree +} + +export const setup = async ({ page }) => { + return { + getDirection: async () => + await page.$eval('.direction', direction => direction.textContent), + } +} + +const sortedPlaces = places.sort( + (a, b) => getDegree(b.coordinates) - getDegree(a.coordinates), +) + +const dataNames = sortedPlaces.map(({ name }) => + name + .split(',')[0] + .toLowerCase() + .split(' ') + .join('-'), +) + +tests.push(async ({ page, eq }) => { + const { width, height } = await page.evaluate(() => ({ + width: window.innerWidth, + height: window.innerHeight, + })) + + const sections = await page.$$eval('section', sections => + sections.map(section => { + return [ + section.getBoundingClientRect().width, + section.getBoundingClientRect().height, + ] + }), + ) + + console.log(`Must contain ${places.length} places`) + // check that the correct amount of sections has been generated + eq(sections.length, places.length) + // check that all the sections are fullscreen-size + eq([...new Set(...sections)], [width, height]) +}) + +tests.push(async ({ page, eq }) => { + // check that the sections have been generated with the correponding images as background, + // and sorted in the right order (from the Northest to the Southest) + const imageNames = await page.$$eval('section', sections => + sections.map(section => { + const test = section.style.background.split('.jpg')[0].split('/') + return test[test.length - 1] + }), + ) + + console.log(`Must be sorted from North to South`) + console.log(`Must have the right images in background`) + eq(imageNames, dataNames) +}) + +tests.push(async ({ page, eq }) => { + // check that the location indicator is updating according to the image displayed + let step = 1 + while (step < 6) { + await page.evaluate(() => { + window.scrollBy(0, window.innerHeight + 200) + }) + + await page.waitForTimeout(150) + + const location = await page.$eval('.location', location => [ + ...location.textContent.split('\n'), + location.style.color, + ]) + + const currentLocationIndex = await page.evaluate(() => + Math.round(window.scrollY / window.innerHeight), + ) + const currentLocation = sortedPlaces[currentLocationIndex] + const { name, coordinates, color } = currentLocation + const expectedLocation = [name, coordinates, color] + + // check that the location indicator and the displayed location contents are matching + console.log(`Scroll #${step}: displaying ${location[0]}`) + eq(location, expectedLocation) + step++ + } +}) + +tests.push(async ({ page, eq, getDirection }) => { + // check that the compass is pointing 'S' when scrolling down + await page.evaluate(() => { + window.scrollBy(0, window.innerHeight) + }) + + await page.waitForTimeout(100) + + const direction = (await getDirection()).includes('S') + ? 'S' + : await getDirection() + + console.log('Scroll down: pointing', direction) + eq(direction, 'S') +}) + +tests.push(async ({ page, eq, getDirection }) => { + // check that the compass is pointing 'N' when scrolling up + await page.evaluate(() => { + window.scrollBy(0, -100) + }) + + await page.waitForTimeout(100) + + const direction = (await getDirection()).includes('N') + ? 'N' + : await getDirection() + + console.log('Scroll up: pointing', direction) + eq(direction, 'N') +}) + +tests.push(async ({ page, eq }) => { + // check that the location target attribute is set to '_blank' to open a new tab + const locationTarget = await page.$eval('.location', ({ target }) => target) + console.log( + `Location tag target attribute ${ + locationTarget === '_blank' ? '' : 'not ' + }set to open a new tab`, + ) + eq(locationTarget, '_blank') +}) + +tests.push(async ({ page, eq }) => { + // check that the location href is valid + const location = await page.$eval('.location', ({ href, textContent }) => ({ + href, + textContent, + })) + const isValidUrl = location.href.includes('google.com/maps') + const coords = location.textContent.split('\n')[1] + const convertedUrl = location.href + .split('%C2%B0') + .join('°') + .split('%22') + .join('"') + .split('%20') + .join(' ') + const isValidCoordinates = convertedUrl.includes(coords) + + console.log('URL', location.href, isValidUrl ? 'valid' : 'invalid') + eq(isValidUrl, true) + console.log( + `Matching coordinates ${coords} ${ + isValidCoordinates ? '' : 'not ' + }found in URL`, + ) + eq(isValidCoordinates, true) +}) diff --git a/subjects/action-reaction-dom/README.md b/subjects/action-reaction-dom/README.md new file mode 100644 index 00000000..1dffd1e1 --- /dev/null +++ b/subjects/action-reaction-dom/README.md @@ -0,0 +1,81 @@ +## Action - reaction! + +### Resources + +We provide you with some content to get started smoothly, check it out! + +- Video [querySelector](https://www.youtube.com/watch?v=m34qd7aGMBo&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=12) +- Video [DOM JS - Add an event listener to an element](https://www.youtube.com/watch?v=ydRv338Fl8Y) +- Video [DOM JS - Set an element's properties](https://www.youtube.com/watch?v=4O6zSVR0ufw&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=14) +- Video [DOM JS - classList: toggle, replace & contains](https://www.youtube.com/watch?v=amEBcoTYw0s&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=20) +- Video [DOM JS - Set an element's inline style](https://www.youtube.com/watch?v=pxlYKvju1z8&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=15) +- [Memo DOM JS](https://github.com/nan-academy/js-training/blob/gh-pages/examples/dom.js) + +### Instructions + +OK, you have now connected HTML, CSS and JS altogether ; big day! Excited? Exhausted? +Well so far, you've only scratched the surface... Let's go deeper into the power of JS! You're going to add some interaction ; the webpage will react when a user action will happen, called an [event](https://developer.mozilla.org/en-US/docs/Web/Events) (a click, a key pressed, a mouse move, etc.). + +Let's put a button on the top right corner of the page, that will toggle (close or open) the left eye when clicked. +Add it in the HTML structure: + +```html + +``` + +Add this CSS style: + +```css +button { + z-index: 1; + position: fixed; + top: 30px; + right: 30px; + padding: 20px; +} +``` + +In the JS file, get the HTML button element with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector), and [add an event listener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) on [`click` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#javascript), triggering a function that will: + +- change the [text content](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) of the button: if the eye is open, write "Click to close the left eye", if the eye is closed, write "Click to open the left eye" +- [toggle](https://css-tricks.com/snippets/javascript/the-classlist-api/) the class `eye-closed` in the `classList` of the `eye-left` HTML element +- change the background color of the `eye-left`: if the eye is open, to "red", if the eye is closed, to "black" + +### Code examples + +Add an event listener on click on a button that triggers a function: + +```js +// events allow you to react to user inputs +// (any action with the mouse, keyboard, etc.) +// it's the foundation of the interactivity of your website +// each event is linked to an element or the window + +// for this example we will attach a click event to a button +// first we select the button HTML element +const button = document.querySelector('button') + +// we need to create a function +// that will be executed when the event is triggered +// let's call it `handleClick` +const handleClick = (event) => { + // do semething when the button has been clicked +} + +// register the event: +button.addEventListener('click', handleClick) +// here we ask the button to call our `handleClick` function +// on the 'click' event, so every time it's clicked +``` + +### Expected output + +[This](https://youtu.be/Wkar5SmswDo) is what you should see in the browser. + +### Notions + +- [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) +- [Text content of a HTML element](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) +- [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) / [`click` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#javascript) +- [`classList` / `toggle`](https://css-tricks.com/snippets/javascript/the-classlist-api/) +- [Setting style with JS](https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style#setting_styles) diff --git a/subjects/bring-it-to-life-dom/README.md b/subjects/bring-it-to-life-dom/README.md new file mode 100644 index 00000000..f3f9a0e2 --- /dev/null +++ b/subjects/bring-it-to-life-dom/README.md @@ -0,0 +1,50 @@ +## Bring it to life + +### Resources + +We provide you with some content to get started smoothly, check it out! + +- Video [DOM JS - getElementById](https://www.youtube.com/watch?v=34kAR8yBtDM&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=8) +- Video [DOM JS - Set an element's inline style](https://www.youtube.com/watch?v=pxlYKvju1z8&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=15) +- Video [DOM JS - classList: add & remove](https://www.youtube.com/watch?v=uQEM-3_4vPA&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=17) +- [Memo DOM JS](https://github.com/nan-academy/js-training/blob/gh-pages/examples/dom.js) + +### Instructions + +Still there? Well done! But hold on, here begins the serious part... In order to control your creation, you're going to plug its brain: JavaScript. + +You're going to close the left eye of your entity. +To do so, you have to target the `eye-left` HTML element by its `id` thanks to the [`getElementById`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) method. Then, [set the `style`](https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style#setting_styles) of your `eye-left` to set its background color to "black". We also need to modify its shape ; for that we are going to add a new class to it. +First, define this new class in your style block: + +``` +.eye-closed { + height: 4px; + padding: 0 5px; + border-radius: 10px; +} +``` + +In the JS code, add the freshly-created class `eye-closed` to the [`classList`](https://css-tricks.com/snippets/javascript/the-classlist-api/) of your `eye-left` element. + +Reload the page - it's alive! Your JS brain has control and orders your HTML/CSS body to close one eye. + +### Code examples + +Get a HTML element by its `id` & set its inline style: + +```js +const myDiv = document.getElementById('my-div') +myDiv.style.color = 'green' +``` + +### Expected output + +This is what you should see in the browser: +![](https://github.com/01-edu/public/raw/master/subjects/bring-it-to-life/bring-it-to-life.png) + +### Notions + +- [`getElementById`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) +- [`classList` / `add()`](https://css-tricks.com/snippets/javascript/the-classlist-api/) +- [Setting style with JS](https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style#setting_styles) diff --git a/subjects/bring-it-to-life-dom/bring-it-to-life.png b/subjects/bring-it-to-life-dom/bring-it-to-life.png new file mode 100644 index 0000000000000000000000000000000000000000..d418d20a40d14640c3c0c32c3797fa9ae2188344 GIT binary patch literal 49625 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 F{|A9bp$q^3 literal 0 HcmV?d00001 diff --git a/subjects/build-brick-and-break-dom/README.md b/subjects/build-brick-and-break-dom/README.md new file mode 100644 index 00000000..d90b8484 --- /dev/null +++ b/subjects/build-brick-and-break-dom/README.md @@ -0,0 +1,44 @@ +## 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 as a `div` and added to the page at a regular interval of 100ms, + - each brick will receive a unique `id` property, like following: + + ```html +

+ ``` + + - each brick in the middle column has to be set with the custom data 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`: write the body of that 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 + - 🧨 triggers the function `destroy`: write the body of that 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/en-US/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) / [`data-*`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) +- [`remove()`](https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove) + +### Files + +You only need to create & submit the JS file `build-brick-and-break.js` ; we're providing you the following file to download (click right and save link) & test locally: + +- the HTML file [build-brick-and-break.html](./build-brick-and-break.html) to open in the browser, which includes: + + - the JS script running some code, and which will also allow to run yours + - some CSS pre-styled classes: feel free to use those as they are, or modify them + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/OjSP_7u9CZ4) diff --git a/subjects/build-brick-and-break-dom/build-brick-and-break.html b/subjects/build-brick-and-break-dom/build-brick-and-break.html new file mode 100644 index 00000000..c8d6bcec --- /dev/null +++ b/subjects/build-brick-and-break-dom/build-brick-and-break.html @@ -0,0 +1,108 @@ + + + + Build brick and break + + + + + + + + \ No newline at end of file diff --git a/subjects/class-that-dom/README.md b/subjects/class-that-dom/README.md new file mode 100644 index 00000000..8679b4dc --- /dev/null +++ b/subjects/class-that-dom/README.md @@ -0,0 +1,57 @@ +## Class that! + +### Resources + +We provide you with some content to get started smoothly, check it out! + +- Video [CSS - Set & style with CSS class](https://www.youtube.com/watch?v=-U397k4VloU&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=6) + +### Instructions + +Alright, your being is almost done, some elements still need a bit more shaping and then we'll make it come to life! +If you look at your page, you can observe that some elements come by pair: the eyes, the arms & the legs. It is the same organ, one on the left and one on the right ; they have exactly the same shape, so for practicity & to avoid to repeat twice the same style, we're not going to use their `id` to style them, but a [`class`](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) ; contrary to an `id`, a `class` can be attributed to several different elements with common rulesets, and so the style defined for that class will apply to all the HTML elements that have it. + +Create the 3 following classes, setting them with the given rulesets, & attribute them to the corresponding HTML elements: + +- class `eye`: + - `width` of 60 pixels + - `height` of 60 pixels + - `background-color` "red" + - `border-radius` of 50% + - attributed to `eye-left` & `eye-right` +- class `arm`: + - `background-color` "aquamarine" + - attributed to `arm-left` & `arm-right` +- class `leg`: + - `background-color` "dodgerblue" + - attributed to `leg-left` & `leg-right` + +Note that you can attribute several classes to a same element ; create the class `body-member`, which set the `width` to 50 pixels and the `margin` to 30 pixels, and add it to the `class` attribute of those elements: `arm-left`, `arm-right`, `leg-left` & `leg-right`. + +### Code examples + +Declare a class `my-first-class` and style it with a `color` to `"blue"` and a `background-color` to `"pink"`: + +```css +.my-first-class { + color: blue; + background-color: pink; +} +``` + +Apply classes to HTML elements: + +```html +
+
+
+``` + +### Expected output + +This is what you should see in the browser: +![](https://github.com/01-edu/public/raw/master/subjects/class-that/class-that.png) + +### Notions + +- [CSS class](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) diff --git a/subjects/class-that-dom/class-that.png b/subjects/class-that-dom/class-that.png new file mode 100644 index 0000000000000000000000000000000000000000..9d7f4d1df22c4d099aa9ce3e37df6df888999bfe GIT binary patch literal 50413 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 tag in the `` tag and generates, for each color of `colors`, a class setting the `background` attribute and taking the color as value, like following: + +```css +.blue { + background: blue; +} +``` + +- Write the `generateColdShades` function which creates a `

+ +...adding +Click 1 -->
` for each color of the `colors` array whose name contains `aqua`, `blue`, `turquoise`, `green`, `cyan`, `navy` or `purple`.\ + Each `
` must have the corresponding generated class and display the name of the color, like following: + +```html +
blue
+``` + +- 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()` + +### Files + +You only need to create & submit the JS file `fifty-shades-of-cold.js` ; we're providing you the following files to download (click right and save link) & test locally: + +- the HTML file [fifty-shades-of-cold.html](./fifty-shades-of-cold.html) to open in the browser, which includes: + + - the JS script running some code, and which will also allow to run yours + - some CSS pre-styled classes: feel free to use those as they are, or modify them + +- the data file [fifty-shades-of-cold.data.js](./fifty-shades-of-cold.data.js) from which you can import `colors` + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/a-3JDEvW-Qg) diff --git a/subjects/fifty-shades-of-cold-dom/fifty-shades-of-cold.data.js b/subjects/fifty-shades-of-cold-dom/fifty-shades-of-cold.data.js new file mode 100644 index 00000000..29d21ec4 --- /dev/null +++ b/subjects/fifty-shades-of-cold-dom/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', +] diff --git a/subjects/fifty-shades-of-cold-dom/fifty-shades-of-cold.html b/subjects/fifty-shades-of-cold-dom/fifty-shades-of-cold.html new file mode 100644 index 00000000..8bb0fe56 --- /dev/null +++ b/subjects/fifty-shades-of-cold-dom/fifty-shades-of-cold.html @@ -0,0 +1,61 @@ + + + + Fifty shades of cold + + + + + + + + \ No newline at end of file diff --git a/subjects/first-words-dom/README.md b/subjects/first-words-dom/README.md new file mode 100644 index 00000000..302341a6 --- /dev/null +++ b/subjects/first-words-dom/README.md @@ -0,0 +1,72 @@ +## First words + +### Resources + +We provide you with some content to get started smoothly, check it out! + +- Video [DOM JS - createElement & append](https://www.youtube.com/watch?v=J-A_pqTqGBU&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=13) +- Video [Set an element's className](https://www.youtube.com/watch?v=h3b7H1ZKvFE&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=16) +- Video [DOM JS - Set an element's properties](https://www.youtube.com/watch?v=4O6zSVR0ufw&list=PLHyAJ_GrRtf979iZZ1N3qYMfsPj9PCCrF&index=14) +- Video [DOM JS - Add an event listener to an element](https://www.youtube.com/watch?v=ydRv338Fl8Y) +- [Memo DOM JS](https://github.com/nan-academy/js-training/blob/gh-pages/examples/dom.js) + +### Instructions + +Now that you know how to make your creation move, what about making it communicate its first words to the world? + +Let's put a second button in the top right corner of the page, that will add some text when clicked. +Add it in the HTML structure: + +``` + +``` + +Add the button style in the CSS file: + +``` +button#speak-button { + top: 100px; +} +``` + +Also add this class to style the text we will add: + +``` +.words { + text-align: center; + font-family: sans-serif; +} +``` + +In the JS file, like in the previous exercise, get the HTML button element with `id` `speak-button` and add an event listener on `click` event, triggering a function that will: + +- [create a new HTML element](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) of type `div` +- set its [text content](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) to "Hello there!" +- set its [`className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) to `words`, that we just added earlier in the CSS +- use the [`append`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append) method to add it inside the `torso` element + +### Code examples + +Create a new element and add it inside the body: + +```js +// create a new `div` element +const div = document.createElement('div') // the argument passed (string) is the html tag + +// select the `body` and add the new `div` inside it +const body = document.querySelector('body') +body.append(div) +``` + +### Expected output + +[This](https://youtu.be/Eq9liRCc-zA) is what you should see in the browser. + +### Notions + +- [`getElementById`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) +- [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) / [`click` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#javascript) +- [`createElement`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) +- [Text content of a HTML element](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) +- [`className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) +- [`append`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append) diff --git a/subjects/get-them-all-dom/README.md b/subjects/get-them-all-dom/README.md new file mode 100644 index 00000000..fc212ccb --- /dev/null +++ b/subjects/get-them-all-dom/README.md @@ -0,0 +1,53 @@ +## 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 arrive 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. + +Launch the provided HTML file in the browser to begin your investigation.
+On top of the webpage, each of the four buttons fires a function: + +- Write the body of the `getArchitects` function, which returns an array containing 2 arrays of HTML elements: + + - the first array contains the architects, all corresponding to a `
` tag + - the second array contains all the non-architects people + +- Write the body of the `getClassical` function, which returns an array containing 2 arrays of HTML elements: + + - the first array contains the architects belonging to the `classical` class + - the second array contains the non-classical architects + +- Write the body of the `getActive` function, which returns an array containing 2 arrays of HTML elements: + + - the first array contains the classical architects who are `active` in their class + - the second array contains the non-active classical architects + +- Write the body of the `getBonannoPisano` function, which returns an array containing: + - the HTML element of the architect you're looking for, whose `id` is `BonannoPisano` + - an array which contains all the remaining HTML elements of active classical architects + +> From now on, don't forget to [**export**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) all the expected functions, so that they can be imported to be tested
> `export const getArchitects = () => {...}` + +### Notions + +- [HTML Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) +- [`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) +- ...and bit of CSS that could help with the [`:not` pseudo class](https://developer.mozilla.org/en-US/docs/Web/CSS/:not) + +### Files + +You only need to create & submit the JS file `get-them-all.js` ; we're providing you the following files to download (click right and save link) & test locally: + +- the HTML file [get-them-all.html](./get-them-all.html) to open in the browser, which includes: + + - the JS script running some code, and which will also allow to run yours + - some CSS pre-styled classes: feel free to use those as they are, or modify them + - the import of the data + +- the data file [get-them-all.data.js](./get-them-all.data.js) used to generate content in the HTML diff --git a/subjects/get-them-all-dom/get-them-all.data.js b/subjects/get-them-all-dom/get-them-all.data.js new file mode 100644 index 00000000..9252ee70 --- /dev/null +++ b/subjects/get-them-all-dom/get-them-all.data.js @@ -0,0 +1,47 @@ +export 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 }, +] diff --git a/subjects/get-them-all-dom/get-them-all.html b/subjects/get-them-all-dom/get-them-all.html new file mode 100644 index 00000000..c936a790 --- /dev/null +++ b/subjects/get-them-all-dom/get-them-all.html @@ -0,0 +1,179 @@ + + + + Get them all + + + + + + + + \ No newline at end of file diff --git a/subjects/gossip-grid-dom/README.md b/subjects/gossip-grid-dom/README.md new file mode 100644 index 00000000..f7acd449 --- /dev/null +++ b/subjects/gossip-grid-dom/README.md @@ -0,0 +1,38 @@ +## 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 (in the same order). +They must be `div` with the `gossip` class. + +The first `gossip` card must be a `form` with a `textarea` and a submit button with the text `Share gossip!` that allows to add a new gossip to the list. + +Create 3 `type="range"` inputs with the class `range`, all wrapped in a `div` with the class `ranges`: + +- one with `id="width"` that control the width of cards _(from 200 to 800 pixels)_ +- one with `id="fontSize"` that control the font size _(from 20 to 40 pixels)_ +- one with `id="background"` that control the background lightness _(from 20% to 75%)_ + +> _tips:_ use `hsl` for colors + +### Notions + +- [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Form) +- [``](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) + +### Files + +You only need to create & submit the JS file `gossip-grid.js` ; we're providing you the following files to download (click right and save link) & test locally: + +- the HTML file [gossip-grid.html](./gossip-grid.html) to open in the browser, which includes: + + - the JS script which will allow to run your code + - some CSS pre-styled classes: feel free to use those as they are, or modify them + +- the data file [gossip-grid.data.js](./gossip-grid.data.js) from which you can import `gossips` + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/nbR2eHBqTxU) diff --git a/subjects/gossip-grid-dom/gossip-grid.data.js b/subjects/gossip-grid-dom/gossip-grid.data.js new file mode 100644 index 00000000..ebecd9b4 --- /dev/null +++ b/subjects/gossip-grid-dom/gossip-grid.data.js @@ -0,0 +1,19 @@ +export const gossips = [ + `Oasis star Noel Gallagher used to gorge on Greggs pastries and donuts every day`, + `Lea Michele's lookalike Monica Moskatow says Glee star called her ugly`, + `WE PAY FOR JUICY INFO!`, + `Trainer to Hollywood's biggest stars reveals how to get an A-list body`, + `Ed Sheeran comes out of music retirement to write brand new song`, + `Kylie Jenner & Travis Scott’s breakup timeline`, + `Quiet on the set: temper tantrums stars hope you forget`, + `The style & grace of Chloë Grace Moretz: her top 20 red carpet looks`, + `Paulina Porizkova feels betrayed after being cut out of husband Ric Ocasek's will`, + `From too hot to not: Paris Hilton and Chris Zylka's relationship history`, + `No bite in the big apple? Celine Dion looks scary skinny in New York`, + `Jennifer Aniston and Brad Pitt relationship timeline`, + `They shouldn’t have said that: 10 celebrity rants heard around the world`, + `The most intense celebrity fights on set`, + `The 18 most bitter real housewives feuds`, + `Tristan Thompson's remarkable transformation from skinny teen to hulking NBA ace`, + `Kim Kardashian 'considers leaving home' with Kanye West to 'save marriage'`, +] diff --git a/subjects/gossip-grid-dom/gossip-grid.html b/subjects/gossip-grid-dom/gossip-grid.html new file mode 100644 index 00000000..b593c46b --- /dev/null +++ b/subjects/gossip-grid-dom/gossip-grid.html @@ -0,0 +1,135 @@ + + + + Gossip grid + + + + + + + + \ No newline at end of file diff --git a/subjects/harder-bigger-bolder-stronger-dom/README.md b/subjects/harder-bigger-bolder-stronger-dom/README.md new file mode 100644 index 00000000..fae93805 --- /dev/null +++ b/subjects/harder-bigger-bolder-stronger-dom/README.md @@ -0,0 +1,28 @@ +## 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! + +Write the function `generateLetters` which creates 120 `div`, each containing a letter randomly picked through the **uppercase** alphabet, and whose style properties have to be increased: + +- each letter `font-size` has to grow from `11` to `130` pixels +- `font-weight` has to be `300` for the first third of the letters, `400` for the second third, and `600` for the last third + +### Files + +You only need to create & submit the JS file `harder-bigger-bolder-stronger.js` ; we're providing you the following file to download (click right and save link) & test locally: + +- the HTML file [harder-bigger-bolder-stronger.html](./harder-bigger-bolder-stronger.html) to open in the browser, which includes: + + - the JS script running some code, and which will also allow to run yours + - some CSS pre-styled classes: feel free to use those as they are, or modify them + +### Notions + +- [`createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) +- [`append()`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append) +- [`style`](https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style) +- [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) diff --git a/subjects/harder-bigger-bolder-stronger-dom/harder-bigger-bolder-stronger.html b/subjects/harder-bigger-bolder-stronger-dom/harder-bigger-bolder-stronger.html new file mode 100644 index 00000000..f771c1eb --- /dev/null +++ b/subjects/harder-bigger-bolder-stronger-dom/harder-bigger-bolder-stronger.html @@ -0,0 +1,57 @@ + + + + Harder, bigger, bolder, stronger + + + + + + + + \ No newline at end of file diff --git a/subjects/keycodes-symphony-dom/README.md b/subjects/keycodes-symphony-dom/README.md new file mode 100644 index 00000000..52ba6ec5 --- /dev/null +++ b/subjects/keycodes-symphony-dom/README.md @@ -0,0 +1,29 @@ +## 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. + +Write the function `compose`: + +- Make it fire every time a key is pressed +- Create a new `div` with the class `note` when a letter of the lowercase alphabet is pressed, which has a unique background color generated using the `key` of the `event`, and displays the corresponding letter pressed +- If the pressed key is the `Backspace` 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) + +### Files + +You only need to create & submit the JS file `keycodes-symphony.js` ; we're providing you the following file to download (click right and save link) & test locally: + +- the HTML file [keycodes-symphony.html](./keycodes-symphony.html) to open in the browser, which includes: + + - the JS script which will allow to run your code + - some CSS pre-styled classes: feel free to use those as they are, or modify them + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/5DdijwBnpAk) diff --git a/subjects/keycodes-symphony-dom/keycodes-symphony.html b/subjects/keycodes-symphony-dom/keycodes-symphony.html new file mode 100644 index 00000000..ffc423f7 --- /dev/null +++ b/subjects/keycodes-symphony-dom/keycodes-symphony.html @@ -0,0 +1,57 @@ + + + + Keycodes symphony + + + + + + + + \ No newline at end of file diff --git a/subjects/mouse-trap-dom/README.md b/subjects/mouse-trap-dom/README.md new file mode 100644 index 00000000..46b801e3 --- /dev/null +++ b/subjects/mouse-trap-dom/README.md @@ -0,0 +1,35 @@ +## 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 `div` at the position of the mouse on the screen, setting its `background` to `white` and its class to `circle` + +- 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 + +- Create a function `setBox` which sets a box with the class `box` in the center of the page ; when a circle is inside that box, it has to be purple (use the CSS global variable `var(--purple)` as `background`) ; once a circle enters the box, it is trapped inside and cannot go out of it anymore. + +> Hint: Be careful, a circle cannot overlap the box which has walls of `1px`, it has to be trapped **strictly** inside. + +### 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) + +### Provided files + +### Files + +You only need to create & submit the JS file `mouse-trap.js` ; we're providing you the following file to download (click right and save link) & test locally: + +- the HTML file [mouse-trap.html](./mouse-trap.html) to open in the browser, which includes: + + - the JS script which will allow to run your code + - some CSS pre-styled classes: feel free to use those as they are, or modify them + +### Expected result + +You can see an example of the expected result [here](https://youtu.be/qF843P-V2Yw) diff --git a/subjects/mouse-trap-dom/mouse-trap.html b/subjects/mouse-trap-dom/mouse-trap.html new file mode 100644 index 00000000..856e78fa --- /dev/null +++ b/subjects/mouse-trap-dom/mouse-trap.html @@ -0,0 +1,63 @@ + + + + Mouse trap + + + + + + + + \ No newline at end of file diff --git a/subjects/nesting-organs-dom/README.md b/subjects/nesting-organs-dom/README.md new file mode 100644 index 00000000..776feeb6 --- /dev/null +++ b/subjects/nesting-organs-dom/README.md @@ -0,0 +1,79 @@ +## Nesting organs + +### Instructions + +Bravo! You displayed the global shape of your entity, but now it's time to populate each division ; let's add up some organs! +To do so, we're going to introduce you the concept of nesting elements inside others. + +So far, you just have a unique layer in your ``: `face`, `upper-body` & `lower-body` are all at the same level. +But as you know, on a face, there are 2 eyes, a nose, and a mouth - and inside that mouth, a tongue, etc. ; any element can potentially be a container for other elements. + +Let's add new elements and wrap them in different layers ; convert this list of organs in a HTML structure with the corresponding given tags! + +- face: `section` tag with `id` `face` + - eyes: `div` tag with `id` `eyes` + - eye left: `p` tag with `id` `eye-left` + - eye right: `p` tag with `id` `eye-right` +- upper body: `section` tag with `id` `upper-body` + - arm left: `div` tag with `id` `arm-left` + - torso: `div` tag with `id` `torso` + - arm right: `div` tag with `id` `arm-right` +- lower body: `section` tag with `id` `lower-body` + - left leg: `div` tag with `id` `leg-left` + - right leg: `div` tag with `id` `leg-right` + +Modify your CSS file to add rulesets to `section` tags: `display` at "flex" and `justify-content` at "center" (this is to turn the `section` tags into [`flex` containers](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox), so the elements inside will be centered) + +Add also the following CSS to your CSS file to see the freshly-added nested elements: + +``` +div, +p { + border: solid 1px black; + padding: 10px; + margin: 0; + border-radius: 30px; +} + +#face { + align-items: center; +} + +#eyes { + display: flex; + background-color: yellow; + justify-content: space-between; + align-items: center; + border-radius: 50px; + width: 200px; +} + +#torso { + width: 200px; + background-color: violet; +} +``` + +### Code examples + +Nest several elements: + +```html +
+ +
+

+
+
+``` + +### Expected output + +This is what you should see in the browser: +![](https://github.com/01-edu/public/raw/master/subjects/nesting-organs/nesting-organs.png) + +### Notions + +- [Anatomy of an HTML element](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started#anatomy_of_an_html_element) +- [Nesting HTML elements](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started#nesting_elements) +- [Flexbox layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox), you can train on [Flexbox froggy](https://flexboxfroggy.com/) diff --git a/subjects/nesting-organs-dom/nesting-organs.png b/subjects/nesting-organs-dom/nesting-organs.png new file mode 100644 index 0000000000000000000000000000000000000000..5d1a4c76171b305134377f43eeac3eb52e59ef6d GIT binary patch literal 44500 zcmaI7cOcu}_b~3=p;NWhR&8ChRY~pIRYg%0ZOs_9YQ|nclvamTwQ5tfs>F;LscHqW zib@HB*g*(MWQbq%{rP;K-yhHS@sGS-_nvdlJ@?$RUvD27>Tn(BKhD9y!KM4)z6l4% z!EFwX122zqvfq?>`|z^=@%d|+` zzXo0w{X50qUG?(6M41~r64eCzI*G~w9)L*oPy|o{w}ju^L2DqGP$q)pS9Res+XVp`+F+^fk8n*fFKzF*w+OprKrfp zAq|w4mSoS6^b7Iww-1)|^1Je14ELS<9DH59{awLcqWc)_pMeAXRWGwC{dWtV-v5Kv z%kMvIVlxaFZ0`+}0^Hu;(tjr!82rDddV2oPY(IY!r~gCm{}r*{;}CBrpoxGFot8WQE5850I{xpp zDE_aoKsGVJ{geIwo%DZSvBhWq=l{qp`{92i-^q(D?!IifPIYwI<>2_DqkCWDaq#%+ z~EhOKV+m4vg^C`OeIQpOHuYIaKpOI8oKDpZ(A~|KHYbqSCy%j!w5l21z_YOV; z2y6ZQ!SO=)@Bg^yu;p|KSgT28(?VuhW1`s3rVcCX>Uu~ex)dcXk(VA|-_i%6-kFsGVB+m@WWZ@)TzOhA8*yB^f@M*36S9hC8U${DBod+yZE zB>2EbDF-Ui=HPjA+CcE(uw-AFF4p;3nyX-hk!A6nTQUZGnJ=ZK_a+FR< z8Yzf-c;3mevnehPzgRvlEzR{vo4mu+<5EZd4x1`=;CljT%{-SEt4$N`@=eHE#~rMv zvvN)@-Z8~H3_CKdeGzQgw0V-O1n#wIxU1dF!;7mpyH#=3EB$JU zkjjih3l4Dmj2=&Q>vAKPsiEv(Ncb;0REoP4*Sfq@n4|a*V&IL#_(N3ETPA!>kIy-y2fE% z%17Id*s9MNmxt?C7(Bs|7>7w?v@91qC*DNcIUD_41-G^QNITxwV;{*5n;5(xN-@E2 zVq}kvHhxd_eeo>%F|FCB%uyimF8J&{$b5fh%Bz&(EVE3{FJTEjciw#RQl3HGLK*!r zOm~VA@=iTsGQRs(0@<@$=EUcu*w$GX$9%&bFd+?UZoigeo!rw^G~wpsA-0w%LZZF} zT-luzKn=k3`QkB)3Vl956urU*d|U?tvhkh7eXewo1;tOHX9N{g2Ym2PwBx-K{B9IB z?#@^SJ?r=J!SYcW(W|t|2JVN)sjj0>Fm|>?02NTr58#>AQWbY2abxmIF(FcnGf%VT zU#Tp-@+e8K%C3S!E-@@wD!^x;v(=JktA$b%#pB;fpK+SD)J`Sn4jVYNqOZH<0Gv{a zCWcdtCtSq7MBQBZBYVs#rA}S!^MrU!oC->s#1^w|b8THz@yN!O``Iap4n?nf{XSE& z%BT#bZ2V`##l~)m>U3y=3|B;gOn?e1gHs7*@Pm3c(^tWCM^NE>Wq`qV^4R0nQS!5% z?=J-vETjw24BJ>AM!?!TTi6A$X7Tf04mRTlq`&vc2-}n?$JA-t`+iFSk`_o0n(8yocsn=*sO_? z9DZXJ#G^jn<}7K&Q@cC6qb8PWJnjqA;nVUQa0FZ_)8s1($#LhSaZzZgAIUVi>xH+l zDr0Oi(4{tuja8lr5-o1%H+~ayHwr_u@il42^?A5D+`(-8#66~?3hL+KvNE=?T4L4& z#pTpczb%J+2>k22aZZ@iFQy)!t8xDbBTJfCyQNvX7o(}|vB8DOZ7Da^AK9bde%ai* zGAab`?csj-r}6CDvAn9y>$>6#vQFm|I)BDml<;o-bT&zAxtj7;pPE%FefBkQx>1^B zl9nX0HQq>V%9NIt5_iWq(ec58O({v~Gh12kxS@!Ma34Y^Up>^v$?>w9x^A9azfZlg zyaf9C*9mtEZBrWix)cAwF!V9yalNv8!teQ0emz!372UX!?q@BBX^!Y+McMgzuUVv7W_zYN}no!+a{-!Mb|Aa8nKS#7diIQ}t1w&$;?| zHkr@_8T=z{*Y%6HQ~P}Y1hz!7#o(_*vSkE$#Ja~%rQfHmky@uGkc)@+k;5(@gTjh&zdK9!^0=DOh|!uj#}8@76$yP`^OC-v z_y3({EGM^w2WjJJG|$h{(#c=r#AYG+nfU8#RO!XW@m%~0NL!cKg1hVV7(D=UWHL#r zx5@oJ!}FF6qbTQ5;y*I|PALoFRGyo+uYUTDma{`W0laB^@&$8i#EfStxfA%A%eeBz z)X2<_N99$-6JiiPgKc>eZJt_|B`{ffRuIL0h=)gL^7$m-xAgf83Z>r>JkL;wrMXuk zQroBMW5XAGyD>ZQn=&uGFni8i^i;6kDDW=k*y&lf(3<|KrJ;&zZdTWvGb)dKdV~F% zp*#Q7M*D2FaG5b*lYd7dV`H>7?vr!9*Jo)x%VK>#T0&l-Wx8?ZfTgNBvU6rjc@q7c zZP)ml*e2Ux-OswBr*z!OH^F_v&w40sqLRVZY@zJo`naJ(^w00}>TtS5-4R6~nkK&0 z=FGKi(X1wZcw6=#^)*#dke9t#Y$m}{(&c7h+=QVT+y0Ndar&{CE>bxPJ z+|VM%h}M|50h#c;QRqCK_(ibGq-?3i=*f{#eDHMhORwb9s|NR$5>K#9mAh4FDUwCLSty%li;;HQ_Os4}jH6{)!VjP<@DE6y5?(hUB z=x3_D_p^o%fHNQ0-miFHh8NcA2q+c{`IWmxk*`lwUJK zC*S~sj|x&y3ETch8>S~Cj-M$|yD^B0FB=FY@a+3=-+sgdC7*7^H;*#TK!-64+N2t@ z&DLi>Yj!`L%#a<9SP;#-HdxX$(DEQmfnNHE_(Wxtmsxj6{YYJX{o2^XMC*??-L!#K zm$c0-0QFNGcCXayj&b5ogs!W&x{r|iObx5C8(>X!X=U5ke70Vu@%4af(vk?r#NGLt zg!S>p+7>7|CmK^s75qsJsmm6E1mcUbzyv%bKVq>_Hd)76Ro;EqLSTUS&%UF02lX2k z_rpFTm8y+^ByZ}=eN zn1;GnZ`ed4AV~D+h6}(7&ip_-aX~nF%NIloYa2@(hc*5>KI?s4;neGwWzRAqcJ!^w zGe0Cw&A4QK#EXVzIH>0hcsMW@mouD3BPOMF(=0%IPuikQYvbJNdw&hY4)ms+$-{jn zbo)Df$NKFbtTK75;_CY}{Q~p% z&kCyCF_k&o>ZGm?++>Xtx*_^?Xmp+!pI3oVR%-18aq7khhNQOLDK(OQgig8;0$bhu zzN+XqRWsXFs5XVg;Hnpv>!k;Az*qM){(9!6ybda^UOhqm*>Z%gMK$8bD8gSh%3#Bc${2(klkm>?47k;5##+KL#<5si-ybtN5v1jM!g%27 zpG!^Ffa{MP_D<|0zpKi1imN+@OTck!p|1uP!C)EKLUEP9ja120>1MN#>RUiw3F)e< zFX@?eLy!ap{_H{rOIUt0hxhC(qUPR5==2LTt|0(Rf#)a(>8a&4*#C_tA~zqoJ)(r$ z=7y1OjDc|nZLlLF{?9fB?`p-!oy9$0lQyy%+WK;QTWlWp!MRT1k}~LKT*1P^LZNa? z^QkGgC$`Qb33E^z%8_t`O{CpybT|}>JbChzd|w`NTJWt`HbpQh{Bal6)R{Q>p?Lx$ z-HQsS#qFA#nPFZ;M{DLLq*GPEvN_Y+wb^Uz?3K1`?)S zTr4^zUxlh){?@|DY()N+9Mn0yQ*~G5!h(d=5+c8@!MyL3M(^}x@iUiB?5>Bm>#gH45&&y0n2(diPXdYo9}v;c!tHt;2BLQKx^38K1S#sLU;Q!F9jThS z!Y78glh@KG_DY#yyJ?~$5T^q3j;b!C&BP7fY@5@3*T)a9RhIz8g=f2!+a>_&Out0` zx{tzz6&FuWgM2#wbjJa zqo#>PRCee!%6}-mk|HOdiQ{=0=eIDB#(OLC3VfhdEKDd-?IQD#{nkMSAhpRL1bYoi zb>|FI#BazqO@E$NoNPDbawD{yL1JsIe8%)uEpE&3-kQz8oQuZPiA(`hT^eFBc>Ol@ zqd(gTpB2oXRJpJuoF5ENh+*H^Cf>$PO#XZymfoAwaCf0M4`|TWSMBgA zKV>bU-yQ_M7V&v$?LDKvy2-YR z91l&4R##pz*F!}@F{?R>gQ&yI&+T-!m4Z#oWIrChC(=6iXAIO~u%2qg^D=RFeB2R9 zk>|U4VsU(u^>IE#S*O6ZO>gquKS`w?!K~jTs)k!i!k= zP0GZy-rPYb9HnU1_%ENOVFn_`V>a7Z5fG!FQ&Tn!CTki;=ih}H4wWPZ1W{LWtBmqW z*!H? zZ2d{{!GqDhU6)?>@VVsFGd><-v!*zshjXRMXB*(K*zrBQ*p8^^nm6pQ?K=ngUhWpt{G5hCkzF1;VGDF=C^3t<~{ z-L0~EGh}e^_wIxJi&>tr;pU5P3ksrEYX_!$yRU&yru{nqa@9+5@h9-Zupy+4D-xY1 zbRFEdL-t+ZE5Y857%`TZ`+lOoA+ezz-lGKzk8hnl4@{>}0tH5FccN^1 zKiddJOy)j4ne9LHHZY{Q(nTb!YInDFt3L^YT)%A5X!lP++o^2?W}UdV-EGu`wh1k$ zn4?V}nF6M_F2}=L-On_WRWa`}rly3kB$zrKynBPl$L0vfLmtIrdFOy0Z<>BM&Q~V| z`Y#0b{Iq#@_;tlq&qx>(>}2-pq2#trl?8OF#;>s)0oLZV=ub{Xgp?ABH#M=kH#!)4$DjMcoM~c3nq;(Qq1ex1zSTWy zr5f#*NXy*33-6DUCxZ&p;3pMm5C=vQ3*3OH>=&n-W&jjB=7vjN zKyesdCNEVyH+bl2&0~EW)*(^(X@dIZRR(KiZGAgDYz)V&;38!ycz%GaH|cOjQM0N0 zrIq2kAszl@$ROhns9rO8GeasvIra11JlUTSBNyq7W9xUqg=pmk3#Zus^jcjX7< z`brY-K%OK>po?0W!H&{Eirp0wUDM#8o=R*p;p{@dtW3E0`1rU-aR0ngRG!#a zoe#pZ9<#pF*B9Za?+VdrnFz-B`O;kn(vc9V@_dH~>rE?j9#~(|l-@Sq#}>};dufuk zVl-=;*8J?u%q&Om=Z`X@1_QtS!@%aZJ^yuYtS$Vt$7HiX1g+c_omrBXmeUeA4!}hp zj2@br5)%>v37;}U(gPGJe;np^iO{^(g?bnsZ|l$zbaP|QR&8sbCivIo z5P}u%(%w!II+}`37unkphaX1r^G_qmxIIZ6jnc68DBcXYa9NR5S~wTo&L~U0-yE^- z%Vv3`YjKLL{);Jz6BL{K-!MpC!FypV-nQlaGXS1>t@>WGgpz@jH(A?oON|ZY#1}bI zsyIeZ?WmwCoNRuNr~wXAb6IuVv85X{gza)`M0P`lCW%;!8SIsHao7u+^rkH{>`&`@ zQ0uhZ&uO`c)1Ov`rD34eBSj4`<)ghI`TJ`VO(1&cYZS1du1*gVml5cJAo8LnhHauT z#Rgf?k%%X(;J*5Hzm~ZKW#R{!eihB!h|r1&2+?-_8nEe+uZ^L+=hQ6%rh@wb{ccr4 z4k=oMdHpD!SpRy28@l5_8^<6orGqGH$_uPz(Lwj;w#W#CN_}YkgbyQ!GzO)m_y-2` zz#_<5qIQ6kSU{-3X?jpM7)n(xFb!N=&N-30mF z^2+9M3cM7KMLb=2JSA)l1x}*d#^?9#b@D^N%)0ie6w3hIRxY*c zyqR^<^;Ck8@~XqQTRG2+=XhOrAQFQ~ zxtG@uhn@e24(1A{tAzJ2^kuEVKT%>N!eRmk>hp;iEX;kulk}j9qkozM^uLgot_H(` z=gS>`zB^Ci7GXxJ3dc*evI78)kEB%)YJB{2QIqS|WDt(_ZrTW|eV_r}%pK1>*Tb@6 znVzI=N<>i62N1Sr)=|B#4IX3CA=598cw9ga-4jtD;_NlWrXTv^^9Jg+40Syr%!jSmT^k1Hp|o@ zyq;!bU@h=4ZK!6O#y_Hu{M2g5+*epnG56^b-9rI8QT7M5} z30yzrSA_atV?~3|p!m+ai=Dlyx9Kz=bb&HuM@YbDukyFjcK?xaQ%woKzqjs7D>^*n zV8F=5(4=ZEtTAZ4&^Or8j2U+CqtO?G2bt#vu8o+n9(LSKlWhH^_ipp^dD+K9Y(TqA z8R@|6Rzf2BBJFd&`ic*%Q+Yv}5FWzID20p|72XDaye5cu4p!p?f0lkzc6>h^OZl`u zej%_W@GT8oKc;M&uW*AU90aB}9wQe&X&jOopoN)&I#mxAxQGH>hza=(4f`VeUTHAv z<7tuKrmD9TP_M%A%~F*GlX7z?g8^F>DIck z2pg8eyB}$+(Nf zmCme$S9z)sIHJ!E3Kc<#vT>X)xRv*`-&aJTJlwo6Fhs2~`yy<+CGS02CqAc78dMLQ zG2IXkXbH-^4on{qKtMyFd*4#BBglnxE(S4yeLt1vEnqfoPC#=&( znc$fnpYv^M`9gr$r7o0q=gYA4Fwa+i%;@<` zher}0>(U>TMIB?K7+$nIy?h|t{<)}ZhhCUw;A6E)=VHd-U0Vqs(3;k))$-a?-jd*S zhPGL=P%e=-AM#@bUdDYZ{-Zmx$0?WXE%b| zzoQR@l%OA2+f=H)8tzICC}(JBV(4^1+oS6bpzn1ott_3^SI34%CL9;du+5%XCpn>* z?w{hp&jTmc#a4Pz<`2V%mJ8nQ!7B9;f-p30_3qhKVc)DZ{ZBLhiAfy+4xR7PCsoI6l z7xa$!RUD}Fa&T1+xLjkxOLfB(pho~Q7V5q1jL2Ji8)%7V|u!NA^bDvA$X#{L;@$8?le2)?3H7 zvqmlxNz7mBd~Pr6PIP{?kSQuJFL##HYZhGVF97=&Jes`YtNJ`NG<20B6t$%bE1C_JAU}nah+!?QD^Fl`n2u1c?1g3! z3``w!0nBe4tdFJ82Zj&KLHW1!SS>N>(9FT-|7+&vRbekLxWR*Hk; zH)DB$U*r{sJmU@@g)$*Ne4ZU2QEZz9@H{khM1t+$*X0^~>A#MCfTZ z$w9W)efH%T+_(VX$uXR#fMoktdA2O>rqzS#-RgT=;`2DO_raN(;E8zre_T%yuSX%eKKcXf27cW*yH)DiUAS@--UQjwy_ z+~`JW;D;(DWl4tafFNWU-1RdC`IkhHT9AwJ$n zGTj7b!JvocgyK)x7QREA8a>U5QLw%RgY0V6*tH*U_3{dAd-{9P_0_kmso-j^arTvI z#-J6@t%pxIwSU)=cIdah@iBW+%~LjPv^MRq1wQo(W@+7jEd9{ff{Qvs7rwfr6Ig4M zImXJcOx{VOjg0*%LWk4zK&Y{nK;_#v-0x^FFR!QWQm{s9d$fUWAe1K2G@oX*b}`m+ zFrPYNYSf)QebAfKE#4h~0l8Sc8xHiuJfgGxCWN5x(j`*2QJ9Hv__h=~C7D{=uV&S@ z@k+vR^OF{-fIIvS*@F}+s?Hc2NP{vaT;|)4h@=Fy;@2n_{M1`WE^zV4^2T?E)-*QS zxm#@$)LD0EJ1F%V)JZ6E_nX_tJ@&&*gZ3ljAS46fiGWQk?nYDR4boe6BQ`oj;_FPf z$9bYzOI!Mg3;u6j=9()hDaBAFcB}w9S!$Ic>sJ!)JnUCv+}aOd<~_mCa^_r#*?1bg zxY*h1*C*I-?&3BeogdV{?v%*vJ26_u*dV1N9#63ng7jTnQ0tbOTpNpd(8{kCTifeY z=5k3M{QNQT9CpT+VJR>LrPSu_rTJ@N>s|klGp07b zkmzc0HUtFOGo77!$!=rNtpr-zc8Z|fmAfR;+8!-|$v%PlG4UI~iaVqSSW+dr>ij>Q zwKHv6u*B#^fBIUxoOiN^Ikm1%9s;-D`kZX|-j=z7A?@Ie9@IJ{x*!s4XkCTAgo*|< zWlcE>>atRmdu|0!3J58MwY~yFIN_0y(N*kRl?G3+nOYH zo9z+GhlF1$VueCJzuAx48oeuohVEKd;eX`OZcq3Q_xaxU_7>tj^c~5h0fz(** z&62N$LrVoA6H=rFeL!jeVRX!Myg48^rV~Liw;Xq&jy_#WJuUccAcE3Pq5;DUBN$j* zw~N{?a6cW;^;(^I>Bfb3B(yC{`e;SYDTY;DvfceuSVXCz`mSt9pSvxPTXnlCZ2O8x z8k<_%HVPU1XB_~B5Q<3Y$cg89RyJbupmlO}^r_IaRzeQ3icv}YEwUFJ&lOCkFRBuo zNYl~W-RVe@Ed>!ugzOno#vtIPU5fG?TklE8pCr8P8ofZFbTe>Nn+%ztN~Y{lO0!^v zYb%okeUG4y>$4*#IZ_2U92-MtUyK~M`!BXLeg(HXMV zDZk6ju)db{ZmCQb)r`1aoiBcQvQmAI#%ZOyU6YO&fw$qjJ8W00qW8l~jYga(uPsw) z42%q4gS&D^!JxZY@Th4pYr)BPF(+s&`N!&*-`qkLIH@p-r?gUE1WM{InahQ&SA!RR z>8KBQq4A08Ubcne8H;MvCU)lVnN`LH200}z@l?>&iq_yY>4w&WJbNBOkaxQl2{7HO zBqpVmKfTLthC&O25gHkj(7jy>ttirV#n}7%G4ZZ!#oGB_IwJLiQQ>3F>igvcIMIe7 zHR}0`yu#QR)Z`u`DQ^DYiCXkO470k%wy-ck?{EJ|gQq6!Y+c5Lg%l3w@l`k~&b?ne zl=my#37uBqixO(U0!&|qvVCa!4LNUDhxayl{U<83*lDYaw8ZIV`h=g&RoXPaH#$v{ z(oAok9yf%I^}q)7Dz@o7>4kcj)X?}A;uJ(T5iw8p#xPW=6Pn|enV#Ue+*~j0G=C6R zDK=~_={1%xY@&5v->QXc>xx9}W@S;4?nAYsJ3pfg5TN#YvVA+bnA0C~7v}VdE5OVq zqzGi2nS1EWKWev9S|&y(m(%=R4#cah7NK!pn-%#<{+HN+a29S&Ffvhv)U`f}{qZiY?H|Pjc3Pr#kK{_D0V8hY zF&ZRR1NQ(&DJ!OV^pa_u%pCig7|%-UPd0W?v>HIsGh7;W(Al@*9_B5C^%kdHI$0w) zOMH(Cg{H*X?kNe3%Z|J@h$s+0z*TL_B0;-^H0~l5j+W#hXK{1i(_5_#kKdai*g})S zD7Nb2;IIyYr`B7}fg=61E-G|GlQt)=21xvB?RwDF)q?F=)=j#}M!I44k_{u;!br&? zwY9da=_BeNxOY2_IO01E0@YrbAy=M|+NdBgUc`F6@0Mi4pLa(q%o_=nEgp92?8I{v z_ZHA2enyS4dIcE1tRIzZ+rr#T#Pp2UyL*ng=8rDAZD0n!5VJ9 z!i@RhMq}FS#W>VW4lMk z7!#WkVwD)Hi`YyITg?EEQc&0)-rek_z~@?Tg?WLYzfO2Gm6;l;Dw+@Q)HrrOgOG;b zaZV<()m_g}k^Wp&$EnnE7%N(}%>D3>o@vDJ@q=sa1@mnvVMtd8x4Oyiurq950TW)P%V-h)NBj;wc^6 z$Y$tJQg|HDv370xlaAwdQ3lD10QRSgXqX)}ORR6Uq0J!jRkC=@mIgCAGK` zgyZ;6GSFgD!v~*s4DW>zpdRHVfm?%i^Onk=ML0cm$tqW_C{IUdI@jRK>t&aXu)J@AL}>+O;I-ruy#kKwuCJ81TA5{H*f1Zz5!yl%Akq z(&^fk)oquV?}y>o_pBvII9jq{tVh6e9@Fy=*H-gMAr}~7aMYv1dl2KseUht>pGp2R z3Sf=rJiuX18g_)*H=)A@4P9_gE1fh&iQurZHH&;=lG5CJ%~jUQF%bF@)j|Z*lVr=B zHG`kM>+Zv?v?MC>ogJZoK z5VtAEr;g-w-Iq}J?|Q*Wds3e3{l%P}qPQCviui(o>}}@R!X-DA?mvcIlIwE2GO2It z`yy;n8J8-uyCY7q1bE!3c8{%Zqh#TB(yVtG5;h z(62Vw$g*#L)+(W-|QH;j>2=dF^T zwg3G1O~iul5kbO(=SlpG@Jjq}dtpTmlo~`#9SB=6m9Ju=OVlMKDqG7UBc^M9Kk=&=iyXsCX1XUzs_o+4XmI8D){Yb zDWl^*qqy9gca`4e8iqEA?%aOdfiYTF&-`G-s(P@A+4`j)c6`^SCBRJhPIS8;*3xRtw~nOsns0u_rwPB>!-$b zRlT+ib~pgpW9?F!io7(7GCPKj=^J@v$$QonfN<`TJd*&f$#Erh@RT5S4a%T+!e>7> zHT@Kp2$deRIn2LM|El@X#pA*Re!i#GPuS(3?^A|_vOYGhUxzkyeS?+R4H3?o^-uMd z!#I@$@jVhyFVqZS;-7JzdlHBA&da8re`I+{Rwc0Qd4s0Jsk*3qw#Br1Yphhp?&ny$ zLJY8pGKv z%CAQ`)U%oGSd0-CO$-ME2=$*>W{Y`4oP{%-UzQ#ga}|%H1F1@$_|>Hr-DSTz9dPB> zw>HD*PCFsj=ND)sXv9x>4!7c8?!!05G7VR%y`%vHiAT4@R;7RrYZ{V4^20Uc$gw)t zTI;~EBc3~qAL`)~G0t2;blY8Bh!K70sx7_nNK*p!&gN$x&#pTA2=D8^r2_=&mC2p) z0`nq!o4zdu{D%U1d)|uYT_=WNl;>WEUM!Lvn(^$#a>v3yxy2#@4YJ+Y?$q#bF95G_ zCwY1lFv1Sm(=ecIem=pAbsAQC#pu1)+&0b!_t>UErC6XoM^$a-w7VVVDfWm+=%Y4T zJgmE+jTT^0WfxHuSH9Lr(FcadG8w#TK(;4o*is?qj6|8MK4O2=cU;1~wp~w$PW8R$w z#k6TlM9M}IAmt7tWJe3N?#U%5d8{|Az@nuQNvVgyC0Rw-7!~q}o0U(#_+wQY;fS3} zR`&UfuRI(p7eiHx>srZ&zD_3Q2G4HhXWMu+=oo!3TBl1&R={7`pm6EU_fv1`>)DEi zPGm#YW7k|BNbvQDPwy+Z@KiDAXlH4jvR-EBF0^CPGTMBAa`` z(8U%wx{32y_*AlynyYw%6<@0%`Z3OhYq;6N(`Qt7yp1`(SA(YoJPa{9Z7FvP$~HyG zgebhGxbtu&t{G{0roi3;0(!0xQE_JEg3m6<=ssIRi?#U^zEAtvH&`S}fMER}U^9$n z0?QF~dHL;KOc@>>kTGrmk^tBJwrOEtXD(@2xJiU9KNV zAKe~u<`6aJ^`UJt_Ztz%^9XG@mNyKOKnZLjvx8xA^`;?ty(W3VW%9~3cFMV&8Qu;q zWefAF#E?X5pP%QA@i>Erk=!Q;Hd^MuT{CE=HVbP_Auc5Com#6UMu?cVF{hK|J;&!J z_$%A*yXV!(>slRj|9#fH`Fy7ES63^x%auR8&QJnaicb#ryu(K2m;7UK@Cm#p1~IoW zeXz`7*)tfkcNj@H@>pEMZ_*}wZM4Gb@}j_CrDW^{K%E?9KymIg-_fY%m?_&`x&nNn zLrhVk&nN%<;n3OZla~GchGP@0E$*=_1FDwQP<_1>6oYiF&t@69ll(1;sAg8!`lrUr zM9VXu64SlF#$UK@P0APE88Xd(+!+cvQG^~G`=c)s!OjMTj7|c<2s6Y7j`*mIVsj&fR}tl)Hj zemg#EoG>1i>j0NztvhVjbm2V0$>jD=C(ZVt1C8 z)!YtudK(>Me_doX4j+P?f$x`(O4Si!?AmCEx98*)b`0bUE0?=UKw#wT+zSoN-~V=3 zL9qOqOg~oQ72C;`{j$i=zG2_V01TG=oZBqtGow=g4CnF%ys(Id8&)!t*cobHT6V?z zCGN^6kSP6$5I-06_a;BbH9Fbk;!+X?9ynGV-oosd7pd-mf*|7Qm16-9N>8016#mj- zaaB>-&Ek3S^^;s)476<})McZj;)+JZ?OB)SbDKST7w$ztrL+bc(j={lxnONV>y{Um z3{EdyyF3VyrbL9Wb5`OGRx?}Y3);$7OPIJm`9WeW@kp9FVXW)`;%2;YrBlO{$=JxV zj3QC<Dg)bdHyx;4|7&ghyGOAYmdTa~rt26Zy%DjTJ-}6x` z7dm7J4RI%lf6iwMG8|C1ehR_(Df%5}+1^qcBy7L>BBoP{xnrsp3n>fo9B~ZJ0fB~o zP0ie?ocQCjRAjNHAA}g&%X zHlU$`hcDb#^%xDoUwi@mMW^wrQ}B&fJF;>Qe2zSJ{R)*#lN01_;r}TIw+JyTSQl*gv1%^^(A%nvTvuJyX7OzB3QAu=m99p&!Qy|RD9bu0KTbN z`xT9|miCbvX5GKln>hB$?d-cUlF!JCh{5fHDk{T!{ga5J&*Lj*@8*5084keQj2u3} zHpq4>&XF_jIz~z@N+vNh`k=Dqa`ux@Oy$0942+4$dEe9mNzd8Ref?tPYOqB|7?+;p zGWT!i`7i65;L8M|4;_fNe zptEk`J(^+L9`rfdF)lLQetBs%B@o$>fD#*Ilg?o(IhZxcb1B{TZlP`VCOgQw(j@dej90-6`^_V%;sIzjTyRU*ZStGGHX#pS ztG>jur1%a8*PAEqp$7FkDN1vfS-qKq!ksVfcPGBm3T@OKh={qASYR60Js0pk)VCj5LQLjzkAzm+ z_BG7cY)Z(JpeEQtnt#L?6mjl6hr?3u%49n<=&+IaUv_YaS(?5#JP{tAN>iw$HlN_B z`TFeL@fXH3%6)dbbuUy`;DHWl_F<QOgVh$7PZ^ye(pX~ zIb`{hX?y{Mm|XURKZiH~JX5sUKa-J$)cwIoU4b>!jj7@_Uveu-H@BX|km?d?x&w>wfWMeH(*sR{G4r%z z1<-TsUV(WtnsvwL;J-tsrwkuc|n z;l-k>MWu4yW0A7T=bD9`oBn0j?c~cZ83C5mkL+(pqL`o0KY1CFV270@I~cTRa@mG(jJIxGY)GOdQsHysL1j}#_<6yX`ir^zf3AHMqw z|NKrH@k265II(A>EUO{8bFd^H6Y<^sVNA|`V=9N;Grq;v>>?_p3?2T(F0TlU$h*}0 zI{xs{oO6kQe~;PqhZCIn<1X>LBgpu{bmG#PyPy8$Zd28|^4#j?I#om1zz_7z)rB$3#Ugh>f{)i8-FDtf97gpK~Uew@)xdWqe=5>+x7}A zMwus#?j3;6T@CYZ{&(-ebwPZLbr);6=`_qK9a7dFLnm8b+NYa?=QN8z;%5sQ&gT`N zV%zbrAboROlwO8ps#s4Is#aEqk4TgFz6Xv%*7)9vIH)s^(6ReUhJ!yVX*}VA!m0BI z9fY^sT1_K*kcoX;bu<4CN_Uv0#)52<%h;^&)nuL&@Nlat+y~=`1L9!4)fNyK3ejnl zoSf`smA)`VawRI}1^#Vm<#>2(;7#^ zaN#chxtqGYeYgFnrjwcucs8m|6O2k=hxIV&41+%NVYcCdXvZx1%s`01peO1{Pv~^ zxBd~I97Bht+LzSQ`D^j05}$t|mo%oFhN!YLYGwbZJg%@>N*3$eGF?6Wcf0YO4|Y_1 zBDm7{>IIX}Eg8H!uT>^r#j~-m{yDfZ@$Q+>;kjq(HwI$~vG;@}i2Tzf=BGfbe~04- zs31R&cUX8LU3JY9$AcOyDYL)sd3VD7vSdmuawGGBCzU=KRo*&2f#{ktW8>x$+-CEX(t z*7>l*>hn0%Ldf`k7@9Ft-t$uzwmDo`n#peUv^W6a3Ou)uHga>Gk*Ww!Ysi-{I7=+) z?q;*g#r2g!!aq|b`#}vm=`-`O(<;^se?H^oyU5SF2T#WTs~vBAs{BV`&6DuKc|LYL z7FS>5Rl#p382z@4K+a_v_Jw05FnJhN${NCx%d0VxMSS)XR^!MYtJ?3GoNR!waKqF z9B29PPZ<3Y!byU%Lb-)?$41@^w;jS>7oiP6dc((GAXiBP#sPtzY+e8uyg+?1?b=up z+rJ&C{LQ*JS5-k}1-TC;fPZ_*w*obhzfsmppFqjP4@ug*=y~Z-%vX(uo2V`;pZ)40 z5Gb5uCPoOZdzz4kVuy?Uny|&(-9E=aJy)4eJ5jpISWZt^JJmM&Ff!-)&rENDPDmJ8 zac7BcQE9*Xr?EAUIWCp=P$MGaplr*QsU{=1)OA{%P@N*f6OtE6K993>U}KbDH&*Ry z{)v^XzbQ_8XF;tEH)qF1ky!2c@x@Ub>gmMFX-CO(>NKPHs3Dnc(d~ZWA$X?W#c)_O zC?I^D1`dKW*k&F^+VVvZ?udTpd+@8SbK^U0c!EAPu8GF{8C(rmdj=-->%27Z%a2;H zt>*k22+mV(th!fMjviZk@Hkx;!6MXnU#olNbiA`E=YE|OsQ!osOvSoNH>vtGLm=u; z@E5Lyyp%>|7uH=uy)F<+EiR&xw7{K;$1|Uu{W@^@!$Kga{-t~3vXb*taq}$l$F16_ z7huFGANO+L@uZDkQZ!KCnL@tYtSZ$!zQgc`b92j~NA5vwR_(OH*w<=#;p$0UMsYd7 zYm&mHXJjGb7gXlEJnm64_)o8rxg1XPvHp>B7h#97?Z1lm1|Nt#qIW5RdB4n&k8Z(+ z+LO1%?K|0+)S@IZHNNDNcN&F8WQMOPkRV@*+|2D2+!8%I76gVFKti|U@2y0aRHQwJ z>xiwovq;?&xw}CfR@4oIfmC^Y8w}7Ni0$!Xgy@K7*95^?*BBf#&YG}oWAE&I0(H)W zYiqFHK>L-z5WwHJMu<61uL$$S)a|ZxmlWUy=6wjoM}W(u$eUl-;7T>{N`RS{ps!Tv zytS1iTmgZev@@EkE1+GX8}NZ5&0ZtP*=lqBiHIHVJy&$bBPZR+?Z-nXrDX=QdReV zG8jHvbaCq3uy$Lx+!V4D6g02d+(9(0G_t|0LwpAXeXP$hF*YoHaoY z6lBb(CaA9F&A-u4oZjw-07c-}LM}WrtMfH}QTVTOEC0FWuW}n#&%o`E@5T#{>YmGg z-%Dxia={YE`0Eyt+^`18^9$X#EF(k%7H*9cd&hu*=^#^6bocnOcw$XfNrwkfYeR{m z*o+n;oatNz1&toL9VG_E2sVrR8pn$#ODG9K(i8f7F#KP375Eq8y?B_u7@j*3YRuKs zx6pcsTHa=+WxTk!@V3KWkUI&-F?@)<%qMVdUR^_XT zf!POUU5d)WSk$KR)#SZLR5K5+L!fVtWzhC|FSgZ(F{6XI4r9KnCU^5amP>Da$%Jeyq-P_pI1aJ4tjR3`B@r%Bf@u>M z7|s&kuKyT@+)EI3^)=;#`{#Fz+un$~0SbC^eA6qu(%xh+FF)fo{soa`a26EA9ni4k zw|dn`8JgE&=g52dSGmZ}r|=z*pW0)%;YpEWaLY34dIo zxcP5gvhS#qy^Cyx?Hyn1j^%0SomSkPF8|#JzJ5zVip2$i9j46c4UqW7UZK*Ov{+Q| zkGqvXinH(uzH=>MR`M8<|LmOhS)NpD=G(TQ)lC#5Xkb#o`^rZA{G6ExnWJnp-AqS) zQ(XjR)%!!p=vL)r05QU?1B+f}KJ{#^@d_zHLSMfbTJW*<#O@$HHh%N=#Y$OXDm{K$ z;*^Zst+K}h19j)9L!Vru@9-k5ET*1fZUAd`C2R3te7y2R@76e{5YM&jl$>oA%PwhG zT3P`G)%Z`GSX_uHFpVs$z_pJ~gt0O1nNS&$%zGa3Qpt@vx|PdyzVk#bTr5AhNNqjg!-5)IpPo~cb4kDIV7Xm zZ0(?e!=I+IWzB2ojRyJYd}js)^;cSE1nLatQD0LQUv)PrQEuiXH$}i3*$TPf`Xi!3>G6eMxNNkHQavI zk?zKy+kFJaa*D=}ri-if|)Xxii#Y zm!L3kJvMCP+Z*z0GAQU%3$qFXc|AJ&DP`gc`RrCkk zOYI{Lk4W%@&fFVI);Z1z(qq4;-jkOj>UMlv9Dv*oET*Gz2m&EKZ_hQUBWdgsWOn9qf>hs_e(V~a`AcB>70trAXe5V>mdY~} zp^WnDJD0CKe$~hWzodX$5r$d+eqoa+57~&eZjQL1Kv8ql(&!EM2Rdc|qw<%i^o;8&^D* zv?JT{#6=@EFZT5m2Equ>QcC=N)rJ(61jZ`Z+Il|Rbxaa&Pa71{;*c4`ot$Zw8eqq2 zPnMmwX>D2xA+3(8ja0e!C=ZP!#?0~!UMAbhj8I&!Ps4i)w&cc9cVxws?{RpR-$9Al<)bs*Fue2TWC2p0M2iTa+bU8(vj!?JACQ^W9Fzt!tcV6ZtFC*^?d7^l z!j$p4d!tazpT*|SxKGK9Q^L2QS5~`b&JitCZjw@CF$v!|plsx^N=jSgu0XU3uCXtc z;=_ChRBqX1Wvg#Z$Y1Pr>0^RP{Jg)z`V6xc??VH2t{-x;Hl(k%fry&AN}6hlzx>o77;*ay$vt8Dh%mT(RHbV^BZlFd8X$WWZTu4 zE44ID^58(`Ev)$(JvKD*=J>l1pk`G8Hv{uG8ub3wQMG&qK-fYZ0IKEh{JD3^3%_3T z$~3`I3(gROXpgghcj%VlF}r^Ep5J>sbL zaHgXbC;*JA6@HL4WU=}u^Q5UY>ngPbZlaconQWgj8MV@HZs_w^J)T(SgCV?O1+Wy$ zK7&a0Xp?M5@;_=n>CL`Xgs%3Ko6I{0(nISNlE>ws6>Zy7kx{`vZjf3&d66c!%AY@V z;V7(BxJzcd^ zC=?zuR5`y0krymoH@*byj`u2lQ)4y`EPUn|2^7@IwF@@WFQxWSPw+f@q!>AV0%Z5i zPWN@1qE8_}Xh04{DFPVA%^cv4>`96$qCK|N24*uhaP~i&IuUfwfQ1G-?f>LysO(dDi$WlIPj3!u~n+R+ChFvTN>FEsq51 zuqx45YEr=|sC|E7Be3Y^`~%)p`7N0o^I7?VfdPx3@PxOA_M<4!vZ6Y!4nqv@`l2_g z#L4dYn<#3;hdEk093$?niNk(5MwDdNq&#x*#u1y`8Gh8TzpR*{hGI7N&kka;JSB*F z<6~1^Sq3@2n#F{eRd9Jc?E|YBr(Io0tqj`Q6Ob~bB-CS2DxhM2=j;W{zlv!GRE1E; zmkV3rx^wqykyl&SS*QE#kdW z(3U5igMA<>^h0=Q%!P&jW`K1>K2B`z{>1!_w7fh(?|y={XCr6*=OpJm=v>&% ztd*fZgFbiMMJT-2Kyz+%GUVFAs8z~(g_k>cX3Cfhr=)y+L*qQI&wuuPoLTN}csolV z8TkKyqboC_Xh-!}Ja~Ro?#atF0Q}@VKO}T%JbNUgCq?ovIixa*u$rsEjCN^JUwjvd zp0?j`7C~VuB4p{*aflZ83p4Fh?_7a>>7^W|+V=(eA66$0Sluzo6KoiYBl`b|sccrP zjF&k_e=+P>XlLIXW8x_54cdgb4!6>2NS(U_zp}7Iew!wxz9fM zQK#ws!FPprS2lih>C5F$m_p?xQ3}p-S_ILZ4eVz!*F~N7-9JDz^Bx~S^DDs1PcI~X zQzc&ePDK(oYQC&cn!KLTuJFL3tk7wC-Lc9kyPhZ+iW#u@`m9^(VQ`uTUB|e@wq;aO zlJ;8$pknL^8JZk)^3p4A6Mb1uc*010UU^kWyvNqwI^j$ql-siVuZJsm3SR8Da!?WP z$;K({YCl^126#(apDb-QI*dkeG6AD!eV-IacRK#8neOMK8ooCxBsv+zRBszUyky+;7ZI71H=OrAxG-NU}Y^4Sl@{+z67__>^i!Ky8!W@ z*)|z``}rD6bQ8c-2G`aCa-hdG#Q1-Y6n}y%7~*M4XDY=h08{xgT(G6&JgpPl?{YS! zM0jfTE0_1vswdFxFGe8v|A+{tcONHgmhsG7XAwAH^D4$5~40F@QUw^~{fc*|${tiXVB;IUlrzubxh= zgq;H2nxGj!x8kU9=RXFW`#d;JgSAR&l#pKTze|7V76nT^LXD*D*ur}gx6^<*Go?cd z1lNroV{F88H2ogwNtmZX)NAn0cvIPn^Sfjn%f;#gzji}v`$~E8neJEooO5|*h8@@x zgy2CmP5-^omEUb7^r^6>9WLWM1?Z#N9gw+by21E&d_13&Me*w+w-L|JCdZU|R#Wtk ztQUp91IzAnb3p4yjS5(&7vkUEK3vC-dTxCL-KZCCV<|A+n#0UHw8nW$eUgNT2Z3n44XG4>zK@W<$BA(iV zWn#~aH_PlpmjrRjniYBqcCj&jvM%3ddx&+v|Ee#aJ1+OfCfhbE=pt4fpg9j)Zixks zuI>K^x_;<^b%6FKnxR&MCqAxh_%^>nxcu8^iE1ZTd(onXMadWHnG9tBtGrbNlYM8m zucaxBFQpdHg@GoWWgWJma-%0cL+htfCl#1H>YN(g+jAB@bWLbeJ^lA)_b&sL3-#2l zcjtEyX{5C`fEU?k)YBKB`zse zVOoKcwi6oYbj$ml!OJhRwJT9noA-c<&<4Njlp=TI0_NA_<2|?8L9chNksj#OP6pac zQ*Y0jT|Kf#-nkt<^p4oj;!bCx|1zISKi_X==7$$<2KZlnG!6=S z?JB4u?vEzLdlf*Fc+Sqj!@}=My@Nk$iDUu%!}};XZOLi&)~w)WtLDjDT+6VKo|KsN z@dt?$wb`CHjw2aPSM;xlyi$Z)V|#Q)WXkUN9XGM=A)febrK09K&PWWCW+eI|%06-R zjYu1el1pC%&W2DduRyB0lWgWC2+?Z{?Ck{&+6^nx7Lj=o+op+QWtC+!Hb(+^rlXI5 zPIB7*EOG4H*!?Ho*$NYT#QFm1_<>R@#C2{VE}P09ozghiH!djvvGr<0ElF8x z;1An7JDl$JsXaFJua@yx8*}KEwLxH|(-K%B$-5wS5XoqNRXlc6jJf5#z$9mg?O9%! zb?h~#)CHuL>}p|b!M1#bcGY#xDogOb8W*I?y^MQ52F{{exXdlcq8 z+0Ae1%i*peN0c*0e|~zQK=z{jd*n`~E>Q!ddgo(c4G}dtTUhd)JT35AtRgt?CaJun zeo;bwH%iSAp-&iDag?`e8j<~W_O!8kxwUutO#$6SV9ju?ur-m9JUzD=*7jnZu`k)R z)Lct;*j;7tSdD4l@3T!O;OAH|+0HvA)`MA3NG+jw>&^&qqfu`|H_?vD{KbGD<&mMA zVNkH}PNR!&*ZWc@E&us*eaBzk$!`y7_Or64q~U^7_C%PyQaBV-efypPTkL4BTqU`d z=jGmGft$+tn>Xov;VgfS-N(a04{HTpJM)@~Ilj0aIQ$-R$<^dBRa42fN(R#su+$wR z)PIqkP$P77P2%llytHgbAs+sC=qw>NWZm$Zrl?=AXL(0##&fmf4UrWmGK)K$QboGQ zAQ|UTt4DpU`qe$gPNQ2x!CKa75t&ZxZ@&#~2^AWp&Q+^3ZKqCF`D9e!BX5N|mFv2( z+H>yNLR0J+c5(?x(?MNR&Ub*${ro)$pdJ+YqUK)eEq>vNFw7TB^9H(VVkY7vJLn4a zkxvOlM?QV(Ca`feAtb7s=gN93pL5vA(B|3>`$&)S`nwjOpoxEc#dh8mp((DoEiLb! z7W4HoRvfMlS4-#rlIIoD_m&49x&1jRWLE_S<+!Bfjiu}amgFWNy2cZ$OC^#UKp|Ww zlPTh%wJ`u*hJ;Tf_7A<)<>1hAOfA7Ei zH|j`eJ?I=rN?@i7^_!q2u?cz`?H zfvb&y0=kM7VuhyEPvaOA-!=?8T78Xw!WXRUHDiz&BCNAV=&#!&+XfgFEY=F6y_L^2 z=L0yx1SQtm+sU^iE;nqz$TRrjIx*>##LW+>nE;b#;TPp0SY1OVh`um4gn0xOl~m2A z`$W3>Ybe)}E1`&KEONQS*xhlnt9(y0HQ+X`QiU?+@q0ks!uc%9h|hrmOT8na%rZzB5NP6+SNq+KL~LA*NwwEPO&{(MNVvl@ zb$MxGtWs$O1tKdN@MTefSZ3u=u`CtNPLGtN2YRtNIPSonQ|iF>eC!I>{2e8c`)r}YsrCGPFC*LBK&qM=E|&}{ zRANmWRpQNIyJ#zzZI~J{B4@LcG_YG&F8tzDSDNNJM zZ{u?B8U;7hwT6%aFhdWnl6Ah~>Zs&Kdz3rAh7gv;d;SQJ-}E#*>1_>P5-CCX>ZhWJ zTq7eB8G1Ue=dY|~qr2Mr*}<{wE_*MZ`hiq!-@r1ehXuUYk=)R}TaVJD^3$FIL!#Ax zl2W=PhihdPZ`;~9!|*%hy24t4B4x7FBQqe02r%$E9op8pn_Mb+lJ)+$`(D-Ei6Y9- zuFd5iWdILy%dIrPJhgW3SdQb5?R!!w>brmD-$`O>1x*l@LRhg4k3%54b}y9082Tx1 zp|n>_UC!Yk>iI(;=cNIKM+S1!4f^bDE}bpw!kuMfJ58hZ@_`QOE{mqAo!$Tzkrcnj zD;C4LS{$|N4?g}W1oX?8T>dkJ5hUR5o@eDq$#N=CkT3Uk;2^k>9yC9!awR8^=N5Kh zfW3TIlRM#?(10QZtr_tXtMN#eUBY{XNuJ{FI|)pn^jAw;5l-L3txWSFd)vpnZKejk zduYxBiML(n*Hb7K(P8iVz$Q0Pu?UUJuEsSR;k(jbKp51yN74gfW z+2+74b-a6yDzj99sppZu#_p1j{;p_>Lx#Z=qeSyy}!OK4^yCmk<)(Zuoc4q&W`ucL-qlF`x6F4Snv^d4sN41le z1u96Z$p>oCE|Xx6#x+s7jsH}|Urx_RF)*fQYI$GFL{QeX)mG^S>PIU>z1j~|-)*5m zPm2~`QMFLCK~5GQ4;ql79V8m8^~#OYuxKgPJ4-c9;Wv`y2)xx) zw~PKS>xB^$Nw-4%u_x6MS0+Au(ZnOrZ*^G?dMv%JnjnZtg8uMehuc#?+M=_^D{0hI z{fp|&aR;tdCE@YP)G$=P;OhC*yN7<994-SPT$ldB6oU}wU!2zmXBKGteW``PJbSH!ndmg9i>bwt%~C} zeq>JAdTIW(rpeJ0FVW{M#SyQtqd1ITY2BL8o=?j`sT9rD8ns8`+%KcV$(JF7uh*E? z2^rb1Xw#s`_&(wfqEFBK)om~K6Ud~_4 zX-*O&Wx*C($@`ZM%E+Rmes+8crP`!-f8{bVEj0%r`ZMn7$~F=-mQ zoF+tn0eYoyMW2L-8?%>`KKftpL3E^azxkbEzf_BN+xNIYV`^k`5q#3jbH7s|t)@3{ zy|xcXu7G9;M`n$U+vC4yl8n4DVp|rp-p^f43ggZPoBN{sa46qQl&kG+_)dP@q4{sL zlj{4b9tTxgEnp5K<6MDqk0v)6-ZM*d=Jo8o^(NMF$8_`w?~2agar|4=zXn!zX`1rM zC9eXOQXNw)zG3u}OzTfasQTE`#B$DmYfYPc1>ogh6@EmXNU7amP_k%uu5~!@2 zraa%cut0PNS{N}t>nXTAp(*f&LB^Ff`-9Hv-D)!RK2zacP0UpYa2Tx_vAmC<&x*?s zzluLcOt}nBEl8#zETvJjE=LH8`J*X38?fy`+fwvijFqJJ14lxu*1K#BeP^LX%aIiV#JO{n&+y2G1q%DFKT;C4XJi( z*@T#(D`v^dhAlq|l7*FS&EVT z=q=|IYvD(f9uq<_w6UCaHfZ(ZAD(#SJd-32Kbg=iLk;xzYqaM8#j7r9cVA}ctR#38 z=0_}H_P|zL=y`hGY|)$(KRf0!5<6=ffd@7Y;5f40Q|68FQaJGCA%B=Eqa1=Y`&qXe z9xlfnIJJfTf0fd{Pmd1WMtiO5zIi5%;R0^Fy8ZuSO`po|(5}PL)G7RQ zA2(HxNP;J#s4LB4>b?YsvElx90zll-i$>y~rR2N~7VLh)OMF>jI`j{nCsKc~B1Up; z*WUa}$fz7TVfXweY8LFab09*nzQ(ZR4s9n@Zovwr>XZj>r%4(3fA0=_nuN%tdz$N4 z3doE+g{=W#>v20y3OcX&xoYgeptR~G2j%w<8P#+&189WEK$~M0pawG+t||r;fkC`Rn%2{w?8}{(@Tc0n;$P&wH)d!*VVM2(U3amj0^KlGO>b36T%4%U zOLr~3sO|jipFdy=-z<9FC2Is+WjKfz5%}Z#0=tJz&1qL64&*W5LUx zlt#fWMH2HKg^bTIPzhBxJw?p}OmTmjGF0X%_~f0S-I!nDf1n0khC1jYeLb%#(zyLx z@~qc^7q2g^rUdsmFFJEKbp~U&*8Na%$dM8P9&cVT_4mEK)lxd?FkMmBXOafZP<6#6 zEh@{LOI=eP^eHN0GnIyh1MGOGUKj`URj()iA{Nu0(&B^ZEv$F3pZ3dL_1uWahf@3${(aaD zw4zUCILEg&sVYu|G90oR@`;))*#i8akp*{QfTo?kFJ4$>x*5GbbhjAlVsj z$w3_E&b8y;yZ7%1Dmo&5_6OLap+bK&@i5ZSUHY1De_x~PYIX|UZ7y25w)TwnS41B1 zn}?`}pX9IEOSPvfrVL+-nQP9kU}{gt!{)Sp!0g~T)D`c|bm_Ac~=(o#}17rC9Z-8rl)jb7hEDTMu^A}syyp8%B2F1RDSHSk)3ORgK z#ZMBz8nh`2y8g1(EZSTlrBJ!DeP>&VgeppvYD=@>2hup8X-!5jJ+Zx8QcWIo^pHCo zjA@e_fjeBU4Y7Vi!7B7$pdD1C3dtM~BEr~Z>Vp}z?`cdG$YBqTQPyn}K6&&3# z+xe!d&D7ngSuB55oT^7V^IW{mZYQsmax5K{noAm(=@?SH)YAWshxw$`z|ErAI=?ul^=Q^;RRI#@_;{>qxc22`|NHA*J2JXlTZThAR!}J0;!Onhq$jVU@ST>ky`tsWG z>6bM~FU_XG&~N*hjB_;|#^(6W(qQcDCl-)hW~%9PS&eMrXD-lF3zc>$N(}|$)#6E+ zMd3FLDT4cs332 z@Tyw52#>wdpB|f|75<*&oqOMS^sflt%^xF8v12}jp;ME#QCh(uP|b$-Kd|)ne~6t5 z&#J>Pk1{9U7eW{dfufw2GVAqZ*i&F@Y>^UW<4jc`8Q(aTP*8gTT^NbmZut zjB;rvVgOEhNgX2hK>c2(hIpK+Yn~)PST$Hdb3Z)WYBc4{KcKNYs>)yDc3=7rj#3@S z1_@^IVQ6K+va=wN-~Dy4oL)9lHzY@uC@Fg;@5vSLI)QY5f9!-4OybshYsSryR|0oq zT-&&VcZbK$xwcTSdl9Cn5+P2xw0L8nZWbT~uA=VjI#*u}v{uV~WftDu31#bPDd`la zJWgT&`IYOtwohd%ixC69kVuF?F&@XHGi^6y3Wjh~Cj2dFb-Qw;@f`D5@CBfFrAA7w z#Ut3X;YXrx>Pc_|wl!fE1jTLk*NI~NfhpfLs8ynjN!N== zjN;MCHzBPV>=}yJ)Cp!cQ*J!r1z8x~_p3uhUvXOUzyc!MW9<-)JV1L97JKs!lDX*8 z4^z`2Vsw>uC^;+kzBW4vICd1Z5ce~Q_|HP5{>L=T{of9oA;dlGMY+`YW?dBtg&4O$ zVtbdJY#*{TASF>$)_DTDZU6$c%!}2n#OAygIp&qvJ#nM|N)Vv%)(;S->!dQNxF$mL zjGYYIiU|U3JMm{IR|<+~UYe^gH4J>5dTu9rd$-*@0a^f#4lma~LwcJOVOOV*jS5_A zLr>4(!lp+`12rpz_fi8jou1kX6EoOt8!(U6u5S{Xx|&OF+;R>87G6Aeu3tx=Iny}l z5IlIoMQ!X5=zK!nA1Com;prKIbv4)=IrfK7s$gh3WC@L1oKxdMl&hng?emsGcy!zP zr_2)yk=wSU{L0d=OgO6P(C56 zqe7i_^%&Ll(at>q$I?k&RN4CppHsUk@f5kwKfVP zQL#G)q?LS%HKbNPkF|a|WP8H~fK%wVze#}4j;?J6e3Yad_|U816Js&Y+SyG)Tw%LZ_U`GC?_JvB zx4hg^rh01tIoO~P9H@p`C#ALV?<_}7PvBkKzU8j}*s*iw-~&EDRaPsZGq~t_eM#*G zFWF)=>Ce{C+0B`+fk1UuzRCwJ*Kk~;RNZBECqiIMcz=21|WKywUgt!)u0 z$+kg3c~^~iXGI3O&B|=fch*JgrtZMDfuIzwn%61%J@L1)97LF5uWIEbuF+YeIC3H+ zP-2-paSY@(N6I1fjbK0UPi*LKCgqvOG|L8>bw%>09-(#`+0~W9tcUJw{k(S$HnW;A z{bxqlIvex0@QLF{V(ogde%gn3TkmTry^f?c_Fdzy016;>H2ASbjJxUy}4Tb=vy{SN>p zbYGUUZlor~&7$p5*#YD8aTL3Efe$PFG;RGss)8T1O{P`g>uB6mep(yCq_9n)(tM=w zD%LSiMFDk1C2&_It-K@q$965UXJ-0MzeI~A>1uVwPI2u{*BEJZtnf|En50>raE5M< zquC4Nl*PrHIZ(8fx_AqGDlI|RMOoBwaBI=~?Zo#wa)i_St~OgDRoi#3E6-zos2w&j zPuVN#Zy@p+20ltvAmfbiNxJg}o7rCI~hM|4D( zv-0cP2JL@i*v5KJjb3R&3jatq>Ze>kGsiy5MHDjGo+8WT5GYp`~;ZLFY^C>Ht_m1*Z zuS~pqVBezP(J)GU8)zYWl0O6|7T&cxGsYVs6Ie*HS(FvR%F7Z$ckEvQM)+Ig^(bej zc>%Cwr(J2)*?ZXRkgDKOP&(q>!hI*^W{T1g(9O_<53q(Y;cN;F0`}6mSa4j{xbHas zr=c#y38phNU6}P9&$vczgk0<=sItu9#G>!lALsXhPtlfiRIo`7s|d8Io^VP5>rLZq zKujMxJ6Ued7yr#56(jjQv(Z1=f;@O`LW~A9#=2g%L z$%3&SDCMj-N1#eAFo$JDtf99gVWYkbSK_i|k+1Xk&D#DAgARS1(bu*aEx7%?@Wyyj zd2=|U|HP!F;F^r%F;fe5RPbtTdxUUQfF(prR+jKO&j-7I7AW2k9!aoI@p-pkPXm0WqPp*?o14OK&Cdw!4cFO5R@-M&R|+0aIUlBHd7DEOU%8jo%h&(Fi27iHdN~t05^?uq zH$gHTs=!@^q|#YYL!o@p99fyO{)?M6R+XX?Q!*rk*T(?tYq&V-Cau5f$JSakN5}Qe z&+I3uAp=d|nM3wE_h@5YBu!{>jX#;n<1$#w`NuV*5_NO%7V+W*APHf7c`*DR>}p@3 z+OOg}g6rD8%lEtw&$^A0Tb4RO%RwycW=e7ko2xI2WHUt>{PZ{l3>tg>@~~xG3|Q zt=tffMOR6bUCE7LcQdWlg}Pp`y%vd$h8CD6IFM@QweYR@th&18IR;WrvjhSB*6|v! zkoD%Nr!t99b^S_8yo(r^FdMS#U%9o}tWG2SC>)Ra6hR{8Oub8s=H#musH31& z&P)VQV$DGRH{$J<86Bd!5~Zx4Y6*#3Yfks1aK??nU9qx{bvt(wi`1S~;n` z>ht&^&`0(T9c}x%5XFgk#Z`y#bQ{7)vP(8*FD?|AK?Jjlb?|xkW}9%@IKsb4<7=9- zf&3;+7^=7>>6TJJ_ z0$FL)GJHw=%p7>c{iT7+(H+frbaEF^esdlg;})63!d7bZ%YHGP6a7VZ4f_9QIJ@{z)`X#rk@E?em{i z$CQe$K5J!QZ5LtoFYR=E#@Bo|#nrdcittC(b!2JgfQiGjpybN=?P6~$_5B+J9dd7-bUC&&vGl4esiNz$ci)}cwJf5q1^Ee*C{>Z?eOaJ~04Fp7_}f@Ps#zQ2=djXk37T;+u6w3}$oJBXbKPizm}* z{}?7ZN$k4+DGLEex$J4at>m2f=b3+~L%6cf*JuY^_Zhk|eg6mC8_0<*=H$a)t`GY? z_Pa)N!k}APM2EK4_*wPDTL>eC{R9i8JRCgi_=sWt7?~~&gf@+kJpfM^B2F=CKz(UP z&hSo6G$$#{@ZRm_eea=r;4dvs!Cmva;(JLk!5Gh@UwKHiO=E%FLjL22F9ZfVB5dbHZPV=8C&nj4#@Q2bN79qznB1>hob+Teg*R3YR-+?l!d1jUD+l z3b7RA2bgNU|IeYXOg~TSPEuY4W_Nke{5?qYa$hrdHSC+*QRZE7#CYXIuhFEf9^je) z16(HgOMa>0@U5}4sJ#YhEat5&>&u3I$6zlZ=>>#82RiD8#*^CyioL20(_N)DQgDF! zb5zqTQ#_L%%nWM_YKCX69Ksp5#O-FLG*8s`O41(95Ny1`EyDzJO-Fa|_P*ft&RKI@ zzd#%N9>{&PpnL@&^d-Nvjg(^a!?SH)&R-siSfwyE(jLr0fMXy<(Q#jUdFwnbX%~c` zo}nCQGU}Y`pQBY>x$a2}(QH!b=wF)E&0RUg;Xvt*nZE{aT+ch<=#n&=0ceU~69%3)ocKm0{N9y51uUa)U1rkZypc#yd%j9ZTtLQCxM(}KmUN4XqQZ5H`!rj3zgTrN@ zGTHSwVn=P|d?kGs1*V@9c%9UkcUm^VQgRT3L=hb^GzyNyn4c9pK#Vpp-Q6cs_(%AT z-0LmKFy)G5DL&6tnKqy-0BAvjN7ulGQW}=Q$_d>3?Qdqg4y^Osx$S?(E>*ZZhn`upR z?AQI}H|6BUldiD@0%m*HHhWN8z18T9N`1l|(v9V~ru6qWZHs)YL_fwEpmV zD8HJwqQPIe`PE*Qx`rLhZO>GqhhFVx^j6Jy!gzgP4;JgE z%8fm7uzLo>pSQPtT&f0>k>8BZzi1zC-8{+9eY1O{ohI1_HGbs5VbJF=v$0+c^{#+1 zl|Puj5}>mNkTI_vGk?Ct)cBx zfeIZH+mSbbyb6faGSmazq)ME*QRIjDyf!H6SMO@dIjSikMfmXZr3l%{2 zBEy~17Nffr>0^=Mt_k_BB>~iyWF$`cEqN4j1y58$dcBC{Jqib_n3!VcIQtfo%cC3~ z@{o=MxF+l%IsEnkC{R{$w`}R%E%ustaZ1;@Kj4}FJ;caKcbU4*4;?6sVahax(arV3 zf5n(Mi9Qp~Rl3GF;O2iVl)$u)D*<{OXxG2|OOz~u?>$3t%>f`7CdOB-;LQ8iBGsoZ zlrO!eYSgMN+yuCpJhKH?%gk>J37^>%uV3y^e`QliU$VJ>45EMd%WqpS?TiH zn)iS9FAuVq-$Z%6EoV)IURpKa<&Oohle{tANBvB2DsuUUpnYJ2%x&-Tvqx~dEhV|U zoU|7Ta_&cHkn@3M;F9GwDCLpcfE7+!Feke!?XpQ8R25j5YJ3+nEU-iQ;gwdw}32hXi&ToWXvXntlPucgUfVQ_om)`Pl2 zYS2506Z_q+w;D7v7zhGzDl0DtTdt^PCp)Wa_rdwOsk4IreFDS~5=lV)q3LL$~ zxdGPaad_#&nRDjkfpVq(d4*%^jsS8?wj$sxn>V^J`aH*8wx%p-pksy6=`PaWAQh z0CsCQP$bX$0w3C=n~dE&SX+LCJpqR9}FC{?-nDS>k`ObyGwM}j<79_Xaw?u^*Y^z^j%qo4a zE!G%oz&EBV({xHcpmY(aY9h9=W$d+(79`)&ut`v3sjS27cL|E@`e)djK98N($%uG* z*j;c)d6&FI$UVk5&M zPR9hjJ;4R7mwTQjS3=ZY()>h^$6@bmt?j3$hCP}FXBqv_e|#Rl@9*mrBPxAE+T-V3 z=}6S-Gn9+V7&gW-9{}j0AJf=ZGN{Cq>-#AjFbgddfu)9(>6Cw5LdZRpIr2pjp~Odp z?p|YHnmMJLKtjf;e#w|J2;%_WPJjI}CY#UK~Y z@p}{Mg{}XMcZ`}ph#aHjIP)G`eGZSBt==p+^r@Kdxx)V078~>6PWd54XV?^F(LcE+ zOgTIBw*~X_!h7DuwtRF%)glA( zp(iR~Q%cNXMAjqakVDQYEh5L6<*-Qy9V{N_CpjiLlN=Vakz`nm!j!Qz48t(YZ0!3{ z{l3@l`u%q8kL|iX$M@%bzwX!ly6?~Z7C!j7LBx?s&g0-k@#eQ12k1TgvNOy* zkCe&q%{~5yO`jix_C33AG_zOGWzw8xo7BB@V6T&d&nNWW|1Zsf4)k} zT?8WiDwoB~#6NKKb25k{ooj$5tuB5Jj0V!?hiV}}+Wf9~qozlH?V?CNG1-E{e z-`V{2uiTlYAf>(Yu321bAv z|DeUc-h*3AsDv-OUAH*&OL`5&Et7tYkl1KzSf|wrD2$-GNdUws&QlnQ!Xgjf|Btt~ z(*@=q<}GJ9Tjtq)`_@t8sW4gKjcCu^QM?EXX<8c-oo)Naw@TziT(7yjTyx8xTMy=S zqxaK2^}~3Kh#JM$fW@UgDmf`8mc`DCwQL`kZY&6{9(RAH&U{v;zgy17WAA z&K|LQOiP48@lAX=7ZRU@V7F6Oy;YX{Gf!z%B3+Tb1h;NA@SQi<0`` z3t!vc70_B2J{2e?a7b$-DusvKb%@&--%`=oP}3~T#Qiq^%X|s3Ku)53M@yXq!~7@8 z3`p7OILi^WYcKl|05%#oqVq&^jh?r_GQ#@yFF@>H!?3ILOgH|xZvaL1*!A@~vQlEa zKRwZc6M1I)D)a7t9*5d>+=(hyhJlurncrSN0uq9Ji+rQK`0Yy&Yv?tS7yw^ZGuKK~ zv1n@pk}06Ycb)&?FdM6HkQLPYp_X=Ye`6nvP_{>nHW?{_&%kl84VjvRmb zcr;qd%PWmtL0T*ozBIRv;ts`|_0!J3;gOX>p1xd<`aWe8wx;93cc}|!#|fLcR!^p< zt!DvQYi9}c1A!7YMQk;+b1s~}m%ioH)x#UpP3GHh0yXOAy!kDqcFuZV$wJWurGLGY z>OHyw%BfkTaKW2~^$!SsPlQpDror0VhPhs4v?(9MvZ4fFM#A_}jl8{K*A|4a@kXk< zvJAoOq}@@-2#aA-@MV%<{Bf{9(saE<6PsvpN=$5Cw?v`zb&~qb`^p<>g?|TTWd_Bd z{zE&WqM#czH&CUw@yvEM6&8{$?9AtlG1ev$?^sE8yj)JLfCtYDL43(1V^UZ+Gf+y< zRZd6GCJR4`7R|zzW_FE@wfk}|_2(zW`fG1r^3tJ=$!15mDrT0VLm38cKx@4@M4xqe z=k`f|hz>29^-LV2@kHPBnr2ywZphqx(HbDMjT{>h_O_-Ao29I*NL#0$%}&!E#Fr4u z7n4|CMwhFK*yjQL_IlVz`(v-@D7lHD>XP+p%G#n!M;8QWgkh}mR7a~Uytx4}rN=N_ zA>GT~KcTPtX7mF;OFv|8>ih&tQp~tTBa&0oM0oIlUBK<7J>_gt z?Pt^Ms)dZ%E$ive!bN}ouRPHh?S6FF(GSFj)!fn8NuV2Nqi|1ily-2@QN2%ZT~*!y zT(Ry(M|I3qZ9HYN+Th`DM}1P4KiAPmN%9Cah#uNUlAzS2Z>{JUu-?9N zI*?lcb6l>m;55`Anf=wP4|u->tZV$@izMD^KJQrL(89xczUv`McdCHJ4P{%Wn7Uij z&Oh0O(th$NqHb|EB1({lh7F`Hp@z?FT>ibW^kZW)Zv0pdarJ9^0QyMS)tlv&&R--Z z@|&X^a%lHIzUw{f$T|AodQ?LVacxQ@)D+E01}|rv)cVbOZofI*mr1Z%Z){vy?raPn zlN5y-NdhuPo>iy5k?23JL4Z$Sh4eW?JBw?RkZaqyK5kc8Qi6MI$uP^gDya z?seiK(6SHsG?{#DXybDidLE|%YkY8{<~7;%SfJI%T}Wg94JCQaeT!A%kzaNH`ksGs z&ZV9vFj-slYN)E>E*PyAh(0#1WS0w*M6-?SWfktpH5YFJpy>7Z&M)>hLG?~FqMbco zoDeA>7}3`QL;}$~4?DLCEFm_n{Y2+uB)E2x9W0p}-)U1(X`H=-+j)J_QzX*|_kQKz z8NwVd3s~0?O=maq8rNr)uuJ%;r8F4tg;%zoXqp-C>UgNmsY3DvvHS8xyLcgk^#htY z{la!TWH7RU&v`&oGB{q112lJ!vQ9Vxy(JfQZ3GMlASfQwn>XSx|1ux1}DA>)|xX~;PJc0~0>*8AL) zp5jdA6$a|+j66#n*ccyn*yEo8Y+JY9U4}q)pDFU^qx-}FNz+{Hx|cJz@+_Y}snfVL zB=TE8USAMMbkzxt8-2wnUQX*FR?b}zP?Us>uyv!gK`VU@p$-%Bd@XZ-hGN!9>ZiC< z4hKPx-jwG=n0aP)uGJGP*|1x}pqB`hCk|cd>N4f+A@%Yw|R@aiO-*f0!PXi9Z*%Kq(&- zV9?I!pgF-}K{=b9B#LSfAtNAH-!wYjyiUeGYM12BI}xOX za|YS!eu@uPX(85S)fp+h%|kMjA!9kc*}C%A+{q-!b8VMPA;Q{I1^Jb5+cw6HjK9YA2AcN{D1V^5ipM#%GV&>yvN)L>7^S@+>V%8zM5z`4KWhV_yFFBL-?P5|SDe zXMu@F#;l>Ekzrgf*y+`GqvyIpkYOmDzs4~;`;W{|4tmzP?B&yY$6$7QS<(S?>(29F zUi}HXXbDE9I(-QKfwOh}3774?er~E&?~2>(rxmkbDd4vUb5kQIr9Pz8#UZ$((F)I! zdCKG83gvhmJgDSe!aspJnx`Qem%Ijyi>+A_ST1jVO%A3$w6^0pOAEGN?|87j%~cMM4l&^`35Cq5T>s25WSc6bl4Eh%6|~o^e_CdvVNg-vkD{sGoIG zX$qSyHvO3+`6fz!B_ec4|g$a?kFrAo7fqEK9fJ3A^c}QDX)(#swZp8w3Er6u~mz&p5x?3n;7Lt=a`$TWvmt2NCJc*hVe zsB5~MJm#`tIS1)FsP&U*4stRr=k%*HFjKjk*jFUpakgTw@Tk;yoQGDKTSCySO#;in z)O#HDe`L4u`dpi`J;>f7UJu<97}*~Ni+dDzS3nQC58Bw9eIJUyF;shz5aF*9T=}m2 zbj>vITF0<$d4VkKUO};_ESR{zW#k`lrsW zpVFu~2C?~l3H@Ve4sR=R~XM|p#* zSyn?`|9)pUf?rx_90x@q8SFE`8b!>hJeFnB^w5Z#>S# zn~A81#$q4Xzj8P?Hb9 z)3*Xu#RNd|a1Jx_vVxa^aW0=xIWxd;ti%MzS)}~MdOwoMtcTF!N8j6TkDnpe~%BXmIpff!*4$n>dl#9u$v=p zZ;oK>LmibE;omp-oj*FMQ5mg;G$B@`4C`2DU3_l_)BZx#?!}*lXc&9EwR#?-+jz;% zy6Van>8~mzFj-8Myo`DXx?CcI znd$`hxvSZ_9#EGyOmk*PFZYXi#QHhcJMskGLFp10e_oR#yCEsim#7k2>vWQIGFxu5 z<{4FYwI+d@ddBtaqx08+fd7M5zNzfjF~@rRL!VEnHt?d`Q5hpQD_Vn{QndCkPdP!F zpq$QuMU~u{;t}GDRq@iIN;-73HHL|z89;-AR3>Xb0$8by2#($xbILZ=3$77^np z&~gsjc$QU8Mbh5^h8Ys)i&WVCk#)U~qRJ95yQx7(!Wd~#^4Mj2-9@c;b{bCiF@`)* z#w-tNlnP~8%%<#uFqRd=*;{UsA~0hodI!#?|Gj?AxWTzuV#Mi9abAg`<+DW1(vn)v z@j9GXS5O91muv%XGEbH}2kXDWkmjZKujB-Oq~&Z0QXmI= zj0>J4<+D9Ho@o6fKp3dULfDm$jbQMY+K7W7Lrm$=5U}Kr^}a>&mM#0GmUI2BI{Q~2 z9EOPk5+cBJTy~k<2q|^Wkb5-myNmO9*^qs3mb#l)X>5LtRM;IujC{#bO9s-Q#1&qd z1&-BF20MDCA$q4}4zSlVZQ*X1{sZ&;PO$Q+2C{6J0R=|R@A~S7#Uee(&^V;4Y&HW% z4a!HHsYcR^JtSiS<1Ch{-C84%_9S+2oQn{3_;|1HVdKZ*#3zl93>O_WS_*1kyu`;! zVA{{<`v?y|gx8^it5Mi>Q76N9J3}*j>XzmW&W%P~`N~mSxt(E|b(E|YtquiTD>(P_ z!YZQ0Ws$yxuoL1rZVOIUu5r^u|M2R+vA^7p+euI6v2JJC0h(hco|4NaL>LODgzuT z2@}U`h$H1UR|MoN@yQrGhSnNYkZLicldV7>+`O70_oBP-dFQTB)qxWESra9nlz3ES z#olOl6`;ZGuMjHuV?^=1?tv-W2cu5dVAFqJ z*^H43Dgn-VY&!Rn(=R2k1hnFL-AVi$Y>}W{N_#GP+}w~~L-c;z5OnMBx)x^TUwbf( zIs~!{q3(;%1Wu;Hy0l+u}0PxBI7lW-O^B zVgf9i^o8am9;wlS2Yn;OzjGXA%hq(&a(3dy*~$CEsMhf0o+J29sJ}MjLL~NtG5|O7 zh!z&_Jr-NGNpD}hEsvx_7$vJoUP;+eNA5Atp_YyL)wVr$p2mO$q}iqPZOf#$?%(wo zHHitZA^M^G64Y{Kc5=_WY91uscboFya*#oC&s0?ZhIt;J=TcDjei@N>Uq@jlEcxN8 z;{e`3QEIX1Uk~|l3vrBj;Vy6O`--*dbmcP z0y=1e!AnK7$IX#QIf7#jHEdd?gHHhN<}bu~5a38u(Zer+-z`voM>Y!}DrL@X_{~E4 zIc^L0uG$1y?SRJqoy4uHw*q&e0QaUFPaH;HgH-ypub};#JjRKJu?JOd_po2%EQc8x zTCZ>$SKkn?`g?fJgO)1&v~OPYu&%WXkc}ArvRa-H>2jtRb2S>aulLjLsr*1PIB$~B zRO@-2bYQSd+;2F|vGO zyKHp>1pSDuT@x-l!H=>px#6JX%m#Lo8emu?N?=@7O2U~`gxWzSw4XAcDSU)s|&ovE`$QIC1Ic_z_XuqiE+lydN&UIydm*Ar3iTdVXwe zH6k4#T`v{ny`WAeQrgVx4_$hx^<}?iUN0-3qBV8HCjpUSb0^b}@{iQ8zNxSj6-4?W zuhRfPOj^$oYLFjHGlN;L&O#*j1i{GjZfMj-EP6TnW9gX9@n!;Q?fXw+|3z6|cY)6_ zQ}B4_eIzKLm@gP-pKn9AnxJD?rg*V4>dK|smn^4|w|quX{dLN=DH=zkSOa>VRSF^Y zaLC*UyB=3sfs#_?C2B1^+9dmp+G|i$Nj*0;@Yo%m%1mexyy|43zx{g5toP%!K-}+j zA|AlPqL_P-uAp)RNZ~P(4zNF6@;8b%h^6b)gUncxuk-=5f+*U|tHh|5%+ze#gv=_H9@eX%}i^AaL z+4m`i1-OfEIwp!89O9io=8}ll5@5h8KLV7i7`JLbuJJST74T=Ks%~L$Nc!6fz;7>X z*cj`*{Yv?vl55@fsYyw~{=0ts7cSvB*^FGZ`K|b^0>`qPv2|&Wv}EhD6gl7a^Iv#7 zSPtP&7L5*HkR9xDW4-GrWM3>wa)%eCuOwZ8_oE=l!C07LSCFfuWi!fHDlVGcG_R{P z&mWT~qld1i+5jCB9bhf9MFFS1A_t!(iY!VA|>P=41g$h>HPMMIZ6Iepj9l(y{{zv4@vn<6={6x@N>pb;-BKq2&OR2YlP#Zj#?Km@DOk@4%?l z$+Y;Rx?9lcj*z|RHHBM zIN|shVc5Thj2ZJa{q$6>2^Jpn;dWJ)YK-o6Wo#&3bXnS)TGVb^gz zK5)zpoCjj#29Wk~k+na}(fR$#MzOr2QrXrFJ6@kvRpaLZ==z(gXf5jD=Y zJi@AEI>YNYm))A7{(T*7!3vHoEPOS9ra^IER0mC9qMCZQYc7%T(&JacgTE-o6HAt=>k3Edgs&Gslc&`4fMvI@z{_TxoF7p& z5obz)2(@|updf%Ih7s+wnqr~XPi;Vwp(O)l;bH3>Q#88*Rc34E<3o>9^1$nkoga2b ziX1)$lXLD8yVNR)n(UU7g zr(pSH8>4Iw&;;QBU5C$t0jL8A-{6wWnEcgnjSQv(F9N_4f0%5n0XZdR!Av@pcoOY0 z0dz`=srIOJPTtdHYh!&S;fG%M<-raq+Q)iqirrvaVofKZtea=#6l8u?F^OG6+3ovXURJ^q$zBut_8!!9dhdSsjZMwXEYFvl HT)Y226{@@8 literal 0 HcmV?d00001 diff --git a/subjects/pick-and-click-dom/README.md b/subjects/pick-and-click-dom/README.md new file mode 100644 index 00000000..6105452c --- /dev/null +++ b/subjects/pick-and-click-dom/README.md @@ -0,0 +1,44 @@ +## Pick & click + +### Instructions + +Today, you're gonna create your own color picker. + +Write the function `pick` which turns the screen into a `hsl` color picker, varying the `hue` and `luminosity` of according to the position of the mouse, which: + +- changes the `background` color of the `body`, so the `hsl` value is different on each mouse position on the screen: + - on the axis X, the hue value has to vary between 0 and 360 + - on the axis Y, the luminosity value has to vary between 0 and 100 +- displays those 3 values using the `text` class: + - the full `hsl` value in a `div` with the class `hsl` in the middle of the screen + - the `hue` value in a `div` with the class `hue` in the top right corner of the screen + - the `luminosity` value in a `div` with the class `luminosity` in the bottom left corner of the screen +- copies that value in the clipboard on click +- displays two SVG lines, with respective ids `axisX` and `axisY`, following the cursor like so: + - the axisX has to set the attributes `x1` and `x2` to the mouse X position + - the axisY has to set the attributes `y1` and `y2` to the mouse Y position + +> Here is how a hsl value is formatted: `hsl(45, 50%, 35%)` + +> Use `Math.round()` to round the values + +### 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) +- Take a look at the [HSL section](https://developer.mozilla.org/en-US/docs/Web/HTML/Applying_color) + +### Files + +You only need to create & submit the JS file `pick-and-click.js` ; we're providing you the following file to download (click right and save link) & test locally: + +- the HTML file [pick-and-click.html](./pick-and-click.html) to open in the browser, which includes: + + - the JS script which will allow to run your code + - some CSS pre-styled classes: feel free to use those as they are, or modify them + +### Expected result + +You can see an example of the expected result: +[![video](https://img.youtube.com/vi/eE4eE9_eKZI/0.jpg)](https://www.youtube.com/watch?v=eE4eE9_eKZI) diff --git a/subjects/pick-and-click-dom/pick-and-click.html b/subjects/pick-and-click-dom/pick-and-click.html new file mode 100644 index 00000000..e7d9220e --- /dev/null +++ b/subjects/pick-and-click-dom/pick-and-click.html @@ -0,0 +1,86 @@ + + + + Pick & click + + + + + + + + \ No newline at end of file diff --git a/subjects/pimp-my-style-dom/README.md b/subjects/pimp-my-style-dom/README.md new file mode 100644 index 00000000..03755fcc --- /dev/null +++ b/subjects/pimp-my-style-dom/README.md @@ -0,0 +1,55 @@ +## Pimp my style + +### Instructions + +Check out that button on the HTML page: + +```html +
+``` + +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 -->