diff --git a/.gitignore b/.gitignore index 6e1a3738b8..f1c4d80d7f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules/ *.log haters/ +.idea/ +.tern-project diff --git a/.imgbotconfig b/.imgbotconfig new file mode 100644 index 0000000000..749b62455a --- /dev/null +++ b/.imgbotconfig @@ -0,0 +1,4 @@ +{ + "schedule": "daily", + "aggressiveCompression": true +} diff --git a/01 - JavaScript Drum Kit/README.md b/01 - JavaScript Drum Kit/README.md new file mode 100644 index 0000000000..63de406248 --- /dev/null +++ b/01 - JavaScript Drum Kit/README.md @@ -0,0 +1,37 @@ +# JavaScript Drum Kit +The initial tutorial shows how to create a music keyboard by adding `keydown` +events on `audio` elements. My own contribution is adding beat repetition +functionality. + +![Drum Kit Presentation](../assets/img/01%20-%20JavaScript%20Drum%20Kit.png?raw=true "Presentation Image") + +## How to use + - use the displayed keys to play the sounds + - click on the boxes to set a rhytm, adding repetition on the first, first two, + or all three measures (each with a distinctive color) + +## Learning notes +Some concepts taught: + - ES6 `template literals` + - `transitioned` event + - `audio` element API + - use of the `data` attribute for custom element data + - use of `querySelector` to get a `NodeList` + +```javascript +// remove the event class after transform is finished +keys.forEach( + key => key.addEventListener('transitionend', e => { + if (e.propertyName !== 'transform') return + + ['playing', 'playing-1', 'playing-2', 'playing-3'].forEach( + c => e.target.classList.remove(c) + ) + }) +) +``` + +```javascript +// detect keydown key code +document.querySelector(`div[data-key="${e.keyCode}"]`).classList.add('playing') +``` diff --git a/01 - JavaScript Drum Kit/index-FINISHED.html b/01 - JavaScript Drum Kit/index-FINISHED.html deleted file mode 100644 index 1a16d0997c..0000000000 --- a/01 - JavaScript Drum Kit/index-FINISHED.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - JS Drum Kit - - - - - -
-
- A - clap -
-
- S - hihat -
-
- D - kick -
-
- F - openhat -
-
- G - boom -
-
- H - ride -
-
- J - snare -
-
- K - tom -
-
- L - tink -
-
- - - - - - - - - - - - - - - - diff --git a/01 - JavaScript Drum Kit/index-START.html b/01 - JavaScript Drum Kit/index-START.html deleted file mode 100644 index 4070d32767..0000000000 --- a/01 - JavaScript Drum Kit/index-START.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - JS Drum Kit - - - - - -
-
- A - clap -
-
- S - hihat -
-
- D - kick -
-
- F - openhat -
-
- G - boom -
-
- H - ride -
-
- J - snare -
-
- K - tom -
-
- L - tink -
-
- - - - - - - - - - - - - - - - diff --git a/01 - JavaScript Drum Kit/index.html b/01 - JavaScript Drum Kit/index.html index 246639f990..f273166dd9 100644 --- a/01 - JavaScript Drum Kit/index.html +++ b/01 - JavaScript Drum Kit/index.html @@ -1,49 +1,112 @@ + JS Drum Kit - - +
-
- A - clap +
+
+ A + clap +
+
+ + + +
-
- S - hihat +
+
+ S + hihat +
+
+ + + +
-
- D - kick +
+
+ D + kick +
+
+ + + +
-
- F - openhat +
+
+ F + openhat +
+
+ + + +
-
- G - boom +
+
+ G + boom +
+
+ + + +
-
- H - ride +
+
+ H + ride +
+
+ + + +
-
- J - snare +
+
+ J + snare +
+
+ + + +
-
- K - tom +
+
+ K + tom +
+
+ + + +
-
- L - tink +
+
+ L + tink +
+
+ + + +
@@ -57,27 +120,7 @@ - - + + diff --git a/01 - JavaScript Drum Kit/script.js b/01 - JavaScript Drum Kit/script.js new file mode 100644 index 0000000000..806dd7ec7d --- /dev/null +++ b/01 - JavaScript Drum Kit/script.js @@ -0,0 +1,59 @@ +const beatPeriod = 1000 +const tempoKeys = (new Array(9)).fill(0) +const keys = Array.from(document.querySelectorAll('.key')) +const audios = Array.from(document.querySelectorAll('audio')) + +let beat = 1 +let tempo = () => { + tempoKeys.forEach((key, idx) => { + // if the current key is set, play it if it matches the beat + if (key >= beat) { + keys[idx].classList.add('playing-' + beat) + audios[idx].currentTime = 0 + audios[idx].play() + } + }) + + beat = beat % 3 + 1 +} + +Array.from(document.querySelectorAll('.key-container')).forEach( + (container, idx) => container.addEventListener('click', () => { + const spans = container.querySelectorAll('.key-repeat span') + let i = tempoKeys[idx] + + // update key + if (i !== 0) { + spans[i - 1].classList.remove('active') + } + + if (i !== 3) { + spans[i].classList.add('active') + // set key + tempoKeys[idx]++ + } else { + // reset key + tempoKeys[idx] = 0 + } + }) +) + +keys.forEach( + key => key.addEventListener('transitionend', e => { + if (e.propertyName !== 'transform') return + + ['playing', 'playing-1', 'playing-2', 'playing-3'].forEach( + c => e.target.classList.remove(c) + ) + }) +) + +window.addEventListener('keydown', e => { + const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`) + if (!audio) return + audio.currentTime = 0 + audio.play() + + document.querySelector(`div[data-key="${e.keyCode}"]`).classList.add('playing') +}) +window.setInterval(tempo, beatPeriod) diff --git a/01 - JavaScript Drum Kit/style.css b/01 - JavaScript Drum Kit/style.css index 3e0a320b37..dbb965b841 100644 --- a/01 - JavaScript Drum Kit/style.css +++ b/01 - JavaScript Drum Kit/style.css @@ -1,50 +1,109 @@ +:root { + --color-primary: #FFC600; + --color-secondary: #00A0FF; + --color-ternary: #5CF2E8; + --color-quaternary: #F71E35; +} + html { font-size: 10px; - background:url(http://i.imgur.com/b9r5sEL.jpg) bottom center; + background: url(http://i.imgur.com/b9r5sEL.jpg) bottom center; background-size: cover; } -body,html { + +body, html { margin: 0; padding: 0; font-family: sans-serif; } .keys { - display:flex; - flex:1; - min-height:100vh; + display: flex; + flex: 1; + min-height: 100vh; align-items: center; justify-content: center; } .key { - border:4px solid black; - border-radius:5px; - margin:1rem; + border: .4rem solid white; + border-radius: .5rem; + margin: 1rem; font-size: 1.5rem; - padding:1rem .5rem; - transition:all .07s; - width:100px; + padding: 1rem .5rem; + transition: all .07s ease; + width: 10rem; text-align: center; - color:white; - background:rgba(0,0,0,0.4); - text-shadow:0 0 5px black; + color: black; + background: rgba(255, 255, 255, 0.6); + text-shadow: 0 0 .5rem white; + cursor: pointer; } .playing { - transform:scale(1.1); - border-color:#ffc600; - box-shadow: 0 0 10px #ffc600; + transform: scale(1.1); + border-color: var(--color-primary); + box-shadow: 0 0 1rem var(--color-primary); + background: var(--color-primary); + opacity: 0.6; +} + +.playing-1 { + transform: scale(1.1); + border-color: var(--color-secondary); + box-shadow: 0 0 1rem var(--color-secondary); + background: var(--color-secondary); + opacity: 0.6; +} + +.playing-2 { + transform: scale(1.1); + border-color: var(--color-ternary); + box-shadow: 0 0 1rem var(--color-ternary); + background: var(--color-ternary); + opacity: 0.6; +} + +.playing-3 { + transform: scale(1.1); + border-color: var(--color-quaternary); + box-shadow: 0 0 1rem var(--color-quaternary); + background: var(--color-quaternary); + opacity: 0.6; } kbd { display: block; - font-size: 40px; + font-size: 4rem; } .sound { font-size: 1.2rem; + font-weight: 600; text-transform: uppercase; - letter-spacing: 1px; - color:#ffc600; + letter-spacing: .1rem; +} + +.key-repeat { + width: 11rem; + margin: 1.4rem; + font-size: 0; +} + +.key-repeat span { + height: 1.2rem; + width: 3.66rem; + display: inline-block; +} + +.repeat-1.active { + background: var(--color-secondary); +} + +.repeat-2.active { + background: var(--color-ternary); +} + +.repeat-3.active { + background: var(--color-quaternary); } diff --git a/02 - JS + CSS Clock/index-FINISHED.html b/02 - JS + CSS Clock/index-FINISHED.html deleted file mode 100644 index 220faf779d..0000000000 --- a/02 - JS + CSS Clock/index-FINISHED.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Document - - - - -
-
-
-
-
-
-
- - - - - - - diff --git a/02 - JS + CSS Clock/index-START.html b/02 - JS + CSS Clock/index-START.html deleted file mode 100644 index 240705d8fe..0000000000 --- a/02 - JS + CSS Clock/index-START.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - Document - - - - -
-
-
-
-
-
-
- - - - - - - diff --git a/02 - JS + CSS Clock/index.html b/02 - JS + CSS Clock/index.html deleted file mode 100644 index d5c9ec9596..0000000000 --- a/02 - JS + CSS Clock/index.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - Document - - - - -
-
-
-
-
-
-
- - - - - - - diff --git a/02 - JS and CSS Clock/index.html b/02 - JS and CSS Clock/index.html new file mode 100644 index 0000000000..adf94efbd8 --- /dev/null +++ b/02 - JS and CSS Clock/index.html @@ -0,0 +1,23 @@ + + + + + + JS + CSS Clock + + + + + +
+
+
+
+
+
+
+ + + + + diff --git a/02 - JS and CSS Clock/script.js b/02 - JS and CSS Clock/script.js new file mode 100644 index 0000000000..cc3efe55ac --- /dev/null +++ b/02 - JS and CSS Clock/script.js @@ -0,0 +1,22 @@ +const secondHand = document.querySelector('.second-hand') +const minsHand = document.querySelector('.min-hand') +const hourHand = document.querySelector('.hour-hand') + +let setDate = () => { + const now = new Date() + + const seconds = now.getSeconds() + const secondsDegrees = (seconds * 6) + 90 + secondHand.style.transform = `rotate(${secondsDegrees}deg)` + + const mins = now.getMinutes() + const minsDegrees = (mins * 6) + (seconds / 10) + 90 + minsHand.style.transform = `rotate(${minsDegrees}deg)` + + const hour = now.getHours() + const hourDegrees = (hour * 30) + (mins / 2) + 90 + hourHand.style.transform = `rotate(${hourDegrees}deg)` +} + +setInterval(setDate, 1000) +setDate() diff --git a/02 - JS and CSS Clock/style.css b/02 - JS and CSS Clock/style.css new file mode 100644 index 0000000000..efd51e3051 --- /dev/null +++ b/02 - JS and CSS Clock/style.css @@ -0,0 +1,61 @@ +html { + background: #018DED url(http://unsplash.it/1500/1000?image=881&blur=50); + background-size: cover; + font-family: 'helvetica neue'; + text-align: center; + font-size: 10px; +} + +body { + margin: 0; + font-size: 2rem; + display: flex; + flex: 1; + min-height: 100vh; + align-items: center; +} + +.clock { + width: 30rem; + height: 30rem; + border: 20px solid white; + border-radius: 50%; + margin: 50px auto; + position: relative; + padding: 2rem; + box-shadow: + 0 0 0 4px rgba(0, 0, 0, 0.1), + inset 0 0 0 3px #EFEFEF, + inset 0 0 10px black, + 0 0 10px rgba(0, 0, 0, 0.2); +} + +.clock-face { + position: relative; + width: 100%; + height: 100%; + transform: translateY(-3px); + /* account for the height of the clock hands */ +} + +.hand { + width: 50%; + height: 6px; + position: absolute; + top: 50%; + transform-origin: 100%; + transform: rotate(90deg); + transition: all 0.5s cubic-bezier(.22, .78, .44, 1.54); +} + +.hour-hand { + background: red; +} + +.min-hand { + background: black; +} + +.second-hand { + background: #DDDDDD; +} diff --git a/03 - CSS Variables/index-FINISHED.html b/03 - CSS Variables/index-FINISHED.html deleted file mode 100644 index 9401d7b339..0000000000 --- a/03 - CSS Variables/index-FINISHED.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - Scoped CSS Variables and JS - - -

Update CSS Variables with JS

- -
- - - - - - - - -
- - - - - - - - - - diff --git a/03 - CSS Variables/index-START.html b/03 - CSS Variables/index.html similarity index 60% rename from 03 - CSS Variables/index-START.html rename to 03 - CSS Variables/index.html index bf0f33e3ba..0f636fde03 100644 --- a/03 - CSS Variables/index-START.html +++ b/03 - CSS Variables/index.html @@ -9,13 +9,13 @@

Update CSS Variables with JS

- + - + - +
@@ -26,11 +26,20 @@

Update CSS Variables with JS

misc styles, nothing to do with CSS variables */ - body { - text-align: center; + :root { + --base: #ffc600; + --spacing: 10px; + --blur: 10px; + } + + img { + padding: var(--spacing); + background: var(--base); + filter: blur(var(--blur)); } body { + text-align: center; background: #193549; color: white; font-family: 'helvetica neue', sans-serif; @@ -42,18 +51,12 @@

Update CSS Variables with JS

margin-bottom: 50px; } - a { - color: var(--base); - text-decoration: none; - } - input { - width:100px; + width: 100px; } - + diff --git a/03 - CSS Variables/script.js b/03 - CSS Variables/script.js new file mode 100644 index 0000000000..b31f27bc72 --- /dev/null +++ b/03 - CSS Variables/script.js @@ -0,0 +1,11 @@ +const inputs = document.querySelectorAll('.controls input') + +var updateHandler = function () { + const suffix = this.dataset.sizing || '' + document.documentElement.style.setProperty(`--${this.name}`, this.value + suffix) +} + +inputs.forEach(input => { + input.addEventListener('change', updateHandler) + input.addEventListener('mousemove', updateHandler) +}) diff --git a/04 - Array Cardio Day 1/index-FINISHED.html b/04 - Array Cardio Day 1/index-FINISHED.html deleted file mode 100644 index f68d8c3545..0000000000 --- a/04 - Array Cardio Day 1/index-FINISHED.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - Array Cardio 💪 - - - - - diff --git a/04 - Array Cardio Day 1/index-START.html b/04 - Array Cardio Day 1/index-START.html deleted file mode 100644 index 6e28e357d0..0000000000 --- a/04 - Array Cardio Day 1/index-START.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - Array Cardio 💪 - - - - - diff --git a/04 - Array Cardio Day 1/index.html b/04 - Array Cardio Day 1/index.html new file mode 100644 index 0000000000..85f8620cad --- /dev/null +++ b/04 - Array Cardio Day 1/index.html @@ -0,0 +1,11 @@ + + + + + Array Cardio 💪 + + +

Psst: have a look at the JavaScript Console 💁

+ + + diff --git a/04 - Array Cardio Day 1/script.js b/04 - Array Cardio Day 1/script.js new file mode 100644 index 0000000000..e0ec76fef6 --- /dev/null +++ b/04 - Array Cardio Day 1/script.js @@ -0,0 +1,104 @@ +// Get your shorts on - this is an array workout! +// Array Cardio Day 1 + +// Some data we can work with + +const inventors = [ + { first: 'Albert', last: 'Einstein', year: 1879, passed: 1955 }, + { first: 'Isaac', last: 'Newton', year: 1643, passed: 1727 }, + { first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 }, + { first: 'Marie', last: 'Curie', year: 1867, passed: 1934 }, + { first: 'Johannes', last: 'Kepler', year: 1571, passed: 1630 }, + { first: 'Nicolaus', last: 'Copernicus', year: 1473, passed: 1543 }, + { first: 'Max', last: 'Planck', year: 1858, passed: 1947 }, + { first: 'Katherine', last: 'Blodgett', year: 1898, passed: 1979 }, + { first: 'Ada', last: 'Lovelace', year: 1815, passed: 1852 }, + { first: 'Sarah E.', last: 'Goode', year: 1855, passed: 1905 }, + { first: 'Lise', last: 'Meitner', year: 1878, passed: 1968 }, + { first: 'Hanna', last: 'Hammarström', year: 1829, passed: 1909 } +] + +const people = ['Beck, Glenn', 'Becker, Carl', 'Beckett, Samuel', 'Beddoes, Mick', 'Beecher, Henry', 'Beethoven, Ludwig', 'Begin, Menachem', 'Belloc, Hilaire', 'Bellow, Saul', 'Benchley, Robert', 'Benenson, Peter', 'Ben-Gurion, David', 'Benjamin, Walter', 'Benn, Tony', 'Bennington, Chester', 'Benson, Leana', 'Bent, Silas', 'Bentsen, Lloyd', 'Berger, Ric', 'Bergman, Ingmar', 'Berio, Luciano', 'Berle, Milton', 'Berlin, Irving', 'Berne, Eric', 'Bernhard, Sandra', 'Berra, Yogi', 'Berry, Halle', 'Berry, Wendell', 'Bethea, Erin', 'Bevan, Aneurin', 'Bevel, Ken', 'Biden, Joseph', 'Bierce, Ambrose', 'Biko, Steve', 'Billings, Josh', 'Biondo, Frank', 'Birrell, Augustine', 'Black, Elk', 'Blair, Robert', 'Blair, Tony', 'Blake, William'] + +// Array.prototype.filter() +// 1. Filter the list of inventors for those who were born in the 1500's +const filteredInventors = inventors.filter(i => i.year >= 1500 && i.year < 1600) +console.log('Filtered inventors') +console.table(filteredInventors) + +// Array.prototype.map() +// 2. Give us an array of the inventors' first and last names +const mappedInventors = inventors.map(i => `${i.first} ${i.last}`) +console.log('Mapped Inventors', mappedInventors) + +// Array.prototype.sort() +// 3. Sort the inventors by birthdate, oldest to youngest +const sortedInventors = inventors.sort((i1, i2) => i1.year - i2.year) +console.log('Sorted inventors') +console.table(sortedInventors) + +// Array.prototype.reduce() +// 4. How many years did all the inventors live? +const reducedInventors = inventors.reduce((acc, i) => acc + (i.passed - i.year), 0) +console.log('Total age', reducedInventors) + +// 5. Sort the inventors by years lived +const ageSortedInventors = inventors.sort((i1, i2) => i1.passed - i1.year - i2.passed + i2.year) +console.log('Inventos sorted by age') +console.table(ageSortedInventors) + +// 6. create a list of Boulevards in Paris that contain 'de' anywhere in the name +// https://en.wikipedia.org/wiki/Category:Boulevards_in_Paris +const boulevards = [ + 'Boulevards of Paris', + 'City walls of Paris', + 'Thiers wall', + 'Wall of Charles V', + 'Wall of Philip II Augustus', + 'City gates of Paris', + "Haussmann's renovation of Paris", + 'Boulevards of the Marshals', + 'Boulevard Auguste-Blanqui', + 'Boulevard Barbès', + 'Boulevard Beaumarchais', + "Boulevard de l'Amiral-Bruix", + 'Boulevard des Capucines', + 'Boulevard de la Chapelle', + 'Boulevard de Clichy', + 'Boulevard du Crime', + 'Boulevard Haussmann', + "Boulevard de l'Hôpital", + 'Boulevard des Italiens', + 'Boulevard de la Madeleine', + 'Boulevard de Magenta', + 'Boulevard Montmartre', + 'Boulevard du Montparnasse', + 'Boulevard Raspail', + 'Boulevard Richard-Lenoir', + 'Boulevard de Rochechouart', + 'Boulevard Saint-Germain', + 'Boulevard Saint-Michel', + 'Boulevard de Sébastopol', + 'Boulevard de Strasbourg', + 'Boulevard du Temple', + 'Boulevard Voltaire', + 'Boulevard de la Zone' +].filter(b => b.includes('de')) +console.log('Boulevards', boulevards) + +// 7. sort Exercise +// Sort the people alphabetically by last name +const sortedPeople = people.sort((p1, p2) => p1.split(', ')[1] - p2.split(', ')[1]) +console.log('Sorted people', sortedPeople) + +// 8. Reduce Exercise +// Sum up the instances of each of these +const data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car', 'truck'] +const tally = data.reduce((acc, i) => { + if (!acc[i]) { + acc[i] = 0 + } + acc[i]++ + return acc +}, {}) +console.table(tally) diff --git a/05 - Flex Panel Gallery/README.md b/05 - Flex Panel Gallery/README.md new file mode 100644 index 0000000000..1490fc0fb8 --- /dev/null +++ b/05 - Flex Panel Gallery/README.md @@ -0,0 +1,37 @@ +# Flex Panel Gallery +The initial tutorial shows how to create a `flexbox` based UI. + +![Flex Panel Gallery](../assets/img/05%20-%20Flex%20Panel%20Gallery.png?raw=true "Presentation Image") + +## My contribution +I have changed both the UI and the UX by removing the cursive font, adding a +grayscale filter and modifying the event interactions. Before, you could have +multiple panels open in the same time, getting less of each subsequent +transition. Now you can only have the last panel you clicked open, increasing +the flow adding focus in every state. Adding greyscale puts further accent on +the currnt panel. + +## Learning notes +Some concepts taught: + - CSS3 `flexbox` layout and nested flexbox + - timing css events + - JavaScript class toggle logic + +```javascript +// remove the open class on all the others and toggle on current +var toggleOpen = function () { + if (!this.classList.contains('open')) { + panels.forEach(panel => { + panel.classList.remove('open') + panel.classList.remove('open-active') + }) + this.classList.toggle('open') + } else { + panels.forEach(panel => { + panel.classList.remove('open') + panel.classList.remove('open-active') + }) + } +} +``` + diff --git a/05 - Flex Panel Gallery/index-FINISHED.html b/05 - Flex Panel Gallery/index-FINISHED.html deleted file mode 100644 index 243f8a221d..0000000000 --- a/05 - Flex Panel Gallery/index-FINISHED.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - Flex Panels 💪 - - - - - - -
-
-

Hey

-

Let's

-

Dance

-
-
-

Give

-

Take

-

Receive

-
-
-

Experience

-

It

-

Today

-
-
-

Give

-

All

-

You can

-
-
-

Life

-

In

-

Motion

-
-
- - - - - diff --git a/05 - Flex Panel Gallery/index-START.html b/05 - Flex Panel Gallery/index-START.html deleted file mode 100644 index e1d643ad5c..0000000000 --- a/05 - Flex Panel Gallery/index-START.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - Flex Panels 💪 - - - - - - -
-
-

Hey

-

Let's

-

Dance

-
-
-

Give

-

Take

-

Receive

-
-
-

Experience

-

It

-

Today

-
-
-

Give

-

All

-

You can

-
-
-

Life

-

In

-

Motion

-
-
- - - - - - - diff --git a/05 - Flex Panel Gallery/index.html b/05 - Flex Panel Gallery/index.html new file mode 100644 index 0000000000..d6c713d568 --- /dev/null +++ b/05 - Flex Panel Gallery/index.html @@ -0,0 +1,42 @@ + + + + + + Flex Panels 💪 + + + + + +
+
+

Hey

+

Let's

+

Dance

+
+
+

Give

+

Take

+

Receive

+
+
+

Experience

+

It

+

Today

+
+
+

Give

+

All

+

You can

+
+
+

Life

+

In

+

Motion

+
+
+ + + + diff --git a/05 - Flex Panel Gallery/script.js b/05 - Flex Panel Gallery/script.js new file mode 100644 index 0000000000..b9df858b01 --- /dev/null +++ b/05 - Flex Panel Gallery/script.js @@ -0,0 +1,28 @@ +const panels = document.querySelectorAll('.panel') + +var toggleOpen = function () { + if (!this.classList.contains('open')) { + panels.forEach(panel => { + panel.classList.remove('open') + panel.classList.remove('open-active') + }) + this.classList.toggle('open') + } else { + panels.forEach(panel => { + panel.classList.remove('open') + panel.classList.remove('open-active') + }) + } +} + +var toggleActive = function (e) { + if (e.propertyName.includes('flex')) { + if (this.classList.contains('open')) { + this.classList.toggle('open-active') + } + } +} + +panels.forEach(panel => panel.addEventListener('click', toggleOpen)) + +panels.forEach(panel => panel.addEventListener('transitionend', toggleActive)) diff --git a/05 - Flex Panel Gallery/style.css b/05 - Flex Panel Gallery/style.css new file mode 100644 index 0000000000..a9bc442770 --- /dev/null +++ b/05 - Flex Panel Gallery/style.css @@ -0,0 +1,109 @@ +html { + box-sizing: border-box; + background: #ffc600; + font-family: 'helvetica neue'; + font-size: 20px; + font-weight: 200; +} + +body { + margin: 0; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +.panels { + min-height: 100vh; + overflow: hidden; + display: flex; +} + +.panel { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + background: #6B0F9C; + box-shadow: inset 0 0 0 5px rgba(255, 255, 255, 0.1); + color: white; + text-align: center; + align-items: center; + /* Safari transitionend event.propertyName === flex */ + /* Chrome + FF transitionend event.propertyName === flex-grow */ + transition: + font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), + flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), + background 0.2s, + filter 0.8s; + font-size: 20px; + background-size: cover; + background-position: center; + filter: grayscale(0.8); +} + +.panel1 { + background-image: url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500); +} + +.panel2 { + background-image: url(https://source.unsplash.com/rFKUFzjPYiQ/1500x1500); +} + +.panel3 { + background-image: url(https://images.unsplash.com/photo-1465188162913-8fb5709d6d57?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&w=1500&h=1500&fit=crop&s=967e8a713a4e395260793fc8c802901d); +} + +.panel4 { + background-image: url(https://source.unsplash.com/ITjiVXcwVng/1500x1500); +} + +.panel5 { + background-image: url(https://source.unsplash.com/3MNzGlQM7qs/1500x1500); +} + +/* Flex Children */ +.panel>* { + margin: 0; + width: 100%; + transition: transform 0.5s; + flex: 1 0 auto; + display: flex; + justify-content: center; + align-items: center; +} + +.panel>*:first-child { + transform: translateY(-100%); +} + +.panel.open-active>*:first-child { + transform: translateY(0); +} + +.panel>*:last-child { + transform: translateY(100%); +} + +.panel.open-active>*:last-child { + transform: translateY(0); +} + +.panel p { + text-transform: uppercase; + font-family: -apple-system, BlinkMacSystemFont, "Roboto", "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, sans-serif; + text-shadow: 0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45); + font-size: 2em; +} + +.panel p:nth-child(2) { + font-size: 4em; +} + +.panel.open { + flex: 5; + font-size: 40px; + filter: none; +} diff --git a/06 - Type Ahead/README.md b/06 - Type Ahead/README.md new file mode 100644 index 0000000000..035084be85 --- /dev/null +++ b/06 - Type Ahead/README.md @@ -0,0 +1,34 @@ +# Type Ahead + +This tutorial shows how to `filter data` for a quick search type ahead. + +![06 - Type Ahead](../assets/img/06 - Type Ahead.png) + +## Learning notes + +Some concepts taught: + +- managing `Promises` with `fetch` +- `ES6 aray spreading` +- `RegEx` string filter +- using `Array.prototype.map` to turn data into **HTML** nodes +- inserting generated nodes with `element.innerHTML` + +```javascript +// turn matched data to a node and then joining it to a string + +const html = matchArr.map(place => { + const cityName = place.city.replace(regex, `${this.value}`) + const stateName = place.state.replace(regex, `${this.value}`) + + return ` +
  • + ${cityName}, ${stateName} + ${numberWithCommas(place.population)} +
  • + ` +}).join('') +``` + + + diff --git a/06 - Type Ahead/index-FINISHED.html b/06 - Type Ahead/index-FINISHED.html deleted file mode 100644 index 5902b43936..0000000000 --- a/06 - Type Ahead/index-FINISHED.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - Type Ahead 👀 - - - - -
    - -
      -
    • Filter for a city
    • -
    • or a state
    • -
    -
    - - - diff --git a/06 - Type Ahead/index-START.html b/06 - Type Ahead/index.html similarity index 68% rename from 06 - Type Ahead/index-START.html rename to 06 - Type Ahead/index.html index 1436886918..b149fcf349 100644 --- a/06 - Type Ahead/index-START.html +++ b/06 - Type Ahead/index.html @@ -1,10 +1,12 @@ + Type Ahead 👀 +
    @@ -14,9 +16,7 @@
  • or a state
  • - + - - diff --git a/06 - Type Ahead/script.js b/06 - Type Ahead/script.js new file mode 100644 index 0000000000..c478475549 --- /dev/null +++ b/06 - Type Ahead/script.js @@ -0,0 +1,42 @@ +const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json' + +const cities = [] + +window.fetch(endpoint) + .then(response => response.json()) + .then(data => cities.push(...data)) + +function findMatches (wordToMatch, cities) { + return cities.filter(place => { + const regex = new RegExp(wordToMatch, 'gi') + return place.city.match(regex) || place.state.match(regex) + }) +} + +function numberWithCommas (x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') +} + +function displayMatches () { + const matchArr = findMatches(this.value, cities) + const regex = new RegExp(this.value, 'gi') + + const html = matchArr.map(place => { + const cityName = place.city.replace(regex, `${this.value}`) + const stateName = place.state.replace(regex, `${this.value}`) + + return ` +
  • + ${cityName}, ${stateName} + ${numberWithCommas(place.population)} +
  • + ` + }).join('') + suggestions.innerHTML = html +} + +const searchInput = document.querySelector('.search') +const suggestions = document.querySelector('.suggestions') + +searchInput.addEventListener('change', displayMatches) +searchInput.addEventListener('keyup', displayMatches) diff --git a/06 - Type Ahead/style.css b/06 - Type Ahead/style.css index 36dc55f30e..0c8c74e01b 100644 --- a/06 - Type Ahead/style.css +++ b/06 - Type Ahead/style.css @@ -1,87 +1,74 @@ - html { - box-sizing: border-box; - background:#ffc600; - font-family:'helvetica neue'; - font-size: 20px; - font-weight: 200; - } - *, *:before, *:after { - box-sizing: inherit; - } - input { - width: 100%; - padding:20px; - } +html { + box-sizing: border-box; + background: #ffc600; + font-family: 'helvetica neue'; + font-size: 20px; + font-weight: 200; +} - .search-form { - max-width:400px; - margin:50px auto; - } +*, *:before, *:after { + box-sizing: inherit; +} - input.search { - margin: 0; - text-align: center; - outline:0; - border: 10px solid #F7F7F7; - width: 120%; - left: -10%; - position: relative; - top: 10px; - z-index: 2; - border-radius: 5px; - font-size: 40px; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.19); - } +input { + width: 100%; + padding: 20px; +} +.search-form { + max-width: 400px; + margin: 50px auto; +} - .suggestions { - margin: 0; - padding: 0; - position: relative; - /*perspective:20px;*/ - } - .suggestions li { - background:white; - list-style: none; - border-bottom: 1px solid #D8D8D8; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.14); - margin:0; - padding:20px; - transition:background 0.2s; - display:flex; - justify-content:space-between; - text-transform: capitalize; - } +input.search { + margin: 0; + text-align: center; + outline: 0; + border: 10px solid #F7F7F7; + width: 120%; + left: -10%; + position: relative; + top: 10px; + z-index: 2; + border-radius: 5px; + font-size: 40px; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.19); +} - .suggestions li:nth-child(even) { - transform: perspective(100px) rotateX(3deg) translateY(2px) scale(1.001); - background: linear-gradient(to bottom, #ffffff 0%,#EFEFEF 100%); - } - .suggestions li:nth-child(odd) { - transform: perspective(100px) rotateX(-3deg) translateY(3px); - background: linear-gradient(to top, #ffffff 0%,#EFEFEF 100%); - } +.suggestions { + margin: 0; + padding: 0; + position: relative; + /*perspective: 20px;*/ +} - span.population { - font-size: 15px; - } +.suggestions li { + background: white; + list-style: none; + border-bottom: 1px solid #D8D8D8; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.14); + margin: 0; + padding: 20px; + transition: background 0.2s; + display: flex; + justify-content: space-between; + text-transform: capitalize; +} +.suggestions li:nth-child(even) { + transform: perspective(100px) rotateX(3deg) translateY(2px) scale(1.001); + background: linear-gradient(to bottom, #ffffff 0%,#EFEFEF 100%); +} - .details { - text-align: center; - font-size: 15px; - } +.suggestions li:nth-child(odd) { + transform: perspective(100px) rotateX(-3deg) translateY(3px); + background: linear-gradient(to top, #ffffff 0%,#EFEFEF 100%); +} - .hl { - background:#ffc600; - } +span.population { + font-size: 15px; +} - .love { - text-align: center; - } - - a { - color:black; - background:rgba(0,0,0,0.1); - text-decoration: none; - } +.hl { + background: #ffc600; +} diff --git a/07 - Array Cardio Day 2/index-FINISHED.html b/07 - Array Cardio Day 2/index-FINISHED.html deleted file mode 100644 index e39d35f79a..0000000000 --- a/07 - Array Cardio Day 2/index-FINISHED.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - Document - - - - - diff --git a/07 - Array Cardio Day 2/index-START.html b/07 - Array Cardio Day 2/index-START.html deleted file mode 100644 index bdf6c44415..0000000000 --- a/07 - Array Cardio Day 2/index-START.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - Document - - - - - diff --git a/07 - Array Cardio Day 2/index.html b/07 - Array Cardio Day 2/index.html new file mode 100644 index 0000000000..378ab0ac4b --- /dev/null +++ b/07 - Array Cardio Day 2/index.html @@ -0,0 +1,11 @@ + + + + + Array Cardio 💪💪 + + +

    Psst: have a look at the JavaScript Console 💁

    + + + diff --git a/07 - Array Cardio Day 2/script.js b/07 - Array Cardio Day 2/script.js new file mode 100644 index 0000000000..9d2b8dcbe9 --- /dev/null +++ b/07 - Array Cardio Day 2/script.js @@ -0,0 +1,63 @@ +// ## Array Cardio Day 2 + +const people = [{ + name: 'Wes', + year: 1988 +}, +{ + name: 'Kait', + year: 1986 +}, +{ + name: 'Irv', + year: 1970 +}, +{ + name: 'Lux', + year: 2015 +} +] + +const comments = [{ + text: 'Love this!', + id: 523423 +}, +{ + text: 'Super good', + id: 823423 +}, +{ + text: 'You are the best', + id: 2039842 +}, +{ + text: 'Ramen is my fav food ever', + id: 123523 +}, +{ + text: 'Nice Nice Nice!', + id: 542328 +} +] + +const currentYear = (new Date()).getFullYear() +// Some and Every Checks +// Array.prototype.some() // is at least one person 19 or older? +var isAdult = people.some(p => currentYear - p.year >= 19) +console.log({ isAdult }) +// Array.prototype.every() // is everyone 19 or older? +var isEveryoneAdult = people.every(p => currentYear - p.year >= 19) +console.log({ isEveryoneAdult }) + +// Array.prototype.find() +// Find is like filter, but instead returns just the one you are looking for +// find the comment with the ID of 823423 +const comment = comments.find(c => c.id === 823423) +console.log(comment) + +// Array.prototype.findIndex() +// Find the comment with this ID +// delete the comment with the ID of 823423 +const index = comments.findIndex(c => c.id === 823423) +comments.splice(index, 1) +console.table(comments) diff --git a/08 - Fun with HTML5 Canvas/index-FINISHED.html b/08 - Fun with HTML5 Canvas/index-FINISHED.html deleted file mode 100644 index 0791e17d0d..0000000000 --- a/08 - Fun with HTML5 Canvas/index-FINISHED.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - HTML5 Canvas - - - - - - - - - diff --git a/08 - Fun with HTML5 Canvas/index-START.html b/08 - Fun with HTML5 Canvas/index-START.html deleted file mode 100644 index 37c148df07..0000000000 --- a/08 - Fun with HTML5 Canvas/index-START.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - HTML5 Canvas - - - - - - - - - diff --git a/08 - Fun with HTML5 Canvas/index.html b/08 - Fun with HTML5 Canvas/index.html new file mode 100644 index 0000000000..803a495e4a --- /dev/null +++ b/08 - Fun with HTML5 Canvas/index.html @@ -0,0 +1,21 @@ + + + + + + HTML5 Canvas + + + + + + + + + + diff --git a/08 - Fun with HTML5 Canvas/script.js b/08 - Fun with HTML5 Canvas/script.js new file mode 100644 index 0000000000..8feffd85c6 --- /dev/null +++ b/08 - Fun with HTML5 Canvas/script.js @@ -0,0 +1,57 @@ +const canvas = document.querySelector('#draw') +canvas.width = window.innerWidth +canvas.height = window.innerHeight + +const ctx = canvas.getContext('2d') +ctx.strokeStyle = '#BADA55' +ctx.lineJoin = 'round' +ctx.lineCap = 'round' +ctx.lineWidth = 36 +ctx.globalCompositeOperation = 'overlay' + +let isDrawing = false +let lastX = 0 +let lastY = 0 +let direction = true +const color = { + hue: 0, + saturation: 100, + lightness: 50 +} + +function draw (e) { + if (!isDrawing) return + + ctx.strokeStyle = `hsl(${color.hue}, ${color.saturation}%, ${color.lightness}%)` + ctx.beginPath() + ctx.moveTo(lastX, lastY) + ctx.lineTo(e.offsetX, e.offsetY) + ctx.stroke(); + [lastX, lastY] = [e.offsetX, e.offsetY] + + color.hue++ + if (color.hue >= 360) { + color.hue = 0 + } + if (ctx.lineWidth >= 100 || ctx.lineWidth <= 1) { + direction = !direction + } + + color.saturation = Math.min((e.x / window.innerWidth * 100) + 20, 100) + color.lightness = e.y / window.innerHeight * 40 - 20 + 50 + + if (direction) { + ctx.lineWidth++ + } else { + ctx.lineWidth-- + } +} + +canvas.addEventListener('mousedown', (e) => { + isDrawing = true; + [lastX, lastY] = [e.offsetX, e.offsetY] +}) + +canvas.addEventListener('mousemove', draw) +canvas.addEventListener('mouseup', () => (isDrawing = false)) +canvas.addEventListener('mouseout', () => (isDrawing = false)) diff --git a/09 - Dev Tools Domination/index-FINISHED.html b/09 - Dev Tools Domination/index-FINISHED.html deleted file mode 100644 index 55cd3a2f42..0000000000 --- a/09 - Dev Tools Domination/index-FINISHED.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - Console Tricks! - - - -

    ×BREAK×DOWN×

    - - - - diff --git a/09 - Dev Tools Domination/index-START.html b/09 - Dev Tools Domination/index-START.html deleted file mode 100644 index 196fffd719..0000000000 --- a/09 - Dev Tools Domination/index-START.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - Console Tricks! - - - -

    ×BREAK×DOWN×

    - - - - diff --git a/09 - Dev Tools Domination/index.html b/09 - Dev Tools Domination/index.html new file mode 100644 index 0000000000..2d05579655 --- /dev/null +++ b/09 - Dev Tools Domination/index.html @@ -0,0 +1,14 @@ + + + + + + Console Tricks! + + + +

    ×BREAK×DOWN×

    + + + + diff --git a/09 - Dev Tools Domination/script.js b/09 - Dev Tools Domination/script.js new file mode 100644 index 0000000000..885bbba61b --- /dev/null +++ b/09 - Dev Tools Domination/script.js @@ -0,0 +1,72 @@ +const dogs = [{ + name: 'Snickers', + age: 2 +}, { + name: 'hugo', + age: 8 +}] + +const p = document.querySelector('p') + +let makeGreen = function () { + p.style.color = '#BADA55' + p.style.fontSize = '50px' +} + +// Regular +console.log('Hello!') + +// Interpolated +console.log('Hello I am %s string!', '@vanntile') + +// Styled +console.log('%c I am some great text', 'background:blue; color:white') + +// warning! +console.warn('WARNING') + +// Error : +console.error('ERROR') + +// Info +console.info('Information probably not useful') + +// Testing +console.assert(1 === 2, 'This assertion should be useful for tests') + +// clearing +// console.clear(); + +// Viewing DOM Elements +console.log(p) +console.dir(p) + +// Grouping together +dogs.forEach(x => { + console.groupCollapsed(`${x.name}`) + console.log(`First computation ${x.name}`) + console.log(`Second computation ${x.age}`) + console.groupEnd(`${x}`) +}) + +// counting + +console.count('VannTile') +console.count('VannTile') +console.count('Ianito') +console.count('VannTile') +console.count('VannTile') +console.count('Ianito') +console.count('Ianito') +console.count('VannTile') +console.count('VannTile') +console.count('Ianito') + +// timing +console.time('fetching data') +window.fetch('https://api.github.com/users/vanntile') + .then(data => data.json()) + .then(data => { + console.timeEnd('fetching data') + console.log(data) + }) diff --git a/10 - Hold Shift and Check Checkboxes/README.md b/10 - Hold Shift and Check Checkboxes/README.md new file mode 100644 index 0000000000..7a0a47d128 --- /dev/null +++ b/10 - Hold Shift and Check Checkboxes/README.md @@ -0,0 +1,26 @@ +# Hold Shift and Check Checkboxes + +This tutorial shows how to create a `shift select` checkbox list. + +![Hold Shift and Check Checkboxes](../assets/img/10%20-%20Hold%20Shift%20and%20Check%20Checkboxes.png?raw=true "Presentation Image") + +## My implementation + +I have changed the two-variable Implementation of Wes into a three-variable one because I had a different experience with a feature like this. The `user experience` I usually have with **select-shift** is the following: I want to select everything from the most top selected or the most bottom one to the **shift-click** target. + +## Learning notes + +Some concepts taught: + +- `e.shiftKey` event + +```javascript +/** + * Check all in-between boxes + */ +const check = (s, e) => { + for (let i = s; i <= e; i++) { + checkboxes[i].checked = true + } +} +``` diff --git a/10 - Hold Shift and Check Checkboxes/index-FINISHED.html b/10 - Hold Shift and Check Checkboxes/index-FINISHED.html deleted file mode 100644 index 3ce296cc4b..0000000000 --- a/10 - Hold Shift and Check Checkboxes/index-FINISHED.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - Document - - - - -
    -
    - -

    This is an inbox layout.

    -
    -
    - -

    Check one item

    -
    -
    - -

    Hold down your Shift key

    -
    -
    - -

    Check a lower item

    -
    -
    - -

    Everything inbetween should also be set to checked

    -
    -
    - -

    Try do it with out any libraries

    -
    -
    - -

    Just regular JavaScript

    -
    -
    - -

    Good Luck!

    -
    -
    - -

    Don't forget to tweet your result!

    -
    -
    - - - - diff --git a/10 - Hold Shift and Check Checkboxes/index-START.html b/10 - Hold Shift and Check Checkboxes/index-START.html deleted file mode 100644 index eb7ed310bb..0000000000 --- a/10 - Hold Shift and Check Checkboxes/index-START.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - Document - - - - -
    -
    - -

    This is an inbox layout.

    -
    -
    - -

    Check one item

    -
    -
    - -

    Hold down your Shift key

    -
    -
    - -

    Check a lower item

    -
    -
    - -

    Everything inbetween should also be set to checked

    -
    -
    - -

    Try do it with out any libraries

    -
    -
    - -

    Just regular JavaScript

    -
    -
    - -

    Good Luck!

    -
    -
    - -

    Don't forget to tweet your result!

    -
    -
    - - - - diff --git a/10 - Hold Shift and Check Checkboxes/index.html b/10 - Hold Shift and Check Checkboxes/index.html new file mode 100644 index 0000000000..3a91ec75fd --- /dev/null +++ b/10 - Hold Shift and Check Checkboxes/index.html @@ -0,0 +1,61 @@ + + + + + + Hold Shift to Check Multiple Checkboxes + + + + + +
    +
    + +

    This is an inbox layout.

    +
    +
    + +

    Check one item

    +
    +
    + +

    Hold down your Shift key

    +
    +
    + +

    Check a lower item

    +
    +
    + +

    Everything in between should also be set to checked

    +
    +
    + +

    Try to do it without any libraries

    +
    +
    + +

    Just regular JavaScript

    +
    +
    + +

    Good Luck!

    +
    +
    + +

    Don't forget to tweet your result!

    +
    +
    + + + + + diff --git a/10 - Hold Shift and Check Checkboxes/script.js b/10 - Hold Shift and Check Checkboxes/script.js new file mode 100644 index 0000000000..aa36d511ae --- /dev/null +++ b/10 - Hold Shift and Check Checkboxes/script.js @@ -0,0 +1,44 @@ +const checkboxes = Array.from(document.querySelectorAll("input[type='checkbox']")) + +const getFirstChecked = () => { + for (let i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].checked) { + return i + } + } + return -1 +} + +const getLastChecked = () => { + for (let i = checkboxes.length - 1; i > 0; i--) { + if (checkboxes[i].checked) { + return i + } + } + return -1 +} + +/** + * Check all in-between boxes + */ +const check = (s, e) => { + for (let i = s; i <= e; i++) { + checkboxes[i].checked = true + } +} + +let shiftSelect = function (e) { + if (e.shiftKey && this.checked) { + const current = checkboxes.indexOf(e.target) + const firstChecked = getFirstChecked() + const lastChecked = getLastChecked() + + if (firstChecked < current) { + check(firstChecked, current) + } else if (current < lastChecked) { + check(current, lastChecked) + } + } +} + +checkboxes.forEach(c => c.addEventListener('click', shiftSelect)) diff --git a/10 - Hold Shift and Check Checkboxes/style.css b/10 - Hold Shift and Check Checkboxes/style.css new file mode 100644 index 0000000000..a1c5489890 --- /dev/null +++ b/10 - Hold Shift and Check Checkboxes/style.css @@ -0,0 +1,42 @@ +html { + font-family: sans-serif; + background: #ffc600; +} + +.inbox { + max-width: 400px; + margin: 50px auto; + background: white; + border-radius: 5px; + box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.1); +} + +.item { + display: flex; + align-items: center; + border-bottom: 1px solid #F1F1F1; +} + +.item:last-child { + border-bottom: 0; +} + +input:checked+p { + background: #F9F9F9; + text-decoration: line-through; +} + +input[type="checkbox"] { + margin: 20px; +} + +p { + margin: 0; + padding: 20px; + transition: background 0.2s; + flex: 1; + font-family: 'helvetica neue'; + font-size: 20px; + font-weight: 200; + border-left: 1px solid #D1E2FF; +} diff --git a/11 - Custom Video Player/652333414.mp4 b/11 - Custom Video Player/652333414.mp4 new file mode 100644 index 0000000000..09ed31eca5 Binary files /dev/null and b/11 - Custom Video Player/652333414.mp4 differ diff --git a/11 - Custom Video Player/README.md b/11 - Custom Video Player/README.md new file mode 100644 index 0000000000..bc0586aa10 --- /dev/null +++ b/11 - Custom Video Player/README.md @@ -0,0 +1,32 @@ +# Custom Video Player + +This tutorial shows how to create a custom video player. + +![Custom Video Player](../assets/img/11%20-%20Custom%20Video%20Player.png?raw=true "Presentation Image") + +## My contribution + +As Wes challenged us, I have added fullscreen video functionality. The only issue to pay attention to was choosing the `player` element instead of `video` to keep the controls on screen. + +## Learning notes + +Some concepts taught: + +- `HTMLMediaElement API` for + - `click`, `play`, `pause` and `timeupdate` events + - `volume`, `playbackRate` and `currentTime` properties +- `Fullscreen API` for the `requestFullscreen()` method + +```javascript +// fullscreen handler function +const handleFullscreen = () => { + if (!document.fullscreenElement) { + player.requestFullscreen() + .catch(err => + alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`) + ) + } else { + document.exitFullscreen() + } +} +``` diff --git a/11 - Custom Video Player/index.html b/11 - Custom Video Player/index.html index fe2b55b394..f3bb02c1c7 100644 --- a/11 - Custom Video Player/index.html +++ b/11 - Custom Video Player/index.html @@ -1,27 +1,31 @@ + HTML Video Player + -
    - +
    + -
    -
    +
    +
    -
    - - - - - -
    -
    +
    + + + + + + +
    +
    + diff --git a/11 - Custom Video Player/scripts-FINISHED.js b/11 - Custom Video Player/scripts-FINISHED.js deleted file mode 100644 index cedacf2f68..0000000000 --- a/11 - Custom Video Player/scripts-FINISHED.js +++ /dev/null @@ -1,55 +0,0 @@ -/* Get Our Elements */ -const player = document.querySelector('.player'); -const video = player.querySelector('.viewer'); -const progress = player.querySelector('.progress'); -const progressBar = player.querySelector('.progress__filled'); -const toggle = player.querySelector('.toggle'); -const skipButtons = player.querySelectorAll('[data-skip]'); -const ranges = player.querySelectorAll('.player__slider'); - -/* Build out functions */ -function togglePlay() { - const method = video.paused ? 'play' : 'pause'; - video[method](); -} - -function updateButton() { - const icon = this.paused ? '►' : '❚ ❚'; - console.log(icon); - toggle.textContent = icon; -} - -function skip() { - video.currentTime += parseFloat(this.dataset.skip); -} - -function handleRangeUpdate() { - video[this.name] = this.value; -} - -function handleProgress() { - const percent = (video.currentTime / video.duration) * 100; - progressBar.style.flexBasis = `${percent}%`; -} - -function scrub(e) { - const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration; - video.currentTime = scrubTime; -} - -/* Hook up the event listners */ -video.addEventListener('click', togglePlay); -video.addEventListener('play', updateButton); -video.addEventListener('pause', updateButton); -video.addEventListener('timeupdate', handleProgress); - -toggle.addEventListener('click', togglePlay); -skipButtons.forEach(button => button.addEventListener('click', skip)); -ranges.forEach(range => range.addEventListener('change', handleRangeUpdate)); -ranges.forEach(range => range.addEventListener('mousemove', handleRangeUpdate)); - -let mousedown = false; -progress.addEventListener('click', scrub); -progress.addEventListener('mousemove', (e) => mousedown && scrub(e)); -progress.addEventListener('mousedown', () => mousedown = true); -progress.addEventListener('mouseup', () => mousedown = false); diff --git a/11 - Custom Video Player/scripts.js b/11 - Custom Video Player/scripts.js index e69de29bb2..f1028ab057 100644 --- a/11 - Custom Video Player/scripts.js +++ b/11 - Custom Video Player/scripts.js @@ -0,0 +1,61 @@ +/* Get Our Elements */ +const player = document.querySelector('.player') +const video = player.querySelector('.viewer') +const progress = player.querySelector('.progress') +const progressBar = player.querySelector('.progress__filled') +const toggle = player.querySelector('.toggle') +const skipButtons = player.querySelectorAll('[data-skip]') +const ranges = player.querySelectorAll('.player__slider') +const fullscreen = player.querySelector('.player__fullscreen') + +const togglePlay = () => + video.paused ? video.play() : video.pause() + +const updateButton = function () { + toggle.textContent = this.paused ? '►' : '❚ ❚' +} + +const skip = function () { + video.currentTime += parseFloat(this.dataset.skip) +} + +const handleRangeUpdate = function () { + video[this.name] = this.value +} + +const handleProgress = function () { + const percent = (video.currentTime / video.duration) * 100 + progressBar.style.flexBasis = `${percent}%` +} + +const handleFullscreen = () => { + if (!document.fullscreenElement) { + player.requestFullscreen() + .catch(err => + alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`) + ) + } else { + document.exitFullscreen() + } +} + +const scrub = (e) => { + const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration + video.currentTime = scrubTime +} + +video.addEventListener('click', togglePlay) +video.addEventListener('play', updateButton) +video.addEventListener('pause', updateButton) +video.addEventListener('timeupdate', handleProgress) + +toggle.addEventListener('click', togglePlay) +skipButtons.forEach(button => button.addEventListener('click', skip)) +ranges.forEach(range => range.addEventListener('change', handleRangeUpdate)) +fullscreen.addEventListener('click', handleFullscreen) + +let mouseDown = false +progress.addEventListener('click', scrub) +progress.addEventListener('mousemove', (e) => mouseDown && scrub(e)) +progress.addEventListener('mousedown', () => (mouseDown = true)) +progress.addEventListener('mouseup', () => (mouseDown = false)) diff --git a/11 - Custom Video Player/style.css b/11 - Custom Video Player/style.css index c07581c1c0..27dd698652 100644 --- a/11 - Custom Video Player/style.css +++ b/11 - Custom Video Player/style.css @@ -7,39 +7,56 @@ html { } body { + margin: 0; padding: 0; - display:flex; - background:#7A419B; - min-height:100vh; - background: linear-gradient(135deg, #7c1599 0%,#921099 48%,#7e4ae8 100%); - background-size:cover; + display: flex; + background: #7A419B; + min-height: 100vh; + background: linear-gradient(135deg, #7c1599 0%, #921099 48%, #7e4ae8 100%); + background-size: cover; align-items: center; justify-content: center; } .player { - max-width:750px; - border:5px solid rgba(0,0,0,0.2); - box-shadow:0 0 20px rgba(0,0,0,0.2); + max-width: 750px; + border: 5px solid rgba(0, 0, 0, 0.2); + box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); position: relative; font-size: 0; overflow: hidden; } +/* This css is only applied when fullscreen is active. */ +.player:fullscreen { + max-width: none; + width: 100%; +} + +.player:-webkit-full-screen { + max-width: none; + width: 100%; +} + .player__video { width: 100%; } .player__button { - background:none; - border:0; - line-height:1; - color:white; + background: none; + border: 0; + line-height: 1; + color: white; text-align: center; - outline:0; + outline: 0; padding: 0; - cursor:pointer; - max-width:50px; + cursor: pointer; + max-width: 50px; + transition: background .25s; +} + +.player__button:hover { + background: rgba(255, 255, 255, 0.3); } .player__button:focus { @@ -47,73 +64,74 @@ body { } .player__slider { - width:10px; - height:30px; + width: 10px; + height: 30px; } .player__controls { - display:flex; + display: flex; position: absolute; - bottom:0; + bottom: 0; width: 100%; transform: translateY(100%) translateY(-5px); - transition:all .3s; - flex-wrap:wrap; - background:rgba(0,0,0,0.1); + transition: all .3s; + flex-wrap: wrap; + background: rgba(0, 0, 0, 0.1); } -.player:hover .player__controls { +.player:hover .player__controls, .player.hover .player__controls { transform: translateY(0); } .player:hover .progress { - height:15px; + height: 15px; } -.player__controls > * { - flex:1; +.player__controls>* { + flex: 1; } .progress { - flex:10; + flex: 10; position: relative; - display:flex; - flex-basis:100%; - height:5px; - transition:height 0.3s; - background:rgba(0,0,0,0.5); - cursor:ew-resize; + display: flex; + flex-basis: 100%; + height: 5px; + transition: height 0.3s; + background: rgba(0, 0, 0, 0.5); + cursor: ew-resize; } .progress__filled { - width:50%; - background:#ffc600; - flex:0; - flex-basis:50%; + width: 50%; + background: #ffc600; + flex: 0; + flex-basis: 50%; } /* unholy css to style input type="range" */ - input[type=range] { -webkit-appearance: none; - background:transparent; + background: transparent; width: 100%; margin: 0 5px; } + input[type=range]:focus { outline: none; } + input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 8.4px; cursor: pointer; box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0); - background: rgba(255,255,255,0.8); + background: rgba(255, 255, 255, 0.8); border-radius: 1.3px; border: 0.2px solid rgba(1, 1, 1, 0); } + input[type=range]::-webkit-slider-thumb { - box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0); height: 15px; width: 15px; border-radius: 50px; @@ -121,11 +139,13 @@ input[type=range]::-webkit-slider-thumb { cursor: pointer; -webkit-appearance: none; margin-top: -3.5px; - box-shadow:0 0 2px rgba(0,0,0,0.2); + box-shadow: 0 0 2px rgba(0, 0, 0, 0.2); } -input[type=range]:focus::-wefbkit-slider-runnable-track { + +input[type=range]:focus::-webkit-slider-runnable-track { background: #bada55; } + input[type=range]::-moz-range-track { width: 100%; height: 8.4px; @@ -135,6 +155,7 @@ input[type=range]::-moz-range-track { border-radius: 1.3px; border: 0.2px solid rgba(1, 1, 1, 0); } + input[type=range]::-moz-range-thumb { box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0); height: 15px; diff --git a/12 - Key Sequence Detection/index-FINISHED.html b/12 - Key Sequence Detection/index-FINISHED.html deleted file mode 100644 index 562127a0d2..0000000000 --- a/12 - Key Sequence Detection/index-FINISHED.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Key Detection - - - - - - diff --git a/12 - Key Sequence Detection/index-START.html b/12 - Key Sequence Detection/index.html similarity index 76% rename from 12 - Key Sequence Detection/index-START.html rename to 12 - Key Sequence Detection/index.html index 8cab786140..a2e661b072 100644 --- a/12 - Key Sequence Detection/index-START.html +++ b/12 - Key Sequence Detection/index.html @@ -1,12 +1,15 @@ + Key Detection + - +

    What's my name?

    + + diff --git a/12 - Key Sequence Detection/script.js b/12 - Key Sequence Detection/script.js new file mode 100644 index 0000000000..e29eaf6f5f --- /dev/null +++ b/12 - Key Sequence Detection/script.js @@ -0,0 +1,11 @@ +const pressed = [] +const secretCode = 'vanntile' + +window.addEventListener('keyup', (e) => { + pressed.push(e.key) + pressed.splice(-secretCode.length - 1, pressed.length - secretCode.length) + if (pressed.join('') + .includes(secretCode)) { + cornify_add() + } +}) diff --git a/13 - Slide in on Scroll/index-FINISHED.html b/13 - Slide in on Scroll/index-FINISHED.html deleted file mode 100644 index bbaf0b6f22..0000000000 --- a/13 - Slide in on Scroll/index-FINISHED.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - Document - - - -
    - -

    Slide in on Scroll

    - -

    Consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariaturlores sunt esse magni, ut, dignissimos.

    -

    Lorem ipsum cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    -

    Adipisicing elit. Tempore tempora rerum..

    -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    - - - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptates, deserunt facilis et iste corrupti omnis tenetur est. Iste ut est dicta dolor itaque adipisci, dolorum minima, veritatis earum provident error molestias. Ratione magni illo sint vel velit ut excepturi consectetur suscipit, earum modi accusamus voluptatem nostrum, praesentium numquam, reiciendis voluptas sit id quisquam. Consequatur in quis reprehenderit modi perspiciatis necessitatibus saepe, quidem, suscipit iure natus dignissimos ipsam, eligendi deleniti accusantium, rerum quibusdam fugit perferendis et optio recusandae sed ratione. Culpa, dolorum reprehenderit harum ab voluptas fuga, nisi eligendi natus maiores illum quas quos et aperiam aut doloremque optio maxime fugiat doloribus. Eum dolorum expedita quam, nesciunt

    - - - -

    at provident praesentium atque quas rerum optio dignissimos repudiandae ullam illum quibusdam. Vel ad error quibusdam, illo ex totam placeat. Quos excepturi fuga, molestiae ea quisquam minus, ratione dicta consectetur officia omnis, doloribus voluptatibus? Veniam ipsum veritatis architecto, provident quas consequatur doloremque quam quidem earum expedita, ad delectus voluptatum, omnis praesentium nostrum qui aspernatur ea eaque adipisci et cumque ab? Ea voluptatum dolore itaque odio. Eius minima distinctio harum, officia ab nihil exercitationem. Tempora rem nemo nam temporibus molestias facilis minus ipsam quam doloribus consequatur debitis nesciunt tempore officiis aperiam quisquam, molestiae voluptates cum, fuga culpa. Distinctio accusamus quibusdam, tempore perspiciatis dolorum optio facere consequatur quidem ullam beatae architecto, ipsam sequi officiis dignissimos amet impedit natus necessitatibus tenetur repellendus dolor rem! Dicta dolorem, iure, facilis illo ex nihil ipsa amet officia, optio temporibus eum autem odit repellendus nisi. Possimus modi, corrupti error debitis doloribus dicta libero earum, sequi porro ut excepturi nostrum ea voluptatem nihil culpa? Ullam expedita eligendi obcaecati reiciendis velit provident omnis quas qui in corrupti est dolore facere ad hic, animi soluta assumenda consequuntur reprehenderit! Voluptate dolor nihil veniam laborum voluptas nisi pariatur sed optio accusantium quam consectetur, corrupti, sequi et consequuntur, excepturi doloremque. Tempore quis velit corporis neque fugit non sequi eaque rem hic. Facere, inventore, aspernatur. Accusantium modi atque, asperiores qui nobis soluta cumque suscipit excepturi possimus doloremque odit saepe perferendis temporibus molestiae nostrum voluptatum quis id sint quidem nesciunt culpa. Rerum labore dolor beatae blanditiis praesentium explicabo velit optio esse aperiam similique, voluptatem cum, maiores ipsa tempore. Reiciendis sed culpa atque inventore, nam ullam enim expedita consectetur id velit iusto alias vitae explicabo nemo neque odio reprehenderit soluta sint eaque. Aperiam, qui ut tenetur, voluptate doloremque officiis dicta quaerat voluptatem rerum natus magni. Eum amet autem dolor ullam.

    - - - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis

    - - -

    laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    - - - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    - - - - -
    - - - - - - - diff --git a/13 - Slide in on Scroll/index-START.html b/13 - Slide in on Scroll/index-START.html deleted file mode 100644 index 12591bad30..0000000000 --- a/13 - Slide in on Scroll/index-START.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - Document - - - -
    - -

    Slide in on Scroll

    - -

    Consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariaturlores sunt esse magni, ut, dignissimos.

    -

    Lorem ipsum cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    -

    Adipisicing elit. Tempore tempora rerum..

    -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    - - - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptates, deserunt facilis et iste corrupti omnis tenetur est. Iste ut est dicta dolor itaque adipisci, dolorum minima, veritatis earum provident error molestias. Ratione magni illo sint vel velit ut excepturi consectetur suscipit, earum modi accusamus voluptatem nostrum, praesentium numquam, reiciendis voluptas sit id quisquam. Consequatur in quis reprehenderit modi perspiciatis necessitatibus saepe, quidem, suscipit iure natus dignissimos ipsam, eligendi deleniti accusantium, rerum quibusdam fugit perferendis et optio recusandae sed ratione. Culpa, dolorum reprehenderit harum ab voluptas fuga, nisi eligendi natus maiores illum quas quos et aperiam aut doloremque optio maxime fugiat doloribus. Eum dolorum expedita quam, nesciunt

    - - - -

    at provident praesentium atque quas rerum optio dignissimos repudiandae ullam illum quibusdam. Vel ad error quibusdam, illo ex totam placeat. Quos excepturi fuga, molestiae ea quisquam minus, ratione dicta consectetur officia omnis, doloribus voluptatibus? Veniam ipsum veritatis architecto, provident quas consequatur doloremque quam quidem earum expedita, ad delectus voluptatum, omnis praesentium nostrum qui aspernatur ea eaque adipisci et cumque ab? Ea voluptatum dolore itaque odio. Eius minima distinctio harum, officia ab nihil exercitationem. Tempora rem nemo nam temporibus molestias facilis minus ipsam quam doloribus consequatur debitis nesciunt tempore officiis aperiam quisquam, molestiae voluptates cum, fuga culpa. Distinctio accusamus quibusdam, tempore perspiciatis dolorum optio facere consequatur quidem ullam beatae architecto, ipsam sequi officiis dignissimos amet impedit natus necessitatibus tenetur repellendus dolor rem! Dicta dolorem, iure, facilis illo ex nihil ipsa amet officia, optio temporibus eum autem odit repellendus nisi. Possimus modi, corrupti error debitis doloribus dicta libero earum, sequi porro ut excepturi nostrum ea voluptatem nihil culpa? Ullam expedita eligendi obcaecati reiciendis velit provident omnis quas qui in corrupti est dolore facere ad hic, animi soluta assumenda consequuntur reprehenderit! Voluptate dolor nihil veniam laborum voluptas nisi pariatur sed optio accusantium quam consectetur, corrupti, sequi et consequuntur, excepturi doloremque. Tempore quis velit corporis neque fugit non sequi eaque rem hic. Facere, inventore, aspernatur. Accusantium modi atque, asperiores qui nobis soluta cumque suscipit excepturi possimus doloremque odit saepe perferendis temporibus molestiae nostrum voluptatum quis id sint quidem nesciunt culpa. Rerum labore dolor beatae blanditiis praesentium explicabo velit optio esse aperiam similique, voluptatem cum, maiores ipsa tempore. Reiciendis sed culpa atque inventore, nam ullam enim expedita consectetur id velit iusto alias vitae explicabo nemo neque odio reprehenderit soluta sint eaque. Aperiam, qui ut tenetur, voluptate doloremque officiis dicta quaerat voluptatem rerum natus magni. Eum amet autem dolor ullam.

    - - - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis

    - - -

    laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    - - - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    - - - - -
    - - - - - - - diff --git a/13 - Slide in on Scroll/index.html b/13 - Slide in on Scroll/index.html new file mode 100644 index 0000000000..d33048a435 --- /dev/null +++ b/13 - Slide in on Scroll/index.html @@ -0,0 +1,89 @@ + + + + + + Document + + + + +
    + +

    Slide in on Scroll

    + +

    Consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in + aspernatur pariaturlores sunt esse magni, ut, dignissimos.

    +

    Lorem ipsum cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse + magni, ut, dignissimos.

    +

    Adipisicing elit. Tempore tempora rerum..

    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum + nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum + nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum + nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    + + + +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptates, deserunt facilis et iste corrupti omnis tenetur est. Iste ut est dicta dolor itaque adipisci, dolorum minima, veritatis earum provident error molestias. Ratione magni illo + sint vel velit ut excepturi consectetur suscipit, earum modi accusamus voluptatem nostrum, praesentium numquam, reiciendis voluptas sit id quisquam. Consequatur in quis reprehenderit modi perspiciatis necessitatibus saepe, quidem, suscipit + iure natus dignissimos ipsam, eligendi deleniti accusantium, rerum quibusdam fugit perferendis et optio recusandae sed ratione. Culpa, dolorum reprehenderit harum ab voluptas fuga, nisi eligendi natus maiores illum quas quos et aperiam aut + doloremque optio maxime fugiat doloribus. Eum dolorum expedita quam, nesciunt

    + + + +

    at provident praesentium atque quas rerum optio dignissimos repudiandae ullam illum quibusdam. Vel ad error quibusdam, illo ex totam placeat. Quos excepturi fuga, molestiae ea quisquam minus, ratione dicta consectetur officia omnis, + doloribus voluptatibus? Veniam ipsum veritatis architecto, provident quas consequatur doloremque quam quidem earum expedita, ad delectus voluptatum, omnis praesentium nostrum qui aspernatur ea eaque adipisci et cumque ab? Ea voluptatum dolore + itaque odio. Eius minima distinctio harum, officia ab nihil exercitationem. Tempora rem nemo nam temporibus molestias facilis minus ipsam quam doloribus consequatur debitis nesciunt tempore officiis aperiam quisquam, molestiae voluptates cum, + fuga culpa. Distinctio accusamus quibusdam, tempore perspiciatis dolorum optio facere consequatur quidem ullam beatae architecto, ipsam sequi officiis dignissimos amet impedit natus necessitatibus tenetur repellendus dolor rem! Dicta dolorem, + iure, facilis illo ex nihil ipsa amet officia, optio temporibus eum autem odit repellendus nisi. Possimus modi, corrupti error debitis doloribus dicta libero earum, sequi porro ut excepturi nostrum ea voluptatem nihil culpa? Ullam expedita + eligendi obcaecati reiciendis velit provident omnis quas qui in corrupti est dolore facere ad hic, animi soluta assumenda consequuntur reprehenderit! Voluptate dolor nihil veniam laborum voluptas nisi pariatur sed optio accusantium quam + consectetur, corrupti, sequi et consequuntur, excepturi doloremque. Tempore quis velit corporis neque fugit non sequi eaque rem hic. Facere, inventore, aspernatur. Accusantium modi atque, asperiores qui nobis soluta cumque suscipit excepturi + possimus doloremque odit saepe perferendis temporibus molestiae nostrum voluptatum quis id sint quidem nesciunt culpa. Rerum labore dolor beatae blanditiis praesentium explicabo velit optio esse aperiam similique, voluptatem cum, maiores ipsa + tempore. Reiciendis sed culpa atque inventore, nam ullam enim expedita consectetur id velit iusto alias vitae explicabo nemo neque odio reprehenderit soluta sint eaque. Aperiam, qui ut tenetur, voluptate doloremque officiis dicta quaerat + voluptatem rerum natus magni. Eum amet autem dolor ullam.

    + + + +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero + perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt + laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab + tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, + doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis

    + + +

    laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic + quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi + tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos + nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? + Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus + accusantium eos veritatis neque.

    + + + +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero + perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. + Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod + doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque + vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione + soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem + harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. + Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio + molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. + Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero + perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. + Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod + doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque + vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione + soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem + harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. + Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio + molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. + Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    +
    + + + + diff --git a/13 - Slide in on Scroll/script.js b/13 - Slide in on Scroll/script.js new file mode 100644 index 0000000000..a3ebdc3545 --- /dev/null +++ b/13 - Slide in on Scroll/script.js @@ -0,0 +1,35 @@ +function debounce (func, wait = 20, immediate = true) { + var timeout + return function () { + var context = this + + var args = arguments + var later = function () { + timeout = null + if (!immediate) func.apply(context, args) + } + var callNow = immediate && !timeout + clearTimeout(timeout) + timeout = setTimeout(later, wait) + if (callNow) func.apply(context, args) + } +} + +const sliderImages = document.querySelectorAll('.slide-in') + +const checkSlide = function (e) { + sliderImages.forEach(img => { + // halfway through te image + const slideInAt = window.scrollY + window.innerHeight - img.height / 2 + // bottom of the image + const imageBottom = img.offsetTop + img.height + + if (slideInAt > img.offsetTop && window.scrollY < imageBottom) { + img.classList.add('active') + } else { + img.classList.remove('active') + } + }) +} + +window.addEventListener('scroll', debounce(checkSlide)) diff --git a/13 - Slide in on Scroll/style.css b/13 - Slide in on Scroll/style.css new file mode 100644 index 0000000000..45cb391de5 --- /dev/null +++ b/13 - Slide in on Scroll/style.css @@ -0,0 +1,55 @@ +html { + box-sizing: border-box; + background: #ffc600; + font-family: 'helvetica neue'; + font-size: 20px; + font-weight: 200; +} + +body { + margin: 0; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +h1 { + margin-top: 0; +} + +.site-wrap { + max-width: 700px; + margin: 100px auto; + background: white; + padding: 40px; + text-align: justify; +} + +.align-left { + float: left; + margin-right: 20px; +} + +.align-right { + float: right; + margin-left: 20px; +} + +.slide-in { + opacity: 0; + transition: all .5s; +} + +.align-left.slide-in { + transform: translateX(-30%) scale(0.95); +} + +.align-right.slide-in { + transform: translateX(30%) scale(0.95); +} + +.slide-in.active { + opacity: 1; + transform: translateX(0%) scale(1); +} diff --git a/14 - JavaScript References VS Copying/index-FINISHED.html b/14 - JavaScript References VS Copying/index-FINISHED.html deleted file mode 100644 index be6d1b7646..0000000000 --- a/14 - JavaScript References VS Copying/index-FINISHED.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - JS Reference VS Copy - - - - - - - diff --git a/14 - JavaScript References VS Copying/index-START.html b/14 - JavaScript References VS Copying/index-START.html deleted file mode 100644 index 4da1bac2ea..0000000000 --- a/14 - JavaScript References VS Copying/index-START.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - JS Reference VS Copy - - - - - - - diff --git a/14 - JavaScript References VS Copying/index.html b/14 - JavaScript References VS Copying/index.html new file mode 100644 index 0000000000..966cfac7d0 --- /dev/null +++ b/14 - JavaScript References VS Copying/index.html @@ -0,0 +1,13 @@ + + + + + + JS Reference VS Copy + + + + + + + diff --git a/14 - JavaScript References VS Copying/script.js b/14 - JavaScript References VS Copying/script.js new file mode 100644 index 0000000000..a2223500ef --- /dev/null +++ b/14 - JavaScript References VS Copying/script.js @@ -0,0 +1,91 @@ +// start with strings, numbers and booleans +let age = 100 +let age2 = age +console.log(age, age2) + +age = 200 +console.log(age, age2) + +let name = 'vanntile' +let name2 = name +console.log(name, name2) + +name = 'ianito' +console.log(name, name2) + +// Let's say we have an array +const players = ['Wes', 'Sarah', 'Ryan', 'Poppy']; + +// and we want to make a copy of it. +const team = players +console.log(players, team) + +// You might think we can just do something like this: +team[3] = 'Lux' + +// however what happens when we update that array? +console.info('An Array is a reference') + +// now here is the problem! + +// oh no - we have edited the original array too! + +// Why? It's because that is an array reference, not an array copy. They both point to the same array! + +// So, how do we fix this? We take a copy instead! +const team2 = players.slice() +team2[3] = 'Alpha' +console.log(team2) + +// one way + +// or create a new array and concat the old one in +const team3 = [].concat(players) +console.log(team3) + +// or use the new ES6 Spread +const team4 = [...players] +team4[3] = 'Another' +console.log(team4) + +const team5 = Array.from(players) +team5[3] = 'Blah blah' +console.log(team5) + +// now when we update it, the original one isn't changed + +// The same thing goes for objects, let's say we have a person object + +// with Objects +const person = { + name: 'Wes Bos', + age: 80 +}; + +// and think we make a copy: +const captain = person +captain.number = 99 + +// how do we take a copy instead? +const cap2 = Object.assign({}, person, { + expectancy: 99 +}) +console.log(person, cap2) + +// We will hopefully soon see the object ...spread + +// Things to note - this is only 1 level deep - both for Arrays and Objects. lodash has a cloneDeep method, but you should think twice before using it. +const vanntile = { + name: 'Vann', + age: 10000, + social: { + twitter: '@vanntile', + fb: '@vanntile' + } +} +console.log(vanntile) + +const dev = Object.assign({}, vanntile) +dev.social.twitter = '@johny' + +console.log(vanntile, dev) diff --git a/15 - LocalStorage/README.md b/15 - LocalStorage/README.md new file mode 100644 index 0000000000..8157d6fd94 --- /dev/null +++ b/15 - LocalStorage/README.md @@ -0,0 +1,41 @@ +# Local Storage + +This tutorial shows how to use local storage to keep user data refresh-safe. This could be the first step for an `offline web application` syncing. + +![Local Storage](../assets/img/15%20-%20LocalStorage.png?raw=true "Presentation Image") + +## My contribution + +As Wes challenged us, I have added buttons for clearing the list, checking and unchecking all the items. + +## Learning notes + +Some concepts taught: + +- `Window.localStorage` property from the `Web Storage API` +- ternary operators in `ES6 template literals` +- parent-children `event delegation` +- `JSON.stringify()` +- `submit` event for forms + +```javascript +// generating the HTML list elemnts +// we use a ternary operation for having the checked attribute checked +const populateList = function (plates = [], platesList) { + platesList.innerHTML = plates.map((plate, idx) => { + return ` +
  • + + +
  • + ` + }) + .join('') +} + +// saving data as 'items' to the localStorage map +localStorage.setItem('items', JSON.stringify(items)) + +// clearing a const array +items.splice(0, items.length) +``` diff --git a/15 - LocalStorage/index-FINISHED.html b/15 - LocalStorage/index-FINISHED.html deleted file mode 100644 index 2c492b1088..0000000000 --- a/15 - LocalStorage/index-FINISHED.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - LocalStorage - - - - - - - -
    -

    LOCAL TAPAS

    -

    -
      -
    • Loading Tapas...
    • -
    -
    - - -
    -
    - - - - - - - diff --git a/15 - LocalStorage/index-START.html b/15 - LocalStorage/index-START.html deleted file mode 100644 index d444f1d68b..0000000000 --- a/15 - LocalStorage/index-START.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - LocalStorage - - - - - - - -
    -

    LOCAL TAPAS

    -

    -
      -
    • Loading Tapas...
    • -
    -
    - - -
    -
    - - - - - - - diff --git a/15 - LocalStorage/index.html b/15 - LocalStorage/index.html new file mode 100644 index 0000000000..6d5f07ebac --- /dev/null +++ b/15 - LocalStorage/index.html @@ -0,0 +1,48 @@ + + + + + + LocalStorage + + + + + + + + + + + + + + + + + +
    +

    LOCAL TAPAS

    +

    +
      +
    • Loading Tapas...
    • +
    +
    + + +
    + Clear items + Clear selection + Select all +
    +
    +
    + + + + + + diff --git a/15 - LocalStorage/script.js b/15 - LocalStorage/script.js new file mode 100644 index 0000000000..30da12a582 --- /dev/null +++ b/15 - LocalStorage/script.js @@ -0,0 +1,73 @@ +const addItems = document.querySelector('.add-items') +const itemsList = document.querySelector('.plates') +const items = JSON.parse(localStorage.getItem('items')) || [] +const clearItemsBtn = document.querySelector('.clear-items') +const clearSelectionBtn = document.querySelector('.clear-selection') +const selectAllBtn = document.querySelector('.select-all') + +/* + * We want a reusable function + */ +const populateList = function (plates = [], platesList) { + platesList.innerHTML = plates.map((plate, idx) => { + return ` +
  • + + +
  • + ` + }) + .join('') +} + +const addItem = function (e) { + e.preventDefault() + + const text = (this.querySelector('[name=item]')) + .value + const item = { + text, + done: false + } + + items.push(item) + populateList(items, itemsList) + localStorage.setItem('items', JSON.stringify(items)) + + this.reset() +} + +const toggleDone = function (e) { + if (!e.target.matches('input')) return + + const idx = e.target.dataset.index + items[idx].done = !items[idx].done + + localStorage.setItem('items', JSON.stringify(items)) + populateList(items, itemsList) +} + +addItems.addEventListener('submit', addItem) + +// Get previous items +populateList(items, itemsList) + +itemsList.addEventListener('click', toggleDone) + +clearItemsBtn.addEventListener('click', () => { + items.splice(0, items.length) + populateList(items, itemsList) + localStorage.setItem('items', JSON.stringify(items)) +}) + +clearSelectionBtn.addEventListener('click', () => { + items.forEach(i => i.done = false) + populateList(items, itemsList) + localStorage.setItem('items', JSON.stringify(items)) +}) + +selectAllBtn.addEventListener('click', () => { + items.forEach(i => i.done = true) + populateList(items, itemsList) + localStorage.setItem('items', JSON.stringify(items)) +}) diff --git a/15 - LocalStorage/style.css b/15 - LocalStorage/style.css index ea5bab179c..f1d5c1c6b1 100644 --- a/15 - LocalStorage/style.css +++ b/15 - LocalStorage/style.css @@ -1,78 +1,96 @@ +html { + box-sizing: border-box; + background: url('http://wes.io/hx9M/oh-la-la.jpg') center no-repeat; + background-size: cover; + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + font-family: Futura,"Trebuchet MS",Arial,sans-serif; +} - html { - box-sizing: border-box; - background:url('http://wes.io/hx9M/oh-la-la.jpg') center no-repeat; - background-size:cover; - min-height:100vh; - display:flex; - justify-content: center; - align-items: center; - text-align: center; - font-family: Futura,"Trebuchet MS",Arial,sans-serif - } - *, *:before, *:after {box-sizing: inherit; } +*, *:before, *:after { + box-sizing: inherit; +} - svg { - fill:white; - background: rgba(0,0,0,0.1); - padding: 20px; - border-radius: 50%; - width:200px; - margin-bottom: 50px; - } +svg { + fill:white; + background: rgba(0,0,0,0.1); + padding: 20px; + border-radius: 50%; + width: 200px; + margin-bottom: 50px; +} - .wrapper { - padding: 20px; - max-width: 350px; - background: rgba(255,255,255,0.95); - box-shadow: 0 0 0 10px rgba(0,0,0,0.1); - } +.wrapper { + padding: 20px; + max-width: 350px; + background: rgba(255,255,255,0.95); + box-shadow: 0 0 0 10px rgba(0,0,0,0.1); +} - h2 { - text-align: center; - margin: 0; - font-weight: 200; - } +h2 { + text-align: center; + margin: 0; + font-weight: 200; +} - .plates { - margin: 0; - padding: 0; - text-align: left; - list-style: none; - } +.plates { + margin: 0; + padding: 0; + text-align: left; + list-style: none; +} - .plates li { - border-bottom: 1px solid rgba(0,0,0,0.2); - padding: 10px 0; - font-weight: 100; - display: flex; - } +.plates li { + border-bottom: 1px solid rgba(0,0,0,0.2); + padding: 10px 0; + font-weight: 100; + display: flex; +} - .plates label { - flex:1; - cursor: pointer; +.plates label { + flex: 1; + cursor: pointer; +} - } +.plates input { + display: none; +} - .plates input { - display: none; - } +.plates input + label:before { + content: '⬜️'; + margin-right: 10px; +} - .plates input + label:before { - content: '⬜️'; - margin-right: 10px; - } +.plates input:checked + label:before { + content: '🌮'; +} - .plates input:checked + label:before { - content: '🌮'; - } +.add-items { + margin-top: 20px; +} - .add-items { - margin-top: 20px; - } +.add-items input { + padding: 10px; + outline: 0; + border: 1px solid rgba(0,0,0,0.1); + max-width: 200px; +} - .add-items input { - padding:10px; - outline:0; - border:1px solid rgba(0,0,0,0.1); - } +.buttons { + margin: 12px 0; +} + +.btn { + display: inline-block; + padding: 6px; + border: 1px solid rgba(0,0,0,0.1); + font-size: 15px; +} + +.btn:hover { + cursor: pointer; + background: rgba(0,0,0,0.25) +} diff --git a/16 - Mouse Move Shadow/index-finished.html b/16 - Mouse Move Shadow/index-finished.html deleted file mode 100644 index 4328eaf6ab..0000000000 --- a/16 - Mouse Move Shadow/index-finished.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - Mouse Shadow - - - -
    -

    🔥WOAH!

    -
    - - - - - - diff --git a/16 - Mouse Move Shadow/index-start.html b/16 - Mouse Move Shadow/index-start.html deleted file mode 100644 index 58a9bba861..0000000000 --- a/16 - Mouse Move Shadow/index-start.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Mouse Shadow - - - -
    -

    🔥WOAH!

    -
    - - - - - - diff --git a/16 - Mouse Move Shadow/index.html b/16 - Mouse Move Shadow/index.html new file mode 100644 index 0000000000..08b44eb9a2 --- /dev/null +++ b/16 - Mouse Move Shadow/index.html @@ -0,0 +1,19 @@ + + + + + + Mouse Shadow + + + + + +
    +

    🔥WOAH!

    +
    + + + + + diff --git a/16 - Mouse Move Shadow/script.js b/16 - Mouse Move Shadow/script.js new file mode 100644 index 0000000000..a7ef7ce9c5 --- /dev/null +++ b/16 - Mouse Move Shadow/script.js @@ -0,0 +1,32 @@ +const hero = document.querySelector('.hero') +const text = hero.querySelector('h1') +const walk = 420 + +const shadow = function (e) { + const { + offsetWidth: width, + offsetHeight: height + } = hero + let { + offsetX: x, + offsetY: y + } = e + + if (this !== e.target) { + x = x + e.target.offsetLeft + y = y + e.target.offsetTop + } + + const xWalk = Math.round((x / width * walk) - (walk / 2)) + const yWalk = Math.round((y / height * walk) - (walk / 2)) + + text.style.textShadow = ` + ${xWalk}px ${yWalk}px 0 rgba(255, 0, 255, 0.7), + ${-xWalk}px ${yWalk}px 0 rgba(0, 255, 255, 0.7), + ${yWalk}px ${-xWalk}px 0 rgba(0, 0, 255, 0.7), + ${-yWalk}px ${xWalk}px 0 rgba(0, 255, 0, 0.7), + ${-xWalk}px ${-yWalk}px 0 rgba(255, 255, 0, 0.7) + ` +} + +hero.addEventListener('mousemove', shadow) diff --git a/16 - Mouse Move Shadow/style.css b/16 - Mouse Move Shadow/style.css new file mode 100644 index 0000000000..6b53541f4b --- /dev/null +++ b/16 - Mouse Move Shadow/style.css @@ -0,0 +1,22 @@ +html { + color: black; + font-family: sans-serif; +} + +body { + margin: 0; +} + +.hero { + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + color: black; + mix-blend-mode: overlay; +} + +h1 { + text-shadow: 10px 10px 0 rgba(0, 0, 0, 1); + font-size: 100px; +} diff --git a/17 - Sort Without Articles/index-FINISHED.html b/17 - Sort Without Articles/index-FINISHED.html deleted file mode 100644 index 5de851cbbd..0000000000 --- a/17 - Sort Without Articles/index-FINISHED.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - Sort Without Articles - - - - - -
      - - - - - diff --git a/17 - Sort Without Articles/index-START.html b/17 - Sort Without Articles/index-START.html deleted file mode 100644 index cfaf3e0440..0000000000 --- a/17 - Sort Without Articles/index-START.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Sort Without Articles - - - - - -
        - - - - - diff --git a/17 - Sort Without Articles/index.html b/17 - Sort Without Articles/index.html new file mode 100644 index 0000000000..ed60e5a561 --- /dev/null +++ b/17 - Sort Without Articles/index.html @@ -0,0 +1,16 @@ + + + + + + Sort Without Articles + + + + +
          + + + + + diff --git a/17 - Sort Without Articles/script.js b/17 - Sort Without Articles/script.js new file mode 100644 index 0000000000..40e5b65393 --- /dev/null +++ b/17 - Sort Without Articles/script.js @@ -0,0 +1,13 @@ +const bands = ['The Plot in You', 'The Devil Wears Prada', 'Pierce the Veil', 'Norma Jean', 'The Bled', 'Say Anything', 'The Midway State', 'We Came as Romans', 'Counterparts', 'Oh, Sleeper', 'A Skylit Drive', 'Anywhere But Here', 'An Old Dog']; + +const strip = function (s) { + return s.replace(/^(a |the |an)/i, '') + .trim() +} + +const sortedBands = bands.sort((a, b) => strip(a) > strip(b)) + + +document.querySelector('#bands') + .innerHTML = (sortedBands.map(band => `
        • ${band}
        • `) + .join('')) diff --git a/17 - Sort Without Articles/style.css b/17 - Sort Without Articles/style.css new file mode 100644 index 0000000000..85dc8c72bd --- /dev/null +++ b/17 - Sort Without Articles/style.css @@ -0,0 +1,33 @@ +body { + margin: 0; + font-family: sans-serif; + background: url("https://source.unsplash.com/nDqA4d5NL0k/2000x2000"); + background-size: cover; + display: flex; + align-items: center; + min-height: 100vh; +} + +#bands { + list-style: inside square; + font-size: 20px; + background: white; + width: 500px; + margin: auto; + padding: 0; + box-shadow: 0 0 0 20px rgba(0, 0, 0, 0.05); +} + +#bands li { + border-bottom: 1px solid #efefef; + padding: 20px; +} + +#bands li:last-child { + border-bottom: 0; +} + +a { + color: #ffc600; + text-decoration: none; +} diff --git a/18 - Adding Up Times with Reduce/index-FINISHED.html b/18 - Adding Up Times with Reduce/index-FINISHED.html deleted file mode 100644 index 9dcbb3d396..0000000000 --- a/18 - Adding Up Times with Reduce/index-FINISHED.html +++ /dev/null @@ -1,207 +0,0 @@ - - - - - Videos - - -
            -
          • - Video 1 -
          • -
          • - Video 2 -
          • -
          • - Video 3 -
          • -
          • - Video 4 -
          • -
          • - Video 5 -
          • -
          • - Video 6 -
          • -
          • - Video 7 -
          • -
          • - Video 8 -
          • -
          • - Video 9 -
          • -
          • - Video 10 -
          • -
          • - Video 11 -
          • -
          • - Video 12 -
          • -
          • - Video 13 -
          • -
          • - Video 14 -
          • -
          • - Video 15 -
          • -
          • - Video 16 -
          • -
          • - Video 17 -
          • -
          • - Video 18 -
          • -
          • - Video 19 -
          • -
          • - Video 20 -
          • -
          • - Video 21 -
          • -
          • - Video 22 -
          • -
          • - Video 23 -
          • -
          • - Video 24 -
          • -
          • - Video 25 -
          • -
          • - Video 26 -
          • -
          • - Video 27 -
          • -
          • - Video 28 -
          • -
          • - Video 29 -
          • -
          • - Video 30 -
          • -
          • - Video 31 -
          • -
          • - Video 32 -
          • -
          • - Video 33 -
          • -
          • - Video 34 -
          • -
          • - Video 35 -
          • -
          • - Video 36 -
          • -
          • - Video 37 -
          • -
          • - Video 38 -
          • -
          • - Video 39 -
          • -
          • - Video 40 -
          • -
          • - Video 41 -
          • -
          • - Video 42 -
          • -
          • - Video 43 -
          • -
          • - Video 44 -
          • -
          • - Video 45 -
          • -
          • - Video 46 -
          • -
          • - Video 47 -
          • -
          • - Video 48 -
          • -
          • - Video 49 -
          • -
          • - Video 50 -
          • -
          • - Video 51 -
          • -
          • - Video 52 -
          • -
          • - Video 53 -
          • -
          • - Video 54 -
          • -
          • - Video 55 -
          • -
          • - Video 56 -
          • -
          • - Video 57 -
          • -
          • - Video 58 -
          • - - - - diff --git a/18 - Adding Up Times with Reduce/index-START.html b/18 - Adding Up Times with Reduce/index.html similarity index 98% rename from 18 - Adding Up Times with Reduce/index-START.html rename to 18 - Adding Up Times with Reduce/index.html index 3eaee0f3ef..2ae240979f 100644 --- a/18 - Adding Up Times with Reduce/index-START.html +++ b/18 - Adding Up Times with Reduce/index.html @@ -1,9 +1,11 @@ + Videos +
            • @@ -180,8 +182,8 @@
            • Video 58
            • - - +
            + + diff --git a/18 - Adding Up Times with Reduce/script.js b/18 - Adding Up Times with Reduce/script.js new file mode 100644 index 0000000000..323bbcbc46 --- /dev/null +++ b/18 - Adding Up Times with Reduce/script.js @@ -0,0 +1,15 @@ +const times = Array.from(document.querySelectorAll('[data-time]')) +const seconds = times.map(node => node.dataset.time) + .map(timeCode => { + const [mins, secs] = timeCode.split(':') + .map(parseFloat) + return (mins * 60) + secs + }) + .reduce((total, seconds) => total + seconds, 0) + +const hours = Math.floor(seconds / 3600) +let secondsLeft = seconds % 3600 +const mins = Math.floor(secondsLeft / 60) +secondsLeft = secondsLeft % 60 + +console.log(hours + ':' + mins + ':' + secondsLeft) diff --git a/19 - Webcam Fun/README.md b/19 - Webcam Fun/README.md new file mode 100644 index 0000000000..d19f99135d --- /dev/null +++ b/19 - Webcam Fun/README.md @@ -0,0 +1,57 @@ +# Webcam Fun + +This tutorial shows how to play the camera feed into a `canvas` and apply filters on the raw data. + +![Webcam Fun](../assets/img/19%20-%20Webcam%20Fun.png?raw=true "Presentation Image") + +## My contribution + +I have improved the performance of applying live filters by applying the +following fixes, gaining **10 fps**, after performance testing, in Firefox 64: + - as the filter functions get an `Array object` as a parameter, they manipulate + directly the contents of the `pixels` array, so the return is redundant + - declared a few variables in the global `scope` to avoid redeclaration every + 16 miliseconds + +## Learning notes + +Some concepts taught: + +- `navigator.mediaDevices` for the camera stream +- a npm local server was needed +- `Uint8ClampedArray` for the video pixel data +- Canvas's `getImageDate()` and `putImageData()` methods +- Canvas's `toDataURL()` method for Base64 image generation +- `insertBefore()` for Node insertion +- manual `RGBA filters` + +```javascript +// splitting the RGB stream +const rgbSplit = (pixels) => { + for (let i = 0; i < pixels.data.length; i += 4) { + pixels.data[i - 150] = pixels.data[i + 0] // Red + pixels.data[i + 100] = pixels.data[i + 1] // Green + pixels.data[i - 150] = pixels.data[i + 2] // Blue + } +} + +// painting to canvas +const paintToCanvas = () => { + const width = video.videoWidth + const height = video.videoHeight + canvas.width = width + canvas.height = height + + return setInterval(() => { + ctx.drawImage(video, 0, 0, width, height) + let pixels = ctx.getImageData(0, 0, width, height) + console.log(pixels) + // ctx.globalAlpha = 0.1 // multiple layer painting - ghosting effect + // redEffect(pixels) // shifting the red pixels + rgbSplit(pixels) // splitting the RGB channels + // greenScreen(pixels) // removing the alpha on pixels + ctx.putImageData(pixels, 0, 0) + }, 16) +} + +``` diff --git a/19 - Webcam Fun/index.html b/19 - Webcam Fun/index.html index d4ffc4dc2a..5c12eeb540 100755 --- a/19 - Webcam Fun/index.html +++ b/19 - Webcam Fun/index.html @@ -1,16 +1,18 @@ + Get User Media Code Along! +
            - +
            @@ -42,4 +44,5 @@ + diff --git a/19 - Webcam Fun/package.json b/19 - Webcam Fun/package.json index 616baf5369..93bfac7cd0 100755 --- a/19 - Webcam Fun/package.json +++ b/19 - Webcam Fun/package.json @@ -4,11 +4,11 @@ "description": "", "main": "scripts.js", "scripts": { - "start" : "browser-sync start --server --files '*.css, *.html, *.js'" + "start": "browser-sync start --server --files \"*.css, *.html, *.js\"" }, "author": "", "license": "ISC", "devDependencies": { - "browser-sync": "^2.12.5" + "browser-sync": "^2.12.5 <2.23.2" } } diff --git a/19 - Webcam Fun/scripts-FINISHED.js b/19 - Webcam Fun/scripts-FINISHED.js deleted file mode 100755 index 0d62c8dc23..0000000000 --- a/19 - Webcam Fun/scripts-FINISHED.js +++ /dev/null @@ -1,102 +0,0 @@ -const video = document.querySelector('.player'); -const canvas = document.querySelector('.photo'); -const ctx = canvas.getContext('2d'); -const strip = document.querySelector('.strip'); -const snap = document.querySelector('.snap'); - -function getVideo() { - navigator.mediaDevices.getUserMedia({ video: true, audio: false }) - .then(localMediaStream => { - console.log(localMediaStream); - video.src = window.URL.createObjectURL(localMediaStream); - video.play(); - }) - .catch(err => { - console.error(`OH NO!!!`, err); - }); -} - -function paintToCanavas() { - const width = video.videoWidth; - const height = video.videoHeight; - canvas.width = width; - canvas.height = height; - - return setInterval(() => { - ctx.drawImage(video, 0, 0, width, height); - // take the pixels out - let pixels = ctx.getImageData(0, 0, width, height); - // mess with them - // pixels = redEffect(pixels); - - pixels = rgbSplit(pixels); - // ctx.globalAlpha = 0.8; - - // pixels = greenScreen(pixels); - // put them back - ctx.putImageData(pixels, 0, 0); - }, 16); -} - -function takePhoto() { - // played the sound - snap.currentTime = 0; - snap.play(); - - // take the data out of the canvas - const data = canvas.toDataURL('image/jpeg'); - const link = document.createElement('a'); - link.href = data; - link.setAttribute('download', 'handsome'); - link.innerHTML = `Handsome Man`; - strip.insertBefore(link, strip.firsChild); -} - -function redEffect(pixels) { - for(let i = 0; i < pixels.data.length; i+=4) { - pixels.data[i + 0] = pixels.data[i + 0] + 200; // RED - pixels.data[i + 1] = pixels.data[i + 1] - 50; // GREEN - pixels.data[i + 2] = pixels.data[i + 2] * 0.5; // Blue - } - return pixels; -} - -function rgbSplit(pixels) { - for(let i = 0; i < pixels.data.length; i+=4) { - pixels.data[i - 150] = pixels.data[i + 0]; // RED - pixels.data[i + 500] = pixels.data[i + 1]; // GREEN - pixels.data[i - 550] = pixels.data[i + 2]; // Blue - } - return pixels; -} - -function greenScreen(pixels) { - const levels = {}; - - document.querySelectorAll('.rgb input').forEach((input) => { - levels[input.name] = input.value; - }); - - for (i = 0; i < pixels.data.length; i = i + 4) { - red = pixels.data[i + 0]; - green = pixels.data[i + 1]; - blue = pixels.data[i + 2]; - alpha = pixels.data[i + 3]; - - if (red >= levels.rmin - && green >= levels.gmin - && blue >= levels.bmin - && red <= levels.rmax - && green <= levels.gmax - && blue <= levels.bmax) { - // take it out! - pixels.data[i + 3] = 0; - } - } - - return pixels; -} - -getVideo(); - -video.addEventListener('canplay', paintToCanavas); diff --git a/19 - Webcam Fun/scripts.js b/19 - Webcam Fun/scripts.js index 00355f5a9c..3588539814 100644 --- a/19 - Webcam Fun/scripts.js +++ b/19 - Webcam Fun/scripts.js @@ -1,5 +1,92 @@ -const video = document.querySelector('.player'); -const canvas = document.querySelector('.photo'); -const ctx = canvas.getContext('2d'); -const strip = document.querySelector('.strip'); -const snap = document.querySelector('.snap'); +const video = document.querySelector('.player') +const canvas = document.querySelector('.photo') +const ctx = canvas.getContext('2d') +const strip = document.querySelector('.strip') +const snap = document.querySelector('.snap') +const inputs = document.querySelectorAll('.rgb input') +const levels = {} +let i, red, blue, green + +const getVideo = () => { + navigator.mediaDevices.getUserMedia({ + video: true, + audio: false + }) + .then(localMediaStream => { + video.srcObject = localMediaStream + video.play() + }) + .catch(err => console.error('Webcam denied, error', err)) +} + +const paintToCanvas = () => { + const width = video.videoWidth + const height = video.videoHeight + canvas.width = width + canvas.height = height + + return setInterval(() => { + ctx.drawImage(video, 0, 0, width, height) + let pixels = ctx.getImageData(0, 0, width, height) + console.log(pixels) + // ctx.globalAlpha = 0.1 + // redEffect(pixels) + rgbSplit(pixels) + // greenScreen(pixels) + ctx.putImageData(pixels, 0, 0) + }, 16) +} + +const takePhoto = () => { + snap.currentTime = 0 + snap.play() + + const data = canvas.toDataURL('image/jpeg') + const link = document.createElement('a') + const date = new Date() + const name = `JS30_Day_19_${date.getHours()}_${date.getMinutes()}_${date.getSeconds()}.jpg` + link.href = data + link.setAttribute('download', name) + link.innerHTML = `Snapshot` + strip.insertBefore(link, strip.firstChild) +} + +const redEffect = (pixels) => { + for (let i = 0; i < pixels.data.length; i += 4) { + pixels.data[i + 0] = pixels.data[i + 0] + 100 // Red + pixels.data[i + 1] = pixels.data[i + 1] - 50 // Green + pixels.data[i + 2] = pixels.data[i + 2] * 0.5 // Blue + } +} + +const rgbSplit = (pixels) => { + for (let i = 0; i < pixels.data.length; i += 4) { + pixels.data[i - 150] = pixels.data[i + 0] // Red + pixels.data[i + 100] = pixels.data[i + 1] // Green + pixels.data[i - 150] = pixels.data[i + 2] // Blue + } +} + +const greenScreen = (pixels) => { + for (i = 0; i < pixels.data.length; i += 4) { + red = pixels.data[i + 0] + green = pixels.data[i + 1] + blue = pixels.data[i + 2] + + if (red >= levels.rmin && red <= levels.rmax && + green >= levels.gmin && green <= levels.gmax && + blue >= levels.bmin && blue <= levels.bmax) { + // take it out! + pixels.data[i + 3] = 0 + } + } +} + +const update = () => { + inputs.forEach(input => (levels[input.name] = input.value)) +} + +getVideo() +update() +video.addEventListener('canplay', paintToCanvas) +inputs.forEach(i => i.addEventListener('change', update)) diff --git a/19 - Webcam Fun/style.css b/19 - Webcam Fun/style.css index 4e8bee57c8..a08c2165ca 100755 --- a/19 - Webcam Fun/style.css +++ b/19 - Webcam Fun/style.css @@ -8,14 +8,14 @@ html { html { font-size: 10px; - background:#ffc600; + background: #ffc600; } .photobooth { - background:white; - max-width:150rem; + background: white; + max-width: 150rem; margin: 2rem auto; - border-radius:2px; + border-radius: 2px; } /*clearfix*/ @@ -26,34 +26,48 @@ html { } .photo { - width:100%; - float:left; + width: 100%; + float: left; } .player { position: absolute; - top:20px; + top: 20px; right: 20px; - width:200px; + width: 200px; } /* Strip! */ - .strip { - padding:2rem; + padding: 2rem; } + .strip img { - width:100px; + width: 100px; overflow-x: scroll; - padding:0.8rem 0.8rem 2.5rem 0.8rem; - box-shadow:0 0 3px rgba(0,0,0,0.2); - background:white; + padding: 0.8rem 0.8rem 2.5rem 0.8rem; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); + background: white; +} + +.strip a:nth-child(5n+1) img { + transform: rotate(10deg); +} + +.strip a:nth-child(5n+2) img { + transform: rotate(-2deg); } -.strip a:nth-child(5n+1) img { rotate: 10deg; } -.strip a:nth-child(5n+2) img { rotate: -2deg; } -.strip a:nth-child(5n+3) img { rotate: 8deg; } -.strip a:nth-child(5n+4) img { rotate: -11deg; } -.strip a:nth-child(5n+5) img { rotate: 12deg; } +.strip a:nth-child(5n+3) img { + transform: rotate(8deg); +} + +.strip a:nth-child(5n+4) img { + transform: rotate(-11deg); +} + +.strip a:nth-child(5n+5) img { + transform: rotate(12deg); +} diff --git a/20 - Speech Detection/index-FINISHED.html b/20 - Speech Detection/index-FINISHED.html index 413c3eeaaf..fdc53090d8 100644 --- a/20 - Speech Detection/index-FINISHED.html +++ b/20 - Speech Detection/index-FINISHED.html @@ -14,7 +14,8 @@ const recognition = new SpeechRecognition(); recognition.interimResults = true; - + recognition.lang = 'en-US'; + let p = document.createElement('p'); const words = document.querySelector('.words'); words.appendChild(p); @@ -47,26 +48,27 @@ } body { - background:#ffc600; + background: #ffc600; font-family: 'helvetica neue'; font-weight: 200; font-size: 20px; } .words { - max-width:500px; - margin:50px auto; - background:white; - border-radius:5px; - box-shadow:10px 10px 0 rgba(0,0,0,0.1); - padding:1rem 2rem 1rem 5rem; + max-width: 500px; + margin: 50px auto; + background: white; + border-radius: 5px; + box-shadow: 10px 10px 0 rgba(0,0,0,0.1); + padding: 1rem 2rem 1rem 5rem; background: -webkit-gradient(linear, 0 0, 0 100%, from(#d9eaf3), color-stop(4%, #fff)) 0 4px; background-size: 100% 3rem; position: relative; - line-height:3rem; + line-height: 3rem; } + p { - margin: 0 0 3rem 0; + margin: 0 0 3rem; } .words:before { diff --git a/20 - Speech Detection/index-START.html b/20 - Speech Detection/index-START.html deleted file mode 100644 index d3395cca35..0000000000 --- a/20 - Speech Detection/index-START.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - Speech Detection - - - -
            -
            - - - - - - - - diff --git a/20 - Speech Detection/index.html b/20 - Speech Detection/index.html new file mode 100644 index 0000000000..407f31e3d9 --- /dev/null +++ b/20 - Speech Detection/index.html @@ -0,0 +1,18 @@ + + + + + + Speech Detection + + + + + +
            +
            + + + + + diff --git a/20 - Speech Detection/package.json b/20 - Speech Detection/package.json index 5118ca0600..6a65e9225c 100755 --- a/20 - Speech Detection/package.json +++ b/20 - Speech Detection/package.json @@ -4,11 +4,11 @@ "description": "", "main": "scripts.js", "scripts": { - "start" : "browser-sync start --directory --server --files '*.css, *.html, *.js'" + "start": "browser-sync start --directory --server --files \"*.css, *.html, *.js\"" }, "author": "", "license": "ISC", "devDependencies": { - "browser-sync": "^2.12.5" + "browser-sync": "^2.12.5 <2.23.2" } } diff --git a/20 - Speech Detection/script.js b/20 - Speech Detection/script.js new file mode 100644 index 0000000000..972f183b5b --- /dev/null +++ b/20 - Speech Detection/script.js @@ -0,0 +1,28 @@ +window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + +const recognition = new SpeechRecognition(); +recognition.interimResults = true; +recognition.lang = 'en-US'; + +let p = document.createElement('p'); +const words = document.querySelector('.words'); +words.appendChild(p); + +recognition.addEventListener('result', e => { + const transcript = Array.from(e.results) + .map(result => result[0]) + .map(result => result.transcript) + .join(''); + + const poopScript = transcript.replace(/poop|poo|shit|dump/gi, '💩'); + p.textContent = poopScript; + + if (e.results[0].isFinal) { + p = document.createElement('p'); + words.appendChild(p); + } +}); + +recognition.addEventListener('end', recognition.start); + +recognition.start(); diff --git a/20 - Speech Detection/style.css b/20 - Speech Detection/style.css new file mode 100644 index 0000000000..c18f29d222 --- /dev/null +++ b/20 - Speech Detection/style.css @@ -0,0 +1,38 @@ +html { + font-size: 10px; +} + +body { + background: #ffc600; + font-family: 'helvetica neue'; + font-weight: 200; + font-size: 20px; +} + +.words { + max-width: 500px; + margin: 50px auto; + background: white; + border-radius: 5px; + box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.1); + padding: 1rem 2rem 1rem 5rem; + background: -webkit-gradient(linear, 0 0, 0 100%, from(#d9eaf3), color-stop(4%, #fff)) 0 4px; + background-size: 100% 3rem; + position: relative; + line-height: 3rem; +} + +p { + margin: 0 0 3rem; +} + +.words:before { + content: ''; + position: absolute; + width: 4px; + top: 0; + left: 30px; + bottom: 0; + border: 1px solid; + border-color: transparent #efe4e4; +} diff --git a/21 - Geolocation/README.md b/21 - Geolocation/README.md new file mode 100644 index 0000000000..e2afc281bb --- /dev/null +++ b/21 - Geolocation/README.md @@ -0,0 +1,29 @@ +# Geolocation + +This tutorial shows how to use the `Geolocation API` to get the speed and orientation of the phone. + +![Geolocation](../assets/img/21%20-%20Geolocation.png?raw=true "Presentation Image") + +## My contribution + +I have tested on the tutorial, debugging on Firefox for Android using the following: + - `adb` for Android debugging + - `live-server-htts` for a secure certificates to access the phone location + - a location spoofer for mocking + +## Learning notes + +Some concepts taught: + + - `Navigator.geolocation` from `Geolocation API` recommandation + - `watchPosition()` method to get the coordinates `data` + +```javascript +// using the Geolocation API +navigator.geolocation.watchPosition((data) => { + speed.textContent = Math.round(data.coords.speed * 100) / 100 + arrow.style.transform = `rotate(${data.coords.heading}deg)` +}, (err) => { + alert('Location permission is needed for speedometer') +}) +``` diff --git a/21 - Geolocation/index-FINISHED.html b/21 - Geolocation/index-FINISHED.html deleted file mode 100644 index 0f2ddecf2a..0000000000 --- a/21 - Geolocation/index-FINISHED.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - Document - - - - - - -

            - 0 - KM/H -

            - - - - - diff --git a/21 - Geolocation/index-START.html b/21 - Geolocation/index-START.html deleted file mode 100644 index d794c144ba..0000000000 --- a/21 - Geolocation/index-START.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Document - - - - - - -

            - 0 - KM/H -

            - - - - - diff --git a/21 - Geolocation/index.html b/21 - Geolocation/index.html new file mode 100644 index 0000000000..f208bbb71b --- /dev/null +++ b/21 - Geolocation/index.html @@ -0,0 +1,33 @@ + + + + + + Document + + + + + + + + + + + + + + + + + + + +

            + 0 + KM/H +

            + + + + diff --git a/21 - Geolocation/package.json b/21 - Geolocation/package.json index 80b7e68219..aad6fe2edc 100755 --- a/21 - Geolocation/package.json +++ b/21 - Geolocation/package.json @@ -4,11 +4,11 @@ "description": "", "main": "scripts.js", "scripts": { - "start" : "browser-sync start --directory --server --files '*.css, *.html, *.js' --https" + "start": "browser-sync start --directory --server --files \"*.css, *.html, *.js\" --https" }, "author": "", "license": "ISC", "devDependencies": { - "browser-sync": "^2.12.5" + "browser-sync": "^2.12.5 <2.23.2" } } diff --git a/21 - Geolocation/script.js b/21 - Geolocation/script.js new file mode 100644 index 0000000000..b9604a0726 --- /dev/null +++ b/21 - Geolocation/script.js @@ -0,0 +1,11 @@ +const arrow = document.querySelector('.arrow') +const speed = document.querySelector('.speed-value') + +navigator.geolocation.watchPosition((data) => { + console.log(data.coords.speed) + speed.textContent = Math.round(data.coords.speed * 100) / 100 + arrow.style.transform = `rotate(${data.coords.heading}deg)` +}, (err) => { + console.error(err) + alert('Location permission is needed for speedometer') +}) diff --git a/21 - Geolocation/style.css b/21 - Geolocation/style.css new file mode 100644 index 0000000000..d530a8c98a --- /dev/null +++ b/21 - Geolocation/style.css @@ -0,0 +1,43 @@ +html { + font-size: 100px; +} + +body { + margin: 0; + font-family: sans-serif; + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + background: + radial-gradient(black 15%, transparent 16%) 0 0, + radial-gradient(black 15%, transparent 16%) 8px 8px, + radial-gradient(rgba(255, 255, 255, .1) 15%, transparent 20%) 0 1px, + radial-gradient(rgba(255, 255, 255, .1) 15%, transparent 20%) 8px 9px; + background-color: #282828; + background-size: 16px 16px; + background-attachment: fixed; +} + +.arrow { + width: 250px; + overflow: hidden; + transition: all 0.2s; + transform: rotate(0deg); + display: inline-block; +} + +h1 { + color: white; + font-weight: 100; + font-size: 60px; + display: flex; + align-items: center; +} + +.units { + font-size: 15px; +} + +/*Compass: https://thenounproject.com/search/?q=compass&i=592352*/ diff --git a/22 - Follow Along Link Highlighter/index-FINISHED.html b/22 - Follow Along Link Highlighter/index-FINISHED.html deleted file mode 100644 index 74bd06e321..0000000000 --- a/22 - Follow Along Link Highlighter/index-FINISHED.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - 👀👀👀Follow Along Nav - - - - - - -
            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Est explicabo unde natus necessitatibus esse obcaecati distinctio, aut itaque, qui vitae!

            -

            Aspernatur sapiente quae sint soluta modi, atque praesentium laborum pariatur earum quaerat cupiditate consequuntur facilis ullam dignissimos, aperiam quam veniam.

            -

            Cum ipsam quod, incidunt sit ex tempore placeat maxime corrupti possimus veritatis ipsum fugit recusandae est doloremque? Hic, quibusdam, nulla.

            -

            Esse quibusdam, ad, ducimus cupiditate nulla, quae magni odit totam ut consequatur eveniet sunt quam provident sapiente dicta neque quod.

            -

            Aliquam dicta sequi culpa fugiat consequuntur pariatur optio ad minima, maxime odio, distinctio magni impedit tempore enim repellendus repudiandae quas!

            -
            - - - - - diff --git a/22 - Follow Along Link Highlighter/index-START.html b/22 - Follow Along Link Highlighter/index-START.html deleted file mode 100644 index 8476112b5e..0000000000 --- a/22 - Follow Along Link Highlighter/index-START.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - 👀👀👀Follow Along Nav - - - - - - -
            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Est explicabo unde natus necessitatibus esse obcaecati distinctio, aut itaque, qui vitae!

            -

            Aspernatur sapiente quae sint soluta modi, atque praesentium laborum pariatur earum quaerat cupiditate consequuntur facilis ullam dignissimos, aperiam quam veniam.

            -

            Cum ipsam quod, incidunt sit ex tempore placeat maxime corrupti possimus veritatis ipsum fugit recusandae est doloremque? Hic, quibusdam, nulla.

            -

            Esse quibusdam, ad, ducimus cupiditate nulla, quae magni odit totam ut consequatur eveniet sunt quam provident sapiente dicta neque quod.

            -

            Aliquam dicta sequi culpa fugiat consequuntur pariatur optio ad minima, maxime odio, distinctio magni impedit tempore enim repellendus repudiandae quas!

            -
            - - - - - diff --git a/22 - Follow Along Link Highlighter/index.html b/22 - Follow Along Link Highlighter/index.html new file mode 100644 index 0000000000..b7e562feb2 --- /dev/null +++ b/22 - Follow Along Link Highlighter/index.html @@ -0,0 +1,33 @@ + + + + + + 👀👀👀Follow Along Nav + + + + + + + +
            +

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Est explicabo unde natus necessitatibus esse obcaecati distinctio, aut itaque, qui vitae!

            +

            Aspernatur sapiente quae sint soluta modi, atque praesentium laborum pariatur earum quaerat cupiditate consequuntur facilis ullam dignissimos, aperiam quam veniam.

            +

            Cum ipsam quod, incidunt sit ex tempore placeat maxime corrupti possimus veritatis ipsum fugit recusandae est doloremque? Hic, quibusdam, nulla.

            +

            Esse quibusdam, ad, ducimus cupiditate nulla, quae magni odit totam ut consequatur eveniet sunt quam provident sapiente dicta neque quod.

            +

            Aliquam dicta sequi culpa fugiat consequuntur pariatur optio ad minima, maxime odio, distinctio magni impedit tempore enim repellendus repudiandae quas!

            +
            + + + + + diff --git a/22 - Follow Along Link Highlighter/script.js b/22 - Follow Along Link Highlighter/script.js new file mode 100644 index 0000000000..4e3a236b60 --- /dev/null +++ b/22 - Follow Along Link Highlighter/script.js @@ -0,0 +1,21 @@ +// 👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀 +const triggers = document.querySelectorAll('a') +const highlight = document.createElement('span') +highlight.classList.add('highlight') +document.body.append(highlight) + +const highlightLink = function () { + const linkCoords = this.getBoundingClientRect() + console.log(linkCoords) + const coords = { + width: linkCoords.width, + height: linkCoords.height, + top: linkCoords.top + window.scrollY, + left: linkCoords.left + window.scrollX + } + highlight.style.width = `${coords.width}px` + highlight.style.height = `${coords.height}px` + highlight.style.transform = `translate(${coords.left}px, ${coords.top}px)` +} + +triggers.forEach(a => a.addEventListener('mouseenter', highlightLink)) diff --git a/22 - Follow Along Link Highlighter/style.css b/22 - Follow Along Link Highlighter/style.css index 222e27ae68..099b15a577 100644 --- a/22 - Follow Along Link Highlighter/style.css +++ b/22 - Follow Along Link Highlighter/style.css @@ -1,23 +1,23 @@ html { box-sizing: border-box; } + *, *:before, *:after { box-sizing: inherit; } + body { min-height: 100vh; margin: 0; /* Important! */ font-family: sans-serif; background: linear-gradient(45deg, hsla(340, 100%, 55%, 1) 0%, hsla(340, 100%, 55%, 0) 70%), - linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%), - linear-gradient(225deg, hsla(140, 90%, 50%, 1) 10%, hsla(140, 90%, 50%, 0) 80%), - linear-gradient(315deg, hsla(35, 95%, 55%, 1) 100%, hsla(35, 95%, 55%, 0) 70%); + linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%) } .wrapper { - margin:0 auto; - max-width:500px; + margin: 0 auto; + max-width: 500px; font-size: 20px; line-height: 2; position: relative; @@ -25,22 +25,22 @@ body { a { text-decoration: none; - color:black; - background:rgba(0,0,0,0.05); - border-radius: 20px + color: black; + background: rgba(0,0,0,0.05); + border-radius: 20px; } .highlight { transition: all 0.2s; - border-bottom:2px solid white; + border-bottom: 2px solid white; position: absolute; - top:0; - background:white; - left:0; + top: 0; + background: white; + left: 0; z-index: -1; - border-radius:20px; + border-radius: 20px; display: block; - box-shadow: 0 0 10px rgba(0,0,0,0.2) + box-shadow: 0 0 10px rgba(0,0,0,0.2); } .menu { @@ -53,7 +53,7 @@ a { .menu a { display: inline-block; - padding:5px; - margin:0 20px; - color:black; + padding: 5px; + margin: 0 20px; + color: black; } diff --git a/23 - Speech Synthesis/README.md b/23 - Speech Synthesis/README.md new file mode 100644 index 0000000000..7d239204f7 --- /dev/null +++ b/23 - Speech Synthesis/README.md @@ -0,0 +1,34 @@ +# Speech Synthesis + +This tutorial shows how to use the `Web Speech API` to create a speech synthesiser. + +![Speech Synthesis](../assets/img/23%20-%20Speech%20Synthesis.png?raw=true "Presentation Image") + +## My contribution + +The implementation didn't work as intented (probably because the browser API +implementation changed meanwhile) and when the parameters changed or the start +button was clicked it didn't play the utterance. The queue didn't take the same +utterance twice, so I created a new utterance for each time play time. + +## Learning notes + +Some concepts taught: + + - `SpeechSynthesis` from `Web Speech API` + - `SpeechSynthesisUtterance` properties like `voice, pitch, rate, text` + - `Array.filter` + +```javascript +// mapping the voices to dropdown options +voices = synth.getVoices() +voicesDropdown.innerHTML = voices + .filter(v => v.lang.includes('en')) + .map(voice => + `` + ) + .join('') + +// create a new utterance +const utterance = new SpeechSynthesisUtterance(text.value) +``` diff --git a/23 - Speech Synthesis/index-FINISHED.html b/23 - Speech Synthesis/index-FINISHED.html deleted file mode 100644 index 5ea9a4a8e7..0000000000 --- a/23 - Speech Synthesis/index-FINISHED.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Speech Synthesis - - - - - -
            - -

            The Voiceinator 5000

            - - - - - - - - - - - - - -
            - - - - - diff --git a/23 - Speech Synthesis/index-START.html b/23 - Speech Synthesis/index-START.html deleted file mode 100644 index e890008d36..0000000000 --- a/23 - Speech Synthesis/index-START.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - Speech Synthesis - - - - - -
            - -

            The Voiceinator 5000

            - - - - - - - - - - - - - -
            - - - - - diff --git a/23 - Speech Synthesis/index.html b/23 - Speech Synthesis/index.html new file mode 100644 index 0000000000..13eec168a1 --- /dev/null +++ b/23 - Speech Synthesis/index.html @@ -0,0 +1,36 @@ + + + + + + Speech Synthesis + + + + + + +
            + +

            The Voiceinator 5000

            + + + + + + + + + + + + + +
            + + + + + diff --git a/23 - Speech Synthesis/script.js b/23 - Speech Synthesis/script.js new file mode 100644 index 0000000000..e48b8734cf --- /dev/null +++ b/23 - Speech Synthesis/script.js @@ -0,0 +1,39 @@ +const voicesDropdown = document.querySelector('[name="voice"]') +const options = document.querySelectorAll('[type="range"], [name="text"]') +const speakButton = document.querySelector('#speak') +const stopButton = document.querySelector('#stop') +const text = document.querySelector('[name="text"]') +const synth = window.speechSynthesis +let voices = [] + +const populateVoices = () => { + voices = synth.getVoices() + voicesDropdown.innerHTML = voices + .filter(v => v.lang.includes('en')) + .map(voice => + `` + ) + .join('') +} + +const speak = () => { + const utterance = new SpeechSynthesisUtterance(text.value) + + utterance.voice = voices.find(voice => voice.name === voicesDropdown.value) + options.forEach(o => (utterance[o.name] = o.value)) + + synth.speak(utterance) +} + +const changeVars = (doSpeak = true) => { + synth.cancel() + if (doSpeak) { + speak() + } +} + +populateVoices() +speakButton.addEventListener('click', speak) +stopButton.addEventListener('click', changeVars.bind(null, false)) +options.forEach(o => o.addEventListener('change', changeVars)) +voicesDropdown.addEventListener('change', changeVars) diff --git a/23 - Speech Synthesis/style.css b/23 - Speech Synthesis/style.css index 0e44fd0522..a948549238 100644 --- a/23 - Speech Synthesis/style.css +++ b/23 - Speech Synthesis/style.css @@ -8,10 +8,11 @@ html { } body { + margin: 0; padding: 0; font-family: sans-serif; - background-color:#3BC1AC; - display:flex; + background-color: #3BC1AC; + display: flex; min-height: 100vh; align-items: center; @@ -24,34 +25,29 @@ body { background-size:100px 50px; } - .voiceinator { - padding:2rem; - width:50rem; - margin:0 auto; - border-radius:1rem; + padding: 2rem; + width: 50rem; + margin: 0 auto; + border-radius: 1rem; position: relative; - background:white; + background: white; overflow: hidden; z-index: 1; - box-shadow:0 0 5px 5px rgba(0,0,0,0.1); + box-shadow: 0 0 5px 5px rgba(0,0,0,0.1); } h1 { - width:calc(100% + 4rem); - margin: 0; - margin-left: -2rem; - margin-top: -2rem; - margin-bottom: 2rem; - padding:.5rem; + width: calc(100% + 4rem); + margin: -2rem 0 2rem -2rem; + padding: .5rem; background: #ffc600; border-bottom: 5px solid #F3C010; text-align: center; font-size: 5rem; font-weight: 100; font-family: 'Pacifico', cursive; - text-shadow:3px 3px 0 #F3C010; - + text-shadow: 3px 3px 0 #F3C010; } .voiceinator input, @@ -60,12 +56,16 @@ h1 { .voiceinator textarea { width: 100%; display: block; - margin:10px 0; - padding:10px; - border:0; + margin: 10px 0; + padding: 10px; + border: 0; font-size: 2rem; - background:#F7F7F7; - outline:0; + background: #F7F7F7; + outline: 0; +} + +input[type=range]::-moz-range-track { + background-color: #F3C010; } textarea { @@ -77,20 +77,20 @@ input[type="select"] { } .voiceinator button { - background:#ffc600; - border:0; + background: #ffc600; + border: 0; width: 49%; - float:left; + float: left; font-family: 'Pacifico', cursive; margin-bottom: 0; font-size: 2rem; border-bottom: 5px solid #F3C010; - cursor:pointer; + cursor: pointer; position: relative; } .voiceinator button:active { - top:2px; + top: 2px; } .voiceinator button:nth-of-type(1) { diff --git a/24 - Sticky Nav/index-FINISHED.html b/24 - Sticky Nav/index-FINISHED.html deleted file mode 100644 index 2e5961192c..0000000000 --- a/24 - Sticky Nav/index-FINISHED.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - Document - - - - -
            -

            A story about getting lost.

            -
            - - - -
            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

            - -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptates, deserunt facilis et iste corrupti omnis tenetur est. Iste ut est dicta dolor itaque adipisci, dolorum minima, veritatis earum provident error molestias. Ratione magni illo sint vel velit ut excepturi consectetur suscipit, earum modi accusamus voluptatem nostrum, praesentium numquam, reiciendis voluptas sit id quisquam. Consequatur in quis reprehenderit modi perspiciatis necessitatibus saepe, quidem, suscipit iure natus dignissimos ipsam, eligendi deleniti accusantium, rerum quibusdam fugit perferendis et optio recusandae sed ratione. Culpa, dolorum reprehenderit harum ab voluptas fuga, nisi eligendi natus maiores illum quas quos et aperiam aut doloremque optio maxime fugiat doloribus. Eum dolorum expedita quam, nesciunt

            - -

            at provident praesentium atque quas rerum optio dignissimos repudiandae ullam illum quibusdam. Vel ad error quibusdam, illo ex totam placeat. Quos excepturi fuga, molestiae ea quisquam minus, ratione dicta consectetur officia omnis, doloribus voluptatibus? Veniam ipsum veritatis architecto, provident quas consequatur doloremque quam quidem earum expedita, ad delectus voluptatum, omnis praesentium nostrum qui aspernatur ea eaque adipisci et cumque ab? Ea voluptatum dolore itaque odio. Eius minima distinctio harum, officia ab nihil exercitationem. Tempora rem nemo nam temporibus molestias facilis minus ipsam quam doloribus consequatur debitis nesciunt tempore officiis aperiam quisquam, molestiae voluptates cum, fuga culpa. Distinctio accusamus quibusdam, tempore perspiciatis dolorum optio facere consequatur quidem ullam beatae architecto, ipsam sequi officiis dignissimos amet impedit natus necessitatibus tenetur repellendus dolor rem! Dicta dolorem, iure, facilis illo ex nihil ipsa amet officia, optio temporibus eum autem odit repellendus nisi. Possimus modi, corrupti error debitis doloribus dicta libero earum, sequi porro ut excepturi nostrum ea voluptatem nihil culpa? Ullam expedita eligendi obcaecati reiciendis velit provident omnis quas qui in corrupti est dolore facere ad hic, animi soluta assumenda consequuntur reprehenderit! Voluptate dolor nihil veniam laborum voluptas nisi pariatur sed optio accusantium quam consectetur, corrupti, sequi et consequuntur, excepturi doloremque. Tempore quis velit corporis neque fugit non sequi eaque rem hic. Facere, inventore, aspernatur. Accusantium modi atque, asperiores qui nobis soluta cumque suscipit excepturi possimus doloremque odit saepe perferendis temporibus molestiae nostrum voluptatum quis id sint quidem nesciunt culpa. Rerum labore dolor beatae blanditiis praesentium explicabo velit optio esse aperiam similique, voluptatem cum, maiores ipsa tempore. Reiciendis sed culpa atque inventore, nam ullam enim expedita consectetur id velit iusto alias vitae explicabo nemo neque odio reprehenderit soluta sint eaque. Aperiam, qui ut tenetur, voluptate doloremque officiis dicta quaerat voluptatem rerum natus magni. Eum amet autem dolor ullam.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

            - -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

            -

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

            -
            - - - - - - diff --git a/24 - Sticky Nav/index-START.html b/24 - Sticky Nav/index.html similarity index 96% rename from 24 - Sticky Nav/index-START.html rename to 24 - Sticky Nav/index.html index 4982537eea..ec43107b6c 100644 --- a/24 - Sticky Nav/index-START.html +++ b/24 - Sticky Nav/index.html @@ -3,7 +3,7 @@ Sticky Nav - + @@ -53,22 +53,7 @@

            A story about getting lost.

            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

          - + diff --git a/24 - Sticky Nav/script.js b/24 - Sticky Nav/script.js new file mode 100644 index 0000000000..708d93f1b8 --- /dev/null +++ b/24 - Sticky Nav/script.js @@ -0,0 +1,14 @@ +const nav = document.querySelector('#main') +const topOfNav = nav.offsetTop + +const fixNav = function (e) { + if (window.scrollY >= topOfNav) { + document.body.style.paddingTop = nav.offsetHeight + document.body.classList.add('fixed-nav') + } else { + document.body.style.paddingTop = 0 + document.body.classList.remove('fixed-nav') + } +} + +window.addEventListener('scroll', fixNav) diff --git a/24 - Sticky Nav/style-FINISHED.css b/24 - Sticky Nav/style-FINISHED.css deleted file mode 100644 index b551473357..0000000000 --- a/24 - Sticky Nav/style-FINISHED.css +++ /dev/null @@ -1,100 +0,0 @@ -html { - box-sizing: border-box; - background:#eeeeee; - font-family:'helvetica neue'; - font-size: 20px; - font-weight: 200; -} -body { - margin: 0; -} -*, *:before, *:after { - box-sizing: inherit; -} - -.site-wrap { - max-width: 700px; - margin: 70px auto; - background:white; - padding:40px; - text-align: justify; - box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.05); - transform: scale(0.98); - transition: transform 0.5s; -} - -body.fixed-nav .site-wrap { - transform: scale(1); -} - - -header { - text-align: center; - height:50vh; - background:url(http://wes.io/iEgP/wow-so-deep.jpg) bottom center no-repeat; - background-size:cover; - display:flex; - align-items:center; - justify-content: center; -} - -h1 { - color:white; - font-size: 7vw; - text-shadow: 3px 4px 0 rgba(0,0,0,0.2) -} - -nav { - background:black; - top:0; - width: 100%; - transition:all 0.5s; - position: relative; - z-index: 1; -} - -body.fixed-nav nav { - position: fixed; - box-shadow:0 5px 0 rgba(0,0,0,0.1); -} - -nav ul { - margin: 0; - padding:0; - list-style: none; - display:flex; -} - -nav li { - flex:1; - text-align: center; - display: flex; - justify-content: center; - align-items: center; -} - -li.logo { - max-width:0; - overflow: hidden; - background: white; - transition: all 0.5s; - font-weight: 600; - font-size: 30px; -} - -li.logo a { - color:black; -} - -.fixed-nav li.logo { - max-width:500px; -} - -nav a { - text-decoration: none; - padding:20px; - display: inline-block; - color:white; - transition:all 0.2s; - text-transform: uppercase; -} diff --git a/24 - Sticky Nav/style-START.css b/24 - Sticky Nav/style.css similarity index 64% rename from 24 - Sticky Nav/style-START.css rename to 24 - Sticky Nav/style.css index 19961112b4..8ea20f3169 100644 --- a/24 - Sticky Nav/style-START.css +++ b/24 - Sticky Nav/style.css @@ -1,13 +1,15 @@ html { box-sizing: border-box; - background:#eeeeee; - font-family:'helvetica neue'; + background: #eeeeee; + font-family: 'helvetica neue'; font-size: 20px; font-weight: 200; } + body { margin: 0; } + *, *:before, *:after { box-sizing: inherit; } @@ -15,8 +17,8 @@ body { .site-wrap { max-width: 700px; margin: 70px auto; - background:white; - padding:40px; + background: white; + padding: 40px; text-align: justify; box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.05); transform: scale(0.98); @@ -29,43 +31,43 @@ body { header { text-align: center; - height:50vh; - background:url(http://wes.io/iEgP/wow-so-deep.jpg) bottom center no-repeat; - background-size:cover; - display:flex; - align-items:center; + height: 50vh; + background: url(http://wes.io/iEgP/wow-so-deep.jpg) bottom center no-repeat; + background-size: cover; + display: flex; + align-items: center; justify-content: center; } h1 { - color:white; + color: white; font-size: 7vw; - text-shadow: 3px 4px 0 rgba(0,0,0,0.2) + text-shadow: 3px 4px 0 rgba(0,0,0,0.2); } nav { - background:black; - top:0; + background: black; + top: 0; width: 100%; - transition:all 0.5s; + transition: all 0.5s; position: relative; z-index: 1; } .fixed-nav nav { position: fixed; - box-shadow: 0 5px rgba(0,0,0,0.1) + box-shadow: 0 5px rgba(0,0,0,0.1); } nav ul { margin: 0; padding:0; list-style: none; - display:flex; + display: flex; } nav li { - flex:1; + flex: 1; text-align: center; display: flex; justify-content: center; @@ -73,7 +75,7 @@ nav li { } li.logo { - max-width:0; + max-width: 0; overflow: hidden; background: white; transition: all .5s; @@ -82,18 +84,18 @@ li.logo { } .fixed-nav li.logo { - max-width:500px; + max-width: 500px; } li.logo a { - color:black; + color: black; } nav a { text-decoration: none; - padding:20px; + padding: 20px; display: inline-block; - color:white; - transition:all 0.2s; + color: white; + transition: all 0.2s; text-transform: uppercase; } diff --git a/25 - Event Capture, Propagation, Bubbling and Once/index-FINISHED.html b/25 - Event Capture, Propagation, Bubbling and Once/index-FINISHED.html deleted file mode 100644 index 8856df9d89..0000000000 --- a/25 - Event Capture, Propagation, Bubbling and Once/index-FINISHED.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - Understanding JavaScript's Capture - - - -
          -
          -
          -
          -
          -
          - - - - - - - diff --git a/25 - Event Capture, Propagation, Bubbling and Once/index-START.html b/25 - Event Capture, Propagation, Bubbling and Once/index-START.html deleted file mode 100644 index 98f5e070c4..0000000000 --- a/25 - Event Capture, Propagation, Bubbling and Once/index-START.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - Understanding JavaScript's Capture - - - -
          -
          -
          -
          -
          -
          - - - - - - - diff --git a/25 - Event Capture, Propagation, Bubbling and Once/index.html b/25 - Event Capture, Propagation, Bubbling and Once/index.html new file mode 100644 index 0000000000..d803203bb0 --- /dev/null +++ b/25 - Event Capture, Propagation, Bubbling and Once/index.html @@ -0,0 +1,23 @@ + + + + + + Understanding JavaScript's Capture + + + + + +
          +
          +
          +
          +
          +
          + + + + + + diff --git a/25 - Event Capture, Propagation, Bubbling and Once/script.js b/25 - Event Capture, Propagation, Bubbling and Once/script.js new file mode 100644 index 0000000000..6901802739 --- /dev/null +++ b/25 - Event Capture, Propagation, Bubbling and Once/script.js @@ -0,0 +1,14 @@ +const divs = document.querySelectorAll('div') + +const logText = function (e) { + console.log(this.classList.value) + // stop bubbling the event up + //e.stopPropagation() +} + +divs.forEach(div => div.addEventListener('click', logText, { + // run the function on the capture down + // capture: true + // unbind the event for further clicks + once: true +})) diff --git a/25 - Event Capture, Propagation, Bubbling and Once/style.css b/25 - Event Capture, Propagation, Bubbling and Once/style.css new file mode 100644 index 0000000000..bde5add57d --- /dev/null +++ b/25 - Event Capture, Propagation, Bubbling and Once/style.css @@ -0,0 +1,24 @@ +html { + box-sizing: border-box; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +div { + width: 100%; + padding: 100px; +} + +.one { + background: thistle; +} + +.two { + background: mistyrose; +} + +.three { + background: coral; +} diff --git a/26 - Stripe Follow Along Nav/README.md b/26 - Stripe Follow Along Nav/README.md new file mode 100644 index 0000000000..8db0ef7772 --- /dev/null +++ b/26 - Stripe Follow Along Nav/README.md @@ -0,0 +1,43 @@ +# Stripe Follow Along Nav + +This tutorial shows how to build a link navigator `micro-animation` UI element like Stripe's. + +![Stripe Follow Along Nav](../assets/img/26%20-%20Stripe%20Follow%20Along%20Nav.png?raw=true "Presentation Image") + +## How does it work + +It mixes JavaScript and CSS to transition a tooltip background behind a +navigation dropdown list. The dropdown element has two events for transitions, +`.trigger-enter` and `.trigger-enter-active`, while the tooltip has only an +`.open` transition. + +```CSS +/* dropdown list */ +.trigger-enter .dropdown { + display: block; +} + +.trigger-enter-active .dropdown { + opacity: 1; +} + +/* tooltip */ +.dropdownBackground.open { + opacity: 1; +} +``` + +## Learning notes + +Some concepts taught: + + - revisioning `getBoundingClientRect()` which returns a `DOMRect` with an objects complete bounding box coordinates + - setting properties with `ES6 template literals` + - `event` delegation and order + +```javascript +// set the second class after a delay only if the first has not been removed +this.classList.add('trigger-enter') +setTimeout(() => + this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'), 150) +``` diff --git a/26 - Stripe Follow Along Nav/index-FINISHED.html b/26 - Stripe Follow Along Nav/index-FINISHED.html deleted file mode 100644 index 9cf05f388d..0000000000 --- a/26 - Stripe Follow Along Nav/index-FINISHED.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - Follow Along Nav - - -

          Cool

          - - - - - - - - diff --git a/26 - Stripe Follow Along Nav/index-START.html b/26 - Stripe Follow Along Nav/index-START.html deleted file mode 100644 index 9780d0d1ac..0000000000 --- a/26 - Stripe Follow Along Nav/index-START.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - Follow Along Nav - - -

          Cool

          - - - - - - - - diff --git a/26 - Stripe Follow Along Nav/index.html b/26 - Stripe Follow Along Nav/index.html new file mode 100644 index 0000000000..69100e6a6c --- /dev/null +++ b/26 - Stripe Follow Along Nav/index.html @@ -0,0 +1,80 @@ + + + + + Follow Along Nav + + + +

          Cool

          + + + + + diff --git a/26 - Stripe Follow Along Nav/script.js b/26 - Stripe Follow Along Nav/script.js new file mode 100644 index 0000000000..e5bbea7645 --- /dev/null +++ b/26 - Stripe Follow Along Nav/script.js @@ -0,0 +1,33 @@ +const triggers = document.querySelectorAll('.cool > li') +const background = document.querySelector('.dropdownBackground') +const nav = document.querySelector('.top') + +const handleEnter = function () { + this.classList.add('trigger-enter') + setTimeout(() => + this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'), 150) + background.classList.add('open') + + const dropdown = this.querySelector('.dropdown') + const dropdownCoords = dropdown.getBoundingClientRect() + const navCoords = nav.getBoundingClientRect() + + const coords = { + height: dropdownCoords.height, + width: dropdownCoords.width, + top: dropdownCoords.top - navCoords.top, + left: dropdownCoords.left - navCoords.left + } + + background.style.setProperty('width', `${coords.width}px`) + background.style.setProperty('height', `${coords.height}px`) + background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`) +} + +const handleLeave = function () { + this.classList.remove('trigger-enter', 'trigger-enter-active') + background.classList.remove('open') +} + +triggers.forEach(t => t.addEventListener('mouseenter', handleEnter)) +triggers.forEach(t => t.addEventListener('mouseleave', handleLeave)) diff --git a/26 - Stripe Follow Along Nav/style.css b/26 - Stripe Follow Along Nav/style.css new file mode 100644 index 0000000000..8c4e2da247 --- /dev/null +++ b/26 - Stripe Follow Along Nav/style.css @@ -0,0 +1,150 @@ +html { + box-sizing: border-box; + font-family: "Arial Rounded MT Bold", "Helvetica Rounded", Arial, sans-serif; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +body { + margin: 0; + min-height: 100vh; + background: + linear-gradient(45deg, hsla(340, 100%, 55%, 1) 0%, hsla(340, 100%, 55%, 0) 70%), + linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%), + linear-gradient(225deg, hsla(140, 90%, 50%, 1) 10%, hsla(140, 90%, 50%, 0) 80%), + linear-gradient(315deg, hsla(35, 95%, 55%, 1) 100%, hsla(35, 95%, 55%, 0) 70%); +} + +h2 { + margin-top: 0; + padding-top: .8em; +} + +nav { + position: relative; + perspective: 600px; +} + +.cool>li>a { + color: yellow; + text-decoration: none; + font-size: 20px; + background: rgba(0, 0, 0, 0.2); + padding: 10px 20px; + display: inline-block; + margin: 20px; + border-radius: 5px; +} + +nav ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + justify-content: center; +} + +.cool>li { + position: relative; + display: flex; + justify-content: center; +} + +.dropdown { + opacity: 0; + position: absolute; + overflow: hidden; + padding: 20px; + top: -20px; + border-radius: 2px; + transition: all 0.5s; + transform: translateY(100px); + will-change: opacity; + display: none; +} + +.trigger-enter .dropdown { + display: block; +} + +.trigger-enter-active .dropdown { + opacity: 1; +} + +.dropdownBackground { + width: 100px; + height: 100px; + position: absolute; + background: #fff; + border-radius: 4px; + box-shadow: 0 50px 100px rgba(50, 50, 93, .1), 0 15px 35px rgba(50, 50, 93, .15), 0 5px 15px rgba(0, 0, 0, .1); + transition: all 0.3s, opacity 0.1s, transform 0.2s; + transform-origin: 50% 0; + display: flex; + justify-content: center; + opacity: 0; +} + +.dropdownBackground.open { + opacity: 1; +} + +.arrow { + position: absolute; + width: 20px; + height: 20px; + display: block; + background: white; + transform: translateY(-50%) rotate(45deg); +} + +.bio { + min-width: 500px; + display: flex; + justify-content: center; + align-items: center; + line-height: 1.7; +} + +.bio img { + float: left; + margin-right: 20px; +} + +.courses { + min-width: 300px; +} + +.courses li { + padding: 10px 0; + display: block; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); +} + +.dropdown a { + text-decoration: none; + color: #ffc600; +} + +a.button { + background: black; + display: block; + padding: 10px; + color: white; + margin-bottom: 10px; +} + +/* Matches Twitter, TWITTER, twitter, tWitter, TWiTTeR... */ +.button[href*=twitter] { + background: #019FE9; +} + +.button[href*=facebook] { + background: #3B5998; +} + +.button[href*=courses] { + background: #ffc600; +} diff --git a/27 - Click and Drag/index-FINISHED.html b/27 - Click and Drag/index-FINISHED.html deleted file mode 100644 index 52eb86628c..0000000000 --- a/27 - Click and Drag/index-FINISHED.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - Click and Drag - - - -
          -
          01
          -
          02
          -
          03
          -
          04
          -
          05
          -
          06
          -
          07
          -
          08
          -
          09
          -
          10
          -
          11
          -
          12
          -
          13
          -
          14
          -
          15
          -
          16
          -
          17
          -
          18
          -
          19
          -
          20
          -
          21
          -
          22
          -
          23
          -
          24
          -
          25
          -
          - - - - - diff --git a/27 - Click and Drag/index-START.html b/27 - Click and Drag/index.html similarity index 96% rename from 27 - Click and Drag/index-START.html rename to 27 - Click and Drag/index.html index b8609315f7..f0caf7c438 100644 --- a/27 - Click and Drag/index-START.html +++ b/27 - Click and Drag/index.html @@ -1,10 +1,12 @@ + Click and Drag +
          01
          @@ -33,9 +35,8 @@
          24
          25
          + - + - diff --git a/27 - Click and Drag/script.js b/27 - Click and Drag/script.js new file mode 100644 index 0000000000..48f8a98576 --- /dev/null +++ b/27 - Click and Drag/script.js @@ -0,0 +1,27 @@ +const items = document.querySelector('.items') +let isDown = false +let startX +let scrollLeft + +items.addEventListener('mousedown', (e) => { + isDown = true + items.classList.add('active') + startX = e.pageX - items.offsetLeft + scrollLeft = items.scrollLeft +}) +items.addEventListener('mouseleave', () => { + isDown = false + items.classList.remove('active') +}) +items.addEventListener('mouseup', () => { + isDown = false + items.classList.remove('active') +}) +items.addEventListener('mousemove', (e) => { + if (!isDown) return + e.preventDefault() + + const x = e.pageX - items.offsetLeft + const walk = (x - startX) * 3 + items.scrollLeft = scrollLeft - walk +}) diff --git a/27 - Click and Drag/style.css b/27 - Click and Drag/style.css index 209b32cae5..debd09247a 100644 --- a/27 - Click and Drag/style.css +++ b/27 - Click and Drag/style.css @@ -19,11 +19,10 @@ body { } .items { - height:800px; + height: 800px; padding: 100px; - width:100%; - border:1px solid white; - box-shadow: 0 0 10px 7px rgba(0, 0, 0, 0.09); + width: 100%; + border: 1px solid white; overflow-x: scroll; overflow-y: hidden; white-space: nowrap; @@ -31,6 +30,7 @@ body { cursor: pointer; transition: all 0.2s; transform: scale(0.98); + will-change: transform; position: relative; background: rgba(255,255,255,0.1); font-size: 0; @@ -45,14 +45,14 @@ body { } .item { - width:200px; + width: 200px; height: calc(100% - 40px); display: inline-flex; align-items: center; justify-content: center; font-size: 80px; font-weight: 100; - color:rgba(0,0,0,0.15); + color: rgba(0,0,0,0.15); box-shadow: inset 0 0 0 10px rgba(0,0,0,0.15); } @@ -68,9 +68,3 @@ body { .item:nth-child(even) { transform: scaleX(1.31) rotateY(40deg); } .item:nth-child(odd) { transform: scaleX(1.31) rotateY(-40deg); } - -.wrap { - width: auto; - border:2px solid green; - height: 100%; -} diff --git a/28 - Video Speed Controller/index-FINISHED.html b/28 - Video Speed Controller/index-FINISHED.html deleted file mode 100644 index a7d2f3e38c..0000000000 --- a/28 - Video Speed Controller/index-FINISHED.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - Video Speed Scrubber - - - - -
          - -
          -
          -
          -
          - - - - diff --git a/28 - Video Speed Controller/index-START.html b/28 - Video Speed Controller/index.html similarity index 60% rename from 28 - Video Speed Controller/index-START.html rename to 28 - Video Speed Controller/index.html index c4cbd4259a..87f7647dc2 100644 --- a/28 - Video Speed Controller/index-START.html +++ b/28 - Video Speed Controller/index.html @@ -1,20 +1,21 @@ + Video Speed Scrubber +
          - +
          - - + + diff --git a/28 - Video Speed Controller/script.js b/28 - Video Speed Controller/script.js new file mode 100644 index 0000000000..3511dc6c13 --- /dev/null +++ b/28 - Video Speed Controller/script.js @@ -0,0 +1,15 @@ +const speed = document.querySelector('.speed') +const bar = speed.querySelector('.speed-bar') +const video = document.querySelector('.flex') + +speed.addEventListener('mousemove', function (e) { + const y = e.pageY - this.offsetTop + const percent = y / this.offsetHeight + const min = 0.4 + const max = 4 + const height = `${Math.round(percent * 100)}%` + const playbackRate = percent * (max - min) + min + bar.style.height = height + bar.textContent = `${playbackRate.toFixed(2)}x` + video.playbackRate = playbackRate +}) diff --git a/28 - Video Speed Controller/style.css b/28 - Video Speed Controller/style.css index c837d991e4..656c796247 100644 --- a/28 - Video Speed Controller/style.css +++ b/28 - Video Speed Controller/style.css @@ -1,38 +1,42 @@ body { - display:flex; + margin: 0; + display: flex; justify-content: center; align-items: center; min-height: 100vh; - background:#4C4C4C url('https://unsplash.it/1500/900?image=1021'); - background-size:cover; + background: #4C4C4C url('https://unsplash.it/1500/900?image=1021'); + background-size: cover; font-family: sans-serif; } + .wrapper { - width:850px; - display:flex; + width: 850px; + display: flex; } + video { - box-shadow:0 0 1px 3px rgba(0,0,0,0.1); + box-shadow: 0 0 1px 3px rgba(0,0,0,0.1); } .speed { - background:#efefef; - flex:1; - display:flex; - align-items:flex-start; - margin:10px; - border-radius:50px; - box-shadow:0 0 1px 3px rgba(0,0,0,0.1); + background: #efefef; + flex: 1; + display: flex; + align-items: flex-start; + margin: 10px; + border-radius: 50px; + box-shadow: 0 0 1px 3px rgba(0,0,0,0.1); overflow: hidden; } + .speed-bar { - width:100%; - background:linear-gradient(-170deg, #2376ae 0%, #c16ecf 100%); - text-shadow:1px 1px 0 rgba(0,0,0,0.2); + width: 100%; + background: linear-gradient(-170deg, #2376ae 0%, #c16ecf 100%); + text-shadow: 1px 1px 0 rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; - padding:2px; - color:white; - height:16.3%; + padding: 2px; + color: white; + height: 16.3%; } diff --git a/29 - Countown Timer/index.html b/29 - Countdown Timer/index.html similarity index 95% rename from 29 - Countown Timer/index.html rename to 29 - Countdown Timer/index.html index d54f447dd9..aadf8bc17b 100644 --- a/29 - Countown Timer/index.html +++ b/29 - Countdown Timer/index.html @@ -24,6 +24,6 @@

          - + diff --git a/29 - Countdown Timer/script.js b/29 - Countdown Timer/script.js new file mode 100644 index 0000000000..1107aecf12 --- /dev/null +++ b/29 - Countdown Timer/script.js @@ -0,0 +1,54 @@ +const timerDisplay = document.querySelector('.display__time-left') +const endTime = document.querySelector('.display__end-time') +const buttons = document.querySelectorAll('[data-time]') +let countdown + +const timer = (seconds) => { + // clear existing interval + clearInterval(countdown) + + const now = Date.now() + const then = now + seconds * 1000 + displayTimeLeft(seconds) + displayEndTime(then) + + countdown = setInterval(() => { + const secondsLeft = Math.round((then - Date.now()) / 1000) + + // check if we should stop + if (secondsLeft < 0) { + clearInterval(countdown) + return + } + + // display + displayTimeLeft(secondsLeft) + }, 1000) +} + +const displayTimeLeft = (seconds) => { + const hours = Math.floor(seconds / 3600) + seconds = seconds % 3600 + const minutes = Math.floor(seconds / 60) + const secondsLeft = seconds % 60 + const display = `${hours > 0 ? hours + ':' : ''}${minutes < 10 ? '0' : ''}${minutes}:${secondsLeft < 10 ? '0' : ''}${secondsLeft}` + timerDisplay.textContent = display + document.title = display +} + +const displayEndTime = (timestamp) => { + const end = new Date(timestamp) + const mins = end.getMinutes() + endTime.textContent = `Be back at ${end.getHours()}:${mins < 10 ? '0' : ''}${mins}` +} + +buttons.forEach(button => button.addEventListener('click', function () { + timer(parseInt(this.dataset.time)) +})) + +document.customForm.addEventListener('submit', function (e) { + e.preventDefault() + const mins = this.minutes.value + timer(mins * 60) + this.reset() +}) diff --git a/29 - Countown Timer/style.css b/29 - Countdown Timer/style.css similarity index 63% rename from 29 - Countown Timer/style.css rename to 29 - Countdown Timer/style.css index f240799477..f8416c0fb0 100644 --- a/29 - Countown Timer/style.css +++ b/29 - Countdown Timer/style.css @@ -10,7 +10,7 @@ html { } body { - margin:0; + margin: 0; text-align: center; font-family: 'Inconsolata', monospace; } @@ -19,14 +19,14 @@ body { font-weight: 100; font-size: 20rem; margin: 0; - color:white; - text-shadow:4px 4px 0 rgba(0,0,0,0.05); + color: white; + text-shadow: 4px 4px 0 rgba(0,0,0,0.05); } .timer { - display:flex; + display: flex; min-height: 100vh; - flex-direction:column; + flex-direction: column; } .timer__controls { @@ -34,43 +34,43 @@ body { } .timer__controls > * { - flex:1; + flex: 1; } .timer__controls form { - flex:1; - display:flex; + flex: 1; + display: flex; } .timer__controls input { - flex:1; - border:0; - padding:2rem; + flex: 1; + border: 0; + padding: 2rem; } .timer__button { - background:none; - border:0; + background: none; + border: 0; cursor: pointer; - color:white; + color: white; font-size: 2rem; text-transform: uppercase; - background:rgba(0,0,0,0.1); - border-bottom:3px solid rgba(0,0,0,0.2); - border-right:1px solid rgba(0,0,0,0.2); - padding:1rem; + background: rgba(0,0,0,0.1); + border-bottom: 3px solid rgba(0,0,0,0.2); + border-right: 1px solid rgba(0,0,0,0.2); + padding: 1rem; font-family: 'Inconsolata', monospace; } .timer__button:hover, .timer__button:focus { - background:rgba(0,0,0,0.2); - outline:0; + background: rgba(0,0,0,0.2); + outline: 0; } .display { - flex:1; - display:flex; + flex: 1; + display: flex; flex-direction: column; align-items: center; justify-content: center; @@ -78,5 +78,5 @@ body { .display__end-time { font-size: 4rem; - color:white; + color: white; } diff --git a/29 - Countown Timer/scripts-FINISHED.js b/29 - Countown Timer/scripts-FINISHED.js deleted file mode 100644 index 581cadb270..0000000000 --- a/29 - Countown Timer/scripts-FINISHED.js +++ /dev/null @@ -1,55 +0,0 @@ -let countdown; -const timerDisplay = document.querySelector('.display__time-left'); -const endTime = document.querySelector('.display__end-time'); -const buttons = document.querySelectorAll('[data-time]'); - -function timer(seconds) { - // clear any existing timers - clearInterval(countdown); - - const now = Date.now(); - const then = now + seconds * 1000; - displayTimeLeft(seconds); - displayEndTime(then); - - countdown = setInterval(() => { - const secondsLeft = Math.round((then - Date.now()) / 1000); - // check if we should stop it! - if(secondsLeft < 0) { - clearInterval(countdown); - return; - } - // display it - displayTimeLeft(secondsLeft); - }, 1000); -} - -function displayTimeLeft(seconds) { - const minutes = Math.floor(seconds / 60); - const remainderSeconds = seconds % 60; - const display = `${minutes}:${remainderSeconds < 10 ? '0' : '' }${remainderSeconds}`; - document.title = display; - timerDisplay.textContent = display; -} - -function displayEndTime(timestamp) { - const end = new Date(timestamp); - const hour = end.getHours(); - const adjustedHour = hour > 12 ? hour - 12 : hour; - const minutes = end.getMinutes(); - endTime.textContent = `Be Back At ${adjustedHour}:${minutes < 10 ? '0' : ''}${minutes}`; -} - -function startTimer() { - const seconds = parseInt(this.dataset.time); - timer(seconds); -} - -buttons.forEach(button => button.addEventListener('click', startTimer)); -document.customForm.addEventListener('submit', function(e) { - e.preventDefault(); - const mins = this.minutes.value; - console.log(mins); - timer(mins * 60); - this.reset(); -}); diff --git a/29 - Countown Timer/scripts-START.js b/29 - Countown Timer/scripts-START.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/30 - Whack A Mole/index-FINISHED.html b/30 - Whack A Mole/index-FINISHED.html deleted file mode 100644 index 8d741d5bf0..0000000000 --- a/30 - Whack A Mole/index-FINISHED.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - Whack A Mole! - - - - - -

          Whack-a-mole! 0

          - - -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          -
          - - - - diff --git a/30 - Whack A Mole/index-START.html b/30 - Whack A Mole/index.html similarity index 72% rename from 30 - Whack A Mole/index-START.html rename to 30 - Whack A Mole/index.html index 2014ff458c..8d33327bed 100644 --- a/30 - Whack A Mole/index-START.html +++ b/30 - Whack A Mole/index.html @@ -1,15 +1,16 @@ + Whack A Mole! + -

          Whack-a-mole! 0

          - +

          Whack-a-mole! 0

          @@ -31,12 +32,9 @@

          Whack-a-mole! 0

          + - + + diff --git a/30 - Whack A Mole/script.js b/30 - Whack A Mole/script.js new file mode 100644 index 0000000000..3dd4b5e424 --- /dev/null +++ b/30 - Whack A Mole/script.js @@ -0,0 +1,70 @@ +const holes = document.querySelectorAll('.hole') +const scoreBoard = document.querySelector('.score') +const title = document.querySelector('.title') +const moles = document.querySelectorAll('.mole') +const startButton = document.querySelector('button') + +const min = 200 // minimum time in miliseconds +const max = 1000 // maximum time in miliseconds +const duration = 20 // maximum game time in seconds + +let lastHole +let timeUp = true +let score + +const randomTime = (min, max) => { + return Math.round(Math.random() * (max - min) + min) +} + +const randomHole = (holes) => { + const idx = Math.floor(Math.random() * holes.length) + const hole = holes[idx] + + if (hole === lastHole) { + return randomHole(holes) + } + + lastHole = hole + return hole +} + +const peep = () => { + const time = randomTime(min, max) + const hole = randomHole(holes) + + hole.classList.add('up') + setTimeout(() => { + hole.classList.remove('up') + + if (!timeUp) { + peep() + } else { + title.textContent = 'Your score is' + } + }, time) +} + +const startGame = () => { + score = 0 + scoreBoard.textContent = 0 + title.textContent = 'Whack-a-mole!' + timeUp = false + + peep() + setTimeout(() => { + timeUp = true + }, (duration * 1000)) +} + +const bonk = function (e) { + if (!e.isTrusted) return + + score++ + this.classList.remove('up') + scoreBoard.textContent = score +} + +moles.forEach(mole => mole.addEventListener('click', bonk)) +moles.forEach(mole => mole.addEventListener('touchstart', bonk)) + +startButton.addEventListener('click', () => timeUp && startGame()) diff --git a/30 - Whack A Mole/style.css b/30 - Whack A Mole/style.css index 8fec3f5d8e..9cbff6509b 100644 --- a/30 - Whack A Mole/style.css +++ b/30 - Whack A Mole/style.css @@ -9,31 +9,37 @@ html { } body { + width: 101vw; + height: 100vh; + overflow-x: hidden; + display: block; padding: 0; - margin:0; + margin: 0; font-family: 'Amatic SC', cursive; } h1 { text-align: center; font-size: 10rem; - line-height:1; + line-height: 1; margin-bottom: 0; } .score { - background:rgba(255,255,255,0.2); - padding:0 3rem; - line-height:1; - border-radius:1rem; + background: rgba(255, 255, 255, 0.2); + padding: 0 3rem; + line-height: 1; + border-radius: 1rem; } .game { - width:600px; - height:400px; - display:flex; - flex-wrap:wrap; - margin:0 auto; + width: 100%; + height: 100%; + max-width: 760px; + max-height: 500px; + display: flex; + flex-wrap: wrap; + margin: 0 auto; } .hole { @@ -45,25 +51,61 @@ h1 { .hole:after { display: block; background: url(dirt.svg) bottom center no-repeat; - background-size:contain; - content:''; + background-size: contain; + content: ''; width: 100%; - height:70px; + height: 70px; position: absolute; z-index: 2; - bottom:-30px; + bottom: -30px; } .mole { - background:url('mole.svg') bottom center no-repeat; - background-size:60%; + background: url('mole.svg') bottom center no-repeat; + background-size: 60%; position: absolute; top: 100%; width: 100%; height: 100%; - transition:all 0.4s; + transition: all 0.4s; } .hole.up .mole { - top:0; + top: 0; +} + +button { + display: block; + border: none; + margin: 5em auto 0; + padding: 18px; + border-radius: 6px; + font-size: 28px; + font-weight: bold; + line-height: 32px; + background: #222; + color: #ffc600; + text-transform: capitalize; + letter-spacing: 1px; + transform: scale(1); + transition: transform .3s; +} + +button:hover { + background: #000; + transform: scale(1.2); + cursor: pointer; +} + +@media screen and (max-width: 768px) { + h1 { + font-size: 5rem + } + .hole:after { + height: 84px; + bottom: -28px; + } + button { + margin-top: 1em + } } diff --git a/README.md b/README.md new file mode 100644 index 0000000000..a984ec2166 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +![](https://javascript30.com/images/JS3-social-share.png) + +# JavaScript30 + +Wes Bos' ([@wesbos](https://github.com/wesbos)) JavaScript 30 Day Challenge. +This is a plain vanilla JavaScript exercise, without any libraries, transpilers +or frameworks. I have a **README** for each day when I have added custom +functionality, that you can check below. + +My own implementations can be found here. + +Check the course at [https://JavaScript30.com](https://JavaScript30.com) + +I have beautified the `HTML` and `CSS` using [atom-beautify](https://atom.io/packages/atom-beautify) +and linted the `JavaScript` using [JavaScript Standard Style](https://standardjs.com/). + +## Some Concepts learned + +- Using methods and properties from the following `Web APIs` and `interfaces` + - `MouseEvent`, `HTMLMediaElement`, `Fullscreen`, `WebStorage`, `Navigator`, `Geolocation`, `Canvas`, `Web Speech` +- `Document Object Model` Node List + - ES6 `Promises`, `template literals`, `array spreading`, `destructuring parameters`, `typed arrays`, `arrow function` + - `transitioned` event + - use of the `data` attribute for custom element data +- `RegEx` string filter +- parent-children `event delegation` +- `JSON.stringify()` +- local servers (through `npm` packages) +- and others + +## Table of contents + +1. [x] [JavaScript Drum Kit](https://vanntile.github.io/JavaScript30/01%20-%20JavaScript%20Drum%20Kit/) + and my [notes](./01%20-%20JavaScript%20Drum%20Kit) +2. [x] [JS + CSS Clock](https://vanntile.github.io/JavaScript30/02%20-%20JS%20and%20CSS%20Clock/) +3. [x] [CSS Variables](https://vanntile.github.io/JavaScript30/03%20-%20CSS%20Variables/) +4. [x] [Array Cardio, Day 1](https://vanntile.github.io/JavaScript30/04%20-%20Array%20Cardio%20Day%201/) +5. [x] [Flex Panel Gallery](https://vanntile.github.io/JavaScript30/05%20-%20Flex%20Panel%20Gallery/) + and my [notes](./05%20-%20Flex%20Panel%20Gallery) +6. [x] [Type Ahead](https://vanntile.github.io/JavaScript30/06%20-%20Type%20Ahead) + and my [notes](./06%20-%20Type%20Ahead) +7. [x] [Array Cardio, Day 2](https://vanntile.github.io/JavaScript30/07%20-%20Array%20Cardio%20Day%202/) +8. [x] [Fun with HTML5 Canvas](https://vanntile.github.io/JavaScript30/08%20-%20Fun%20with%20HTML5%20Canvas) +9. [x] [Dev Tools Domination](https://vanntile.github.io/JavaScript30/09%20-%20Dev%20Tools%20Domination/) +10. [x] [Hold Shift and Check Checkboxes](https://vanntile.github.io/JavaScript30/10%20-%20Hold%20Shift%20and%20Check%20Checkboxes) + and my [notes](./10%20-%20Hold%20Shift%20and%20Check%20Checkboxes) +11. [x] [Custom Video Player](https://vanntile.github.io/JavaScript30/11%20-%20Custom%20Video%20Player/) + and my [notes](./11%20-%20Custom%20Video%20Player) +12. [x] [Key Sequence Detection](https://vanntile.github.io/JavaScript30/12%20-%20Key%20Sequence%20Detection) +13. [x] [Slide in on Scroll](https://vanntile.github.io/JavaScript30/13%20-%20Slide%20in%20on%20Scroll) +14. [x] [JavaScript References vs. Copying](https://vanntile.github.io/JavaScript30/14%20-%20JavaScript%20References%20VS%20Copying) +15. [x] [LocalStorage](https://vanntile.github.io/JavaScript30/15%20-%20LocalStorage) + and my [notes](./15%20-%20LocalStorage) +16. [x] [Mouse Move Shadow](https://vanntile.github.io/JavaScript30/16%20-%20Mouse%20Move%20Shadow) +17. [x] [Sort Without Articles](https://vanntile.github.io/JavaScript30/17%20-%20Sort%20Without%20Articles) +18. [x] [Adding Up Times with Reduce](https://vanntile.github.io/JavaScript30/18%20-%20Adding%20Up%20Times%20with%20Reduce) +19. [x] [Webcam Fun](https://vanntile.github.io/JavaScript30/19%20-%20Webcam%20Fun) + and my [notes](./19%20-%20Webcam%20Fun) +20. [ ] Speech Detection (not working in browsers for now) +21. [x] [Geolocation](https://vanntile.github.io/JavaScript30/21%20-%20Geolocation) + and my [notes](./21%20-%20Geolocation) +22. [x] [Follow Along Link Highlighter](https://vanntile.github.io/JavaScript30/22%20-%20Follow%20Along%20Link%20Highlighter) +23. [x] [Speech Synthesis](https://vanntile.github.io/JavaScript30/23%20-%20Speech%20Synthesis) + and my [notes](./23%20-%20Speech%20Synthesis) +24. [x] [Sticky Nav](https://vanntile.github.io/JavaScript30/24%20-%20Sticky%20Nav) +25. [x] [Event Capture, Propagation, Bubbling, and Once](https://vanntile.github.io/JavaScript30/25%20-%20Event%20Capture,%20Propagation,%20Bubbling%20and%20Once) +26. [x] [Stripe Follow Along Nav](https://vanntile.github.io/JavaScript30/26%20-%20Stripe%20Follow%20Along%20Nav) + and my [notes](./26%20-%20Stripe%20Follow%20Along%20Nav) +27. [x] [Click and Drag](https://vanntile.github.io/JavaScript30/27%20-%20Click%20and%20Drag) +28. [x] [Video Speed Controller](https://vanntile.github.io/JavaScript30/28%20-%20Video%20Speed%20Controller) +29. [x] [Countdown Timer](https://vanntile.github.io/JavaScript30/29%20-%20Countdown%20Timer) +30. [x] [Whack A Mole](https://vanntile.github.io/JavaScript30/30%20-%20Whack%20A%20Mole) diff --git a/assets/img/01 - JavaScript Drum Kit.png b/assets/img/01 - JavaScript Drum Kit.png new file mode 100644 index 0000000000..310c270fbc Binary files /dev/null and b/assets/img/01 - JavaScript Drum Kit.png differ diff --git a/assets/img/02 - JS and CSS Clock.png b/assets/img/02 - JS and CSS Clock.png new file mode 100644 index 0000000000..11f10f1aca Binary files /dev/null and b/assets/img/02 - JS and CSS Clock.png differ diff --git a/assets/img/03 - CSS Variables.png b/assets/img/03 - CSS Variables.png new file mode 100644 index 0000000000..b778ef0e26 Binary files /dev/null and b/assets/img/03 - CSS Variables.png differ diff --git a/assets/img/04 - Array Cardio Day 1.png b/assets/img/04 - Array Cardio Day 1.png new file mode 100644 index 0000000000..7906c1c758 Binary files /dev/null and b/assets/img/04 - Array Cardio Day 1.png differ diff --git a/assets/img/05 - Flex Panel Gallery.png b/assets/img/05 - Flex Panel Gallery.png new file mode 100644 index 0000000000..046378c0f7 Binary files /dev/null and b/assets/img/05 - Flex Panel Gallery.png differ diff --git a/assets/img/06 - Type Ahead.png b/assets/img/06 - Type Ahead.png new file mode 100644 index 0000000000..16c93d498e Binary files /dev/null and b/assets/img/06 - Type Ahead.png differ diff --git a/assets/img/07 - Array Cardio Day 2.png b/assets/img/07 - Array Cardio Day 2.png new file mode 100644 index 0000000000..00d52b741d Binary files /dev/null and b/assets/img/07 - Array Cardio Day 2.png differ diff --git a/assets/img/08 - Fun with HTML5 Canvas.png b/assets/img/08 - Fun with HTML5 Canvas.png new file mode 100644 index 0000000000..da6a080fcf Binary files /dev/null and b/assets/img/08 - Fun with HTML5 Canvas.png differ diff --git a/assets/img/09 - Dev Tools Domination.png b/assets/img/09 - Dev Tools Domination.png new file mode 100644 index 0000000000..2bf18502ea Binary files /dev/null and b/assets/img/09 - Dev Tools Domination.png differ diff --git a/assets/img/10 - Hold Shift and Check Checkboxes.png b/assets/img/10 - Hold Shift and Check Checkboxes.png new file mode 100644 index 0000000000..b0a1984253 Binary files /dev/null and b/assets/img/10 - Hold Shift and Check Checkboxes.png differ diff --git a/assets/img/11 - Custom Video Player.png b/assets/img/11 - Custom Video Player.png new file mode 100644 index 0000000000..88d218d7c9 Binary files /dev/null and b/assets/img/11 - Custom Video Player.png differ diff --git a/assets/img/12 - Key Sequence Detection.png b/assets/img/12 - Key Sequence Detection.png new file mode 100644 index 0000000000..f753fafd96 Binary files /dev/null and b/assets/img/12 - Key Sequence Detection.png differ diff --git a/assets/img/13 - Slide in on Scroll.png b/assets/img/13 - Slide in on Scroll.png new file mode 100644 index 0000000000..557236b9f0 Binary files /dev/null and b/assets/img/13 - Slide in on Scroll.png differ diff --git a/assets/img/14 - JavaScript References VS Copying.png b/assets/img/14 - JavaScript References VS Copying.png new file mode 100644 index 0000000000..df01764170 Binary files /dev/null and b/assets/img/14 - JavaScript References VS Copying.png differ diff --git a/assets/img/15 - LocalStorage.png b/assets/img/15 - LocalStorage.png new file mode 100644 index 0000000000..5a6604bc75 Binary files /dev/null and b/assets/img/15 - LocalStorage.png differ diff --git a/assets/img/16 - Mouse Move Shadow.png b/assets/img/16 - Mouse Move Shadow.png new file mode 100644 index 0000000000..6cbbb6d5c5 Binary files /dev/null and b/assets/img/16 - Mouse Move Shadow.png differ diff --git a/assets/img/17 - Sort Without Articles.png b/assets/img/17 - Sort Without Articles.png new file mode 100644 index 0000000000..ff4d80d17d Binary files /dev/null and b/assets/img/17 - Sort Without Articles.png differ diff --git a/assets/img/18 - Adding Up Times with Reduce.png b/assets/img/18 - Adding Up Times with Reduce.png new file mode 100644 index 0000000000..425918e184 Binary files /dev/null and b/assets/img/18 - Adding Up Times with Reduce.png differ diff --git a/assets/img/19 - Webcam Fun.png b/assets/img/19 - Webcam Fun.png new file mode 100644 index 0000000000..306fca06de Binary files /dev/null and b/assets/img/19 - Webcam Fun.png differ diff --git a/assets/img/21 - Geolocation.png b/assets/img/21 - Geolocation.png new file mode 100644 index 0000000000..9626242091 Binary files /dev/null and b/assets/img/21 - Geolocation.png differ diff --git a/assets/img/22 - Follow Along Link Highlighter.png b/assets/img/22 - Follow Along Link Highlighter.png new file mode 100644 index 0000000000..9a2744361d Binary files /dev/null and b/assets/img/22 - Follow Along Link Highlighter.png differ diff --git a/assets/img/23 - Speech Synthesis.png b/assets/img/23 - Speech Synthesis.png new file mode 100644 index 0000000000..79f741daf8 Binary files /dev/null and b/assets/img/23 - Speech Synthesis.png differ diff --git a/assets/img/24 - Sticky Nav.png b/assets/img/24 - Sticky Nav.png new file mode 100644 index 0000000000..147e6a3de2 Binary files /dev/null and b/assets/img/24 - Sticky Nav.png differ diff --git a/assets/img/25 - Event Capture, Propagation, Bubbling and Once.png b/assets/img/25 - Event Capture, Propagation, Bubbling and Once.png new file mode 100644 index 0000000000..6cf0f327f6 Binary files /dev/null and b/assets/img/25 - Event Capture, Propagation, Bubbling and Once.png differ diff --git a/assets/img/26 - Stripe Follow Along Nav.png b/assets/img/26 - Stripe Follow Along Nav.png new file mode 100644 index 0000000000..cac05cee10 Binary files /dev/null and b/assets/img/26 - Stripe Follow Along Nav.png differ diff --git a/assets/img/27 - Click and Drag.png b/assets/img/27 - Click and Drag.png new file mode 100644 index 0000000000..195a96f552 Binary files /dev/null and b/assets/img/27 - Click and Drag.png differ diff --git a/assets/img/28 - Video Speed Controller.png b/assets/img/28 - Video Speed Controller.png new file mode 100644 index 0000000000..a961e827f8 Binary files /dev/null and b/assets/img/28 - Video Speed Controller.png differ diff --git a/assets/img/29 - Countdown Timer.png b/assets/img/29 - Countdown Timer.png new file mode 100644 index 0000000000..716a9c02c6 Binary files /dev/null and b/assets/img/29 - Countdown Timer.png differ diff --git a/assets/img/30 - Whack A Mole.png b/assets/img/30 - Whack A Mole.png new file mode 100644 index 0000000000..8555cd55ad Binary files /dev/null and b/assets/img/30 - Whack A Mole.png differ diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000000..96cd68a3c6 --- /dev/null +++ b/assets/style.css @@ -0,0 +1,224 @@ +:root { + --color-primary: #3f51b5; + --color-secondary: #c5cae9; + --color-accent: #f6be15; +} + +html, +body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + position: relative; + font-family: -apple-system, BlinkMacSystemFont, "Roboto", "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, sans-serif; + font-size: 100%; + -webkit-text-size-adjust: 100%; + line-height: 1.15; + text-align: center; + background: var(--color-primary); + color: var(--color-secondary); +} + +h1 { + font-size: 64px; + margin: 15px 0; +} + +h2 { + font-size: 25px; + margin: 0; +} + +p { + font-size: 18px; +} + +.header a { + color: inherit; + text-decoration: none; + font-weight: 900; + letter-spacing: 1.2px; + transition: .2s ease; + display: inline-block; + min-width: 0; + padding: 0 4px; +} + +.header a:hover { + font-size: 1.25em; + color: var(--color-primary); + background: var(--color-secondary); + border-radius: 18px; + min-width: 85px; +} + +.container { + max-width: 1500px; + margin: 0 auto; + padding: 15px; + display: -webkit-flex; + display: flex; + -webkit-flex-flow: row wrap; + flex-flow: row wrap; + justify-content: center; +} + +.container-block { + display: block; + float: left; + position: relative; + margin: 15px; + flex-basis: calc(25% - 30px); + order: 3; + border-radius: 4px; +} + +.container-block>a { + display: block; + width: 100%; + background: + linear-gradient(rgba(51, 51, 51, 0.2), + rgba(51, 51, 51, 0.7)); + transition: .15s ease-in; + border-radius: 4px; +} + +.container-block img { + display: block; + width: 100%; + border-radius: 4px; + mix-blend-mode: luminosity; +} + +.container-block:hover>a { + background: + linear-gradient(rgba(0, 0, 0, 0), + rgba(0, 0, 0, 0.5)); + transform: scale(1.1); +} + +.description { + position: absolute; + bottom: 10px; + left: 15px; + padding: 1px 4px; + border-radius: 1px; + background: #000000; + color: #fff; + font-size: 12px; + text-transform: uppercase; + font-weight: 700; + letter-spacing: .5px; + transition: .15s ease-in; +} + +.container-block:hover .description { + background: var(--color-accent); + color: #333333; +} + +.panel { + width: 100%; + height: 100%; + border-radius: 4px; + background: #333333; + color: #ffffff; +} + +.panel, .panel-icons, .panel-text { + display: -webkit-flex; + display: flex; + -webkit-flex-flow: row wrap; + flex-flow: row wrap; + align-items: center; + justify-content: center; +} + +.panel-icons { + flex-basis: 35%; + border-radius: 6px; +} + +.panel-icons a { + display: block; + flex-basis: calc(50% - 20px); + margin: 10px; + fill: #ffffff; +} + +.panel:hover { + background: #ffffff; + color: #333333; +} + +.panel:hover a { + fill: #333333; +} + +.icon:hover { + fill: var(--color-primary); +} + +.panel-text { + flex-basis: calc(50% - 20px); + margin: 20px; + font-size: 18px; + font-weight: 500; + line-height: 26px; +} + +@media (max-width: 1350px) { + .container-block { + flex-basis: calc(33% - 30px); + } + + .container-special { + order: 2; + } + + .container-before { + order: 1; + } +} + +@media (max-width: 1050px) { + .container-block { + flex-basis: calc(50% - 30px); + } +} + +@media (max-width: 650px) { + h1 { + font-size: 48px; + } + + h2 { + font-size: 18px; + } + + p { + font-size: 12px; + } + + .container-block { + flex-basis: calc(100% - 30px); + } + + .container-special { + order: 1; + } + + .container-before { + order: 2; + } + + .panel { + min-height: 300px; + } +} + +.header { + margin: 3em auto; + padding: 0 30px; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000000..38e944bf9d --- /dev/null +++ b/index.html @@ -0,0 +1,229 @@ + + + + + + + + + + + VannTile's JavaScript30 + + + + +
          +

          VanTile's JavaScript30

          +

          This is a page showcasing my demo implementations of a JavaScript tutorial series by Wes Bos.

          +

          This is a plain vanilla JavaScript exercise, without any libraries, transpilers or frameworks. You can find the original JavaScript30 here and my public code on Github.

          +
          + + + + diff --git a/readme.md b/readme.md deleted file mode 100644 index 829f4d07f5..0000000000 --- a/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -![](https://javascript30.com/images/JS3-social-share.png) - -# JavaScript30 - -Starter Files + Completed solutions for the JavaScript 30 Day Challenge. - -Grab the course at [https://JavaScript30.com](https://JavaScript30.com) - -## Pull Requests - -These are meant to be 1:1 copies of what is done in the video. If you found a better / different way to do things, great, but I will be keeping them the same as the videos. - -The starter files + solutions will be updated if/when the videos are updated. - -Thank!