<!-- .slide: data-background-color="var(--spearmint)" --> <div class="content-warning"> <div>Content</div> <div>Advisory</div> <div>Creative Content</div> </div> --- <!-- .slide: data-background-color="var(--fuschia)" --> <img class="qr-intro" src="/shared/images/remote-qr.svg" alt="" /> ## Don't scan this ###### tinyurl.com/jh3y-remote --- <!-- End Deck --><br><!-- .slide: data-background-color="var(--white)" --> <div class="block-reveal"> # Here # we # gooooo! </div> <img class="watermark-bear" src="/shared/images/bear-2022--black.png" alt=""/> --- <!-- End Slide --><br><!-- .slide: data-background-color="var(--off-white)" data-background-iframe="/demos/wdc-splats/circle" data-background-interactive --> --- <!-- .slide: data-background-color="var(--off-white)" data-background-iframe="/demos/wdc-splats/circles" data-background-interactive --> --- <!-- .slide: data-background-color="var(--off-white)" data-background-iframe="/demos/wdc-splats/filter" data-background-interactive --> --- <!-- .slide: data-background-color="var(--off-white)" data-background-iframe="/demos/wdc-splats/animate" data-background-interactive --> --- <!-- End section --><br><!-- .slide: class="title-slide" data-background-color="var(--selective)" --> ###### Web Dev Conf 2022 ## Supercharge your skills with creative coding <div class="volume-title">Vol. IV</div> --- <!-- End Slide --><br><!-- .slide: class="title-slide title-slide--left" data-background-color="var(--white)" --> ## I'm Jhey Tompkins #### Developer Relations Engineer<br>@ <span class="googley">Google</span> <!-- <img class="polaroid" src="/shared/images/bear-baby-chilling.jpg" width="300" /> --> <img class="polaroid" src="/shared/images/baby-photo--resized.jpeg" width="300" /> --- <!-- End Slide --><br><!-- .slide: data-background-color="var(--white)" data-background-iframe="/demos/voice-range-inputs/basic" data-background-interactive --> --- <!-- .slide: data-background-color="var(--white)" data-background-iframe="/demos/voice-range-inputs/accent-color" data-background-interactive --> --- <!-- .slide: data-background-color="var(--white)" data-background-iframe="/demos/voice-range-inputs/changing-accent-color" data-background-interactive --> --- <!-- .slide: class="title-slide title-slide--top" data-background-color="var(--color-four)" data-background-iframe="/demos/voice-range-inputs/eq-range-inputs" data-background-interactive --> ## Expression through the "impractically practical" --- <!-- .slide: class="title-slide" data-background-color="var(--black)" --> ### Give yourself the <span style="color: var(--citric)">confidence</span> to create <span style="color: var(--fuschia)">anything</span> your imagination brings.<br>Go <span style="color: var(--cinnabar);">beyond documentation</span>, become it. --- <!-- .slide: data-background-video="/shared/video/handshake.mp4" data-background-video-loop="true" data-background-size="cover" data-background-video-muted="true" --> --- <!-- .slide: data-background-color="var(--black)" --> <div class="fuel"> <div class="word-waterfall"> <div data-word="Perspective">Perspective</div> <div data-word="Creativity">Creativity</div> <div data-word="Innovation">Innovation</div> </div> <div class="word-waterfall"> <div data-word="Perspective">Perspective</div> <div data-word="Creativity">Creativity</div> <div data-word="Innovation">Innovation</div> </div> </div> --- <!-- .slide: class="title-slide" data-background-color="var(--black)"--> ## Hoard your <span style="color: var(--citric)">inspiration</span>.<br><span style="color: var(--blueberry)">Focus</span> on <span style="color: var(--fuschia)">"What"</span> and not the "How" or "Why". --- <!-- .slide: data-background-color="var(--black)" --> <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> --- <!-- .slide: data-background-color="var(--white)" data-background-iframe="/demos/supplementary/boombox-button" data-background-interactive --> --- <!-- .slide: class="title-slide title-slide--right" .slide: data-background-color="var(--black)"--> ## It's a <span style="color: var(--chateau)">personal</span> journey. It's a <span style="color: var(--selective)">process</span>. Something you can <span style="color: var(--fuschia)">train over time</span>. --- <!-- .slide: data-background-color="var(--black)" --> <img src="https://media.giphy.com/media/t79McFW5Yxxff4FP09/giphy.gif" alt="" /> --- <!-- .slide: data-background-color="var(--white)" data-background-iframe="/demos/supplementary/grogu" data-background-interactive --> --- <!-- .slide: class="experimenting-slide" data-background-color="var(--selective)" --> <div class="content-warning"> <div>Content</div> <div>Advisory</div> <div>Experimental Content</div> </div> --- <!-- End Creative Intro<br>.slide: class="title-slide" data-background-color="var(--citric)" --> ## CSS :has --- <!-- .slide: data-background-color="var(--black)" --> <div class="support-grid"> <span class="browser-logo" data-browser="chrome"></span> <span class="browser-logo" data-browser="edge"></span> <span class="browser-logo" data-browser="safari"></span> <span class="browser-logo" data-browser="firefox"></span> <span class="browser-version" data-supported>105</span> <span class="browser-version" data-supported>105</span> <span class="browser-version" data-supported>15.4</span> <span class="browser-version"> <span class="material-symbols-outlined"> flag </span> </span> </div> --- <!-- .slide: data-background-color="var(--fuschia)" data-background-video="/shared/video/family.mp4" data-background-video-loop="true" data-background-size="contain" data-background-video-muted="true" --> --- <!-- .slide: data-background-color="var(--white)" --> ```css [] <target>:has(<condition>) { <styles> } ``` --- <!-- .slide: data-background-color="var(--blueberry)" --> <div> ```css [] .everybody:has(.a-good-time) { animation: party-like-its 1999s forwards; } .jeff:has(~ .aunt) { animation: visit 10000s; } ``` </div> ```html [] <!-- Selected! โ --> <div class="everybody"> <div class="body body--at-web-dev-conf"> <div class="a-good-time"></div> </div> </div> <!-- Not Selected โ--> <div class="everybody"></div> <!-- Selected! โ --> <div class="jeff"></div> <div class="some-distance"></div> <div class="aunt"></div> ``` --- <!-- .slide: class="title-slide" data-background-color="var(--fuschia)" --> ## Break the mental model <span class="flipper-wrapper"><span class="flipper">๐น</span></span> --- <!-- ### Layout --- --> <!-- .slide: data-background-color="hsl(210 80% 20%)"--> <!-- ```html [] <div class="card"> <h2 class="card__title"> <a href="#">Some Awesome Article</a> </h2> <p class="card__blurb">Here's a description for this awesome article.</p> <small class="card__author">Chrome DevRel</small> </div> ``` <iframe class="demo-embed" src="/demos/css-has/has-cards/basic"></iframe> --> <!-- --- --> <!-- ```html [2] <div class="card"> <img class="card__image" src="laptop.png"> <h2 class="card__title"> <a href="#">Some Awesome Article</a> </h2> <p class="card__blurb">Here's a description for this awesome article.</p> <small class="card__author">Chrome DevRel</small> </div> ``` ```css [] .card:has(.card__image) { --color: var(--cyan-3-hsl); grid-template-columns: 120px 1fr; } ``` --> <!-- --- --> <!-- slide: data-background-color="hsl(210 80% 20%)" data-background-iframe="/demos/css-has/has-cards/with-image" data-background-interactive --> <!-- --- --> <!-- ```html [] <div class="card"> <img class="card__image" src="forest.png"> <div class="card__content"> <h2 class="card__title"> <a href="#">Explore</a> </h2> <p class="card__blurb">Here's a description for this awesome article.</p> <small class="card__author">Chrome DevRel</small> </div> </div> ``` --> <!-- --- --> <!-- .slide: data-background-color="var(--selective)" data-background-iframe="/demos/css-has/has-cards/explore" data-background-interactive --> --- <!-- .slide: data-background-color="var(--spearmint)" --> ```html [|7,8] <div class="card"> <img class="card__image" src="forest.png"> <div class="card__content"> <h2 class="card__title"> <a href="#">Explore</a> </h2> <p class="card__blurb">Here's a description for this awesome article.</p> <small class="card__author">Chrome DevRel</small> </div> </div> ``` --- <!-- .slide: data-background-color="var(--blueberry)" --> ```css .card:not(:has(.card__blurb)):not(:has(.card__author)) { aspect-ratio: 3 / 4; } .card:not(:has(.card__blurb)):not(:has(.card__author)) .card__image { height: 100%; } .card:not(:has(.card__blurb)):not(:has(.card__author)) .card__content { align-items: end; background: linear-gradient(0deg, hsl(0 0% 0% / 0.75) 50%, transparent 80%); height: 100%; } ``` --- <!-- .slide: data-background-color="var(--fuschia)" data-background-iframe="/demos/css-has/has-cards/explore-not" data-background-interactive --> --- <!-- .slide: data-background-color="var(--chateau)" data-background-iframe="/demos/css-has/has-cards/explore-feature" data-background-interactive --> --- <!-- .slide: data-background-color="var(--citric)" --> ```css .card { box-shadow: var(--shadow-elevation-low); } .card:has(.card__feature) { box-shadow: var(--shadow-elevation-high); } @media (prefers-reduced-motion: no-preference) { .card:has(.card__feature) { animation: wiggle 6s infinite; } } ``` --- <!-- .slide: data-background-color="var(--grey)" data-background-iframe="/demos/css-has/has-cards/explore-feature-not" data-background-interactive --> --- <!-- .slide: data-background-color="var(--black)" --> ## use cases for days ```css [] /* Change the grid layout based on content */ .card:has(.card__image) { grid-template-columns: 100px 1fr; } /* Full width card when containing feature */ .card:has(.card__feature) { grid-column: 1 / -1; } /* Non icon links */ a:not(:has(> svg)) { ... } /* Update the :root based on some state change */ :root:has([aria-pressed=true]) { โฆ } /* Select the .container that has an odd number of children */ .container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... } /* Custom elements are supported too */ main:has(todo-list) { ... } ``` <sub>There are a bunch of examples in ":has(): the family selector" over on [developer.chrome.com](https://developer.chrome.com/blog/has-m105).</sub> --- <!-- .slide: data-background-color="var(--selective)" --> <!-- ## Forms --- --> ```html [] <div class="form-group"> <label for="email" class="form-group__label">Email</label> <input required type="email" id="email" class="form-input" title="Enter valid email address" placeholder="Enter valid email address" /> </div> ``` --- <!-- .slide: data-background-iframe="/demos/css-has/has-forms/no-style" data-background-interactive data-background-color="var(--black)" --> --- <!-- .slide: style="--code-size: 0.4em" data-background-color="var(--spearmint)"--> ```css [] label { color: var(--color); } input { border: 4px solid var(--color); } input:focus-visible { outline-color: var(--color); } input::placeholder { color: transparent; } /* Cascade in effect */ .form-group:has(:invalid) { --color: var(--invalid); } /* :focus-within also valid */ .form-group:has(:focus) { --color: var(--focus); } .form-group:has(:valid) { --color: var(--valid); } ``` --- <!-- .slide: data-background-color="var(--black)" data-background-iframe="/demos/css-has/has-forms/color-indication" data-background-interactive --> --- <!-- slide: data-background-iframe="/demos/css-has/has-forms/with-error" data-background-interactive --> <!-- --- --> <!-- ```html [11] <div class="form-group"> <label for="email" class="form-group__label">Email</label> <input required type="email" id="email" class="form-input" title="Enter valid email address" placeholder="Enter valid email address" /> <div class="form-group__error">Enter a valid email address</div> </div> ``` ```css [] .form-group:has(:invalid:not(:focus):not(:placeholder-shown)) .form-group__error { display: block; } ``` --- --> <!-- .slide: data-background-color="var(--citric)" --> <div> ```html [] <label for="email" class="form-group__label"> <span data-splitting="" aria-hidden="true"> <span class="char" data-char="E" style="--char-index:0;">E</span> <span class="char" data-char="m" style="--char-index:1;">m</span> <span class="char" data-char="a" style="--char-index:2;">a</span> <span class="char" data-char="i" style="--char-index:3;">i</span> <span class="char" data-char="l" style="--char-index:4;">l</span> </span> <span class="sr-only">Email</span> </label> ``` </div> ```css [] @media (prefers-reduced-motion: no-preference) { .form-group:has(:valid) label span { display: inline-block; animation: jump 0.25s calc(var(--char-index) * 0.05s); } @keyframes jump { 50% { transform: translateY(-50%); } } } /* Show an error message? */ .form-group:has(:invalid:not(:focus):not(:placeholder-shown)) .form-group__error { display: block; } ``` --- <!-- .slide: data-background-color="var(--black)" data-background-iframe="/demos/css-has/has-forms/with-whimsy" data-background-interactive --> --- <!-- ## Responding to State --- --> <!-- ```js [] const BUTTON = document.querySelector('.nav-control') const TOGGLE = () => { BUTTON.setAttribute( 'aria-pressed', BUTTON.matches('[aria-pressed="false"]') ? true : false ) } BUTTON.addEventListener('click', TOGGLE) ``` --- --> <!-- slide: data-background-iframe="/demos/css-has/has-nav" data-background-interactive --> <!-- --- --> <!-- ```css [] :root { --nav-width: 240px; } body { translate: calc(var(--open, 0) * (var(--nav-width) * -1)) 0; } :root:has(.nav-control[aria-expanded=true]) { --open: 1; } ``` --- --> <!-- .slide: data-background-color="var(--cinnabar)" --> ```css [] :root { --dark-mode: 0; --text-1: var(--gray-9); --text-2: var(--gray-8); } /* Don't do it with a checkbox ๐ */ :root:has(.theme-toggle[aria-pressed=true]) { --dark-mode: 1; --text-1: var(--gray-0); --text-2: var(--gray-1); } body { color: var(--text-2); } h1 { color: var(--text-1); } ``` --- <!-- .slide: data-background-iframe="/demos/css-has/has-dark-mode" data-background-interactive --> --- <!-- ## Interaction --- --> <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/css-has/has-interaction/has-hover" data-background-interactive --> --- <!-- .slide: data-background-color="var(--blueberry)" --> ```css [] li:hover { background: var(--pink-6); } li:hover + li, /* Previous sibling! */ li:has(+ li:hover) { background: var(--pink-3); } ``` --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/css-has/has-interaction/has-previous" data-background-interactive --> --- <!-- slide: data-background-iframe="/demos/css-has/has-interaction/has-lerp" data-background-interactive --- --> <!-- .slide: data-background-color="var(--selective)" --> ```css [] :root { --lerp-0: 1; --lerp-1: 0.5625; --lerp-2: 0.25; --lerp-3: 0.0625; --lerp-4: 0; } .blocks { display: flex; } .block { transition: flex 0.2s; flex: calc(0.2 + (var(--lerp, 0) * 1.5)); } :is(.block:hover, .block:focus-visible) { --lerp: var(--lerp-0); z-index: 5; } .block:has( + :is(.block:hover, .block:focus-visible)), :is(.block:hover, .block:focus-visible) + .block { --lerp: var(--lerp-1); z-index: 4; } .block:has( + .block + :is(.block:hover, .block:focus-visible)), :is(.block:hover, .block:focus-visible) + .block + .block { --lerp: var(--lerp-2); z-index: 3; } ``` --- <!-- .slide: data-background-iframe="/demos/css-has/has-interaction/has-dock" data-background-interactive --> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/css-has/has-calendar" data-background-interactive --> --- <!-- .slide: data-background-color="var(--chateau)" --> ```html [] <table> <tr> <td class="calendar__cell"> <span class="calendar__number">30</span> <input type="radio" class="sr-only" name="from" id="from-09-30" /> <input type="radio" class="sr-only" name="to" id="to-09-30" /> <label for="from-09-30">from 09-30</label> <label for="to-09-30">to 09-30</label> </td> <td class="calendar__cell"> <span class="calendar__number">1</span> <input type="radio" class="sr-only" name="from" id="from-10-01" /> <input type="radio" class="sr-only" name="to" id="to-10-01" /> <label for="from-10-01">from 10-01</label> <label for="to-10-01">to 10-01</label> </td> </tr> </table> ``` --- <!-- .slide: data-background-color="var(--fuschia)" --> ```css [] .calendar__cell:has(:checked) { --background: var(--primary); --alpha: 1; --color: var(--light); } .calendar:has([id*=from]:checked):not(:has([id*=to]:checked)):has(.calendar__cell:hover) tr:has(:checked) ~ tr:has(:hover) .calendar__cell:has(~ .calendar__cell:hover) { --background: var(--potential); --alpha: 1; } .calendar:has([id*=from]:checked):has([id*=to]:checked) tr:has([id*=from]:checked):has(~ tr .calendar__cell > :checked) .calendar__cell:has([id*=from]:checked) ~ .calendar__cell:not(:hover) { --background: var(--in-range); --alpha: 1; --color: var(--light); } ``` --- <!-- ## Games --- --> <!-- .slide: data-background-color="#060d13" data-background-iframe="/demos/css-has/has-tic-tac-toe" data-background-interactive --> --- <!-- .slide: data-background-color="var(--blueberry)" --> ```html [] <form class="game"> <div class="board"> <!-- Obfuscated markup --> <input class="x-check" type="checkbox" id="x-8"/> <input class="o-check" type="checkbox" id="o-8"/> <div class="board__cell"> <label for="x-0"> <svg class="x ghost"></svg> </label> <label for="o-0"> <svg class="o ghost"></svg> </label> <svg class="x board__x"></svg> <svg class="o board__o"></svg> </div> </div> </form> ``` --- <!-- .slide: data-background-color="var(--selective)" --> ```css [] [for*="x"] { z-index: calc(1 + var(--turn)); } /* Counters don't work with calc... yet */ .board:has(:checked), .board:has(:checked ~ :checked ~ :checked) { --turn: 1; } .board:has(:checked ~ :checked), .board:has(:checked ~ :checked ~ :checked ~ :checked) { --turn: 0; } :root:has(#x-0:checked ~ #x-1:checked ~ #x-2:checked), :root:has(#x-3:checked ~ #x-4:checked ~ #x-5:checked) { --show-cross: 1; --show-draw: 0; } :root:has(#o-0:checked ~ #o-1:checked ~ #o-2:checked), :root:has(#o-3:checked ~ #o-4:checked ~ #o-5:checked) { --show-naught: 1; --show-draw: 0; } ``` --- <!-- .slide: data-background-color="var(--chateau)" --> ```html [4,5] <form class="game"> <div class="board"> <!-- Obfuscated markup --> <input class="x-check" type="checkbox" id="x-8"/> <input class="o-check" type="checkbox" id="o-8"/> <div class="board__cell"> <label for="x-0"> <svg class="x ghost"></svg> </label> <label for="o-0"> <svg class="o ghost"></svg> </label> <svg class="x board__x"></svg> <svg class="o board__o"></svg> </div> </div> </form> ``` --- <!-- .slide: data-background-color="var(--cinnabar)" --> ```css [] /* You can :has a :has */ :root:has(#x-0:checked):has(#x-1:checked):has(#x-2:checked), :root:has(#x-3:checked):has(#x-4:checked):has(#x-5:checked) { --show-cross: 1; --show-draw: 0; } /* But you can't :has a :has */ .board:has(.board__cell:has(:checked):has(+ .board__cell:has(:checked))) { --turn: 0; } ``` --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/css-has/has-connect-4" data-background-interactive --> --- <!-- .slide: data-background-color="var(--selective)" --> ```html [|3,4,18,19] <div class="board__column"> <div class="board__cell move-6" style="--row: 1;"> <input type="checkbox" id="s-6" data-row="s-1" data-column="s-1"/> <input type="checkbox" id="p-6" data-row="p-1" data-column="p-1"/> <div class="board__disc disc"> <div class="disc__piece"> </div> </div> </div> <!-- Other cells --> </div> <div class="board__labels move-6" data-for="move-6" style="--row: 1;"> <div class="cuboid"> <div class="cuboid__side"></div> <div class="cuboid__side"></div> <div class="cuboid__side"> <div class="move-controls"> <label class="board__move" for="s-6"></label> <label class="board__move" for="p-6"></label> </div> </div> </div> </div> ``` --- <!-- .slide: data-background-color="var(--cinnabar)" --> ```css [] /* Horizontal */ .board__column:has([data-row=p-1]:checked) + .board__column:has([data-row=p-1]:checked) + .board__column:has([data-row=p-1]:checked) + .board__column:has([data-row=p-1]:checked) ~ .win, /* Diagonal */ .board__column:has([data-row=p-1]:checked) + .board__column:has([data-row=p-2]:checked) + .board__column:has([data-row=p-3]:checked) + .board__column:has([data-row=p-4]:checked) ~ .win { display: block; --winner: var(--primary); --show-win: 1; } ``` --- <!-- .slide: data-background-color="var(--blueberry)" --> ```css [] /* The "Magic" bullet for turn switching */ /* Creates a width to reveal the other label */ .board__labels .move-controls:after { content: counter(turn, lower-roman); font-size: 1px; letter-spacing: var(--cell-size); color: transparent; } .board__move[for*="s"] { left: 0; } .board__move[for*="p"] { right: 0; } body { counter-reset: turn 1; } [id*="p"]:checked { counter-increment: turn 2; } [id*="s"]:checked { counter-increment: turn -2; } ``` <sub>h/t Bence Szabรณ for this one ๐</sub> --- <!-- .slide: class="title-slide title-slide--bottom" data-background-color="var(--fuschia)" --> ## But, really? How much wood could a woodchuck chuck if a woodchuck could chuck wood? --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/css-has/has-woodchuck" data-background-interactive --> --- <!-- .slide: data-background-color="var(--spearmint)" --> ```js [] const PARTICLES = [ { trigger: 'cat', emoji: '๐น' }, { trigger: 'lit', emoji: '๐ฅ' }, { trigger: 'boom', emoji: '๐ฃ' }, { trigger: 'wood', emoji: '๐ด', }, { trigger: 'chuck', emoji: '๐ช' } ] ``` --- <!-- ```js [] const EMOJIS = document.querySelector('.emojis') const PROCESS_AUDIO = e => { const TRANSCRIPT = e.results[e.results.length - 1][0].transcript .toLowerCase() .trim() if (e.results[e.results.length - 1].isFinal === true) { for (const WORD of TRANSCRIPT.split(' ')) { const PARTICLE = PARTICLES.filter(p => { return WORD.indexOf(p.trigger) !== -1 })[0] if (PARTICLE) { const P = document.createElement('div') P.innerText = PARTICLE.emoji P.className = PARTICLE.trigger EMOJIS.appendChild(P) if (PARTICLE.trigger === 'wood' || PARTICLE.trigger === 'chuck') { P.addEventListener('animationend', () => { P.remove() }) } if (PARTICLE.trigger === 'boom') { setTimeout(() => EMOJIS.innerHTML = '', 2000) } P.style.setProperty('--index', EMOJIS.children.length) } } } document.querySelector('main').innerText = TRANSCRIPT } ``` --- --> <!-- .slide: data-background-color="var(--blueberry)" --> ```css [] .wood:has(+ .chuck) { animation: chucked 1s forwards; } :is(.wood, .chuck):has(+ .lit) { animation: burned 0.25s forwards; } :is(.cat):has(+ .lit):after, .lit + .cat:after { content: "๐"; position: absolute; inset: 0; } @keyframes burned { to { filter: brightness(0); } } @keyframes chucked { to { transform: rotate(-90deg); } } ``` --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/css-has/has-woodchuck-interim" data-background-interactive --> --- <!-- End Section<br><!-- .slide: class="title-slide title-slide--top" data-background-color="var(--fuschia)" --> ## Open UI Pop-up --- <!-- .slide: data-background-color="var(--white)" --> <div class="support-grid"> <span class="browser-logo" data-browser="canary"></span> <span class="browser-logo" data-browser="chrome"></span> <span class="browser-logo" data-browser="edge"></span> <span class="browser-logo" data-browser="safari"></span> <span class="browser-logo" data-browser="firefox"></span> <span class="browser-version" data-supported> <span class="material-symbols-outlined"> flag </span> </span> <span class="browser-version">×</span> <span class="browser-version">×</span> <span class="browser-version">×</span> <span class="browser-version">×</span> </div> --- <!-- .slide: data-background-video="/shared/video/pop-up-stack.mp4" data-background-video-loop="true" data-background-video-muted="true" data-background-video-size="cover" --> --- <!-- .slide: class="title-slide title-slide--bottom" data-background-color="var(--black)" --> ## The goal of the <span style="color: var(--citric);">Open UI</span> initiative is to make it <span style="color: var(--blueberry)">easier</span> for developers to make <span style="color: var(--fuschia);">great user experiences</span> --- <!-- .slide: data-background-color="var(--citric)" --> ```js [] const PopUp = ({ children }) => { return ( <div className="pop-up" style={{ zIndex: Date.now() }}> {children} </div> ) } ``` --- <!-- .slide: class="title-slide title-slide--bottom" data-background-image="/shared/images/king.jpg" data-background-opacity="0.4" --> ## What's the top layer? A place outside of the <span style="background-color: var(--cinnabar);">document</span> flow. A place where <span style="background-color: var(--chateau);">z-index</span> has no effect. Where every element has a styleable <span style="background-color: var(--selective);">::backdrop</span>. <!-- --- --> <!-- slide: data-background-color="var(--cinnabar)" --> <!-- ```js [] // Current ways to get into the "Top Layer" Dialog.showModal(); Element.requestFullscreen(); ``` --- --> <!-- ```html [] <div id="my-first-popup" popup>PopUp Content!</div> <button popuptoggletarget="my-first-popup">Toggle Pop-Up</button> ``` <iframe src="/demos/openui-pop-ups/first" class="demo-embed"></iframe> --- --> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/with-backdrop" --> --- <!-- .slide: data-background-color="var(--white)" --> <ul class="bullets"> <li>Hidden by default</li> <li>No JavaScript</li> <li>No z-index fighting</li> <li>Light dismiss</li> </ul> --- <!-- .slide: data-background-color="var(--fuschia)" --> ```html [8,9,11] <html> <head> <title>First Pop-up</title> </head> <body> <main> <!-- Throw all your z-index at me! --> <button popovertoggletarget="my-first-pop-up"> </button> <!-- Don't care where this is to be honest --> <div id="my-first-pop-up" popover>Pop-up content!</div> <header> <h1>Awesome Website</h1> </header> <article> <p> Lorem ipsum dolor sit...</p> </article> </main> </body> </html> ``` --- <!-- .slide: data-background-color="var(--chateau)" --> ```css [|9] [popover] { transform: translate(-50%, -50%) translateY(calc((1 - var(--open, 0)) * 100vh)) scale(var(--open, 0)); transition: transform 0.5s; } [popover]:open { --open: 1; } ``` --- <!-- .slide: class="title-slide title-slide--top" data-background-color="var(--black)" --> ## <span style="color: var(--fuschia)">Better developer experience</span> leads to less opportunity for poor <span style="color: var(--citric)">user experience</span> --- <!-- slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/ascension" --> <!-- --- --> <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/balloon-animation" --> --- <!-- .slide: data-background-color="var(--blueberry)" --> ```html [|3,8] <div id="code-pop-up" class="balloon" popover> <div class="balloon__content"> <button class="button ripple" popoverhidetarget="code-pop-up"> Hide Code </button> </div> </div> <button class="button ripple" popovershowtarget="code-pop-up"> Reveal Code </button> ``` --- <!-- slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/balloon-animation" --> <!-- --- --> <!-- ```css [] @media (prefers-reduced-motion: no-preference) { [popover] { animation: exit-animation 250ms ease-out both; } [popover]:open { animation: entry-animation 1s ease-in both; } @keyframes exit-animation { 100% { transform: translate(-50%, -50%) translateY(-100vh) scale(0); } } @keyframes entry-animation { 0% { transform: translate(-50%, -50%) translateY(100vh) scale(0); } } } ``` --> <!-- --- --> <!-- slide: class="title-slide title-slide--left" data-background-color="var(--cinnabar)" --> <!-- ## Types && Behavior --- --> <!-- .slide: data-background-color="var(--cinnabar)" --> ## Types && Behavior ```html [] <!-- Nesting support --> <div popover=auto> <!-- Explicit dismiss --> <div popover=manual> <!-- Open on render, no "hint" --> <div popover defaultopen> <!-- Focus management --> <div popover> <input autofocus type="text"> </div> ``` --- <!-- - Nesting support via ancestral pop-ups - Dismisses pop-ups that aren't ancestral - Dismissing in the stack only dismisses those above --- --> <!-- slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/auto" --> <!-- --- ```html [|3] <div id="blue-two" class="blue" popover> <div class="card elevated"> <button popovershowtarget="red-one" class="button ripple"> Take first red candy </button> <button popovershowtarget="blue-three" class="button ripple"> Take another blue candy </button> <button popoverhidetarget="blue-two" class="button ripple"> Put this candy back </button> <button popoverhidetarget="blue-one" class="button ripple"> Put back blue candies </button> </div> </div> ``` --> <!-- --- --> <!-- ## Hint ```html [] <div popover=hint> ``` --- --> <!-- - Singleton - Doesn't dismiss other types - Can't use `defaultopen` --- --> <!-- slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/hint" --> <!-- --- ```html [|3] <div id="blue-pill" class="blue" popover="hint"> <div class="card elevated"> <button popovershowtarget="red-pill" class="button ripple"> Actually, take the red pill </button> <button popoverhidetarget="blue-pill" class="button ripple"> Still deciding </button> </div> </div> ``` --- --> <!-- ## Manual ```html [] <div popover=manual> ``` --- --> <!-- - Doesn't dismiss others - No light dismiss - Only closed explicitly via trigger or JavaScript --- --> <!-- slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/manual" --> <!-- --- ```html [|6,14] <div popover="manual" defaultopen id="window"> <div class="window"> <div class="title-bar"> <div class="title-bar-text">Manual Pop-up</div> <div class="title-bar-controls"> <button aria-label="Close" popoverhidetarget="window"></button> </div> </div> <div class="window-body"> <p> The only way to remove me is via a trigger element, or with JavaScript. </p> <button popoverhidetarget="window">Close</button> </div> </div> </div> ``` --> <!-- --- --> <!-- .slide: data-background-color="var(--selective)" --> ## JS API ```js [] /* Show a pop-up */ popUpElement.showPopover() /* Hide a pop-up */ popUpElement.hidePopover() /* Is the pop-up in the top layer */ popUpElement.matches(':open') /* Listen for pop-up events */ popUpElement.addEventListener('popovershow', doSomethingWhenPopUpShows) popUpElement.addEventListener('popoverhide', doSomethingWhenPopUpHides) /* You can cancel a show */ popUpElement.addEventListener('popovershow',event => { event.preventDefault(); console.warn(โWe blocked a pop-up from being shownโ); }) /* You can't cancel a hide */ popUpElement.addEventListener('popoverhide',event => { event.preventDefault(); console.warn("You aren't allowed to cancel the hiding of a pop-up"); }) ``` --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/poppers" --> --- <!-- ```html [] <button popover="manual" class="balloon" id="P" defaultopen title="Pop 'P'" style="--index: -2.5; --hue: 107; --bob-speed: 1; --float-speed: 0.9;" > <span class="balloon__content"> <span class="balloon__letter">P</span> <span class="balloon__handle"></span> </span> </button> ``` <div> ```js [] POPUP.addEventListener("click", () => { AUDIO_POP.currentTime = 0; AUDIO_POP.play(); POPUP.hidePopover(); Object.assign(POPUP, { style: ` --index: ${START_INDEX + p}; --hue: ${Math.random() * 359}; --bob-speed: ${Math.random() + 0.5}; --float-speed: ${Math.random() + 0.5}; ` }); requestAnimationFrame(() => POPUP.showPopover()); }); ``` </div> --> <!-- --- --> <!-- ## Accessibility && Focus ```html [|4] <div id="input-pop-up" popover> <div class="card elevated"> <label for="name">Name</label> <input id="name" autofocus type="text"> <button class="button ripple" popoverhidetarget="input-pop-up">Close</button> </div> </div> ``` --- --> <!-- slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/autofocus" --> <!-- --- --> <!-- .slide: class="title-slide title-slide--top" data-background-color="var(--fuschia)"--> ## Breathe new life into old friends --- <!-- ### Nav Drawer --- --> <!-- .slide: data-background-color="var(--white)" data-background-iframe="/demos/openui-pop-ups/nav-drawer" --> --- <!-- .slide: data-background-color="var(--citric)" --> ```css [|17, 18, 19] [popover] { left: 100%; width: var(--nav-width); transition: transform 0.2s; transform: translateX(calc(var(--open, 0) * -100%)); } [popover]::backdrop { transition: opacity 0.2s; opacity: var(--open, 0); } [popover]:open, [popover]:open::backdrop { --open: 1; } body:has([popover]:open) { transform: translateX(calc(var(--nav-width) * -1)); } ``` --- <!-- ### Custom Cursor --- --> <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/custom-cursor" --> --- <!-- <div> ```html [] <canvas id="custom-cursor" class="custom-cursor" popover="manual" defaultOpen ></canvas> ``` </div> ```js [] document.body.addEventListener("popovershow", (e) => { if (canvas.matches(":open") && e.target !== canvas) { canvas.hidePopover(); requestAnimationFrame(() => { canvas.showPopover(); }); } }); ``` --- --> <!-- ### Toasts --- --> <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/toasts" --> <!-- --- ```html [] <div popover="manual" class="toasts"> <ul class="toasts__drawer"> </ul> </div> ``` --> --- <!-- ### Command Palette --- --> <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/command-palette" --> --- <!-- .slide: data-background-color="var(--selective)" --> ```html [] <div id="spotlight" popover> <input autocomplete="off" role="combobox" spellcheck="false" aria-expanded="false" aria-controls="spotlight-options" aria-activedescendant="" autofocus id="spotlight-search" type="text" placeholder="Pop-up search..." /> <div popover defaultopen id="spotlight-options" role="listbox"> <!-- "Options" get injected here --> </div> </div> ``` --- <!-- --- ```js [] /* Show the pop-up then you get light dismiss etc. for free! */ const handleActivation = (e) => { if (e.keyCode === CMD && !STATE.cmd) STATE.cmd = true; if (e.keyCode === MOD && STATE.cmd && !STATE.mod) STATE.mod = true; if (STATE.cmd && STATE.mod && !POPUP.matches(":open")) { STATE.cmd = STATE.mod = false; POPUP.showPopover(); OPTIONS.showPopover(); } }; ``` --> <!-- --- ### Screensaver --- --> <!-- .slide: data-background-color="var(--white)" data-background-iframe="/demos/openui-pop-ups/screensaver" --> --- <!-- .slide: data-background-color="var(--fuschia)" --> ```html [] <div id="screensaver" popover> <div class="dvd"> <div class="dvd__scale"> <div class="dvd__slide"> <svg xmlns="http://www.w3.org/2000/svg" viewbox="8 44 178 104"> </svg> </div> </div> </div> </div> ``` --- <!-- ### Floating Actions --- --> <!-- slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/floating-action" --> <!-- --- --> <!-- .slide: style="--code-size: 0.325em;" --> <!-- ```html [] <button class="fab secondary" popover="manual" defaultopen popovertoggletarget="menu" > <i class="material-icons">add</i> </button> <div id="menu" class="fab__menu" popover="auto" style="--count: 3"> <ul class="fab__menu-items"> <li class="fab__menu-item"> <button autofocus class="fab" style="--index: 0" popoverhidetarget="menu" > <i class="material-icons">chat</i> </button> </li> <li class="fab__menu-item"> <button class="fab" style="--index: 1" popoverhidetarget="menu" > <i class="material-icons">photo_camera</i> </button> </li> <li class="fab__menu-item"> <button class="fab" style="--index: 2" popoverhidetarget="menu" > <i class="material-icons">pin_drop</i> </button> </li> </ul> </div> ``` --- --> <!-- ### Webcam --- --> <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/webcam-throw" --> --- <!-- .slide: style="--code-size: 0.325em;" data-background-color="var(--spearmint)" --> ```html [] <button popover="manual" defaultopen popovertoggletarget="menu" > <i class="material-icons">add</i> </button> <div id="menu" class="fab__menu" popover="auto" style="--count: 3"> <ul class="fab__menu-items"> <li class="fab__menu-item"> <button autofocus class="fab" style="--index: 0" popoverhidetarget="menu" > <i class="material-icons">chat</i> </button> </li> <li class="fab__menu-item"> <button class="fab" style="--index: 1" popoverhidetarget="menu" > <i class="material-icons">photo_camera</i> </button> </li> <li class="fab__menu-item"> <button class="fab" style="--index: 2" popoverhidetarget="menu" > <i class="material-icons">pin_drop</i> </button> </li> </ul> </div> ``` --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/strange-portal" --> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/openui-pop-ups/error-heaven" --> --- <!-- End Section<br>.slide: data-background-color="var(--blueberry)" class="title-slide title-slide--bottom" --> ## CSS Anchoring --- <!-- .slide: data-background-color="var(--citric)" --> <img src="/tpac-2022/assets/tooltip-anatomy.png" /> --- <!-- .slide: data-background-color="var(--fuschia)" --> <div style="display: grid; grid-auto-flow:column; align-items:center"> <div> ```css .anchor { anchor-name: --anchor; } .boat { position-fallback: --top-to-bottom; } @position-fallback --top-to-bottom { @try { bottom: anchor(--anchor top); right: anchor(--anchor left); } @try { top: anchor(--anchor bottom); right: anchor(--anchor left); } } ``` </div> <video loop autoplay controls muted src="/tpac-2022/assets/viewport-resizing.mp4"></video> </div> --- <!-- .slide: data-background-color="var(--cinnabar)" --> <div style="display:grid; grid-auto-flow:column; align-items:center;"> <div> ```css .chart__tooltip--max { position: absolute; left: anchor(--chart right); bottom: max( anchor(--anchor-1 top), anchor(--anchor-2 top), anchor(--anchor-3 top) ); translate: 0 50%; } ``` </div> <video loop autoplay controls muted src="/tpac-2022/assets/anchor-charts.mp4"></video> </div> --- <!-- .slide: data-background-color="var(--selective)" class="code-grid-with-header" style="--code-size: 0.65rem;"--> <div style="display:inline-grid; grid-auto-flow:column; align-items:center; align-content:center;"> <div style="display:grid; gap:1rem; align-items:center; align-content:center;"> ```html <!-- Markup obfuscated --> <header style="position: relative;"> <nav style="position: relative;"> <span class="nav-links"> <button id="products-btn">Products</button> </span> <div popup id="products"> <button id="css-btn">CSS</button> </div> </nav> <div popup id="css"></div> </header> ``` ```css #products-btn { anchor-name: --products; } #products { position: absolute; left: anchor(--products left); top: anchor(--products bottom); } #css-btn { anchor-name: --css; } #css { position: absolute; top: anchor(--css top); left: anchor(--css right); } ``` </div> <video loop autoplay controls muted src="/tpac-2022/assets/nested-menu.mp4"></video> </div> --- <!-- End Section<br><!-- .slide: class="title-slide title-slide--bottom" data-background-color="var(--citric)"--> <!-- If you want to do everything in Canary with the polyfill --> <!-- open -a /Applications/Google\ Chrome\ Canary.app --args --disable-blink-features=CSSScrollTimeline,ScrollTimeline --> ## Scroll Linked Animations --- <!-- .slide: data-background-color="hsl(0 0% 0%)" --> <div class="support-grid"> <span class="browser-logo" data-browser="chrome"></span> <span class="browser-logo" data-browser="edge"></span> <span class="browser-logo" data-browser="safari"></span> <span class="browser-logo" data-browser="firefox"></span> <span class="browser-version" data-supported> <span class="material-symbols-outlined"> format_paint </span> </span> <span class="browser-version" data-supported> <span class="material-symbols-outlined"> format_paint </span> </span> <span class="browser-version" data-supported> <span class="material-symbols-outlined"> format_paint </span> </span> <span class="browser-version" data-supported> <span class="material-symbols-outlined"> format_paint </span> </span> </div> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/scroll-linked-animations/image-reveals" --> --- <!-- .slide: data-background-color="var(--spearmint)" --> ```html [] <html> <head> <title>Scroll Linked Image Reveals</title> </head> <body> <ul> <li> <h1>Scroll ๐</h1> </li> <li> <img src="/shared/images/starry-night.jpeg"> </li> <li> <img src="/shared/images/night-mountain.jpeg"> </li> </ul> </body> </html> ``` --- <!-- .slide: data-background-color="var(--blueberry)" --> ```css [|2,3,7,8,9,10] li { view-timeline-name: curtain; view-timeline-axis: block; } li img { animation: curtains both linear; animation-timeline: curtain; animation-delay: cover 30%; animation-end-delay: cover 50%; mask: linear-gradient(45deg, transparent 50%, black 50%); mask-repeat: no-repeat; mask-size: 200% 200%; mask-position: -100% 0; } @keyframes curtains { to { mask-position: 100% 0; } } ``` --- <!-- .slide: data-background-color="var(--selective)" --> ```js [|1-6,15,17-24] const TIMELINE = new ViewTimeline({ // Thing you're tracking in the viewport subject: LI, // Vertical or Horizontal axis: 'block' || 'inline' }) // WAAPI Support IMG.animate( [ { maskPosition: '100% 0' } ], { timeline: TIMELINE, fill: 'both', delay: { phase: 'cover' || 'exit' || 'enter' || 'contain', percent: CSS.percent(30), }, endDelay: { phase: 'cover', percent: CSS.percent(50), } } ) ``` --- <!-- .slide: data-background-color="hsl(0 0% 0%)" data-background-iframe="/demos/scroll-linked-animations/fast-and-scrolly" --> --- <!-- .slide: data-background-color="hsl(0 0% 0%)" data-background-iframe="/demos/scroll-linked-animations/dj-deck" --> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/scroll-linked-animations/this-is-a-box" --> --- <!-- .slide: data-background-color="hsl(0 0% 0%)" data-background-iframe="/demos/scroll-linked-animations/snap-parallax" --> --- <!-- .slide: data-background-color="var(--citric)" --> ```html [] <body> <main> <ul class="backdrops"> <li> <img src="backdrop-one.png"> </li> <li> <img src="backdrop-two.png"> </li> </ul> <ul class="contents"> <li> <h2>Scroll</h2> </li> <li> <h2>Scroll more</h2> </li> </ul> </main> </body> ``` --- <!-- .slide: data-background-color="var(--cinnabar)" --> ```css [] .scroller { animation-timeline: --main; /* scroll() (Soon) */ animation: progress both linear; animation-delay: cover 0%; animation-end-delay: cover 100%; } @keyframes progress { to { transform: rotate(360deg); } } ``` --- <!-- .slide: data-background-color="var(--chateau)" --> ```js [] const TIMELINES = [] // Each content section TRIGGERS.forEach(subject => { TIMELINES.push(new ViewTimeline({ subject, axis: 'block' })) }) // Animate each backdrop BACKDROPS.forEach((backdrop, index) => { const IMG = backdrop.querySelector('img') // Entry IMG.animate([{ transform: 'translateY(0%)' } ], { timeline: TIMELINES[index - 1], fill: 'both', delay: { phase: 'exit', percent: CSS.percent(5), }, endDelay: { phase: 'exit', percent: CSS.percent(75), } }) }) ``` --- <!-- .slide: class="title-slide title-slide--top" data-background-color="var(--black)" --> ## Think in <span style="color: var(--fuschia)">space</span>. --- <!-- .slide: data-background-color="hsl(0 0% 0%)" data-background-iframe="/demos/scroll-linked-animations/snap-directions" --> --- <!-- ## Micro interactions --- --> <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/scroll-linked-animations/search-micro" --> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/scroll-linked-animations/avatar-micro" --> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/scroll-linked-animations/dynamic-island" --> --- <!-- .slide: data-background-video="/shared/video/peter.mp4" data-background-video-loop="true" data-background-video-muted="true" data-background-size="contain" data-background-color="var(--spearmint)"--> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/demos/scroll-linked-animations/peters-blinds" --> --- <!-- .slide: data-background-color="var(--fuschia)" --> ```css [] .blind { transform: translate( 0, calc(((-95 * (var(--index, 0))) * 0) * 1%) ); animation: slide both; animation-timeline: --trigger; animation-delay: enter 0%; animation-end-delay: enter 100%; } @keyframes slide { to { transform: translate( 0, calc(((-95 * (var(--index, 0))) * 1) * 1%) ); } } ``` <!-- --- ## Sneaker Carousel --> --- <!-- .slide: data-background-iframe="/demos/scroll-linked-animations/scrolltrigger-book" --> --- <!-- .slide: data-background-iframe="/demos/scroll-linked-animations/prototype-book" --> --- <!-- .slide: data-background-color="hsl(0 0% 100%)" data-background-iframe="/chrometober-2022/index.html" --> --- <!-- End Section<br> <!-- .slide: class="title-slide title-slide--top" data-background-color="var(--chateau)" --> ## That's it. --- <!-- .slide: class="title-slide title-slide--right" --> ## If you haven't, <span style="color: var(--fuschia)">start</span> your journey. <span style="color: var(--selective)">Supercharge</span> yourself. It's <span style="color: var(--blueberry)">just pixels</span>. --- <!-- .slide: class="title-slide" --> ## The more you <span style="color: var(--fuschia)">"play"</span> around, the more you are gonna <span style="color:var(--chateau)">find out</span> <video style="width: 30%;" src="/shared/video/mantra.mp4" controls></video> --- <!-- .slide: class="title-slide title-slide--top" data-background-color="var(--black)" --> ## <span style="color: var(--selective)">We</span>'re here to <span style="color: var(--chateau)">help</span> <img class="chrome-logo" src="/shared/images/chrome-logo.svg"> --- <!-- End Section<br><!-- .slide: data-background-video="/shared/video/jhey-card-sunglasses.mp4" data-background-video-loop="true" data-background-video-muted="true" data-background-video-size="cover" --> <h2 class="outro-cheers">Cheers</h2> <!-- End Deck --><br>
@jh3yy