## Take your skills to the moon with creative coding <h4 class="">All Day <del>J</del>Hey! 2022</h4> Note: This will only display in notes window. --- <!-- .slide: class="multi-column" --> <div> I'm Jhey Tompkins DevRel Engineer @ <span class="googley">Google</span> <sub>I've always had a thing for bears... Supposedly</sub> </div> <img class="polaroid" src="/shared/images/baby-photo--resized.jpeg" width="300"> --- <div class="block-reveal"> # Here # we # gooooo! </div> --- <!--- Slide of creative demos vidoes --> <div class="demo-row"> <video autoplay controls muted loop src="/shared/video/demo-two.mp4"></video> <video autoplay controls muted loop src="/shared/video/demo-one.mp4"></video> <video autoplay controls muted loop src="/shared/video/demo-three.mp4"></video> </div> --- ## You !== Me ## <span class="runway"><span class="flight">โ๏ธ</span></span> <sub>It's not for everyone. And that's fine. It wasn't for me once.</sub> --- <!-- .slide: data-background-iframe="/demos/supplementary/hmmm/index.html" --> ## What? ## How? ## Why? --- <!-- .slide: data-background-iframe="/demos/voice-range-inputs/eq-range-inputs/index.html" data-background-interactive --> ~~Creative over function~~ ## Expression through the "Impractically Practical" --- - Supercharge your learning <span class="lightning">โก๏ธ</span> - Go beyond the docs <span class="rocket">๐</span> - Become a problem solver or a challenge crusher! <span class="muscle">๐ช</span> - Don't limit yourself with your tech stack! <span class="wag">โ๏ธ</span> --- <!-- .slide: data-background-iframe="/demos/supplementary/perspective-steps/index.html" --> <h2 style="text-align: left; margin: 0 auto; width: 50%;"><span class="phone">๐ฑ</span></h2> --- ## <span class="puff">๐ก</span> Make for yourself. <sub>You're much more than a TODO app! ๐ </sub> <img class="commit-graph" src="/shared/images/commit-graph.png" width="300" /> --- ```zsh yarn add super-obscure-thing npm i awesome-library-with-slight-change-please yarn add this-but-for-react ``` <sub>Pssst. The web platform is awesome. Future proof yo'self!</sub> --- # How? --- - Red and white toadstools ๐ - Checkbox spin โญ๏ธ - Peter Griffin Blinds CSS ๐ - Bear glare parallax from documentary ๐ป - Array slice/splice ๐ <sub>Remember `you !== me`?</sub> <sub>The limit is __your__ `imagination` โจ</sub> --- <!-- .slide: class="multi-column" --> <div> ## Train It ๐ 1. Start your list ๐ (Notion, Trello, ~~Post-It~~, Keep, Analog ๐คฏ) 2. Document all the thingz!!11! สยทแดฅยทใส 3. No idea is a bad idea 4. __No idea is a bad idea__ <sub>Heck! You can even make a demo of your notebook</sub> </div> <img src="/shared/images/notebook.jpeg" width="300"> --- > __"Plucking ideas out of nothing is something you can train your mind to do over time"__ โ <cite>Me; Now</cite> --- <!-- .slide: class="multi-col" --> - CodePen โจ - TV ๐บ - Outside ๐ (_I know_) - Books ๐ - Newsletters ๐ฐ - Events ๐ - Film ๐ฌ - News ๐ - Muzli โ๏ธ - Seasonal ๐ ๐ป - Dribbble ๐จ - Reddit ๐ง - Social Media ๐ค - etc. etc. ๐ <sub>I can provide a story for the majority of my `1000+` CodePen demos</sub> --- ## โฑ 5 minutes && 10 lifetimes --- <!-- .slide: data-auto-animate --> ## What? โก๏ธ How? โก๏ธ Why? --- <!-- .slide: data-auto-animate --> <!-- Can we back drop impossible bear smashing "Why?" out of the way? --> ## What? โก๏ธ How? ~~โก๏ธ Why?~~ <sub>Banish the thought of "Why?"</sub> --- <!-- .slide: data-auto-animate --> ## What? โก๏ธ How? ~~โก๏ธ Why?~~ <sub>Don't dwell on "How?"</sub> --- <h1 data-splitting class="animated-title">Example Time!</h1> --- <!-- End All Day Hey Intro --><br><!-- .slide: data-auto-animate --> # <span>CSS</span><span>, UI, && DevTools</span> --- <!-- .slide: data-auto-animate --> # <span>CSS</span><span style="opacity: 0.2;">, UI, && DevTools</span> --- <!-- .slide: class="table-shot" --> <!-- Try and limit each demo to 10 slides. That's a minute a slideish. --> <!-- Slide 1 --> |Source | What? | How? | |---|---|---| | Socials/Gym | Houdini @property exploration. | Explore! | <sub>I have a soft spot for this one...</sub> --- <!-- Slide 2 --> <div class="collage-grid"> <img src="/shared/images/houdini-smarts.png" width="300"> <img src="/shared/images/caniuse-houdini.png" width="300"> <img src="/shared/images/magic.gif" width="300"> <img src="/shared/images/is-houdini-ready-yet.png" width="300"> <img src="/shared/images/houdini-superpower.png" width="300"> </div> --- <!-- .slide: class="shrink-code" --> ```css [1-8|10-19|11,17-18] :root { --my-untyped-color: red; } .untyped { --my-untyped-color: url('bear.png'); background: var(--my-untyped-color); } @property --my-color { syntax: '<color>'; initial-value: red; inherits: false; } .typed { --my-color: url('bear.png'); background: var(--my-color); } ``` --- <!-- .slide: class="multi-column"--> <!-- Slide 3 --> ```css [1-8|10-19] :root { --my-untyped-color: red; } .untyped { --my-untyped-color: url('bear.png'); background: var(--my-untyped-color); } @property --my-color { syntax: '<color>'; initial-value: red; inherits: false; } .typed { --my-color: url('bear.png'); background: var(--my-color); } ``` <iframe class="demo-embed" src="/demos/houdini/typestyle/index.html" ></iframe> --- <!-- .slide: data-auto-animate --> <!-- Slide 4 --> ## Type~~Script~~Style ๐ --- <!-- .slide: data-auto-animate --> ## Type~~Script~~Style ๐ Context โก๏ธ Interpolation โก๏ธ Motion --- <!-- .slide: data-auto-animate --> ## Type~~Script~~Style ๐ Context โก๏ธ Interpolation โก๏ธ Motion ### Less Tricks! ๐ค --- <!-- .slide: class="multi-column" data-auto-animate --> <!-- Slide 5 --> ```css[7-10|12-16|18-22|24-28|1-5,24-28] @property --hue { syntax: '<number>'; inherits: false; initial-value: 0; } .box { background: hsl(var(--hue), 100%, 50%); animation: hue-rotate 5s infinite linear; } @keyframes hue-rotate { to { background: hsl(360, 100%, 50%); } } @keyframes hue-rotate { to { filter: hue-rotate(360deg); } } @keyframes hue-rotate { to { --hue: 360; } } ``` <iframe class="demo-embed" src="/demos/houdini/typeanimation/index.html" ></iframe> --- <!-- .slide: data-class="wow-slide" data-background-iframe="/demos/houdini/wow/index.html" data-background-color="#e6b319" --> <!-- Slide 6 --> ```css [1-100] h1 { --color: hsl(var(--hue), 80%, 60%); color: var(--color); animation-name: party animation-delay: calc(var(--index) * -0.1s); animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes party { 100% { --hue: 360; } } ``` --- <!-- .slide: class="multi-column" --> <!-- Slide 7 --> ```css[] @property --wave { inherits: false; initial-value: 0%; syntax: '<percentage>'; } body { --half-wave: calc(var(--wave) * 0.5); background: linear-gradient( transparent 0 calc(35% + var(--half-wave)), var(--wave-four) calc(75% + var(--wave)) 100% ), var(--sand); animation: waves 5s infinite ease-in-out; } @keyframes waves { 50% { --wave: 25%; } } ``` <iframe class="demo-embed" src="/demos/houdini/waves/index.html" ></iframe> --- <!-- .slide: class="multi-column" style="--split-one: 0.5fr; --split-two: 0.5fr;" --> ```css[1-18,28-42] @property --nose { syntax: '<percentage>'; initial-value: 0%; inherits: false; } @property --tail { syntax: '<percentage>'; initial-value: 0%; inherits: false; } .loader { mask: conic-gradient( from 45deg, transparent 0 var(--tail), #000 0 var(--nose), transparent 0 var(--nose) ); animation: load 2.5s both infinite ease-in-out, spin 3.25s infinite linear; } @keyframes spin { to { transform: rotate(360deg); } } @keyframes load { 0% { --tail: 0%; --nose: 0%; } 40%, 60% { --nose: 100%; --tail: 0%; } 100% { --nose: 100%; --tail: 100%; } } ``` <iframe class="demo-embed" src="/demos/houdini/that-loader/index.html" ></iframe> --- <!-- .slide: class="multi-column" --> ```css[] .car { animation: journey 5s infinite linear; transform: translate( calc(var(--x) * 1vmin), calc(var(--y) * 1vmin) ) rotate(var(--r)); } @keyframes journey { 0% { --x: -22.5; --y: 0; --r: 0deg; } 10% { --r: 0deg; } 12.5% { --x: -22.5; --y: -22.5; } 15% { --r: 90deg; } 25% { --x: 0; --y: -22.5; } /* Obfuscated keyframes */ } ``` <iframe class="demo-embed" src="/demos/houdini/track-race/index.html" ></iframe> --- <!-- .slide: class="multi-column" --> ```css[] .button[aria-pressed="true"] { --accessory-translation: 30%; --main-rotation: -45deg; --final-translation: -55%; } .button[aria-pressed="true"] .icon span:nth-of-type(odd) { transition: --hover-translation var(--transition), --accessory-translation var(--transition), --main-rotation var(--transition) calc(var(--transition) * 2), --final-translation var(--transition) calc(var(--transition) * 3); } .button[aria-pressed="true"] .icon span:nth-of-type(2) { transition: --main-rotation var(--transition) var(--transition); } .icon > span:nth-of-type(odd) { width: 60%; transform: translate(calc(var(--hover-translation) * var(--coefficient))) translate(calc(var(--accessory-translation) * var(--coefficient)), 0) rotate(calc(var(--main-rotation) * -1)) translate(calc(var(--final-translation) * var(--coefficient))); } ``` <iframe class="demo-embed" src="/demos/houdini/transition-toggle/index.html" ></iframe> --- <!-- Slide 8 --> <div class="multi-column"> ```css[] [data-char]:after { --txt: attr(data-char); animation: glitch 0.2s 0.5s steps(1) infinite; content: var(--txt); color: var(--color); position: absolute; left: 0; top: 0; } @keyframes glitch-switch { 0% { content: var(--char-0); } 10% { content: var(--char-1); } 20% { content: var(--char-2); } 30% { content: var(--char-3); } /* Obfuscated keyframes */ } ``` <iframe class="demo-embed" src="/demos/houdini/glitchy-text/index.html" ></iframe> </div> --- <!-- .slide: class="multi-column" --> ```css[] @property --count { inherits: false; initial-value: 0; syntax: '<integer>'; } .counter { counter-reset: count var(--count); animation: count 1s steps(100) infinite; } .counter:nth-of-type(2) { animation-duration: 5s; } .counter:after { font-variant: tabular-nums; content: counter(ms); } @keyframes count { to { --count: 100; } } ``` <iframe class="demo-embed" src="/demos/houdini/animating-numbers/index.html" ></iframe> <sub>Note the `tabular-nums` usage</sub> --- <!-- Slide 9 --> <!-- .slide: class="multi-column" --> ```css[1-5|33-39|23-31|7-21|1-39] @property --ms-tens { initial-value: 0; inherits: false; syntax: '<integer>'; } #start:checked ~ .stopwatch__face .digit, #pause:checked ~ .stopwatch__face .digit { animation: var(--name, none) var(--duration, 1s) infinite steps(var(--steps)) var(--state); } #start:checked ~ .stopwatch__face { --state: running; } #pause:checked ~ .stopwatch__face { --state: paused; } .digit { color: transparent; counter-reset: var(--counter-name) var(--counter-variable); } .digit:after { content: counter(var(--counter-name)); color: white; font-variant: tabular-nums; } .ms--tens { --duration: 1s; --steps: 10; --counter-name: ms-tens; --counter-variable: var(--ms-tens); --name: ms-tens; } ``` <iframe class="demo-embed" src="/demos/houdini/bland-stopwatch/index.html"></iframe> <div style="grid-column: span 2; margin-top: 1em;"><sub>No drift! ๐</sub></div> --- ## Debrief ๐ซ <div class="col-count" style="column-count: 2;"> <div> ### Learned - Houdini Properties and Values API - Semantic HTML tricks - Drawbacks of JavaScript timers </div> <div> ### Next - Make it pretty? - 3D ๐คฏ - More interaction - Pure CSS all the way? - Wrote an article [CSS Tricks] </div> <div class="practical-post-it"> ### Practical Corner - Awesome use cases - Less tricks! - Type checking </div> </div> --- <!-- .slide: data-background-color="#ead9f2" data-background-iframe="/demos/houdini/css-stopwatch/index.html" data-background-interactive --> --- <!-- End CSS Section --><br><!-- .slide: data-auto-animate --> # <span style="opacity: 0.2;">CSS, </span><span style="opacity: 1;">UI<span style="opacity: 0.2;">, && DevTools</span> --- <!-- .slide: class="table-shot" --> <!-- Slide 1 --> |Source | What? | How? | |---|---|---| | TikTok | Sliding Puzzle Captcha | HTML Drag and Drop, CSS Grid, Custom Properties | --- <div class="collage-grid capy-collage"> <img src="/shared/images/capy-puzzle.png" width="300"> <img src="/shared/images/drag-and-drop.png" width="300"> <img src="/shared/images/homer-puzzle.gif" width="300"> <img src="/shared/images/grid-inspector.png" width="300"> <img src="/shared/images/capy-range.png" width="300"> </div> --- <!-- Slide 2 --> ```html [1-100] <!-- The main element --> <div class="captcha"> <!-- An element for layout --> <div class="captcha__grid"> <!-- An element for displaying the image --> <div class="captcha__img"></div> <!-- Pieces && Slots would be added here? --> </div> </div> ``` --- <!-- Slide 3 --> ```javascript[1-100|1,10|2-7|11,12] class Captcha { static defaults = { gridSize: 5, image: 'https://source.unsplash.com/random/720x720?moon', pieces: 4, onComplete: () => alert('You are not a robot?') } constructor(options) {...} } const element = document.querySelector('.captcha') new Captcha({ element, gridSize: 4, pieces: 2 }) ``` --- <!-- Slide 4 --> ```javascript[1-100|2|12-13|14-15] constructor(options) { const opts = (this._options = { ...Captcha.defaults, ...options }) this._element = opts.element this._progress = 0 this._grid = opts.element.querySelector('.captcha__grid') this._img = opts.element.querySelector('.captcha__img') if (!this._element || !this._img || !this._grid) throw Error('Captcha: Required elements missing!') if (opts.pieces > Math.pow(opts.gridSize, 2)) throw Error('Captcha: Can not have more pieces than available') // Set CSS inline custom properties this._element.style.setProperty('--captcha-image', `url(${opts.image})`) this._element.style.setProperty('--captcha-grid-size', opts.gridSize) // Now set up the moveable pieces this.setup() } ``` --- <!-- Slide 5 --> <!-- .slide: class="multi-column" style="--split-one: 1fr; --split-two: 1fr;" --> ```js[1-100|2|4,6,11|7-10|14] generatePieces() { const pieces = [] const generatePiece = () => {...} while (pieces.length !== this._options.pieces) { const pushPiece = () => { const newPiece = generatePiece() // Don't want duplicates const alreadyExists = pieces.filter(...).length if (alreadyExists) pushPiece() else pieces.push(newPiece) } pushPiece() } this._pieces = pieces } ``` <img src="/shared/images/grid-inspection.png" width="300" /> --- <!-- Slide 6 --> <!-- .slide: class="multi-column" style="--split-one: 1fr; --split-two: 1fr;" --> <img src="/shared/images/dotted-inspection.png" width="300" /> ```js[1-29|2-13|14-17|18-22|23-28] const generatePiece = () => { // Create an Array of two items // One is 1 || -2 [CSS Grid] // The other is the gap // Flip a coin to reverse and return the Array. const positions = [ // This can either be the start or the finish Math.random() > 0.5 ? -2 : 1, // This can be 3, 4, 5, or 6. Gutter is 2 Math.floor( Math.random() * (this._options.gridSize - 1) ) + 3, ] // Which cell is it? const slot = Math.floor( Math.random() * Math.pow(this._options.gridSize, 2) ) // What are the coordinates? const slotPositions = [ slot % this._options.gridSize, Math.floor(slot / this._options.gridSize), ] return { // Flip positions? positions: Math.random() > 0.5 ? positions.reverse() : positions, slots: slotPositions, } } ``` --- <!-- .slide: class="multi-column" style="--split-one: 1fr; --split-two: 1fr;" --> <!-- Slide 7 --> ```js[1-25|2-5|6-11|12-20|21-23] createPieces() { for (const { positions: [px, py], segments: [sx, sy], } of this._pieces) { const piece = document.createElement('div') piece.className = 'captcha__piece' // Position the piece and make it draggable piece.setAttribute('draggable', true) piece.style.setProperty('--captcha-x', px) piece.style.setProperty('--captcha-y', py) const slot = document.createElement('div') slot.className = 'captcha__slot' // Account for the gutter slot.style.setProperty('--slot-x', sx + 3) slot.style.setProperty('--slot-y', sy + 3) // Needs to be able to show the correct image // Negate for background-position piece.style.setProperty('--slot-x', -sx) piece.style.setProperty('--slot-y', -sy) // Throw in the grid this._grid.appendChild(piece) this._grid.appendChild(slot) } } ``` <img src="/shared/images/dotted-grid-tool.png" width="300" /> --- <!-- Slide 8 --> ```css[1-13|15-21|23-31] :root { --size: 50vmin; } .captcha { /* "4" is "2*" the gutter which is "2" */ --grid-size: calc(var(--captcha-grid-size) + 4); --cell-size: calc(var(--size) / var(--grid-size)); --image-size: calc(var(--cell-size) * var(--captcha-grid-size)); height: var(--size); aspect-ratio: 1 / 1; position: relative; } .captcha__grid { height: 100%; width: 100%; display: grid; grid-template-columns: repeat(var(--grid-size), 1fr); grid-template-rows: repeat(var(--grid-size), 1fr); } .captcha__img { position: relative; object-fit: cover; background-color: hsl(0 0% 75%); background-image: var(--captcha-image); background-size: var(--image-size); grid-row: 3 / -3; grid-column: 3 / -3; } ``` --- <!-- Slide 9 --> <img src="/shared/images/moon-grid.png" width="300" /> --- <!-- Slide 10 --> ```css[1-16|7,8,13,14|2,3,4,5,6] .captcha__piece { background-image: var(--captcha-image); background-size: var(--image-size); background-position: calc( var(--segment-x) * var(--cell-size)) calc(var(--segment-y) * var(--cell-size) ); grid-column: var(--captcha-x); grid-row: var(--captcha-y); z-index: 2; } .captcha__slot { background: blue; grid-column: var(--segment-x); grid-row: var(--segment-y); z-index: 2; } ``` --- <!-- Slide 11 --> <!-- .slide: data-background-iframe="/demos/draggable/draggable-captcha-step-one/index.html" data-background-interactive --> --- <!-- Slide 12 --> ```js[] piece.addEventListener('dragstart',handleDragStart({ piece, px, py, sx, sy })) piece.addEventListener('dragend', handleDragEnd(piece)) slot.addEventListener('drop', handleDrop({ piece, slot, px, py, sx, sy })) slot.addEventListener('dragover', doNothing) ``` <sub>The business end ๐ผ</sub> --- ```js[1-10|2,3|4|5|8-10] const handleDragStart = ({ piece, px, py, sx, sy }) => (e) => { piece.style.opacity = 0.25 e.dataTransfer.setData('text/json', JSON.stringify({ px, py, sx, sy })) } const handleDragEnd = piece => () => { piece.style.opacity = 1 } ``` --- <!-- Slide 13 --> <!-- .slide: data-background-iframe="/demos/draggable/draggable-captcha-step-two/index.html" data-background-interactive --> --- <!-- Slide 14 --> ## The Drop ```js[1-22|4-10|11-13|14|15-18|19-20] const handleDrop = ({ piece, slot, px, py, sx, sy }) => (e) => { const transferred = JSON.parse(e.dataTransfer.getData('text/json')) if ( transferred.sx === sx && transferred.sy === sy && transferred.px === px && transferred.py === py ) { // It's a match! piece.remove() slot.remove() this._progress++ this._element.style.setProperty( '--progress', (this._progress / this._options.pieces) * 100 ) if (this._progress === this._options.pieces && this._options.onComplete) this._options.onComplete() } } ``` --- <!-- .slide: data-background-iframe="/demos/draggable/draggable-captcha-step-three/index.html" data-background-interactive --> --- ## Remember this? ```css[1-18|1-5|7,18,9,10|7,18,11-15,16] @property --progress { syntax: '<number>'; inherits: true; initial-value: 0; } .captcha__img:before { content: ""; position: absolute; inset: -1vmin; background: conic-gradient( from 315deg, hsl(130 80% 50%) calc(var(--progress) * 1%), transparent calc(var(--progress) * 1%) ); transition: --progress 0.5s; z-index: -1; } ``` --- <!-- .slide: data-background-iframe="/demos/draggable/draggable-captcha-step-four/index.html" data-background-interactive --> --- <!-- Slide 13 --> ## Debrief ๐ซ <div class="col-count" style="column-count: 2;"> <div> ### Learned - Math - Reusable Classes - CSS Grid - HTML Drag and Drop - Applied @property - CSS Custom Properties </div> <div> ### Next - A11y? - Sounds - FLIP - Whimsy? - Masking/Clipping Shapes </div> <div class="practical-post-it"> ### Practical Corner - Create components - Layout with CSS Grid - Drag and Drop interactions (Reordering, etc.) </div> </div> --- <!-- Slide 14 --> <!-- .slide: data-background-iframe="/demos/draggable/draggable-captcha/index.html" data-background-interactive data-background-color="hsl(210 80% 99%)"--> --- <!-- End UI Slides --><br> <!-- .slide: data-auto-animate --> # <span style="opacity: 0.2;">CSS, UI, && </span><span style="opacity: 1;">DevTools</span> --- <!-- .slide: class="table-shot" --> <!-- Slide 1 --> |Source | What? | How? | |---|---|---| | Freelance/Dribbble | Safe Cracking | DeviceOrientation, DeviceOrientation Simulator | --- <!-- Slide 2 --> <div class="collage-grid safe-collage"> <img src="/shared/images/3d-knob.png" width="300"> <img src="/shared/images/sensors.png" width="300"> <img src="/shared/images/safe-crack.gif" width="300"> <img src="/shared/images/kent-parallax.png" width="300"> <img src="/shared/images/thermostat.png" width="300"> </div> --- <!-- Slide 3 --> <!-- .slide: class="multi-column" --> ```html [] <label for="alpha" class="control__label" > Alpha </label> <input id="alpha" name="alpha" class="control__input" type="range" min="0" max="100" step="1" /> ``` <img src="/shared/images/petes-diagram.png" width="300" /> --- <!-- Slide 4 --> ```js[1-15|1-8|10-15|10,11|12,13|14,15] // Given two ranges, return a function to map a value const mapRange = (inputLower, inputUpper, outputLower, outputUpper) => { const INPUT_RANGE = inputUpper - inputLower const OUTPUT_RANGE = outputUpper - outputLower return value => outputLower + ( ((value - inputLower) / INPUT_RANGE) * OUTPUT_RANGE || 0 ) } // Z-axis (0deg - 360deg) const ALPHA_MAPPER = mapRange(0, 360, 0, 100) // X-axis (-180deg - 180deg) const BETA_MAPPER = mapRange(-180, 180, 0, 100) // Y-axis (-90deg - 90deg) const GAMMA_MAPPER = mapRange(-90, 90, 0, 100) ``` --- <!-- Slide 5 --> ```js[1-11|1-3|11|5-9] const ALPHA = document.querySelector('#alpha') const BETA = document.querySelector('#beta') const GAMMA = document.querySelector('#gamma') const handleOrientation = ({ alpha, beta, gamma }) => { ALPHA.value = ALPHA_MAPPER(alpha) BETA.value = BETA_MAPPER(beta) GAMMA.value = GAMMA_MAPPER(gamma) } window.addEventListener('deviceorientation', handleOrientation) ``` --- <iframe src="/demos/device/detecting-deviceorientation/index.html"></iframe> <!-- <iframe src="https://cdpn.io/pen/debug/mdpYOKY/de75efaa529aa7c7026cfe054ca23ada"></iframe> --> <a class="demo-link" href="/demos/device/detecting-deviceorientation/index.html" target="_blank" rel="noreferrer noopener"> Let's check out the demo! <span class="rocket">๐</span> </a> --- <!-- Slide 7 --> ## That's it? ๐ ```js[1-15|2,15|3,10-12|4-11] const START = () => { BUTTON.remove() if (DeviceOrientationEvent?.requestPermission) { DeviceOrientationEvent .requestPermission() .then(permission => { if (permission === 'granted') window.addEventListener('deviceorientation', handleOrientation) else alert('You denied permission to play!') }) } else { window.addEventListener('deviceorientation', handleOrientation) } } BUTTON.addEventListener('click', START) ``` <sub>h/t to `@Vanaf1979` for pinging me about this one</sub> --- <!-- Slide 8 --> ## Setting a combo ```js[1-13|1|4,13|5-11] const GET_RANDOM = (min, max) => Math.floor(Math.random() * (max - min + 1) + min) const ALPHA_MAPPER = mapRange(0, 360, 0, 100) const COMBINATIONS = [ { id: 'alpha', unlock: GET_RANDOM(0, 100), input: ALPHA, label: ALPHA_LABEL, mapper: ALPHA_MAPPER, }, ...otherCombinations ] ``` --- <!-- Slide 9 --> ## Get things rolling ```js[1-27|1-8,12|18,23] let step = 0 const showCombination = step => { COMBINATIONS.forEach((combo, index) => { combo.input.style.display = combo.label.style.display = index === step ? 'block' : 'none' }) } const START = () => { BUTTON.remove() showCombination(step) if (DeviceOrientationEvent?.requestPermission) { DeviceOrientationEvent .requestPermission() .then(permission => { if (permission === 'granted') window.addEventListener('deviceorientation', handleOrientation) else alert('You denied permission to play!') }) } else { window.addEventListener('deviceorientation', handleOrientation) } } BUTTON.addEventListener('click', START) ``` --- <!-- Slide 10 --> ## Cracking Logic ๐ด๐ผ ```js[1-32|30-32|23-28|27,31|11-21|13,14|15-19|1-9] const complete = () => { TITLE.innerText = 'You cracked the code!' TITLE.style.display = 'block' window.removeEventListener('deviceorientation', handleOrientation) COMBINATIONS.forEach(({ input }) => { input.removeEventListener('input', handleUpdate) }) } const handleUpdate = () => { const { input, label, unlock } = COMBINATIONS[step] if (parseInt(input.value, 10) === unlock) { step += 1 if (step === COMBINATIONS.length) { input.style.display = label.style.display = 'none' complete() } else { showCombination(step) } } } const handleOrientation = e => { // Set the value of the step input const { input, id, label, mapper, unlock } = COMBINATIONS[step] input.value = mapper(e[id]) handleUpdate() } COMBINATIONS.forEach(({ input }) => { input.addEventListener('input', handleUpdate) }) ``` --- <iframe class="demo-embed" src="/demos/device/detecting-deviceorientation-no-indication/index.html"></iframe> <!-- <iframe src="https://cdpn.io/pen/debug/mdpYOKY/de75efaa529aa7c7026cfe054ca23ada"></iframe> --> <a class="demo-link" href="/demos/device/detecting-deviceorientation-no-indication/index.html" target="_blank" rel="noreferrer noopener"> Let's check out the demo! <span class="lightning">โจ</span> </a> --- <!-- Slide 12 --> ## There's no indication <div class="stacked"> ```css[1-14|1-5|7-14|14] [type="range"] { --rotation: 20deg; --proximity-hue: calc(130 - ((130 / 100) * var(--proximity, 0))); accent-color: hsl(var(--proximity-hue) 80% 50%); /* Animation speed will be 1 to 0.1 */ animation: shake calc((1 - ((var(--proximity, 0) / 100) * 0.9)) * 1s) infinite; } @keyframes shake { 25% { transform: rotate(calc((var(--proximity, 0) / 100) * var(--rotation))); } 75% { transform: rotate(calc((var(--proximity, 0) / -100) * var(--rotation))); } } ``` ```js[1-10|1-2|9-10] const PROXIMITY_THRESHOLD = 15 const PROXIMITY_MAPPER = mapRange(0, PROXIMITY_THRESHOLD, 100, 0) // Inside the updater const CLAMPED = Math.min( Math.max(0, Math.abs(input.value - unlock)), PROXIMITY_THRESHOLD ) const proximity = PROXIMITY_MAPPER(CLAMPED) input.style.setProperty('--proximity', proximity) ``` </div> --- <iframe class="demo-embed" src="/demos/device/detecting-deviceorientation-with-indication/index.html"></iframe> <a class="demo-link" href="/demos/device/detecting-deviceorientation-with-indication/index.html" target="_blank" rel="noreferrer noopener"> Let's check out the demo! <span class="muscle">๐ช</span> </a> --- <!-- Slide 14 --> ## Debrief ๐ซ <div class="col-count" style="column-count: 2;"> <div> ### Learned - DeviceOrientation Event - Cross Browser DeviceOrientation - Mapping values - Dynamic Animations w/ CSS Custom Properties </div> <div> ### Next - A11y? - Sounds - Turn it into a safe! - Whimsy? - Web Vibration API (Yep, that's a thing!) </div> <div class="practical-post-it"> ### Practical Corner - New APIs! - Interesting Utilities (Map) - Custom Property Magic </div> </div> --- <iframe class="demo-embed demo-embed--big" src="/demos/device/deviceorientation-safe/index.html"></iframe> <!-- <iframe src="https://cdpn.io/pen/debug/mdpYOKY/de75efaa529aa7c7026cfe054ca23ada"></iframe> --> <a class="demo-link" href="/demos/device/deviceorientation-safe/index.html" target="_blank" rel="noreferrer noopener"> Let's check out the demo! <span class="rocket">โญ๏ธ</span> </a> --- <!-- End DeviceOrientation Safe --><br><!-- .slide --> # That's It! <span class="raise-hands">๐</span> <sub>Almost...</sub> --- 1. Explore ๐ง 2. Hoard ๐ 3. Inspire ๐ก 4. Make ๐ค 5. Profit ๐ฐ <sub>Curiosity may have killed the cat, but it built the bear สเธโขแดฅโขสเธ</sub> --- ## Your Homework ๐ <sub>Should you choose to accept it</sub> --- <!-- > "Being playful with your code and what might seem like "lateral" learning can be a huge driving factor in evolving your skills. Who knows where it could take you" โ <cite>๐</cite> --> <!-- --- --> <!-- .slide: data-auto-animate --> <!-- ## Make ## Things --> <!-- --- --> <!-- .slide: data-auto-animate --> <!-- ## Break ## Things --> <!-- --- --> <!-- .slide: data-auto-animate --> <!-- ## Talk About ## Things --- --> # Thank you! <div class="perspective"><span class="pray-hands">๐</span></div> <sub>Grab me `@jh3yy` || `jhey.dev/links`</sub><br>
@jh3yy