├── .editorconfig ├── CNAME ├── README.md ├── assets ├── innoq-logo--apricotpetrol.svg ├── innoq-logo--bluered.svg └── responsible-web-cover.jpg ├── fonts.css ├── index.html ├── prism-theme.css ├── resize-observer.js ├── responsive-examples.css ├── slides ├── assets │ ├── highlight.js │ ├── images │ │ ├── accessible-mascot.svg │ │ └── responsive-mascot.svg │ ├── monokai.css │ ├── plugin.js │ ├── reset.css │ ├── reveal.css │ ├── reveal.js │ └── theme │ │ ├── components.css │ │ └── custom-theme.css └── index.html └── style.css /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | max_line_length = 80 9 | indent_style = tab 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | responsibleweb.app -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Responsible Web Apps 2 | 3 | This is the content of a web page (and presentation) that I am working on. 4 | I would love feedback in the form of Issues in this repository, 5 | but I would ask that you do not reuse the content without listing me as a reference. 6 | -------------------------------------------------------------------------------- /assets/innoq-logo--apricotpetrol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/innoq-logo--bluered.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/responsible-web-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyheron/responsible-web-apps/77560bd7cd6ae068fe8956338b90d13b5537452b/assets/responsible-web-cover.jpg -------------------------------------------------------------------------------- /fonts.css: -------------------------------------------------------------------------------- 1 | /* Source: https://github.com/innoq/innoq-styleguide/blob/master/lib/styles/generic/_fontfaces.scss */ 2 | @font-face { 3 | font-family: FreightTextPro; 4 | font-weight: bold; 5 | font-style: normal; 6 | font-display: swap; 7 | src: url("https://assets.innoq.com/fonts/350ED6_0_unhinted_0.woff2?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff2"), url("https://assets.innoq.com/fonts/350ED6_0_unhinted_0.woff?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff"), url("https://assets.innoq.com/fonts/350ED6_0_unhinted_0.ttf?cachebuster=7d0a3995eec718ff34fa844413298153") format("truetype"); 8 | } 9 | @font-face { 10 | font-family: FreightTextPro; 11 | font-weight: normal; 12 | font-style: italic; 13 | font-display: swap; 14 | src: url("https://assets.innoq.com/fonts/350ED6_1_unhinted_0.woff2?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff2"), url("https://assets.innoq.com/fonts/350ED6_1_unhinted_0.woff?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff"), url("https://assets.innoq.com/fonts/350ED6_1_unhinted_0.ttf?cachebuster=7d0a3995eec718ff34fa844413298153") format("truetype"); 15 | } 16 | @font-face { 17 | font-family: FreightTextPro; 18 | font-weight: normal; 19 | font-style: normal; 20 | font-display: swap; 21 | src: url("https://assets.innoq.com/fonts/350ED6_2_unhinted_0.woff2?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff2"), url("https://assets.innoq.com/fonts/350ED6_2_unhinted_0.woff?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff"), url("https://assets.innoq.com/fonts/350ED6_2_unhinted_0.ttf?cachebuster=7d0a3995eec718ff34fa844413298153") format("truetype"); 22 | } 23 | @font-face { 24 | font-family: FreightTextPro; 25 | font-weight: bold; 26 | font-style: italic; 27 | font-display: swap; 28 | src: url("https://assets.innoq.com/fonts/350ED6_3_unhinted_0.woff2?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff2"), url("https://assets.innoq.com/fonts/350ED6_3_unhinted_0.woff?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff"), url("https://assets.innoq.com/fonts/350ED6_3_unhinted_0.ttf?cachebuster=7d0a3995eec718ff34fa844413298153") format("truetype"); 29 | } 30 | @font-face { 31 | font-family: FFMarkWebProBook; 32 | font-display: swap; 33 | src: url("https://assets.innoq.com/fonts/MarkPro-Book.woff2?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("woff2"), url("https://assets.innoq.com/fonts/MarkPro-Book.woff?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("woff"), url("https://assets.innoq.com/fonts/MarkPro-Book.ttf?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("truetype"); 34 | } 35 | @font-face { 36 | font-family: FFMarkWebProBold; 37 | font-display: swap; 38 | src: url("https://assets.innoq.com/fonts/MarkPro-Bold.woff2?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("woff2"), url("https://assets.innoq.com/fonts/MarkPro-Bold.woff?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("woff"), url("https://assets.innoq.com/fonts/MarkPro-Bold.ttf?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("truetype"); 39 | } 40 | /* @font-face { 41 | font-family: FFMarkWebProHeavy; 42 | font-display: swap; 43 | src: url("https://assets.innoq.com/fonts/MarkPro-Heavy.woff2?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("woff2"), url("https://assets.innoq.com/fonts/MarkPro-Heavy.woff?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("woff"), url("https://assets.innoq.com/fonts/MarkPro-Heavy.ttf?cachebuster=ebbf66fc8344ca6f16c5d660323ab3b4") format("truetype"); 44 | } */ 45 | @font-face { 46 | font-family: HackRegular; 47 | font-display: swap; 48 | src: url("https://assets.innoq.com/fonts/hack-regular.woff2?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff2"), url("https://assets.innoq.com/fonts/hack-regular.woff?cachebuster=7d0a3995eec718ff34fa844413298153") format("woff"); 49 | } 50 | /* @font-face { 51 | font-family: "icons"; 52 | font-display: swap; 53 | src: url("https://assets.innoq.com/fonts/icomoon.woff2?cachebuster=7d0a3995eec718ff34fa8444132981c6") format("woff2"), url("https://assets.innoq.com/fonts/icomoon.woff?cachebuster=7d0a3995eec718ff34fa8444132981c6") format("woff"), url("https://assets.innoq.com/fonts/icomoon.ttf?cachebuster=7d0a3995eec718ff34fa8444132981c6") format("truetype"), url("https://assets.innoq.com/fonts/icomoon.svg?cachebuster=7d0a3995eec718ff34fa8444132981c6#icons") format("svg"); 54 | font-style: normal; 55 | font-weight: normal; 56 | } */ 57 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Responsible Web Applications 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |

33 | Responsible Web Applications 34 |

35 |
36 | Picture of Joy Heron 37 |

By Joy Heron

38 |
39 |
40 | 84 |
85 |
86 |
87 |

88 | The title for this page came from a slip of my tongue. 89 | I actually had wanted to say "responsive and accessible" web applications, 90 | but somehow "responsible" slipped out. 91 |

92 |

93 | Regardless of how I came up with the term, I do consider it to be fitting 94 | because I feel that we as developers have a responsibility to ensure that 95 | our application is responsive and accessible. 96 | If we don't, who will? 97 |

98 |

99 | It is extremely difficult and expensive to add responsiveness and accessibility after the fact. 100 | For this reason, we need to take them into account 101 | from the very beginning. 102 |

103 |

104 | Luckily, with modern HTML and CSS, we can create responsive and 105 | accessible web apps with relative ease. In my years of doing software development, 106 | I have learned some HTML and CSS tips and tricks, and I want to present these in 107 | this post. This list is not exhaustive, but these are tried and true patterns that 108 | I frequently use in different projects. 109 |

110 |
111 |
112 |

Responsive Web Design

113 | 114 |

Do we really need responsive web design for our web application? We will only use our web application on desktop computers!

115 | 116 |

117 | I've heard this argument many times, but in the long term, it has never turned out to be true. 118 | For this reason, I've created Joy's two laws of web development: 119 |

120 | 121 |

Joy’s First Law of Web Development

122 |
123 | 124 |

There is no such thing as a non-responsive web application

125 |
126 | 127 |

Your web application WILL be opened in a mobile phone or tablet at some time in the future and your users WILL expect it to work correctly.

128 | 129 |

Joy’s Second Law of Web Development

130 |
131 | 132 | Any work you do now to ensure that your web application behaves responsively WILL be appreciated in the future. 133 |
134 | 135 |

When it comes to the technical implementation of responsive design, there are two main categories of components that we need to develop:

136 | 141 |

I want to cover these two aspects in the next sections

142 | 143 |

Responsive Layout Containers

144 |

145 | We firstly need to make sure that we put a lot of thought into designing layout containers which adjust themselves based on the size of our viewport. 146 |

147 | 148 |

149 | In the following demo, we can see how we can define a layout conceptually using different grid areas. 150 |

151 |
152 | 191 |
192 | A UI example showing how content will be stacked vertically on smaller devices, 193 | but on larger viewports we can then use the increased horizontal space to display 194 | certain content areas next to each other. 195 |

196 | The demo shows five content areas: Header, Sidebar, Main Content, Second Sidebar, and Footer. 197 | On a mobile device, these areas are shown vertically stacked. 198 | On larger viewports, the example layout now has three rows and three columns. 199 | The Header area spans the full width of the top of the viewport in the first row. 200 | In the middle row, the Sidebar, Main Content, and Second Sidebar areas are positioned 201 | next to each other in a three column layout, with the Main Content area expanding 202 | to take up as much space as it can. The Footer area then spans the full width of 203 | the viewport in the bottom row of the layout. 204 |

205 |
206 |
207 |

How can we implement this? To do this, I like to use the following technique.

208 |

Explicit CSS Grid Layout with Breakpoints

209 | 210 |

211 | Using CSS Grid, I like to use the following technique to declaratively define a default CSS grid for our content. 212 | This is then the CSS which is used for smaller devices. 213 |

214 | 215 |
CSS for Mobile Devices
216 | 217 |
.layout {
218 | 	display: grid;
219 | 	grid-template-areas:
220 | 		"header"
221 | 		"sidebar"
222 | 		"main"
223 | 		"sidebar-right"
224 | 		"footer";
225 | 	grid-template-rows: auto auto 1fr auto auto;
226 | }
227 | .layout > header {
228 | 	grid-area: header;
229 | }
230 | .layout > aside:nth-of-type(1) {
231 | 	grid-area: sidebar;
232 | }
233 | .layout > main {
234 | 	grid-area: main;
235 | }
236 | .layout > aside:nth-of-type(2) {
237 | 	grid-area: sidebar-right;
238 | }
239 | .layout > footer {
240 | 	grid-area: footer;
241 | }
242 | 
243 | 244 |

245 | Note that this markup explicitly references the 246 | main, 247 | header, 248 | footer, and 249 | aside semantic HTML elements in the CSS code. 250 | This was intentional here, because it forces us to then use the semantic HTML elements and add important landmarks to our web application which improves its accessibility. 251 | With the CSS child combinator operator (>) we ensure that this element targeted is the direct child of the parent with my layout class (which we would probably add directly to our HTML body). 252 |

253 | 254 |
CSS for tablets or larger devices
255 | 256 |

257 | Since we have already defined the grid-areas for our HTML elements, we can now declaratively change the layout using a relatively short CSS snippet within a media query: 258 |

259 | 260 |
@media (min-width: 40rem) { /* Breakpoint Tablet portrait up */
261 | 	.layout {
262 | 		grid-template-areas:
263 | 			"header header header"
264 | 			"sidebar main sidebar-right"
265 | 			"footer footer footer";
266 | 		grid-template-rows: auto 1fr auto;
267 | 		grid-template-columns: 20% 1fr 20%;
268 | 	}
269 | }
270 | 
271 | 272 |

273 | Note that when you are defining your breakpoints for your application, 274 | you should consider the correct way to do CSS breakpoints. 275 |

276 | 277 |

Squishy Components

278 | 279 |

280 | After we have a responsive layout, the next step is to make sure that all of our components are "squishy". 281 | This means that when we place a component into a designated area of a layout, 282 | it should never push itself outside of its designated area. 283 | Instead it should "squish" down to fit inside of the available space. 284 |

285 |

286 | This is especially important because our layout container assumes that all of its 287 | content is going to fit, and if this assumption is not true, this could cause the 288 | whole layout to be wider than the viewport and make it necessary for the user to scroll 289 | horizontally. 290 |

291 | 292 |

Flexbox + Flex Wrap

293 | 294 |
.container {
295 | 	display: flex;
296 | 	flex-wrap: wrap;
297 | }
298 | 
299 | 300 |

301 | We can use the display: flex; rule to make the container 302 | a flexbox and 303 | then use the flex-wrap: wrap; rule to wrap content within the flexbox. 304 |

305 |

306 | When there is not enough space for the items to be placed horizontally, 307 | they will begin to wrap and be shown stacked vertically instead. 308 |

309 |
310 | 349 |
350 | An example showing how we can use flex-wrap to wrap content in a flexbox. 351 |

352 | The demo shows a flexbox containing two blocks: a block containing a title and a description, 353 | and a block containing a price, time, and amount. 354 | On a larger devices, these block can be positioned next to each other horizontally. 355 | On smaller devices with flex-wrap activated, the second block containing the price, time and amount 356 | will wrap onto the next line and be positioned underneath the first block. 357 |

358 |
359 |
360 | 361 |

Nested Flexboxes

362 |

363 | It is also possible to nest flexboxes inside each other to create more advanced responsive behavior. 364 | This next example shows the same flex container from our last example, but highlights the 365 | inner flexbox instead of the outer flexbox. 366 |

367 |
368 | 388 |
389 | An example highlighting the items in the inner flexbox that can also wrap. 390 |

391 | The demo shows the same demo as the one in the previous example, but in this instance, 392 | the price, time, and amount items are highlighted because they are also contained within 393 | an inner flexbox. This adds an extra layer to the responsiveness: when the viewport gets 394 | so small that there is no longer space for these three items to be positioned next to each 395 | other horizontally, they will begin to wrap as well. 396 |

397 |
398 |
399 | 400 |

Intrinsic Grid

401 |
.container {
402 | 	display: grid;
403 | 	grid-template-columns:
404 | 		repeat(auto-fill, minmax(var(—col-width), auto));
405 | }
406 | 
407 | 408 |

409 | We can use the CSS repeat function 410 | with grid-template-columns in order to create an intrinsic grid which will generate as 411 | many columns as fit in the given space. An example using this type of grid is available in 412 | my demo for live-coding css layout. 413 |

414 |
415 | 436 |
437 | An example of an intrinsic grid which fits as many columns as possible within the viewport. 438 | 439 |

440 | The demo shows a grid with 6 different content blocks that are positioned within an intrinsic CSS grid. 441 | On mobile devices, there is only enough space for a single column, so the blocks are all positioned within 442 | this column. When there is enough space for two columns, the layout will shift and the elements will be 443 | positioned within the grid in two columns and end up filling three rows. 444 | When there is enough space for three columns, 445 | the layout will shift again and the elements will be positioned within the grid in three columns and will 446 | end up filling two rows. This layout pattern will continue, and a grid will always be generated with 447 | as many columns as fit within the viewport size. 448 |

449 |
450 |
451 | 452 |

Horizontal Scrolling

453 | 454 |
.horizontal-scroll {
455 | 	overflow-x: auto;
456 | }
457 | 
458 | 459 |

460 | When we talk about creating an application which is responsive, this doesn't mean that we need to optimize 461 | all of our layouts for small device screens. In certain contexts, we will have larger data representations 462 | (e.g. tables or code examples), which we also want to be available for smaller devices even if they are not 463 | optimized for that layout. 464 |

465 |

466 | For this, I like to use a wrapper around tables and code examples which make them able to be scrolled horizontally 467 | when there is not enough space for them in the current layout. 468 |

469 | 470 |
471 | 527 |
528 | An example showing how to use a container with horizontal scrolling to make larger 529 | content available on smaller viewports. 530 |

531 | In the example, a large table with twenty columns is shown. On smaller devices, there is 532 | not enough space for the whole table to be shown. In this case, the large table is shown 533 | within a container that fits within the viewport, and the user can scroll within that 534 | container in order to view all of the contents. 535 |

536 |
537 |
538 | 539 |

Squishy Text

540 | 541 |
.squishy-text {
542 | 	word-break: break-word; /* Samsung browser */
543 | 	word-wrap: break-word; /* IE 11 */
544 | 	overflow-wrap: anywhere;
545 | 	-webkit-hyphens: auto;
546 | 	-ms-hyphens: auto;
547 | 	hyphens: auto;
548 | }
549 | 
550 | 551 |

552 | The following example is a CSS meme now. 553 | By default, long words in an HTML document will not be hyphenated by default, 554 | so they will break out of their containing box instead of squishing to fit inside of it. 555 | This is especially important when we are dealing with a language which has a lot of long words 556 | (*cough* German *cough*). 557 |

558 |

559 | The previous CSS snippet is one I have successfully used to make my text squishy in different contexts. 560 |

561 |
562 |
563 |
564 | CSS is awesome 565 |
566 |
567 | CSS is awesome 568 |
569 |
570 |
571 |
572 |
573 |

Accessible Web Design

574 | 575 |

576 | We now come to accessibility. 577 | Here I feel that I can only scratch the surface of the different things that we should consider. 578 | I also am learning new things all the time, so I'm sure that this list is not exhaustive. 579 | But I do think it is a good starting off point. 580 |

581 | 582 |

Headings

583 |

Don’t skip heading levels.

584 |

585 | In your HTML Document, you need to ensure that your document hierarchy is complete and doesn't skip levels. 586 | Otherwise, users who depend on assistive technologies will get confused because it will seem like content is 587 | missing. 588 |

589 |

590 | This is a mistake that many developers make, 591 | because we pay attention to how the heading appears visually without making sure that an h2 is always 592 | directly followed by a h3. 593 |

594 | 595 |

Landmarks

596 |

597 | We should make sure that we use elements like 598 | main and 599 | header 600 | because then we get HTML landmarks 601 | out of the box. 602 | This makes the page much easier to navigate. 603 |

604 |

605 | Note that the main element is not well supported for IE11, so if you have to 606 | support older browsers, you should also consider using skip links. 607 |

608 | 609 | 610 |

611 | When you are providing links for a user to navigate within your page (e.g. a navbar or a table of contents), 612 | you should wrap them in a nav. 613 |

614 | 615 |

label Element

616 |

617 | Always add a label to 618 | let assistive technologies know which data is supposed to be entered in an input field. 619 |

620 | 621 |
<label>
622 | 	First Name
623 | 	<input type="text" value="name" placeholder="Jane" />
624 | </label>
625 | 
626 | 627 |

628 | Here we either wrap the input field directly in the label, 629 | or we can use the for attribute and link it to a specific input field. 630 |

631 |

632 | Here it is important to not use the placeholder attribute to label the input field. 633 | The placeholder attribute should be used to show an example of how the data we expect should appear. 634 | This is particularly important for users who have congnitive disabilities, because when the instructions 635 | in the placeholder disappear, they may not remember what they were supposed to enter into the field. 636 | Please also read this article about why placeholder are problematic. 637 |

638 | 639 |

List Elements

640 |

641 | Using lists (an unordered list, 642 | an ordered list, or a 643 | description list) for things in a UI will also add extra context for assistive technologies. 644 | For instance, it will tell assistive technologies how many elements are contained in a list. 645 |

646 |

647 | In practice, description lists can be difficult to style because we are not allowed to add an extra div as a wrapper around the dt and dd elements. 648 | For this reason, I've also done some experiments about how to best group information in the UI. 649 | Here there isn't a single correct solution. 650 | You will have to find out what works best for your UI. 651 |

652 | 653 |

654 | Update: Originally, I discussed having difficulties styling description lists. 655 | This statement is no longer correct for modern browsers which implement the 656 | HTML 5.2 Recommendation 657 | because they now allow a div as a wrapper around the dt and dd elements. 658 |

659 | 660 |

Accordions

661 | 662 |

663 | If we need an accordion, or an element which shows/hides a content area, 664 | we can use the HTML details element. 665 |

666 | 667 |
<details>
668 | 	<summary>Toggle Button</summary>
669 | 	Content which will be expanded/collapsed when clicking the
670 | 	summary element
671 | </details>
672 | 
673 | 674 |

675 | We can also implement this in JavaScript using a button and the 676 | aria-expanded attribute. 677 | The aria-expanded attribute adds context information to the button which will tell the screenreader 678 | if the area that the button is toggling is currently collapsed or expanded. 679 |

680 |

681 | I have seen incorrect implementations of this where the developer thought that the attribute was intended to be added to 682 | the content block which is expanded or collapsed, but this is not the case and that implementation would be confusing to any user of an assistive technology. 683 | If we do spend the effort to set aria roles in our application (which we should), 684 | we need to test our application and make sure they are used correctly! 685 | No aria usage is better than incorrect aria usage. 686 |

687 |

688 | I have implemented this behavior many times, and when I do, I prefer to use a custom element and activate 689 | the toggle with progressive enhancement. 690 | This means that my toggle button is hidden by default before JavaScript is activated, and the content area that is to be collapsed/expanded 691 | will only be hidden once JavaScript is activated. 692 |

693 |

694 | The contract for the toggle-button component that I usually end up writing therefore usually looks something like this: 695 |

696 | 697 |
<button is="toggle-button" data-target="#section2"
698 | 		  aria-expanded="false" hidden>
699 | 		Toggle Section 2
700 | </button>
701 | 
702 | 703 |

aria-expanded is an attribute of the BUTTON which tells assistive technologies if the content area that is being expanded/collapsed is currently visible or not.

704 | 705 |

706 | And my toggle-button implementation usually looks a lot like the following 707 | (I really need to standardize this and publish a custom element one of these days...): 708 |

709 | 710 |
class ToggleButton extends HTMLButtonElement {
711 | 	connectedCallback () {
712 | 		this.removeAttribute("hidden");
713 | 		if (this.getAttribute("aria-expanded") !== "true") {
714 | 			this.setAttribute("aria-expanded", "false");
715 | 			this.target.classList.add("hide");
716 | 		}
717 | 		this.addEventListener("click", this.toggle.bind(this));
718 | 	}
719 | 
720 | 	toggle () {
721 | 		let classList = this.target.classList;
722 | 		if (classList.contains("hide")) {
723 | 			classList.remove("hide");
724 | 			this.setAttribute("aria-expanded", "true");
725 | 		} else {
726 | 			classList.add("hide");
727 | 			this.setAttribute("aria-expanded", "false");
728 | 		}
729 | 	}
730 | 
731 | 	get target () {
732 | 		return document.querySelector(this.getAttribute("data-target"));
733 | 	}
734 | }
735 | customElements.define("toggle-button", ToggleButton, { extends: "button" });
736 | 
737 | 738 |

Here is a code demo of this component.

739 | 740 |

aria-label and aria-labelledby

741 |

742 | Sometimes we have visual elements which add information to the UI, 743 | but are missing in the UI. For these cases, we can use 744 | an aria-label attribute 745 | with a textual description of what the visual element is showing. If there is already a UI 746 | element available, we can also use aria-labelledby and reference the HTML id of 747 | the existing element. 748 | The HTML title 749 | attribute is not well supported by assistive technologies, so it should not be used to do this. 750 |

751 |

752 | Here it really helps to test your UI in a screenreader so that you can figure out where 753 | extra labels would be helpful 😉. 754 |

755 | 756 |

Hiding content visually (but not from screenreaders)

757 | 758 |
.visually-hidden {
759 | 	clip: rect(0 0 0 0);
760 | 	clip-path: inset(50%);
761 | 	height: 1px;
762 | 	overflow: hidden;
763 | 	position: absolute;
764 | 	white-space: nowrap;
765 | 	width: 1px;
766 | }
767 | 
768 | 769 |

770 | Sometimes we want to hide elements from our UI. 771 | If we use a CSS rule like display: none; 772 | this not only hides the element visually, 773 | but also hides it from assistive technologies. 774 | We can use the previous CSS snippet (or one like it), 775 | to hide the content visually without removing it from 776 | the accessibility tree. 777 |

778 | 779 |

Hiding content from assistive technologies

780 |

781 | In the same vein, sometimes we add visual elements to our UI which are not necessary 782 | for assistive technologies and could potentially cause confusion or unnecessary noise. 783 | This could be the case, for instance, when we add icons to our visual design in addition 784 | to existing textual labels. We may then want to hide these from assistive technologies 785 | using the aria-hidden attribute. 786 |

787 | 788 |

Descriptive alt text for images

789 |

790 | When using images in a user interface, we need to set 791 | the alt attribute for the image. 792 | This alt-text should provide a description of the same information that we are trying to 793 | convey visually with the image that we have chosen. 794 | This guide provides more information about alt-texts. 795 |

796 | 797 |

Adding clear focus styles

798 |

799 | By default, the browser will add focus styles to the UI to indicate to users which element on the 800 | screen is currently active. This is especially important for keyboard users, who need the focus styles 801 | in order to correctly navigate throughout your page. 802 |

803 |

804 | If you find the default focus styles irritating in your design, you can customize them with the 805 | :focus CSS pseudo-selector. 806 | However, here it is very important that you still provide a focus style which has a high 807 | contrast and is easily identifiable within the UI. 808 |

809 | 810 |

Setting focus correctly

811 |

812 | As long as you are writing only HTML and CSS, 813 | there isn't any good reason why you would want to mess with the focus within your webpage, 814 | because the semantics and structure of HTML is very well designed. 815 | However, when we begin to modify the HTML of our application with client-side JavaScript, 816 | we need to think about the focus and if we need to update it as well. 817 |

818 |

819 | For instance, if we are adding content to our DOM with JavaScript, 820 | as I did with this 821 | “Show More” Pagination Example, 822 | we should consider whether we should move the focus to the new content after it has loaded. 823 |

824 |

825 | In this case, we need to make absolutely sure that you 826 | test the application with a screenreader and keyboard 827 | to ensure that the focus is set correctly. 828 | There is little that can break your UI more than incorrectly setting the focus. 829 |

830 |
831 |
832 |

833 | I hope you enjoyed this little collection of tips and tricks for creating responsive and accessible web applications. 834 | I also hope you were able to learn something that you can use in your next project. 835 | Let's make a responsible web together. 836 |

837 |

838 | If you have suggestions for other useful tricks or tips, please open an issue 839 | on the GitHub Repo for this page 840 | and I will do my best to add it to this list. 841 |

842 |
843 |
844 | 852 | 853 | 854 | 886 | 889 | 890 | 891 | -------------------------------------------------------------------------------- /prism-theme.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --code-green: #74FB9E; 3 | --code-pink: #FF60AD; 4 | --code-blue: #AEDFFF; 5 | --code-purple: #D3B9FF; 6 | --code-orange: #FFD174; 7 | } 8 | 9 | pre { 10 | background-color: var(--gray-900); 11 | color: var(--blue-300); 12 | -moz-tab-size: 3; 13 | tab-size: 3; 14 | } 15 | 16 | .token { 17 | color: var(--blue-300); 18 | } 19 | .token.rule { 20 | font-weight: bold; 21 | } 22 | .token.selector, 23 | .token.string, 24 | .token.attr-name { 25 | color: var(--purple-400); 26 | } 27 | .token.function, 28 | .token.attr-value { 29 | color: var(--code-pink); 30 | } 31 | .token.punctuation { 32 | color: var(--blue-300); 33 | } 34 | .token.property, 35 | .token.tag, 36 | .token.class-name { 37 | color: var(--code-green) 38 | } 39 | .token.comment { 40 | color: var(--gray-200); 41 | } 42 | .token.keyword { 43 | color: var(--code-orange); 44 | } 45 | -------------------------------------------------------------------------------- /resize-observer.js: -------------------------------------------------------------------------------- 1 | // Only run if ResizeObserver is supported. 2 | if ('ResizeObserver' in self) { 3 | // Create a single ResizeObserver instance to handle all 4 | // container elements. The instance is created with a callback, 5 | // which is invoked as soon as an element is observed as well 6 | // as any time that element's size changes. 7 | var ro = new ResizeObserver(function(entries) { 8 | // Default breakpoints that should apply to all observed 9 | // elements that don't define their own custom breakpoints. 10 | var breakpoints = {tabletUp: 384, MD: 576, LG: 768, XL: 960}; 11 | 12 | entries.forEach(function(entry) { 13 | // Update the matching breakpoints on the observed element. 14 | Object.keys(breakpoints).forEach(function(breakpoint) { 15 | var minWidth = breakpoints[breakpoint]; 16 | if (entry.contentRect.width >= minWidth) { 17 | entry.target.classList.add(breakpoint); 18 | } else { 19 | entry.target.classList.remove(breakpoint); 20 | } 21 | }); 22 | }); 23 | }); 24 | 25 | // Find all elements with the `data-observe-resizes` attribute 26 | // and start observing them. 27 | var elements = document.querySelectorAll('[data-observe-resizes]'); 28 | for (var element, i = 0; element = elements[i]; i++) { 29 | ro.observe(element); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /responsive-examples.css: -------------------------------------------------------------------------------- 1 | .responsive-example { 2 | --gap: var(--spacer-base); 3 | 4 | margin: calc(-1 * var(--spacer-sm) / 2); 5 | padding: var(--spacer-base) var(--spacer-base) var(--spacer-sm); 6 | } 7 | .responsive-example > * { 8 | margin: calc(var(--gap) / 2); 9 | } 10 | .responsive-example article { 11 | margin-bottom: var(--spacer-base); 12 | font-family: var(--font-family-sans); 13 | } 14 | .responsive-example .caption { 15 | font-style: italic; 16 | color: var(--gray-700); 17 | margin-bottom: 0; 18 | } 19 | .responsive-example .caption.tabletUp { 20 | display: none; 21 | } 22 | .responsive-example article:first-of-type { 23 | max-width: var(--example-mobile-width); 24 | } 25 | @media (min-width: 30rem) { 26 | .responsive-example { 27 | display: grid; 28 | grid-template-columns: var(--example-mobile-width, 12.5rem) 1fr; 29 | } 30 | .responsive-example article { 31 | grid-row-start: 2; 32 | } 33 | .responsive-example footer.caption { 34 | grid-column: span 2; 35 | grid-row-start: 3; 36 | margin-top: 0; 37 | } 38 | } 39 | @media screen and (hover: hover) and (min-width: 25rem) { 40 | .js .responsive-example > article { 41 | flex: auto 0; 42 | resize: both; 43 | overflow: auto; 44 | max-width: unset; 45 | box-shadow: var(--box-shadow-lg); 46 | } 47 | .js .responsive-example .caption:not(.tabletUp) { 48 | display: none; 49 | } 50 | .js .responsive-example .caption.tabletUp { 51 | display: block; 52 | } 53 | .js .responsive-example article:nth-of-type(2) { 54 | display: none; 55 | } 56 | } 57 | 58 | .layout-example { 59 | display: grid; 60 | grid-template-areas: 61 | "header" 62 | "sidebar" 63 | "main" 64 | "sidebar-right" 65 | "footer"; 66 | grid-template-rows: auto auto 1fr auto auto; 67 | } 68 | .layout-example > * { 69 | padding: var(--spacer-sm); 70 | margin: 0; 71 | } 72 | .layout-example header { 73 | grid-area: header; 74 | background-color: var(--pink-200); 75 | border: 1px dashed var(--pink-400); 76 | } 77 | .layout-example aside:first-of-type { 78 | grid-area: sidebar; 79 | background-color: var(--teal-200); 80 | border: 1px dashed var(--teal-400); 81 | } 82 | .layout-example .main { 83 | grid-area: main; 84 | background-color: var(--purple-200); 85 | border: 1px dashed var(--purple-400); 86 | } 87 | .layout-example aside:nth-of-type(2) { 88 | grid-area: sidebar-right; 89 | background-color: var(--green-200); 90 | border: 1px dashed var(--green-400); 91 | } 92 | .layout-example footer { 93 | grid-area: footer; 94 | background-color: var(--orange-200); 95 | border: 1px dashed var(--orange-400); 96 | } 97 | .layout-example.tabletUp { 98 | grid-template-areas: 99 | "header header header" 100 | "sidebar main sidebar-right" 101 | "footer footer footer"; 102 | grid-template-rows: auto 1fr auto; 103 | grid-template-columns: 20% 1fr 20%; 104 | } 105 | 106 | .flexbox-example { 107 | display: flex; 108 | flex-wrap: wrap; 109 | } 110 | .flexbox-example h5 { 111 | margin-bottom: var(--spacer-xxs); 112 | } 113 | .flexbox-example > * { 114 | margin: 0; 115 | padding: var(--spacer-sm); 116 | background-color: var(--gray-100); 117 | border: 1px dashed var(--gray-300); 118 | flex-grow: 1; 119 | } 120 | .flexbox-example:nth-of-type(2) { 121 | flex-wrap: nowrap; 122 | } 123 | .flexbox-example > * > * { 124 | margin: 0; 125 | } 126 | 127 | .inner-flex { 128 | display: flex; 129 | justify-content: space-between; 130 | align-items: center; 131 | flex-wrap: wrap; 132 | } 133 | 134 | .highlight-flex > :nth-child(1) { 135 | background-color: var(--pink-200); 136 | border: 1px dashed var(--pink-400); 137 | } 138 | .highlight-flex > :nth-child(2) { 139 | background-color: var(--green-200); 140 | border: 1px dashed var(--green-400); 141 | } 142 | .highlight-flex > :nth-child(3) { 143 | background-color: var(--blue-200); 144 | border: 1px dashed var(--blue-400); 145 | } 146 | 147 | .intrinsic-grid-example { 148 | display: grid; 149 | grid-template-columns: repeat(auto-fill, minmax(7rem, auto)); 150 | } 151 | .intrinsic-grid-example:nth-of-type(1) { 152 | grid-template-columns: repeat(auto-fill, minmax(11rem, auto)); 153 | } 154 | .intrinsic-grid-example > * { 155 | margin: 0; 156 | padding: var(--spacer-sm); 157 | background-color: var(--pink-200); 158 | border: 1px dashed var(--pink-400); 159 | text-align: center; 160 | } 161 | .intrinsic-grid-example > :nth-child(2n + 1) { 162 | background-color: var(--green-200); 163 | border: 1px dashed var(--green-400); 164 | } 165 | 166 | .example-table { 167 | overflow-x: scroll; 168 | } 169 | .example-table table { 170 | border-collapse: collapse; 171 | background-color: var(--blue-100); 172 | } 173 | .example-table th, 174 | .example-table td { 175 | padding: var(--spacer-sm); 176 | border: 1px solid var(--gray-400); 177 | } 178 | 179 | .box-example { 180 | font-family: var(--font-family-sans); 181 | padding: var(--spacer-base); 182 | border: 1px dashed var(--gray-500); 183 | font-size: var(--box-example-font-size, 2rem); 184 | width: 8rem; 185 | margin-right: 5rem; 186 | line-height: 1; 187 | background-color: white; 188 | } 189 | 190 | .flex-wrapper { 191 | --gap: var(--flex-wrapper-gap, --spacer-sm); 192 | 193 | display: flex; 194 | margin: calc(-1 * var(--gap) / 2); 195 | padding-top: var(--spacer-base); 196 | } 197 | .flex-wrapper > * { 198 | margin: calc(var(--gap) / 2); 199 | } 200 | 201 | .squishy-text { 202 | word-break: break-word; 203 | word-wrap: break-word; 204 | overflow-wrap: anywhere; 205 | -webkit-hyphens: auto; 206 | -ms-hyphens: auto; 207 | hyphens: auto; 208 | } 209 | 210 | .example-container-queries { 211 | contain: style layout inline-size; 212 | } 213 | .example-container-queries .card { 214 | background-color: white; 215 | font-size: 0.8em; 216 | display: grid; 217 | grid-template-areas: "picture" "heading" "text"; 218 | box-shadow: var(--box-shadow-md); 219 | } 220 | .example-container-queries .image-container { 221 | display: grid; 222 | background-color: var(--purple-100); 223 | place-items: center; 224 | grid-area: picture; 225 | } 226 | .example-container-queries h2 { 227 | grid-area: heading; 228 | padding: 0 1rem; 229 | } 230 | .example-container-queries p { 231 | grid-area: text; 232 | padding: 0 1rem; 233 | } 234 | .example-container-queries .image-container img { 235 | width: 60%; 236 | } 237 | 238 | .example-container-queries:nth-of-type(2) .card { 239 | grid-template-areas: "picture heading" "picture text"; 240 | } 241 | .example-container-queries:nth-of-type(2) .image-container { 242 | aspect-ratio: 1 / 1; 243 | background-color: var(--indigo-100); 244 | } 245 | .example-container-queries:nth-of-type(2) h2 { 246 | margin: 0; 247 | align-self: end; 248 | } 249 | @container (min-width: 25rem) { 250 | .example-container-queries .card { 251 | grid-template-areas: "picture heading" "picture text"; 252 | } 253 | 254 | .example-container-queries .image-container { 255 | aspect-ratio: 1 / 1; 256 | background-color: var(--indigo-100); 257 | } 258 | 259 | .example-container-queries h2 { 260 | margin: 0; 261 | align-self: end; 262 | } 263 | } 264 | @container (min-width: 50rem) { 265 | .example-container-queries .card { 266 | grid-template-areas: "heading picture" "text picture"; 267 | background-color: var(--pink-100); 268 | } 269 | 270 | .example-container-queries .image-container { 271 | aspect-ratio: 1 / 1; 272 | background-color: white; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /slides/assets/images/accessible-mascot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /slides/assets/images/responsive-mascot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /slides/assets/monokai.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; 10 | color: #ddd; 11 | } 12 | 13 | .hljs-tag, 14 | .hljs-keyword, 15 | .hljs-selector-tag, 16 | .hljs-literal, 17 | .hljs-strong, 18 | .hljs-name { 19 | color: #f92672; 20 | } 21 | 22 | .hljs-code { 23 | color: #66d9ef; 24 | } 25 | 26 | .hljs-class .hljs-title { 27 | color: white; 28 | } 29 | 30 | .hljs-attribute, 31 | .hljs-symbol, 32 | .hljs-regexp, 33 | .hljs-link { 34 | color: #bf79db; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-bullet, 39 | .hljs-subst, 40 | .hljs-title, 41 | .hljs-section, 42 | .hljs-emphasis, 43 | .hljs-type, 44 | .hljs-built_in, 45 | .hljs-builtin-name, 46 | .hljs-selector-attr, 47 | .hljs-selector-pseudo, 48 | .hljs-addition, 49 | .hljs-variable, 50 | .hljs-template-tag, 51 | .hljs-template-variable { 52 | color: #a6e22e; 53 | } 54 | 55 | .hljs-comment, 56 | .hljs-quote, 57 | .hljs-deletion, 58 | .hljs-meta { 59 | color: #75715e; 60 | } 61 | 62 | .hljs-keyword, 63 | .hljs-selector-tag, 64 | .hljs-literal, 65 | .hljs-doctag, 66 | .hljs-title, 67 | .hljs-section, 68 | .hljs-type, 69 | .hljs-selector-id { 70 | font-weight: bold; 71 | } 72 | -------------------------------------------------------------------------------- /slides/assets/plugin.js: -------------------------------------------------------------------------------- 1 | import hljs from 'highlight.js' 2 | 3 | /* highlightjs-line-numbers.js 2.6.0 | (C) 2018 Yauheni Pakala | MIT License | github.com/wcoder/highlightjs-line-numbers.js */ 4 | /* Edited by Hakim for reveal.js; removed async timeout */ 5 | !function(n,e){"use strict";function t(){var n=e.createElement("style");n.type="text/css",n.innerHTML=g(".{0}{border-collapse:collapse}.{0} td{padding:0}.{1}:before{content:attr({2})}",[v,L,b]),e.getElementsByTagName("head")[0].appendChild(n)}function r(t){"interactive"===e.readyState||"complete"===e.readyState?i(t):n.addEventListener("DOMContentLoaded",function(){i(t)})}function i(t){try{var r=e.querySelectorAll("code.hljs,code.nohighlight");for(var i in r)r.hasOwnProperty(i)&&l(r[i],t)}catch(o){n.console.error("LineNumbers error: ",o)}}function l(n,e){"object"==typeof n&&f(function(){n.innerHTML=s(n,e)})}function o(n,e){if("string"==typeof n){var t=document.createElement("code");return t.innerHTML=n,s(t,e)}}function s(n,e){e=e||{singleLine:!1};var t=e.singleLine?0:1;return c(n),a(n.innerHTML,t)}function a(n,e){var t=u(n);if(""===t[t.length-1].trim()&&t.pop(),t.length>e){for(var r="",i=0,l=t.length;i
{6}
',[j,m,L,b,p,i+1,t[i].length>0?t[i]:" "]);return g('{1}
',[v,r])}return n}function c(n){var e=n.childNodes;for(var t in e)if(e.hasOwnProperty(t)){var r=e[t];h(r.textContent)>0&&(r.childNodes.length>0?c(r):d(r.parentNode))}}function d(n){var e=n.className;if(/hljs-/.test(e)){for(var t=u(n.innerHTML),r=0,i="";r0?t[r]:" ";i+=g('{1}\n',[e,l])}n.innerHTML=i.trim()}}function u(n){return 0===n.length?[]:n.split(y)}function h(n){return(n.trim().match(y)||[]).length}function f(e){e()}function g(n,e){return n.replace(/\{(\d+)\}/g,function(n,t){return e[t]?e[t]:n})}var v="hljs-ln",m="hljs-ln-line",p="hljs-ln-code",j="hljs-ln-numbers",L="hljs-ln-n",b="data-line-number",y=/\r\n|\r|\n/g;hljs?(hljs.initLineNumbersOnLoad=r,hljs.lineNumbersBlock=l,hljs.lineNumbersValue=o,t()):n.console.error("highlight.js not detected!")}(window,document); 6 | 7 | /*! 8 | * reveal.js plugin that adds syntax highlight support. 9 | */ 10 | 11 | const Plugin = { 12 | 13 | id: 'highlight', 14 | 15 | HIGHLIGHT_STEP_DELIMITER: '|', 16 | HIGHLIGHT_LINE_DELIMITER: ',', 17 | HIGHLIGHT_LINE_RANGE_DELIMITER: '-', 18 | 19 | hljs: hljs, 20 | 21 | /** 22 | * Highlights code blocks withing the given deck. 23 | * 24 | * Note that this can be called multiple times if 25 | * there are multiple presentations on one page. 26 | * 27 | * @param {Reveal} reveal the reveal.js instance 28 | */ 29 | init: function( reveal ) { 30 | 31 | // Read the plugin config options and provide fallbacks 32 | let config = reveal.getConfig().highlight || {}; 33 | config.highlightOnLoad = typeof config.highlightOnLoad === 'boolean' ? config.highlightOnLoad : true; 34 | config.escapeHTML = typeof config.escapeHTML === 'boolean' ? config.escapeHTML : true; 35 | 36 | Array.from( reveal.getRevealElement().querySelectorAll( 'pre code' ) ).forEach( block => { 37 | 38 | block.parentNode.className = 'code-wrapper'; 39 | 40 | // Code can optionally be wrapped in script template to avoid 41 | // HTML being parsed by the browser (i.e. when you need to 42 | // include <, > or & in your code). 43 | let substitute = block.querySelector( 'script[type="text/template"]' ); 44 | if( substitute ) { 45 | // textContent handles the HTML entity escapes for us 46 | block.textContent = substitute.innerHTML; 47 | } 48 | 49 | // Trim whitespace if the "data-trim" attribute is present 50 | if( block.hasAttribute( 'data-trim' ) && typeof block.innerHTML.trim === 'function' ) { 51 | block.innerHTML = betterTrim( block ); 52 | } 53 | 54 | // Escape HTML tags unless the "data-noescape" attrbute is present 55 | if( config.escapeHTML && !block.hasAttribute( 'data-noescape' )) { 56 | block.innerHTML = block.innerHTML.replace( //g, '>' ); 57 | } 58 | 59 | // Re-highlight when focus is lost (for contenteditable code) 60 | block.addEventListener( 'focusout', function( event ) { 61 | hljs.highlightElement( event.currentTarget ); 62 | }, false ); 63 | 64 | if( config.highlightOnLoad ) { 65 | Plugin.highlightBlock( block ); 66 | } 67 | 68 | } ); 69 | 70 | // If we're printing to PDF, scroll the code highlights of 71 | // all blocks in the deck into view at once 72 | reveal.on( 'pdf-ready', function() { 73 | [].slice.call( reveal.getRevealElement().querySelectorAll( 'pre code[data-line-numbers].current-fragment' ) ).forEach( function( block ) { 74 | Plugin.scrollHighlightedLineIntoView( block, {}, true ); 75 | } ); 76 | } ); 77 | 78 | }, 79 | 80 | /** 81 | * Highlights a code block. If the node has the 82 | * 'data-line-numbers' attribute we also generate slide 83 | * numbers. 84 | * 85 | * If the block contains multiple line highlight steps, 86 | * we clone the block and create a fragment for each step. 87 | */ 88 | highlightBlock: function( block ) { 89 | 90 | hljs.highlightElement( block ); 91 | 92 | // Don't generate line numbers for empty code blocks 93 | if( block.innerHTML.trim().length === 0 ) return; 94 | 95 | if( block.hasAttribute( 'data-line-numbers' ) ) { 96 | hljs.lineNumbersBlock( block, { singleLine: true } ); 97 | 98 | var scrollState = { currentBlock: block }; 99 | 100 | // If there is at least one highlight step, generate 101 | // fragments 102 | var highlightSteps = Plugin.deserializeHighlightSteps( block.getAttribute( 'data-line-numbers' ) ); 103 | if( highlightSteps.length > 1 ) { 104 | 105 | // If the original code block has a fragment-index, 106 | // each clone should follow in an incremental sequence 107 | var fragmentIndex = parseInt( block.getAttribute( 'data-fragment-index' ), 10 ); 108 | 109 | if( typeof fragmentIndex !== 'number' || isNaN( fragmentIndex ) ) { 110 | fragmentIndex = null; 111 | } 112 | 113 | // Generate fragments for all steps except the original block 114 | highlightSteps.slice(1).forEach( function( highlight ) { 115 | 116 | var fragmentBlock = block.cloneNode( true ); 117 | fragmentBlock.setAttribute( 'data-line-numbers', Plugin.serializeHighlightSteps( [ highlight ] ) ); 118 | fragmentBlock.classList.add( 'fragment' ); 119 | block.parentNode.appendChild( fragmentBlock ); 120 | Plugin.highlightLines( fragmentBlock ); 121 | 122 | if( typeof fragmentIndex === 'number' ) { 123 | fragmentBlock.setAttribute( 'data-fragment-index', fragmentIndex ); 124 | fragmentIndex += 1; 125 | } 126 | else { 127 | fragmentBlock.removeAttribute( 'data-fragment-index' ); 128 | } 129 | 130 | // Scroll highlights into view as we step through them 131 | fragmentBlock.addEventListener( 'visible', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock, scrollState ) ); 132 | fragmentBlock.addEventListener( 'hidden', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock.previousSibling, scrollState ) ); 133 | 134 | } ); 135 | 136 | block.removeAttribute( 'data-fragment-index' ) 137 | block.setAttribute( 'data-line-numbers', Plugin.serializeHighlightSteps( [ highlightSteps[0] ] ) ); 138 | 139 | } 140 | 141 | // Scroll the first highlight into view when the slide 142 | // becomes visible. Note supported in IE11 since it lacks 143 | // support for Element.closest. 144 | var slide = typeof block.closest === 'function' ? block.closest( 'section:not(.stack)' ) : null; 145 | if( slide ) { 146 | var scrollFirstHighlightIntoView = function() { 147 | Plugin.scrollHighlightedLineIntoView( block, scrollState, true ); 148 | slide.removeEventListener( 'visible', scrollFirstHighlightIntoView ); 149 | } 150 | slide.addEventListener( 'visible', scrollFirstHighlightIntoView ); 151 | } 152 | 153 | Plugin.highlightLines( block ); 154 | 155 | } 156 | 157 | }, 158 | 159 | /** 160 | * Animates scrolling to the first highlighted line 161 | * in the given code block. 162 | */ 163 | scrollHighlightedLineIntoView: function( block, scrollState, skipAnimation ) { 164 | 165 | cancelAnimationFrame( scrollState.animationFrameID ); 166 | 167 | // Match the scroll position of the currently visible 168 | // code block 169 | if( scrollState.currentBlock ) { 170 | block.scrollTop = scrollState.currentBlock.scrollTop; 171 | } 172 | 173 | // Remember the current code block so that we can match 174 | // its scroll position when showing/hiding fragments 175 | scrollState.currentBlock = block; 176 | 177 | var highlightBounds = this.getHighlightedLineBounds( block ) 178 | var viewportHeight = block.offsetHeight; 179 | 180 | // Subtract padding from the viewport height 181 | var blockStyles = getComputedStyle( block ); 182 | viewportHeight -= parseInt( blockStyles.paddingTop ) + parseInt( blockStyles.paddingBottom ); 183 | 184 | // Scroll position which centers all highlights 185 | var startTop = block.scrollTop; 186 | var targetTop = highlightBounds.top + ( Math.min( highlightBounds.bottom - highlightBounds.top, viewportHeight ) - viewportHeight ) / 2; 187 | 188 | // Account for offsets in position applied to the 189 | // that holds our lines of code 190 | var lineTable = block.querySelector( '.hljs-ln' ); 191 | if( lineTable ) targetTop += lineTable.offsetTop - parseInt( blockStyles.paddingTop ); 192 | 193 | // Make sure the scroll target is within bounds 194 | targetTop = Math.max( Math.min( targetTop, block.scrollHeight - viewportHeight ), 0 ); 195 | 196 | if( skipAnimation === true || startTop === targetTop ) { 197 | block.scrollTop = targetTop; 198 | } 199 | else { 200 | 201 | // Don't attempt to scroll if there is no overflow 202 | if( block.scrollHeight <= viewportHeight ) return; 203 | 204 | var time = 0; 205 | var animate = function() { 206 | time = Math.min( time + 0.02, 1 ); 207 | 208 | // Update our eased scroll position 209 | block.scrollTop = startTop + ( targetTop - startTop ) * Plugin.easeInOutQuart( time ); 210 | 211 | // Keep animating unless we've reached the end 212 | if( time < 1 ) { 213 | scrollState.animationFrameID = requestAnimationFrame( animate ); 214 | } 215 | }; 216 | 217 | animate(); 218 | 219 | } 220 | 221 | }, 222 | 223 | /** 224 | * The easing function used when scrolling. 225 | */ 226 | easeInOutQuart: function( t ) { 227 | 228 | // easeInOutQuart 229 | return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t; 230 | 231 | }, 232 | 233 | getHighlightedLineBounds: function( block ) { 234 | 235 | var highlightedLines = block.querySelectorAll( '.highlight-line' ); 236 | if( highlightedLines.length === 0 ) { 237 | return { top: 0, bottom: 0 }; 238 | } 239 | else { 240 | var firstHighlight = highlightedLines[0]; 241 | var lastHighlight = highlightedLines[ highlightedLines.length -1 ]; 242 | 243 | return { 244 | top: firstHighlight.offsetTop, 245 | bottom: lastHighlight.offsetTop + lastHighlight.offsetHeight 246 | } 247 | } 248 | 249 | }, 250 | 251 | /** 252 | * Visually emphasize specific lines within a code block. 253 | * This only works on blocks with line numbering turned on. 254 | * 255 | * @param {HTMLElement} block a block 256 | * @param {String} [linesToHighlight] The lines that should be 257 | * highlighted in this format: 258 | * "1" = highlights line 1 259 | * "2,5" = highlights lines 2 & 5 260 | * "2,5-7" = highlights lines 2, 5, 6 & 7 261 | */ 262 | highlightLines: function( block, linesToHighlight ) { 263 | 264 | var highlightSteps = Plugin.deserializeHighlightSteps( linesToHighlight || block.getAttribute( 'data-line-numbers' ) ); 265 | 266 | if( highlightSteps.length ) { 267 | 268 | highlightSteps[0].forEach( function( highlight ) { 269 | 270 | var elementsToHighlight = []; 271 | 272 | // Highlight a range 273 | if( typeof highlight.end === 'number' ) { 274 | elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child(n+'+highlight.start+'):nth-child(-n+'+highlight.end+')' ) ); 275 | } 276 | // Highlight a single line 277 | else if( typeof highlight.start === 'number' ) { 278 | elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child('+highlight.start+')' ) ); 279 | } 280 | 281 | if( elementsToHighlight.length ) { 282 | elementsToHighlight.forEach( function( lineElement ) { 283 | lineElement.classList.add( 'highlight-line' ); 284 | } ); 285 | 286 | block.classList.add( 'has-highlights' ); 287 | } 288 | 289 | } ); 290 | 291 | } 292 | 293 | }, 294 | 295 | /** 296 | * Parses and formats a user-defined string of line 297 | * numbers to highlight. 298 | * 299 | * @example 300 | * Plugin.deserializeHighlightSteps( '1,2|3,5-10' ) 301 | * // [ 302 | * // [ { start: 1 }, { start: 2 } ], 303 | * // [ { start: 3 }, { start: 5, end: 10 } ] 304 | * // ] 305 | */ 306 | deserializeHighlightSteps: function( highlightSteps ) { 307 | 308 | // Remove whitespace 309 | highlightSteps = highlightSteps.replace( /\s/g, '' ); 310 | 311 | // Divide up our line number groups 312 | highlightSteps = highlightSteps.split( Plugin.HIGHLIGHT_STEP_DELIMITER ); 313 | 314 | return highlightSteps.map( function( highlights ) { 315 | 316 | return highlights.split( Plugin.HIGHLIGHT_LINE_DELIMITER ).map( function( highlight ) { 317 | 318 | // Parse valid line numbers 319 | if( /^[\d-]+$/.test( highlight ) ) { 320 | 321 | highlight = highlight.split( Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER ); 322 | 323 | var lineStart = parseInt( highlight[0], 10 ), 324 | lineEnd = parseInt( highlight[1], 10 ); 325 | 326 | if( isNaN( lineEnd ) ) { 327 | return { 328 | start: lineStart 329 | }; 330 | } 331 | else { 332 | return { 333 | start: lineStart, 334 | end: lineEnd 335 | }; 336 | } 337 | 338 | } 339 | // If no line numbers are provided, no code will be highlighted 340 | else { 341 | 342 | return {}; 343 | 344 | } 345 | 346 | } ); 347 | 348 | } ); 349 | 350 | }, 351 | 352 | /** 353 | * Serializes parsed line number data into a string so 354 | * that we can store it in the DOM. 355 | */ 356 | serializeHighlightSteps: function( highlightSteps ) { 357 | 358 | return highlightSteps.map( function( highlights ) { 359 | 360 | return highlights.map( function( highlight ) { 361 | 362 | // Line range 363 | if( typeof highlight.end === 'number' ) { 364 | return highlight.start + Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER + highlight.end; 365 | } 366 | // Single line 367 | else if( typeof highlight.start === 'number' ) { 368 | return highlight.start; 369 | } 370 | // All lines 371 | else { 372 | return ''; 373 | } 374 | 375 | } ).join( Plugin.HIGHLIGHT_LINE_DELIMITER ); 376 | 377 | } ).join( Plugin.HIGHLIGHT_STEP_DELIMITER ); 378 | 379 | } 380 | 381 | } 382 | 383 | // Function to perform a better "data-trim" on code snippets 384 | // Will slice an indentation amount on each line of the snippet (amount based on the line having the lowest indentation length) 385 | function betterTrim(snippetEl) { 386 | // Helper functions 387 | function trimLeft(val) { 388 | // Adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill 389 | return val.replace(/^[\s\uFEFF\xA0]+/g, ''); 390 | } 391 | function trimLineBreaks(input) { 392 | var lines = input.split('\n'); 393 | 394 | // Trim line-breaks from the beginning 395 | for (var i = 0; i < lines.length; i++) { 396 | if (lines[i].trim() === '') { 397 | lines.splice(i--, 1); 398 | } else break; 399 | } 400 | 401 | // Trim line-breaks from the end 402 | for (var i = lines.length-1; i >= 0; i--) { 403 | if (lines[i].trim() === '') { 404 | lines.splice(i, 1); 405 | } else break; 406 | } 407 | 408 | return lines.join('\n'); 409 | } 410 | 411 | // Main function for betterTrim() 412 | return (function(snippetEl) { 413 | var content = trimLineBreaks(snippetEl.innerHTML); 414 | var lines = content.split('\n'); 415 | // Calculate the minimum amount to remove on each line start of the snippet (can be 0) 416 | var pad = lines.reduce(function(acc, line) { 417 | if (line.length > 0 && trimLeft(line).length > 0 && acc > line.length - trimLeft(line).length) { 418 | return line.length - trimLeft(line).length; 419 | } 420 | return acc; 421 | }, Number.POSITIVE_INFINITY); 422 | // Slice each line with this amount 423 | return lines.map(function(line, index) { 424 | return line.slice(pad); 425 | }) 426 | .join('\n'); 427 | })(snippetEl); 428 | } 429 | 430 | export default () => Plugin; 431 | -------------------------------------------------------------------------------- /slides/assets/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } -------------------------------------------------------------------------------- /slides/assets/reveal.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * reveal.js 4.0.2 3 | * https://revealjs.com 4 | * MIT licensed 5 | * 6 | * Copyright (C) 2020 Hakim El Hattab, https://hakim.se 7 | */ 8 | .reveal .r-stretch,.reveal .stretch{max-width:none;max-height:none}.reveal pre.r-stretch code,.reveal pre.stretch code{height:100%;max-height:100%;box-sizing:border-box}.reveal .r-fit-text{display:inline-block;white-space:nowrap}.reveal .r-stack{display:grid}.reveal .r-stack>*{grid-area:1/1;margin:auto}.reveal .r-hstack,.reveal .r-vstack{display:flex}.reveal .r-hstack img,.reveal .r-hstack video,.reveal .r-vstack img,.reveal .r-vstack video{min-width:0;min-height:0;-o-object-fit:contain;object-fit:contain}.reveal .r-vstack{flex-direction:column;align-items:center;justify-content:center}.reveal .r-hstack{flex-direction:row;align-items:center;justify-content:center}.reveal .items-stretch{align-items:stretch}.reveal .items-start{align-items:flex-start}.reveal .items-center{align-items:center}.reveal .items-end{align-items:flex-end}.reveal .justify-between{justify-content:space-between}.reveal .justify-around{justify-content:space-around}.reveal .justify-start{justify-content:flex-start}.reveal .justify-center{justify-content:center}.reveal .justify-end{justify-content:flex-end}html.reveal-full-page{width:100%;height:100%;height:100vh;height:calc(var(--vh,1vh) * 100);overflow:hidden}.reveal-viewport{height:100%;overflow:hidden;position:relative;line-height:1;margin:0;background-color:#fff;color:#000}.reveal .slides section .fragment{opacity:0;visibility:hidden;transition:all .2s ease;will-change:opacity}.reveal .slides section .fragment.visible{opacity:1;visibility:inherit}.reveal .slides section .fragment.disabled{transition:none}.reveal .slides section .fragment.grow{opacity:1;visibility:inherit}.reveal .slides section .fragment.grow.visible{transform:scale(1.3)}.reveal .slides section .fragment.shrink{opacity:1;visibility:inherit}.reveal .slides section .fragment.shrink.visible{transform:scale(.7)}.reveal .slides section .fragment.zoom-in{transform:scale(.1)}.reveal .slides section .fragment.zoom-in.visible{transform:none}.reveal .slides section .fragment.fade-out{opacity:1;visibility:inherit}.reveal .slides section .fragment.fade-out.visible{opacity:0;visibility:hidden}.reveal .slides section .fragment.semi-fade-out{opacity:1;visibility:inherit}.reveal .slides section .fragment.semi-fade-out.visible{opacity:.5;visibility:inherit}.reveal .slides section .fragment.strike{opacity:1;visibility:inherit}.reveal .slides section .fragment.strike.visible{text-decoration:line-through}.reveal .slides section .fragment.fade-up{transform:translate(0,40px)}.reveal .slides section .fragment.fade-up.visible{transform:translate(0,0)}.reveal .slides section .fragment.fade-down{transform:translate(0,-40px)}.reveal .slides section .fragment.fade-down.visible{transform:translate(0,0)}.reveal .slides section .fragment.fade-right{transform:translate(-40px,0)}.reveal .slides section .fragment.fade-right.visible{transform:translate(0,0)}.reveal .slides section .fragment.fade-left{transform:translate(40px,0)}.reveal .slides section .fragment.fade-left.visible{transform:translate(0,0)}.reveal .slides section .fragment.current-visible,.reveal .slides section .fragment.fade-in-then-out{opacity:0;visibility:hidden}.reveal .slides section .fragment.current-visible.current-fragment,.reveal .slides section .fragment.fade-in-then-out.current-fragment{opacity:1;visibility:inherit}.reveal .slides section .fragment.fade-in-then-semi-out{opacity:0;visibility:hidden}.reveal .slides section .fragment.fade-in-then-semi-out.visible{opacity:.5;visibility:inherit}.reveal .slides section .fragment.fade-in-then-semi-out.current-fragment{opacity:1;visibility:inherit}.reveal .slides section .fragment.highlight-blue,.reveal .slides section .fragment.highlight-current-blue,.reveal .slides section .fragment.highlight-current-green,.reveal .slides section .fragment.highlight-current-red,.reveal .slides section .fragment.highlight-green,.reveal .slides section .fragment.highlight-red{opacity:1;visibility:inherit}.reveal .slides section .fragment.highlight-red.visible{color:#ff2c2d}.reveal .slides section .fragment.highlight-green.visible{color:#17ff2e}.reveal .slides section .fragment.highlight-blue.visible{color:#1b91ff}.reveal .slides section .fragment.highlight-current-red.current-fragment{color:#ff2c2d}.reveal .slides section .fragment.highlight-current-green.current-fragment{color:#17ff2e}.reveal .slides section .fragment.highlight-current-blue.current-fragment{color:#1b91ff}.reveal:after{content:'';font-style:italic}.reveal iframe{z-index:1}.reveal a{position:relative}@keyframes bounce-right{0%,10%,25%,40%,50%{transform:translateX(0)}20%{transform:translateX(10px)}30%{transform:translateX(-5px)}}@keyframes bounce-left{0%,10%,25%,40%,50%{transform:translateX(0)}20%{transform:translateX(-10px)}30%{transform:translateX(5px)}}@keyframes bounce-down{0%,10%,25%,40%,50%{transform:translateY(0)}20%{transform:translateY(10px)}30%{transform:translateY(-5px)}}.reveal .controls{display:none;position:absolute;top:auto;bottom:12px;right:12px;left:auto;z-index:11;color:#000;pointer-events:none;font-size:10px}.reveal .controls button{position:absolute;padding:0;background-color:transparent;border:0;outline:0;cursor:pointer;color:currentColor;transform:scale(.9999);transition:color .2s ease,opacity .2s ease,transform .2s ease;z-index:2;pointer-events:auto;font-size:inherit;visibility:hidden;opacity:0;-webkit-appearance:none;-webkit-tap-highlight-color:transparent}.reveal .controls .controls-arrow:after,.reveal .controls .controls-arrow:before{content:'';position:absolute;top:0;left:0;width:2.6em;height:.5em;border-radius:.25em;background-color:currentColor;transition:all .15s ease,background-color .8s ease;transform-origin:.2em 50%;will-change:transform}.reveal .controls .controls-arrow{position:relative;width:3.6em;height:3.6em}.reveal .controls .controls-arrow:before{transform:translateX(.5em) translateY(1.55em) rotate(45deg)}.reveal .controls .controls-arrow:after{transform:translateX(.5em) translateY(1.55em) rotate(-45deg)}.reveal .controls .controls-arrow:hover:before{transform:translateX(.5em) translateY(1.55em) rotate(40deg)}.reveal .controls .controls-arrow:hover:after{transform:translateX(.5em) translateY(1.55em) rotate(-40deg)}.reveal .controls .controls-arrow:active:before{transform:translateX(.5em) translateY(1.55em) rotate(36deg)}.reveal .controls .controls-arrow:active:after{transform:translateX(.5em) translateY(1.55em) rotate(-36deg)}.reveal .controls .navigate-left{right:6.4em;bottom:3.2em;transform:translateX(-10px)}.reveal .controls .navigate-left.highlight{animation:bounce-left 2s 50 both ease-out}.reveal .controls .navigate-right{right:0;bottom:3.2em;transform:translateX(10px)}.reveal .controls .navigate-right .controls-arrow{transform:rotate(180deg)}.reveal .controls .navigate-right.highlight{animation:bounce-right 2s 50 both ease-out}.reveal .controls .navigate-up{right:3.2em;bottom:6.4em;transform:translateY(-10px)}.reveal .controls .navigate-up .controls-arrow{transform:rotate(90deg)}.reveal .controls .navigate-down{right:3.2em;bottom:-1.4em;padding-bottom:1.4em;transform:translateY(10px)}.reveal .controls .navigate-down .controls-arrow{transform:rotate(-90deg)}.reveal .controls .navigate-down.highlight{animation:bounce-down 2s 50 both ease-out}.reveal .controls[data-controls-back-arrows=faded] .navigate-up.enabled{opacity:.3}.reveal .controls[data-controls-back-arrows=faded] .navigate-up.enabled:hover{opacity:1}.reveal .controls[data-controls-back-arrows=hidden] .navigate-up.enabled{opacity:0;visibility:hidden}.reveal .controls .enabled{visibility:visible;opacity:.9;cursor:pointer;transform:none}.reveal .controls .enabled.fragmented{opacity:.5}.reveal .controls .enabled.fragmented:hover,.reveal .controls .enabled:hover{opacity:1}.reveal:not(.rtl) .controls[data-controls-back-arrows=faded] .navigate-left.enabled{opacity:.3}.reveal:not(.rtl) .controls[data-controls-back-arrows=faded] .navigate-left.enabled:hover{opacity:1}.reveal:not(.rtl) .controls[data-controls-back-arrows=hidden] .navigate-left.enabled{opacity:0;visibility:hidden}.reveal.rtl .controls[data-controls-back-arrows=faded] .navigate-right.enabled{opacity:.3}.reveal.rtl .controls[data-controls-back-arrows=faded] .navigate-right.enabled:hover{opacity:1}.reveal.rtl .controls[data-controls-back-arrows=hidden] .navigate-right.enabled{opacity:0;visibility:hidden}.reveal[data-navigation-mode=linear].has-horizontal-slides .navigate-down,.reveal[data-navigation-mode=linear].has-horizontal-slides .navigate-up{display:none}.reveal:not(.has-vertical-slides) .controls .navigate-left,.reveal[data-navigation-mode=linear].has-horizontal-slides .navigate-left{bottom:1.4em;right:5.5em}.reveal:not(.has-vertical-slides) .controls .navigate-right,.reveal[data-navigation-mode=linear].has-horizontal-slides .navigate-right{bottom:1.4em;right:.5em}.reveal:not(.has-horizontal-slides) .controls .navigate-up{right:1.4em;bottom:5em}.reveal:not(.has-horizontal-slides) .controls .navigate-down{right:1.4em;bottom:.5em}.reveal.has-dark-background .controls{color:#fff}.reveal.has-light-background .controls{color:#000}.reveal.no-hover .controls .controls-arrow:active:before,.reveal.no-hover .controls .controls-arrow:hover:before{transform:translateX(.5em) translateY(1.55em) rotate(45deg)}.reveal.no-hover .controls .controls-arrow:active:after,.reveal.no-hover .controls .controls-arrow:hover:after{transform:translateX(.5em) translateY(1.55em) rotate(-45deg)}@media screen and (min-width:500px){.reveal .controls[data-controls-layout=edges]{top:0;right:0;bottom:0;left:0}.reveal .controls[data-controls-layout=edges] .navigate-down,.reveal .controls[data-controls-layout=edges] .navigate-left,.reveal .controls[data-controls-layout=edges] .navigate-right,.reveal .controls[data-controls-layout=edges] .navigate-up{bottom:auto;right:auto}.reveal .controls[data-controls-layout=edges] .navigate-left{top:50%;left:.8em;margin-top:-1.8em}.reveal .controls[data-controls-layout=edges] .navigate-right{top:50%;right:.8em;margin-top:-1.8em}.reveal .controls[data-controls-layout=edges] .navigate-up{top:.8em;left:50%;margin-left:-1.8em}.reveal .controls[data-controls-layout=edges] .navigate-down{bottom:-.3em;left:50%;margin-left:-1.8em}}.reveal .progress{position:absolute;display:none;height:3px;width:100%;bottom:0;left:0;z-index:10;background-color:rgba(0,0,0,.2);color:#fff}.reveal .progress:after{content:'';display:block;position:absolute;height:10px;width:100%;top:-10px}.reveal .progress span{display:block;height:100%;width:100%;background-color:currentColor;transition:transform .8s cubic-bezier(.26,.86,.44,.985);transform-origin:0 0;transform:scaleX(0)}.reveal .slide-number{position:absolute;display:block;right:8px;bottom:8px;z-index:31;font-family:Helvetica,sans-serif;font-size:12px;line-height:1;color:#fff;background-color:rgba(0,0,0,.4);padding:5px}.reveal .slide-number a{color:currentColor}.reveal .slide-number-delimiter{margin:0 3px}.reveal{position:relative;width:100%;height:100%;overflow:hidden;touch-action:pinch-zoom}.reveal.embedded{touch-action:pan-y}.reveal .slides{position:absolute;width:100%;height:100%;top:0;right:0;bottom:0;left:0;margin:auto;pointer-events:none;overflow:visible;z-index:1;text-align:center;perspective:600px;perspective-origin:50% 40%}.reveal .slides>section{perspective:600px}.reveal .slides>section,.reveal .slides>section>section{display:none;position:absolute;width:100%;pointer-events:auto;z-index:10;transform-style:flat;transition:transform-origin .8s cubic-bezier(.26,.86,.44,.985),transform .8s cubic-bezier(.26,.86,.44,.985),visibility .8s cubic-bezier(.26,.86,.44,.985),opacity .8s cubic-bezier(.26,.86,.44,.985)}.reveal[data-transition-speed=fast] .slides section{transition-duration:.4s}.reveal[data-transition-speed=slow] .slides section{transition-duration:1.2s}.reveal .slides section[data-transition-speed=fast]{transition-duration:.4s}.reveal .slides section[data-transition-speed=slow]{transition-duration:1.2s}.reveal .slides>section.stack{padding-top:0;padding-bottom:0;pointer-events:none;height:100%}.reveal .slides>section.present,.reveal .slides>section>section.present{display:block;z-index:11;opacity:1}.reveal .slides>section:empty,.reveal .slides>section>section:empty,.reveal .slides>section>section[data-background-interactive],.reveal .slides>section[data-background-interactive]{pointer-events:none}.reveal.center,.reveal.center .slides,.reveal.center .slides section{min-height:0!important}.reveal .slides>section:not(.present),.reveal .slides>section>section:not(.present){pointer-events:none}.reveal.overview .slides>section,.reveal.overview .slides>section>section{pointer-events:auto}.reveal .slides>section.future,.reveal .slides>section.past,.reveal .slides>section>section.future,.reveal .slides>section>section.past{opacity:0}.reveal.slide section{-webkit-backface-visibility:hidden;backface-visibility:hidden}.reveal .slides>section[data-transition=slide].past,.reveal .slides>section[data-transition~=slide-out].past,.reveal.slide .slides>section:not([data-transition]).past{transform:translate(-150%,0)}.reveal .slides>section[data-transition=slide].future,.reveal .slides>section[data-transition~=slide-in].future,.reveal.slide .slides>section:not([data-transition]).future{transform:translate(150%,0)}.reveal .slides>section>section[data-transition=slide].past,.reveal .slides>section>section[data-transition~=slide-out].past,.reveal.slide .slides>section>section:not([data-transition]).past{transform:translate(0,-150%)}.reveal .slides>section>section[data-transition=slide].future,.reveal .slides>section>section[data-transition~=slide-in].future,.reveal.slide .slides>section>section:not([data-transition]).future{transform:translate(0,150%)}.reveal.linear section{-webkit-backface-visibility:hidden;backface-visibility:hidden}.reveal .slides>section[data-transition=linear].past,.reveal .slides>section[data-transition~=linear-out].past,.reveal.linear .slides>section:not([data-transition]).past{transform:translate(-150%,0)}.reveal .slides>section[data-transition=linear].future,.reveal .slides>section[data-transition~=linear-in].future,.reveal.linear .slides>section:not([data-transition]).future{transform:translate(150%,0)}.reveal .slides>section>section[data-transition=linear].past,.reveal .slides>section>section[data-transition~=linear-out].past,.reveal.linear .slides>section>section:not([data-transition]).past{transform:translate(0,-150%)}.reveal .slides>section>section[data-transition=linear].future,.reveal .slides>section>section[data-transition~=linear-in].future,.reveal.linear .slides>section>section:not([data-transition]).future{transform:translate(0,150%)}.reveal .slides section[data-transition=default].stack,.reveal.default .slides section.stack{transform-style:preserve-3d}.reveal .slides>section[data-transition=default].past,.reveal .slides>section[data-transition~=default-out].past,.reveal.default .slides>section:not([data-transition]).past{transform:translate3d(-100%,0,0) rotateY(-90deg) translate3d(-100%,0,0)}.reveal .slides>section[data-transition=default].future,.reveal .slides>section[data-transition~=default-in].future,.reveal.default .slides>section:not([data-transition]).future{transform:translate3d(100%,0,0) rotateY(90deg) translate3d(100%,0,0)}.reveal .slides>section>section[data-transition=default].past,.reveal .slides>section>section[data-transition~=default-out].past,.reveal.default .slides>section>section:not([data-transition]).past{transform:translate3d(0,-300px,0) rotateX(70deg) translate3d(0,-300px,0)}.reveal .slides>section>section[data-transition=default].future,.reveal .slides>section>section[data-transition~=default-in].future,.reveal.default .slides>section>section:not([data-transition]).future{transform:translate3d(0,300px,0) rotateX(-70deg) translate3d(0,300px,0)}.reveal .slides section[data-transition=convex].stack,.reveal.convex .slides section.stack{transform-style:preserve-3d}.reveal .slides>section[data-transition=convex].past,.reveal .slides>section[data-transition~=convex-out].past,.reveal.convex .slides>section:not([data-transition]).past{transform:translate3d(-100%,0,0) rotateY(-90deg) translate3d(-100%,0,0)}.reveal .slides>section[data-transition=convex].future,.reveal .slides>section[data-transition~=convex-in].future,.reveal.convex .slides>section:not([data-transition]).future{transform:translate3d(100%,0,0) rotateY(90deg) translate3d(100%,0,0)}.reveal .slides>section>section[data-transition=convex].past,.reveal .slides>section>section[data-transition~=convex-out].past,.reveal.convex .slides>section>section:not([data-transition]).past{transform:translate3d(0,-300px,0) rotateX(70deg) translate3d(0,-300px,0)}.reveal .slides>section>section[data-transition=convex].future,.reveal .slides>section>section[data-transition~=convex-in].future,.reveal.convex .slides>section>section:not([data-transition]).future{transform:translate3d(0,300px,0) rotateX(-70deg) translate3d(0,300px,0)}.reveal .slides section[data-transition=concave].stack,.reveal.concave .slides section.stack{transform-style:preserve-3d}.reveal .slides>section[data-transition=concave].past,.reveal .slides>section[data-transition~=concave-out].past,.reveal.concave .slides>section:not([data-transition]).past{transform:translate3d(-100%,0,0) rotateY(90deg) translate3d(-100%,0,0)}.reveal .slides>section[data-transition=concave].future,.reveal .slides>section[data-transition~=concave-in].future,.reveal.concave .slides>section:not([data-transition]).future{transform:translate3d(100%,0,0) rotateY(-90deg) translate3d(100%,0,0)}.reveal .slides>section>section[data-transition=concave].past,.reveal .slides>section>section[data-transition~=concave-out].past,.reveal.concave .slides>section>section:not([data-transition]).past{transform:translate3d(0,-80%,0) rotateX(-70deg) translate3d(0,-80%,0)}.reveal .slides>section>section[data-transition=concave].future,.reveal .slides>section>section[data-transition~=concave-in].future,.reveal.concave .slides>section>section:not([data-transition]).future{transform:translate3d(0,80%,0) rotateX(70deg) translate3d(0,80%,0)}.reveal .slides section[data-transition=zoom],.reveal.zoom .slides section:not([data-transition]){transition-timing-function:ease}.reveal .slides>section[data-transition=zoom].past,.reveal .slides>section[data-transition~=zoom-out].past,.reveal.zoom .slides>section:not([data-transition]).past{visibility:hidden;transform:scale(16)}.reveal .slides>section[data-transition=zoom].future,.reveal .slides>section[data-transition~=zoom-in].future,.reveal.zoom .slides>section:not([data-transition]).future{visibility:hidden;transform:scale(.2)}.reveal .slides>section>section[data-transition=zoom].past,.reveal .slides>section>section[data-transition~=zoom-out].past,.reveal.zoom .slides>section>section:not([data-transition]).past{transform:scale(16)}.reveal .slides>section>section[data-transition=zoom].future,.reveal .slides>section>section[data-transition~=zoom-in].future,.reveal.zoom .slides>section>section:not([data-transition]).future{transform:scale(.2)}.reveal.cube .slides{perspective:1300px}.reveal.cube .slides section{padding:30px;min-height:700px;-webkit-backface-visibility:hidden;backface-visibility:hidden;box-sizing:border-box;transform-style:preserve-3d}.reveal.center.cube .slides section{min-height:0}.reveal.cube .slides section:not(.stack):before{content:'';position:absolute;display:block;width:100%;height:100%;left:0;top:0;background:rgba(0,0,0,.1);border-radius:4px;transform:translateZ(-20px)}.reveal.cube .slides section:not(.stack):after{content:'';position:absolute;display:block;width:90%;height:30px;left:5%;bottom:0;background:0 0;z-index:1;border-radius:4px;box-shadow:0 95px 25px rgba(0,0,0,.2);transform:translateZ(-90px) rotateX(65deg)}.reveal.cube .slides>section.stack{padding:0;background:0 0}.reveal.cube .slides>section.past{transform-origin:100% 0;transform:translate3d(-100%,0,0) rotateY(-90deg)}.reveal.cube .slides>section.future{transform-origin:0 0;transform:translate3d(100%,0,0) rotateY(90deg)}.reveal.cube .slides>section>section.past{transform-origin:0 100%;transform:translate3d(0,-100%,0) rotateX(90deg)}.reveal.cube .slides>section>section.future{transform-origin:0 0;transform:translate3d(0,100%,0) rotateX(-90deg)}.reveal.page .slides{perspective-origin:0 50%;perspective:3000px}.reveal.page .slides section{padding:30px;min-height:700px;box-sizing:border-box;transform-style:preserve-3d}.reveal.page .slides section.past{z-index:12}.reveal.page .slides section:not(.stack):before{content:'';position:absolute;display:block;width:100%;height:100%;left:0;top:0;background:rgba(0,0,0,.1);transform:translateZ(-20px)}.reveal.page .slides section:not(.stack):after{content:'';position:absolute;display:block;width:90%;height:30px;left:5%;bottom:0;background:0 0;z-index:1;border-radius:4px;box-shadow:0 95px 25px rgba(0,0,0,.2);-webkit-transform:translateZ(-90px) rotateX(65deg)}.reveal.page .slides>section.stack{padding:0;background:0 0}.reveal.page .slides>section.past{transform-origin:0 0;transform:translate3d(-40%,0,0) rotateY(-80deg)}.reveal.page .slides>section.future{transform-origin:100% 0;transform:translate3d(0,0,0)}.reveal.page .slides>section>section.past{transform-origin:0 0;transform:translate3d(0,-40%,0) rotateX(80deg)}.reveal.page .slides>section>section.future{transform-origin:0 100%;transform:translate3d(0,0,0)}.reveal .slides section[data-transition=fade],.reveal.fade .slides section:not([data-transition]),.reveal.fade .slides>section>section:not([data-transition]){transform:none;transition:opacity .5s}.reveal.fade.overview .slides section,.reveal.fade.overview .slides>section>section{transition:none}.reveal .slides section[data-transition=none],.reveal.none .slides section:not([data-transition]){transform:none;transition:none}.reveal .pause-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:#000;visibility:hidden;opacity:0;z-index:100;transition:all 1s ease}.reveal .pause-overlay .resume-button{position:absolute;bottom:20px;right:20px;color:#ccc;border-radius:2px;padding:6px 14px;border:2px solid #ccc;font-size:16px;background:0 0;cursor:pointer}.reveal .pause-overlay .resume-button:hover{color:#fff;border-color:#fff}.reveal.paused .pause-overlay{visibility:visible;opacity:1}.reveal .no-transition,.reveal .no-transition *,.reveal .slides.disable-slide-transitions section{transition:none!important}.reveal .slides.disable-slide-transitions section{transform:none!important}.reveal .backgrounds{position:absolute;width:100%;height:100%;top:0;left:0;perspective:600px}.reveal .slide-background{display:none;position:absolute;width:100%;height:100%;opacity:0;visibility:hidden;overflow:hidden;background-color:rgba(0,0,0,0);transition:all .8s cubic-bezier(.26,.86,.44,.985)}.reveal .slide-background-content{position:absolute;width:100%;height:100%;background-position:50% 50%;background-repeat:no-repeat;background-size:cover}.reveal .slide-background.stack{display:block}.reveal .slide-background.present{opacity:1;visibility:visible;z-index:2}.print-pdf .reveal .slide-background{opacity:1!important;visibility:visible!important}.reveal .slide-background video{position:absolute;width:100%;height:100%;max-width:none;max-height:none;top:0;left:0;-o-object-fit:cover;object-fit:cover}.reveal .slide-background[data-background-size=contain] video{-o-object-fit:contain;object-fit:contain}.reveal>.backgrounds .slide-background[data-background-transition=none],.reveal[data-background-transition=none]>.backgrounds .slide-background{transition:none}.reveal>.backgrounds .slide-background[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background{opacity:1;-webkit-backface-visibility:hidden;backface-visibility:hidden}.reveal>.backgrounds .slide-background.past[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background.past{transform:translate(-100%,0)}.reveal>.backgrounds .slide-background.future[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background.future{transform:translate(100%,0)}.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background>.slide-background.past{transform:translate(0,-100%)}.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background>.slide-background.future{transform:translate(0,100%)}.reveal>.backgrounds .slide-background.past[data-background-transition=convex],.reveal[data-background-transition=convex]>.backgrounds .slide-background.past{opacity:0;transform:translate3d(-100%,0,0) rotateY(-90deg) translate3d(-100%,0,0)}.reveal>.backgrounds .slide-background.future[data-background-transition=convex],.reveal[data-background-transition=convex]>.backgrounds .slide-background.future{opacity:0;transform:translate3d(100%,0,0) rotateY(90deg) translate3d(100%,0,0)}.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=convex],.reveal[data-background-transition=convex]>.backgrounds .slide-background>.slide-background.past{opacity:0;transform:translate3d(0,-100%,0) rotateX(90deg) translate3d(0,-100%,0)}.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=convex],.reveal[data-background-transition=convex]>.backgrounds .slide-background>.slide-background.future{opacity:0;transform:translate3d(0,100%,0) rotateX(-90deg) translate3d(0,100%,0)}.reveal>.backgrounds .slide-background.past[data-background-transition=concave],.reveal[data-background-transition=concave]>.backgrounds .slide-background.past{opacity:0;transform:translate3d(-100%,0,0) rotateY(90deg) translate3d(-100%,0,0)}.reveal>.backgrounds .slide-background.future[data-background-transition=concave],.reveal[data-background-transition=concave]>.backgrounds .slide-background.future{opacity:0;transform:translate3d(100%,0,0) rotateY(-90deg) translate3d(100%,0,0)}.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=concave],.reveal[data-background-transition=concave]>.backgrounds .slide-background>.slide-background.past{opacity:0;transform:translate3d(0,-100%,0) rotateX(-90deg) translate3d(0,-100%,0)}.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=concave],.reveal[data-background-transition=concave]>.backgrounds .slide-background>.slide-background.future{opacity:0;transform:translate3d(0,100%,0) rotateX(90deg) translate3d(0,100%,0)}.reveal>.backgrounds .slide-background[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background{transition-timing-function:ease}.reveal>.backgrounds .slide-background.past[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background.past{opacity:0;visibility:hidden;transform:scale(16)}.reveal>.backgrounds .slide-background.future[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background.future{opacity:0;visibility:hidden;transform:scale(.2)}.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background>.slide-background.past{opacity:0;visibility:hidden;transform:scale(16)}.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background>.slide-background.future{opacity:0;visibility:hidden;transform:scale(.2)}.reveal[data-transition-speed=fast]>.backgrounds .slide-background{transition-duration:.4s}.reveal[data-transition-speed=slow]>.backgrounds .slide-background{transition-duration:1.2s}.reveal [data-auto-animate-target^=unmatched]{will-change:opacity}.reveal section[data-auto-animate]:not(.stack):not([data-auto-animate=running]) [data-auto-animate-target^=unmatched]{opacity:0}.reveal.overview{perspective-origin:50% 50%;perspective:700px}.reveal.overview .slides{-moz-transform-style:preserve-3d}.reveal.overview .slides section{height:100%;top:0!important;opacity:1!important;overflow:hidden;visibility:visible!important;cursor:pointer;box-sizing:border-box}.reveal.overview .slides section.present,.reveal.overview .slides section:hover{outline:10px solid rgba(150,150,150,.4);outline-offset:10px}.reveal.overview .slides section .fragment{opacity:1;transition:none}.reveal.overview .slides section:after,.reveal.overview .slides section:before{display:none!important}.reveal.overview .slides>section.stack{padding:0;top:0!important;background:0 0;outline:0;overflow:visible}.reveal.overview .backgrounds{perspective:inherit;-moz-transform-style:preserve-3d}.reveal.overview .backgrounds .slide-background{opacity:1;visibility:visible;outline:10px solid rgba(150,150,150,.1);outline-offset:10px}.reveal.overview .backgrounds .slide-background.stack{overflow:visible}.reveal.overview .slides section,.reveal.overview-deactivating .slides section{transition:none}.reveal.overview .backgrounds .slide-background,.reveal.overview-deactivating .backgrounds .slide-background{transition:none}.reveal.rtl .slides,.reveal.rtl .slides h1,.reveal.rtl .slides h2,.reveal.rtl .slides h3,.reveal.rtl .slides h4,.reveal.rtl .slides h5,.reveal.rtl .slides h6{direction:rtl;font-family:sans-serif}.reveal.rtl code,.reveal.rtl pre{direction:ltr}.reveal.rtl ol,.reveal.rtl ul{text-align:right}.reveal.rtl .progress span{transform-origin:100% 0}.reveal.has-parallax-background .backgrounds{transition:all .8s ease}.reveal.has-parallax-background[data-transition-speed=fast] .backgrounds{transition-duration:.4s}.reveal.has-parallax-background[data-transition-speed=slow] .backgrounds{transition-duration:1.2s}.reveal>.overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1000;background:rgba(0,0,0,.9);transition:all .3s ease}.reveal>.overlay .spinner{position:absolute;display:block;top:50%;left:50%;width:32px;height:32px;margin:-16px 0 0 -16px;z-index:10;background-image:url(data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf%2F%2F%2F6%2Bvr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs%2FLy8vz8%2FAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ%2FV%2FnmOM82XiHRLYKhKP1oZmADdEAAAh%2BQQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY%2FCZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB%2BA4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6%2BHo7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq%2BB6QDtuetcaBPnW6%2BO7wDHpIiK9SaVK5GgV543tzjgGcghAgAh%2BQQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK%2B%2BG%2Bw48edZPK%2BM6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE%2BG%2BcD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm%2BFNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk%2BaV%2BoJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0%2FVNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc%2BXiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30%2FiI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE%2FjiuL04RGEBgwWhShRgQExHBAAh%2BQQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR%2BipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY%2BYip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd%2BMFCN6HAAIKgNggY0KtEBAAh%2BQQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1%2BvsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d%2BjYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg%2BygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0%2Bbm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h%2BKr0SJ8MFihpNbx%2B4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX%2BBP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA%3D%3D);visibility:visible;opacity:.6;transition:all .3s ease}.reveal>.overlay header{position:absolute;left:0;top:0;width:100%;padding:5px;z-index:2;box-sizing:border-box}.reveal>.overlay header a{display:inline-block;width:40px;height:40px;line-height:36px;padding:0 10px;float:right;opacity:.6;box-sizing:border-box}.reveal>.overlay header a:hover{opacity:1}.reveal>.overlay header a .icon{display:inline-block;width:20px;height:20px;background-position:50% 50%;background-size:100%;background-repeat:no-repeat}.reveal>.overlay header a.close .icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABkklEQVRYR8WX4VHDMAxG6wnoJrABZQPYBCaBTWAD2g1gE5gg6OOsXuxIlr40d81dfrSJ9V4c2VLK7spHuTJ/5wpM07QXuXc5X0opX2tEJcadjHuV80li/FgxTIEK/5QBCICBD6xEhSMGHgQPgBgLiYVAB1dpSqKDawxTohFw4JSEA3clzgIBPCURwE2JucBR7rhPJJv5OpJwDX+SfDjgx1wACQeJG1aChP9K/IMmdZ8DtESV1WyP3Bt4MwM6sj4NMxMYiqUWHQu4KYA/SYkIjOsm3BXYWMKFDwU2khjCQ4ELJUJ4SmClRArOCmSXGuKma0fYD5CbzHxFpCSGAhfAVSSUGDUk2BWZaff2g6GE15BsBQ9nwmpIGDiyHQddwNTMKkbZaf9fajXQca1EX44puJZUsnY0ObGmITE3GVLCbEhQUjGVt146j6oasWN+49Vph2w1pZ5EansNZqKBm1txbU57iRRcZ86RWMDdWtBJUHBHwoQPi1GV+JCbntmvok7iTX4/Up9mgyTc/FJYDTcndgH/AA5A/CHsyEkVAAAAAElFTkSuQmCC)}.reveal>.overlay header a.external .icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAcElEQVRYR+2WSQoAIQwEzf8f7XiOMkUQxUPlGkM3hVmiQfQR9GYnH1SsAQlI4DiBqkCMoNb9y2e90IAEJPAcgdznU9+engMaeJ7Azh5Y1U67gAho4DqBqmB1buAf0MB1AlVBek83ZPkmJMGc1wAR+AAqod/B97TRpQAAAABJRU5ErkJggg==)}.reveal>.overlay .viewport{position:absolute;display:flex;top:50px;right:0;bottom:0;left:0}.reveal>.overlay.overlay-preview .viewport iframe{width:100%;height:100%;max-width:100%;max-height:100%;border:0;opacity:0;visibility:hidden;transition:all .3s ease}.reveal>.overlay.overlay-preview.loaded .viewport iframe{opacity:1;visibility:visible}.reveal>.overlay.overlay-preview.loaded .viewport-inner{position:absolute;z-index:-1;left:0;top:45%;width:100%;text-align:center;letter-spacing:normal}.reveal>.overlay.overlay-preview .x-frame-error{opacity:0;transition:opacity .3s ease .3s}.reveal>.overlay.overlay-preview.loaded .x-frame-error{opacity:1}.reveal>.overlay.overlay-preview.loaded .spinner{opacity:0;visibility:hidden;transform:scale(.2)}.reveal>.overlay.overlay-help .viewport{overflow:auto;color:#fff}.reveal>.overlay.overlay-help .viewport .viewport-inner{width:600px;margin:auto;padding:20px 20px 80px 20px;text-align:center;letter-spacing:normal}.reveal>.overlay.overlay-help .viewport .viewport-inner .title{font-size:20px}.reveal>.overlay.overlay-help .viewport .viewport-inner table{border:1px solid #fff;border-collapse:collapse;font-size:16px}.reveal>.overlay.overlay-help .viewport .viewport-inner table td,.reveal>.overlay.overlay-help .viewport .viewport-inner table th{width:200px;padding:14px;border:1px solid #fff;vertical-align:middle}.reveal>.overlay.overlay-help .viewport .viewport-inner table th{padding-top:20px;padding-bottom:20px}.reveal .playback{position:absolute;left:15px;bottom:20px;z-index:30;cursor:pointer;transition:all .4s ease;-webkit-tap-highlight-color:transparent}.reveal.overview .playback{opacity:0;visibility:hidden}.reveal .hljs{min-height:100%}.reveal .hljs table{margin:initial}.reveal .hljs-ln-code,.reveal .hljs-ln-numbers{padding:0;border:0}.reveal .hljs-ln-numbers{opacity:.6;padding-right:.75em;text-align:right;vertical-align:top}.reveal .hljs.has-highlights tr:not(.highlight-line){opacity:.4}.reveal .hljs:not(:first-child).fragment{position:absolute;top:0;left:0;width:100%;box-sizing:border-box}.reveal pre[data-auto-animate-target]{overflow:hidden}.reveal pre[data-auto-animate-target] code{height:100%}.reveal .roll{display:inline-block;line-height:1.2;overflow:hidden;vertical-align:top;perspective:400px;perspective-origin:50% 50%}.reveal .roll:hover{background:0 0;text-shadow:none}.reveal .roll span{display:block;position:relative;padding:0 2px;pointer-events:none;transition:all .4s ease;transform-origin:50% 0;transform-style:preserve-3d;-webkit-backface-visibility:hidden;backface-visibility:hidden}.reveal .roll:hover span{background:rgba(0,0,0,.5);transform:translate3d(0,0,-45px) rotateX(90deg)}.reveal .roll span:after{content:attr(data-title);display:block;position:absolute;left:0;top:0;padding:0 2px;-webkit-backface-visibility:hidden;backface-visibility:hidden;transform-origin:50% 0;transform:translate3d(0,110%,0) rotateX(-90deg)}.reveal aside.notes{display:none}.reveal .speaker-notes{display:none;position:absolute;width:33.33333%;height:100%;top:0;left:100%;padding:14px 18px 14px 18px;z-index:1;font-size:18px;line-height:1.4;border:1px solid rgba(0,0,0,.05);color:#222;background-color:#f5f5f5;overflow:auto;box-sizing:border-box;text-align:left;font-family:Helvetica,sans-serif;-webkit-overflow-scrolling:touch}.reveal .speaker-notes .notes-placeholder{color:#ccc;font-style:italic}.reveal .speaker-notes:focus{outline:0}.reveal .speaker-notes:before{content:'Speaker notes';display:block;margin-bottom:10px;opacity:.5}.reveal.show-notes{max-width:75%;overflow:visible}.reveal.show-notes .speaker-notes{display:block}@media screen and (min-width:1600px){.reveal .speaker-notes{font-size:20px}}@media screen and (max-width:1024px){.reveal.show-notes{border-left:0;max-width:none;max-height:70%;max-height:70vh;overflow:visible}.reveal.show-notes .speaker-notes{top:100%;left:0;width:100%;height:42.85714%;height:30vh;border:0}}@media screen and (max-width:600px){.reveal.show-notes{max-height:60%;max-height:60vh}.reveal.show-notes .speaker-notes{top:100%;height:66.66667%;height:40vh}.reveal .speaker-notes{font-size:14px}}.zoomed .reveal *,.zoomed .reveal :after,.zoomed .reveal :before{-webkit-backface-visibility:visible!important;backface-visibility:visible!important}.zoomed .reveal .controls,.zoomed .reveal .progress{opacity:0}.zoomed .reveal .roll span{background:0 0}.zoomed .reveal .roll span:after{visibility:hidden}html.print-pdf *{-webkit-print-color-adjust:exact}html.print-pdf{width:100%;height:100%;overflow:visible}html.print-pdf body{margin:0 auto!important;border:0;padding:0;float:none!important;overflow:visible}html.print-pdf .nestedarrow,html.print-pdf .reveal .controls,html.print-pdf .reveal .playback,html.print-pdf .reveal .progress,html.print-pdf .reveal.overview,html.print-pdf .state-background{display:none!important}html.print-pdf .reveal pre code{overflow:hidden!important;font-family:Courier,'Courier New',monospace!important}html.print-pdf .reveal{width:auto!important;height:auto!important;overflow:hidden!important}html.print-pdf .reveal .slides{position:static;width:100%!important;height:auto!important;zoom:1!important;pointer-events:initial;left:auto;top:auto;margin:0!important;padding:0!important;overflow:visible;display:block;perspective:none;perspective-origin:50% 50%}html.print-pdf .reveal .slides .subsection,html.print-pdf .reveal .slides .subsubsection{padding:15rem!important}html.print-pdf .reveal .slides .pdf-page{position:relative;overflow:hidden;z-index:1;page-break-after:always}html.print-pdf .reveal .slides section{visibility:visible!important;display:block!important;position:absolute!important;margin:0!important;padding:0!important;box-sizing:border-box!important;min-height:1px;opacity:1!important;transform-style:flat!important;transform:none!important}html.print-pdf .reveal section.stack{position:relative!important;margin:0!important;padding:0!important;page-break-after:avoid!important;height:auto!important;min-height:auto!important}html.print-pdf .reveal img{box-shadow:none}html.print-pdf .reveal .backgrounds{display:none}html.print-pdf .reveal .slide-background{display:block!important;position:absolute;top:0;left:0;width:100%;height:100%;z-index:auto!important}html.print-pdf .reveal.show-notes{max-width:none;max-height:none}html.print-pdf .reveal .speaker-notes-pdf{display:block;width:100%;height:auto;max-height:none;top:auto;right:auto;bottom:auto;left:auto;z-index:100}html.print-pdf .reveal .speaker-notes-pdf[data-layout=separate-page]{position:relative;color:inherit;background-color:transparent;padding:20px;page-break-after:always;border:0}html.print-pdf .reveal .slide-number-pdf{display:block;position:absolute;font-size:14px}html.print-pdf .aria-status{display:none}@media print{html:not(.print-pdf){background:#fff;width:auto;height:auto;overflow:visible}html:not(.print-pdf) body{background:#fff;font-size:20pt;width:auto;height:auto;border:0;margin:0 5%;padding:0;overflow:visible;float:none!important}html:not(.print-pdf) .controls,html:not(.print-pdf) .fork-reveal,html:not(.print-pdf) .nestedarrow,html:not(.print-pdf) .reveal .backgrounds,html:not(.print-pdf) .reveal .progress,html:not(.print-pdf) .reveal .slide-number,html:not(.print-pdf) .share-reveal,html:not(.print-pdf) .state-background{display:none!important}html:not(.print-pdf) body,html:not(.print-pdf) li,html:not(.print-pdf) p,html:not(.print-pdf) td{font-size:20pt!important;color:#000}html:not(.print-pdf) h1,html:not(.print-pdf) h2,html:not(.print-pdf) h3,html:not(.print-pdf) h4,html:not(.print-pdf) h5,html:not(.print-pdf) h6{color:#000!important;height:auto;line-height:normal;text-align:left;letter-spacing:normal}html:not(.print-pdf) h1{font-size:28pt!important}html:not(.print-pdf) h2{font-size:24pt!important}html:not(.print-pdf) h3{font-size:22pt!important}html:not(.print-pdf) h4{font-size:22pt!important;font-variant:small-caps}html:not(.print-pdf) h5{font-size:21pt!important}html:not(.print-pdf) h6{font-size:20pt!important;font-style:italic}html:not(.print-pdf) a:link,html:not(.print-pdf) a:visited{color:#000!important;font-weight:700;text-decoration:underline}html:not(.print-pdf) div,html:not(.print-pdf) ol,html:not(.print-pdf) p,html:not(.print-pdf) ul{visibility:visible;position:static;width:auto;height:auto;display:block;overflow:visible;margin:0;text-align:left!important}html:not(.print-pdf) .reveal pre,html:not(.print-pdf) .reveal table{margin-left:0;margin-right:0}html:not(.print-pdf) .reveal pre code{padding:20px}html:not(.print-pdf) .reveal blockquote{margin:20px 0}html:not(.print-pdf) .reveal .slides{position:static!important;width:auto!important;height:auto!important;left:0!important;top:0!important;margin-left:0!important;margin-top:0!important;padding:0!important;zoom:1!important;transform:none!important;overflow:visible!important;display:block!important;text-align:left!important;perspective:none;perspective-origin:50% 50%}html:not(.print-pdf) .reveal .slides section{visibility:visible!important;position:static!important;width:auto!important;height:auto!important;display:block!important;overflow:visible!important;left:0!important;top:0!important;margin-left:0!important;margin-top:0!important;padding:60px 20px!important;z-index:auto!important;opacity:1!important;page-break-after:always!important;transform-style:flat!important;transform:none!important;transition:none!important}html:not(.print-pdf) .reveal .slides section.stack{padding:0!important}html:not(.print-pdf) .reveal section:last-of-type{page-break-after:avoid!important}html:not(.print-pdf) .reveal section .fragment{opacity:1!important;visibility:visible!important;transform:none!important}html:not(.print-pdf) .reveal section img{display:block;margin:15px 0;background:#fff;border:1px solid #666;box-shadow:none}html:not(.print-pdf) .reveal section small{font-size:.8em}html:not(.print-pdf) .reveal .hljs{max-height:100%;white-space:pre-wrap;word-wrap:break-word;word-break:break-word;font-size:15pt}html:not(.print-pdf) .reveal .hljs .hljs-ln-numbers{white-space:nowrap}html:not(.print-pdf) .reveal .hljs td{font-size:inherit!important;color:inherit!important}} -------------------------------------------------------------------------------- /slides/assets/theme/components.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Styles taken from https://tailwindcss.com/ */ 3 | --box-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 4 | 0 2px 4px -1px rgba(0, 0, 0, 0.06); 5 | --box-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 6 | 0 4px 6px -2px rgba(0, 0, 0, 0.05); 7 | 8 | --pink-100: #fff5f7; 9 | --pink-200: #fed7e2; 10 | --pink-300: #fbb6ce; 11 | --pink-400: #f687b3; 12 | --pink-500: #ed64a6; 13 | --pink-600: #d53f8c; 14 | --pink-700: #b83280; 15 | --gray-100: #f7fafc; 16 | --gray-200: #edf2f7; 17 | --gray-300: #e2e8f0; 18 | --gray-400: #cbd5e0; 19 | --gray-500: #a0aec0; 20 | --gray-600: #718096; 21 | --gray-700: #4a5568; 22 | --gray-800: #2d3748; 23 | --gray-900: #1a202c; 24 | --black: #000; 25 | --red-100: #fff5f5; 26 | --red-200: #fed7d7; 27 | --red-300: #feb2b2; 28 | --red-400: #fc8181; 29 | --red-500: #f56565; 30 | --red-600: #e53e3e; 31 | --red-700: #c53030; 32 | --orange-100: #fffaf0; 33 | --orange-200: #feebc8; 34 | --orange-300: #fbd38d; 35 | --orange-400: #f6ad55; 36 | --orange-500: #ed8936; 37 | --orange-600: #dd6b20; 38 | --orange-700: #c05621; 39 | --orange-800: #9c4221; 40 | --orange-900: #7b341e; 41 | --green-100: #f0fff4; 42 | --green-200: #c6f6d5; 43 | --green-300: #9ae6b4; 44 | --green-400: #68d391; 45 | --green-500: #48bb78; 46 | --green-600: #38a169; 47 | --green-700: #2f855a; 48 | --teal-100: #e6fffa; 49 | --teal-200: #b2f5ea; 50 | --teal-300: #81e6d9; 51 | --teal-400: #4fd1c5; 52 | --teal-500: #38b2ac; 53 | --teal-600: #319795; 54 | --teal-700: #2c7a7b; 55 | --blue-100: #ebf8ff; 56 | --blue-200: #bee3f8; 57 | --blue-300: #90cdf4; 58 | --blue-400: #63b3ed; 59 | --blue-500: #4299e1; 60 | --blue-600: #3182ce; 61 | --blue-700: #2b6cb0; 62 | --blue-800: #2c5282; 63 | --blue-900: #2a4365; 64 | --indigo-100: #ebf4ff; 65 | --indigo-200: #c3dafe; 66 | --indigo-300: #a3bffa; 67 | --indigo-400: #7f9cf5; 68 | --indigo-500: #667eea; 69 | --indigo-600: #5a67d8; 70 | --indigo-700: #4c51bf; 71 | --indigo-800: #434190; 72 | --indigo-900: #3c366b; 73 | --purple-100: #faf5ff; 74 | --purple-200: #e9d8fd; 75 | --purple-300: #d6bcfa; 76 | --purple-400: #b794f4; 77 | --purple-500: #9f7aea; 78 | --purple-600: #805ad5; 79 | --purple-700: #6b46c1; 80 | 81 | --spacer-xxxs: 0.125rem; 82 | --spacer-xxs: 0.25rem; 83 | --spacer-xs: 0.5rem; 84 | --spacer-sm: 0.75rem; 85 | --spacer-base: 1.25rem; 86 | --spacer-md: 2rem; 87 | --spacer-lg: 3.25rem; 88 | --spacer-xl: 5.25rem; 89 | 90 | --example-mobile-width: 20rem; 91 | --flex-wrapper-gap: 5rem; 92 | --box-example-font-size: 1.125em; 93 | } 94 | 95 | .intro { 96 | margin-top: 3em; /* Magic Number */ 97 | display: grid; 98 | grid-template-areas: 99 | "innoq avatar name" 100 | "innoq avatar position"; 101 | grid-template-columns: 1fr auto auto; 102 | align-items: center; 103 | justify-items: start; 104 | } 105 | 106 | .intro > a { 107 | grid-area: innoq; 108 | } 109 | .intro > img { 110 | grid-area: avatar; 111 | border-radius: 50%; 112 | } 113 | .intro > small:nth-of-type(1) { 114 | align-self: end; 115 | } 116 | .intro > small:nth-of-type(2) { 117 | align-self: start; 118 | } 119 | 120 | pre { 121 | -moz-tab-size: 4; 122 | tab-size: 4; 123 | } 124 | 125 | .tip-wrapper { 126 | width: min-content; 127 | /* margin: auto; */ 128 | } 129 | .tip-wrapper .symbol { 130 | margin: 0; 131 | line-height: 0; 132 | text-align: left; 133 | } 134 | .tip-wrapper .symbol.ok { 135 | color: var(--green-600); 136 | } 137 | .tip-wrapper .symbol.ko { 138 | color: var(--red-600); 139 | } 140 | .reveal .tip-wrapper pre { 141 | width: min-content; 142 | } 143 | 144 | .responsive-example { 145 | font-size: 0.5em; 146 | } 147 | 148 | .flex-wrapper { 149 | justify-content: center; 150 | margin-bottom: 0; 151 | gap: var(--spacer-sm); 152 | } 153 | 154 | .flex-wrapper pre { 155 | margin: 0; 156 | width: 100%; 157 | } 158 | 159 | .reveal .mascot { 160 | /* height: 10em; */ 161 | max-width: var(--mascot-width, 3em); 162 | max-height: 10em; 163 | margin-top: -5em; 164 | margin-bottom: -5em; 165 | position: absolute; 166 | left: var(--mascot-left); 167 | top: var(--mascot-top); 168 | right: var(--mascot-right); 169 | } 170 | 171 | .reveal .mascot.centered { 172 | left: 50%; 173 | transform: translateX(-50%); 174 | } 175 | 176 | div.responsive-web-design { 177 | background-image: url("/slides/assets/images/responsive-mascot.svg"); 178 | background-repeat: no-repeat; 179 | background-size: 2em 4em; 180 | background-position: left 1rem bottom; 181 | } 182 | 183 | .first-slide div.responsive-web-design, 184 | .first-slide div.accessible-web-design { 185 | background-image: none; 186 | } 187 | 188 | h2::before { 189 | content: var(--slide-width); 190 | } 191 | 192 | div.accessible-web-design { 193 | background-image: url("/slides/assets/images/accessible-mascot.svg"); 194 | background-repeat: no-repeat; 195 | background-size: 1em 1em; 196 | background-position: right 1rem bottom 7rem; 197 | } 198 | 199 | @media (min-width: 900px) { 200 | div.responsive-web-design { 201 | background-size: 5em 8em; 202 | } 203 | 204 | div.accessible-web-design { 205 | background-size: 3.5em 3.5em; 206 | } 207 | 208 | .reveal .controls { 209 | right: 2.25rem; 210 | } 211 | } 212 | 213 | @media (min-width: 1700px) { 214 | div.accessible-web-design { 215 | background-size: 6em 6em; 216 | } 217 | 218 | .reveal .controls { 219 | right: 5.5rem; 220 | } 221 | } 222 | 223 | .cost-analysis { 224 | display: flex; 225 | flex-direction: column; 226 | justify-content: flex-end; 227 | max-width: 20rem; 228 | } 229 | .cost-analysis > div > span { 230 | font-size: 0.4em; 231 | display: block; 232 | line-height: 1; 233 | font-weight: 300; 234 | text-transform: uppercase; 235 | letter-spacing: 1px; 236 | } 237 | .cost-analysis > figcaption { 238 | font-size: 0.5em; 239 | height: 3rem; 240 | margin-top: var(--spacer-xs); 241 | } 242 | 243 | .testing { 244 | background-color: var(--pink-100); 245 | padding: var(--spacer-sm); 246 | border: 3px dashed var(--pink-300); 247 | margin-bottom: -3px; 248 | z-index: 2; 249 | } 250 | 251 | .developing { 252 | display: grid; 253 | padding: var(--spacer-sm); 254 | justify-items: center; 255 | background-color: var(--green-100); 256 | border: 3px dashed var(--green-300); 257 | } 258 | 259 | .redeveloping { 260 | display: grid; 261 | padding: var(--spacer-sm); 262 | justify-items: center; 263 | background-color: var(--blue-100); 264 | border: 3px dashed var(--blue-300); 265 | margin-bottom: -3px; 266 | z-index: 1; 267 | } 268 | 269 | .developing > div, 270 | .redeveloping > div { 271 | display: grid; 272 | grid-template-columns: 3rem 3rem 3rem 3rem 3rem; 273 | width: max-content; 274 | } 275 | .developing > div::before, 276 | .redeveloping > div::before { 277 | display: block; 278 | content: ""; 279 | } 280 | .developing > div > :nth-child(4), 281 | .developing > div > :nth-child(5), 282 | .developing > div > :nth-child(6), 283 | .developing > div > :nth-child(7), 284 | .redeveloping > div > :nth-child(3), 285 | .redeveloping > div > :nth-child(4), 286 | .redeveloping > div > :nth-child(5), 287 | .redeveloping > div > :nth-child(6) { 288 | grid-row: 2; 289 | transform: translateX(50%); 290 | margin-top: calc(-1 * var(--spacer-sm)); 291 | } 292 | .developing > div > :nth-child(8), 293 | .developing > div > :nth-child(9), 294 | .developing > div > :nth-child(10), 295 | .developing > div > :nth-child(11), 296 | .developing > div > :nth-child(12), 297 | .redeveloping > div > :nth-child(7), 298 | .redeveloping > div > :nth-child(8), 299 | .redeveloping > div > :nth-child(9), 300 | .redeveloping > div > :nth-child(10), 301 | .redeveloping > div > :nth-child(11) { 302 | grid-row: 3; 303 | margin-top: calc(-1 * var(--spacer-sm)); 304 | } 305 | -------------------------------------------------------------------------------- /slides/assets/theme/custom-theme.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | section.has-dark-background, 4 | section.has-dark-background h1, 5 | section.has-dark-background h2, 6 | section.has-dark-background h3, 7 | section.has-dark-background h4, 8 | section.has-dark-background h5, 9 | section.has-dark-background h6 { 10 | color: #fff; 11 | } 12 | 13 | /********************************************* 14 | * GLOBAL STYLES 15 | *********************************************/ 16 | :root { 17 | --background-color: #fff; 18 | --main-font: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif; 19 | --main-font-size: 42px; 20 | --main-color: #004153; 21 | --block-margin: 20px; 22 | --heading-margin: 0 0 20px 0; 23 | --heading-font: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", 24 | sans-serif; 25 | --heading-color: #004153; 26 | --heading-line-height: 1.2; 27 | --heading-letter-spacing: normal; 28 | --heading-text-transform: none; 29 | --heading-text-shadow: none; 30 | --heading-font-weight: 600; 31 | --heading1-text-shadow: none; 32 | --heading1-size: 2em; 33 | --heading2-size: 1.6em; 34 | --heading3-size: 1.3em; 35 | --heading4-size: 1em; 36 | --code-font: monospace; 37 | --link-color: #ff9c66; 38 | --link-color-hover: #ffb58c; 39 | --selection-background-color: #40707e; 40 | --selection-color: #fff; 41 | } 42 | 43 | .reveal-viewport { 44 | background: #fff; 45 | background-color: var(--background-color); 46 | } 47 | 48 | .reveal { 49 | font-family: var(--main-font); 50 | font-size: 42px; 51 | font-weight: normal; 52 | color: #004153; 53 | } 54 | 55 | .reveal ::selection { 56 | color: #fff; 57 | background: #40707e; 58 | text-shadow: none; 59 | } 60 | 61 | .reveal ::-moz-selection { 62 | color: #fff; 63 | background: #40707e; 64 | text-shadow: none; 65 | } 66 | 67 | .reveal .slides section, 68 | .reveal .slides section > section { 69 | line-height: 1.3; 70 | font-weight: inherit; 71 | } 72 | 73 | /********************************************* 74 | * HEADERS 75 | *********************************************/ 76 | .reveal h1, 77 | .reveal h2, 78 | .reveal h3, 79 | .reveal h4, 80 | .reveal h5, 81 | .reveal h6 { 82 | margin: 0 0 20px 0; 83 | color: #004052; 84 | font-family: var(--heading-font); 85 | font-weight: 600; 86 | line-height: 1.2; 87 | letter-spacing: normal; 88 | text-transform: none; 89 | text-shadow: none; 90 | word-wrap: break-word; 91 | } 92 | 93 | .reveal h1 { 94 | font-size: 2em; 95 | } 96 | 97 | .reveal h2 { 98 | font-size: 1.6em; 99 | } 100 | 101 | .reveal h3 { 102 | font-size: 1.3em; 103 | } 104 | 105 | .reveal h4 { 106 | font-size: 1em; 107 | } 108 | 109 | .reveal h1 { 110 | text-shadow: none; 111 | } 112 | 113 | /********************************************* 114 | * OTHER 115 | *********************************************/ 116 | .reveal p { 117 | margin: 20px 0; 118 | line-height: 1.3; 119 | } 120 | 121 | /* Remove trailing margins after titles */ 122 | .reveal h1:last-child, 123 | .reveal h2:last-child, 124 | .reveal h3:last-child, 125 | .reveal h4:last-child, 126 | .reveal h5:last-child, 127 | .reveal h6:last-child { 128 | margin-bottom: 0; 129 | } 130 | 131 | /* Ensure certain elements are never larger than the slide itself */ 132 | .reveal img, 133 | .reveal video, 134 | .reveal iframe { 135 | max-width: 95%; 136 | max-height: 95%; 137 | } 138 | 139 | .reveal strong, 140 | .reveal b { 141 | font-weight: bold; 142 | } 143 | 144 | .reveal em { 145 | font-style: italic; 146 | } 147 | 148 | .reveal ol, 149 | .reveal dl, 150 | .reveal ul { 151 | display: inline-block; 152 | text-align: left; 153 | margin: 0 0 0 1em; 154 | } 155 | 156 | .reveal ol { 157 | list-style-type: decimal; 158 | } 159 | 160 | .reveal ul { 161 | list-style-type: disc; 162 | } 163 | 164 | .reveal ul ul { 165 | list-style-type: square; 166 | } 167 | 168 | .reveal ul ul ul { 169 | list-style-type: circle; 170 | } 171 | 172 | .reveal ul ul, 173 | .reveal ul ol, 174 | .reveal ol ol, 175 | .reveal ol ul { 176 | display: block; 177 | margin-left: 40px; 178 | } 179 | 180 | .reveal dt { 181 | font-weight: bold; 182 | } 183 | 184 | .reveal dd { 185 | margin-left: 40px; 186 | } 187 | 188 | .reveal blockquote { 189 | display: block; 190 | position: relative; 191 | width: 70%; 192 | margin: 20px auto; 193 | padding: 5px; 194 | font-style: italic; 195 | background: rgba(255, 255, 255, 0.05); 196 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); 197 | } 198 | 199 | .reveal blockquote p:first-child, 200 | .reveal blockquote p:last-child { 201 | display: inline-block; 202 | } 203 | 204 | .reveal q { 205 | font-style: italic; 206 | } 207 | 208 | .reveal pre { 209 | display: block; 210 | position: relative; 211 | width: 90%; 212 | margin: 20px auto; 213 | text-align: left; 214 | font-size: 0.55em; 215 | font-family: monospace; 216 | line-height: 1.2em; 217 | word-wrap: break-word; 218 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); 219 | } 220 | 221 | .reveal code { 222 | font-family: monospace; 223 | text-transform: none; 224 | } 225 | 226 | .reveal pre code { 227 | display: block; 228 | padding: 5px; 229 | overflow: auto; 230 | max-height: 400px; 231 | word-wrap: normal; 232 | } 233 | 234 | .reveal table { 235 | margin: auto; 236 | border-collapse: collapse; 237 | border-spacing: 0; 238 | } 239 | 240 | .reveal table th { 241 | font-weight: bold; 242 | } 243 | 244 | .reveal table th, 245 | .reveal table td { 246 | text-align: left; 247 | padding: 0.2em 0.5em 0.2em 0.5em; 248 | border-bottom: 1px solid; 249 | } 250 | 251 | .reveal table th[align="center"], 252 | .reveal table td[align="center"] { 253 | text-align: center; 254 | } 255 | 256 | .reveal table th[align="right"], 257 | .reveal table td[align="right"] { 258 | text-align: right; 259 | } 260 | 261 | .reveal table tbody tr:last-child th, 262 | .reveal table tbody tr:last-child td { 263 | border-bottom: none; 264 | } 265 | 266 | .reveal sup { 267 | vertical-align: super; 268 | font-size: smaller; 269 | } 270 | 271 | .reveal sub { 272 | vertical-align: sub; 273 | font-size: smaller; 274 | } 275 | 276 | .reveal small { 277 | display: inline-block; 278 | font-size: 0.6em; 279 | line-height: 1.2em; 280 | vertical-align: top; 281 | } 282 | 283 | .reveal small * { 284 | vertical-align: top; 285 | } 286 | 287 | .reveal img { 288 | margin: 20px 0; 289 | } 290 | 291 | /********************************************* 292 | * LINKS 293 | *********************************************/ 294 | .reveal a { 295 | color: var(--link-color); 296 | text-decoration: none; 297 | transition: color 0.15s ease; 298 | } 299 | 300 | .reveal a:hover { 301 | color: var(--link-color-hover); 302 | text-shadow: none; 303 | border: none; 304 | } 305 | 306 | .reveal .roll span:after { 307 | color: #fff; 308 | background: #1a53a1; 309 | } 310 | 311 | /********************************************* 312 | * Frame helper 313 | *********************************************/ 314 | .reveal .r-frame { 315 | border: 4px solid #24244c; 316 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); 317 | } 318 | 319 | .reveal a .r-frame { 320 | transition: all 0.15s linear; 321 | } 322 | 323 | .reveal a:hover .r-frame { 324 | border-color: #40707e; 325 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); 326 | } 327 | 328 | /********************************************* 329 | * NAVIGATION CONTROLS 330 | *********************************************/ 331 | .reveal .controls { 332 | color: #40707e; 333 | } 334 | 335 | /********************************************* 336 | * PROGRESS BAR 337 | *********************************************/ 338 | .reveal .progress { 339 | background: rgba(0, 0, 0, 0.2); 340 | color: #40707e; 341 | } 342 | 343 | /********************************************* 344 | * PRINT BACKGROUND 345 | *********************************************/ 346 | @media print { 347 | .backgrounds { 348 | background-color: var(--background-color); 349 | } 350 | } 351 | 352 | .next-to-each-other { 353 | --gap: 1em; 354 | display: inline-flex; 355 | gap: var(--gap); 356 | } 357 | 358 | .arrow-before > * + * { 359 | display: flex; 360 | align-items: center; 361 | } 362 | 363 | .arrow-before > * + ::before { 364 | content: "➡️"; 365 | margin-right: 1em; 366 | } 367 | 368 | .squishy-text { 369 | word-break: break-word; 370 | word-wrap: break-word; 371 | overflow-wrap: anywhere; 372 | -webkit-hyphens: auto; 373 | -ms-hyphens: auto; 374 | hyphens: auto; 375 | } 376 | 377 | .visually-hidden { 378 | clip: rect(0 0 0 0); 379 | clip-path: inset(50%); 380 | height: 1px; 381 | overflow: hidden; 382 | position: absolute; 383 | white-space: nowrap; 384 | width: 1px; 385 | } 386 | -------------------------------------------------------------------------------- /slides/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Responsible Web Apps 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |

Responsible Web Applications

24 | 25 | 26 | 27 |
28 | INNOQ 29 | 30 | Picture of Joy Heron 31 | Joy Heron 32 | Senior Consultant 33 |
34 |
35 |
36 |
37 |
38 |

Responsive Web Design

39 | 40 | 41 | 49 |
50 |
51 |

Joy’s First Law of Web Development

52 |

There is no such thing as a non-responsive web application

53 | 56 |
57 |
58 |
59 |
60 |
61 | 62 | 63 | Cost of testing responsive & accessible behavior 64 | is shown here as with two money bags. 65 | 66 |
67 |
68 | 73 | 74 | Cost of developing features 75 | is shown here as twelve money bags. 76 | 77 |
78 |
Cost of creating a responsive, accessible app
79 |
80 |
81 |
82 | 83 | 84 | Cost of testing responsive & accessible behavior 85 | is shown here as two money bags. 86 | 87 |
88 |
89 | 94 | 95 | Cost of redoing features which don't work on mobile 96 | is shown here as eleven stacks of money flying away. 97 | 98 |
99 |
100 | 105 | 106 | Cost of developing features 107 | is shown here as twelve money bags. 108 | 109 |
110 |
Cost of creating a non-responsive app and adding mobile and accessibility later
111 |
112 |
113 |
114 |
115 |

Joy’s Second Law of Web Development

116 |

Any work you do now to ensure that your web application behaves responsively WILL be appreciated in the future.

117 |
118 |
119 |

Responsive

120 |
=
121 |
Responsive Layout Containers
122 |
+
123 |
Squishy Components
124 | 125 | 133 | 134 |
135 |
136 |

Responsive Layout Containers

137 |

138 | We firstly need to make sure that we put a lot of thought into designing layout containers which adjust themselves based on the size of our viewport. 139 |

140 |
141 |
142 |

Explicit CSS Grid Layout with Breakpoints

143 | 151 |
152 | 191 |
192 | A UI example showing how content will be stacked vertically on smaller devices, 193 | but on larger viewports we can then use the increased horizontal space to display 194 | certain content areas next to each other. 195 |

196 | The demo shows five content areas: Header, Sidebar, Main Content, Second Sidebar, and Footer. 197 | On a mobile device, these areas are shown vertically stacked. 198 | On larger viewports, the example layout now has three rows and three columns. 199 | The Header area spans the full width of the top of the viewport in the first row. 200 | In the middle row, the Sidebar, Main Content, and Second Sidebar areas are positioned 201 | next to each other in a three column layout, with the Main Content area expanding 202 | to take up as much space as it can. The Footer area then spans the full width of 203 | the viewport in the bottom row of the layout. 204 |

205 |
206 |
207 |
208 |
209 |

Explicit CSS Grid Layout with Breakpoints

210 | 211 |
CSS for Mobile Devices
212 | 213 |
.layout {
 214 | 	display: grid;
 215 | 	grid-template-areas:
 216 | 		"header"
 217 | 		"sidebar"
 218 | 		"main"
 219 | 		"sidebar-right"
 220 | 		"footer";
 221 | 	grid-template-rows: auto auto 1fr auto auto;
 222 | }
 223 | .layout > header {
 224 | 	grid-area: header;
 225 | }
 226 | .layout > aside:nth-of-type(1) {
 227 | 	grid-area: sidebar;
 228 | }
 229 | .layout > main {
 230 | 	grid-area: main;
 231 | }
 232 | .layout > aside:nth-of-type(2) {
 233 | 	grid-area: sidebar-right;
 234 | }
 235 | .layout > footer {
 236 | 	grid-area: footer;
 237 | }
 238 | 
239 | 240 | 255 |
256 |
257 |
CSS for tablets or larger devices
258 | 259 |
@media (min-width: 40rem) { /* Tablet portrait up */
 260 | 	.layout {
 261 | 		grid-template-areas:
 262 | 			"header header header"
 263 | 			"sidebar main sidebar-right"
 264 | 			"footer footer footer";
 265 | 		grid-template-rows: auto 1fr auto;
 266 | 		grid-template-columns: 20% 1fr 20%;
 267 | 	}
 268 | }
 269 | 
270 | 271 | 272 | 273 | 274 | 275 | 276 | Correct way to do CSS breakpoints 277 | 278 | 279 | 280 | 289 |
290 |
291 |

Squishy Components

292 | 293 | 307 |
308 |
309 |

Flexbox + Flex Wrap

310 | 311 |
.container {
 312 | 	display: flex;
 313 | 	flex-wrap: wrap;
 314 | }
 315 | 
316 | 317 | 328 | 329 |
330 | 369 |
370 | An example showing how we can use flex-wrap to wrap content in a flexbox. 371 |

372 | The demo shows a flexbox containing two blocks: a block containing a title and a description, 373 | and a block containing a price, time, and amount. 374 | On a larger devices, these block can be positioned next to each other horizontally. 375 | On smaller devices with flex-wrap activated, the second block containing the price, time and amount 376 | will wrap onto the next line and be positioned underneath the first block. 377 |

378 |
379 |
380 |
381 |
382 |

Nested Flexboxes

383 | 390 | 391 |
392 | 412 |
413 | An example highlighting the items in the inner flexbox that can also wrap. 414 |

415 | The demo shows the same demo as the one in the previous example, but in this instance, 416 | the price, time, and amount items are highlighted because they are also contained within 417 | an inner flexbox. This adds an extra layer to the responsiveness: when the viewport gets 418 | so small that there is no longer space for these three items to be positioned next to each 419 | other horizontally, they will begin to wrap as well. 420 |

421 |
422 |
423 |
424 |
425 |

Intrinsic Grid

426 |
.container {
 427 | 	display: grid;
 428 | 	grid-template-columns:
 429 | 		repeat(auto-fill, minmax(var(—col-width), auto));
 430 | }
 431 | 
432 | 440 | 441 |
442 | 463 |
464 | An example of an intrinsic grid which fits as many columns as possible within the viewport. 465 | 466 |

467 | The demo shows a grid with 6 different content blocks that are positioned within an intrinsic CSS grid. 468 | On mobile devices, there is only enough space for a single column, so the blocks are all positioned within 469 | this column. When there is enough space for two columns, the layout will shift and the elements will be 470 | positioned within the grid in two columns and end up filling three rows. 471 | When there is enough space for three columns, 472 | the layout will shift again and the elements will be positioned within the grid in three columns and will 473 | end up filling two rows. This layout pattern will continue, and a grid will always be generated with 474 | as many columns as fit within the viewport size. 475 |

476 |
477 |
478 |
479 |
480 |

Horizontal Scrolling

481 | 482 |
.horizontal-scroll {
 483 | 	overflow-x: auto;
 484 | }
 485 | 
486 | 487 | 499 | 500 |
501 |
504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 |
ABCDEFGHIJKLMNOPQRST
--------------------
553 | 554 |
Scroll horizontally to see the whole table
555 |
Use the lower right corner to resize the table and see it respond to change
556 | 557 |
558 | An example showing how to use a container with horizontal scrolling to make larger 559 | content available on smaller viewports. 560 |

561 | In the example, a large table with twenty columns is shown. On smaller devices, there is 562 | not enough space for the whole table to be shown. In this case, the large table is shown 563 | within a container that fits within the viewport, and the user can scroll within that 564 | container in order to view all of the contents. 565 |

566 |
567 | 568 | 569 |
570 |

Squishy Text

571 | 572 |
.squishy-text {
 573 | 	word-break: break-word; /* Samsung browser */
 574 | 	word-wrap: break-word; /* IE 11 */
 575 | 	overflow-wrap: anywhere;
 576 | 	-webkit-hyphens: auto;
 577 | 	-ms-hyphens: auto;
 578 | 	hyphens: auto;
 579 | }
 580 | 
581 | 582 | 594 | 595 |
596 |
597 | CSS is awesome 598 |
599 |
600 | CSS is awesome 601 |
602 |
603 |
604 |
605 |

Container Queries

606 | ‼️ Coming soon to a browser near you ‼️ ⚠️ Currently only available in Chrome behind a flag 607 | 608 |
609 | 632 |
633 | An example showing how to use container queries. 634 |

635 | In the example, on small devices, the image is shown above the heading and text. 636 |

637 |
638 |
639 |
640 |
641 |

CSS for Container Queries

642 |
.container {
 643 |   contain: layout style inline-size;
 644 | }
 645 | .card {
 646 | 	/* Default Styles */
 647 | }
 648 | @container (min-width: 30rem) {
 649 | 	/* Styles for larger contexts */
 650 | }
 651 | @container (min-width: 50rem) {
 652 | 	/* Styles for even larger contexts */
 653 | }
 654 | 
655 | 656 | Demo of Container Queries from Una Kravets 657 | 658 |
659 |
660 |

CSS for targeting types of devices

661 |
662 |
663 |
@media (hover: none) {
 664 | 	...
 665 | }
 666 | 
667 |

Target devices without hover

668 |
669 |
670 |
@media (pointer: coarse) {
 671 | 	...
 672 | }
 673 | 
674 |

Target devices with a pointed device of limited accuracy (e.g. a finger)

675 |
676 |
677 | 678 |
679 | 680 |
681 |
682 |

Accessible Web Design

683 | 684 | 685 | 693 |
694 |
695 |

Headings

696 |

Don’t skip heading levels.

697 | 698 | 699 |
700 |
701 |

702 | 705 | The following HTML is correct 706 |

707 |

Heading Level 1

708 |

Heading Level 2

709 |

...

710 |

Heading Level 3

711 |

...

712 |

Heading Level 3

713 |
714 |
715 | 716 |
717 |

718 | 721 | The following HTML is incorrect 722 |

723 |

Heading Level 3

724 |

...

725 |

Heading Level 3

726 |

Heading Level 1

727 |
Heading Level 5
728 |
729 |
730 |
731 | 732 |

Do NOT choose heading based on visual size.

733 | 745 |
746 |
747 |

Landmarks

748 | 749 |
750 |
751 |

752 | 755 | The following HTML is uses landmarks correctly 756 |

757 |
<body>
 758 | 	
Header Area
759 |
Main Area
760 | 761 |
Footer Area
762 | </body> 763 |
764 |
765 |
766 | 767 |

Use elements like main and 768 | header to get 769 | landmarks out of the box.

770 | 776 |
777 |
778 | 779 |

780 | Wrap navigational links in a nav 781 |

782 | 783 |
784 |
785 |

786 | 789 | The following HTML is uses the nav element correctly 790 |

791 |

 796 | 
797 |
798 | 799 |
800 |

801 | 804 | The following HTML is uses the nav element correctly 805 |

806 |

 813 | 
814 | Additionally wrap links in a list for more context 815 |
816 |
817 |
818 |
819 |

label Element

820 |

821 | Always correctly add labels to 822 | input fields! 823 |

824 | 825 |
826 |
827 |

828 | 831 | The following HTML is uses the label element correctly 832 |

833 |
<label for="name">
 834 | 	First Name
 835 | </label>
 836 | <input id="name"
 837 | 	type="text"
 838 | 	value="name"
 839 | 	placeholder="Jane Smith">
 840 | 
841 |
842 |
843 |

844 | 847 | The following HTML is uses the label element correctly 848 |

849 |
<label>
 850 | 	First Name
 851 | 	<input type="text"
 852 | 		value="name"
 853 | 		placeholder="Jane Smith">
 854 | </label>
 855 | 
856 |
857 |
858 | 859 | 872 |
873 |
874 |

List Elements

875 | 876 |
877 |
878 |

879 | 882 | The following HTML is uses the an unordered list element correctly 883 |

884 |
    885 |
  • ...
  • 886 |
  • ...
  • 887 |
  • ...
  • 888 |
889 |
890 |
891 |

892 | 895 | The following HTML is uses an ordered list element correctly 896 |

897 |
    898 |
  1. ...
  2. 899 |
  3. ...
  4. 900 |
  5. ...
  6. 901 |
902 |
903 |
904 |
905 |

906 | 909 | The following HTML is uses a description list element correctly 910 |

911 |
912 |
Term 1
913 |
...
914 |
Term 2
915 |
...
916 |
Term 3
917 |
...
918 |
919 |
920 |
921 |
922 | 923 | 937 |
938 |
939 |

Use buttons for interactive elements which do not navigate anywhere

940 | 941 |
942 |
943 |

944 | 947 | The following HTML is uses a button correctly 948 |

949 |
<button>Click me!</button>
 950 | 
951 |
952 |
953 |

954 | 957 | The following HTML is not a semantically correct button 958 |

959 |
Click me!
960 |
961 |
962 |
963 | 964 |

965 | Adding ARIA only tells assistive technology that it should be a button. 966 | It doesn't actually implement button behavior. 967 |

968 |
969 |
970 |

First Rule of Aria

971 |
972 | If you can use a native HTML element or attribute with the semantics and behavior 973 | you require already built in, instead of re-purposing an element and adding an 974 | ARIA role, state or property to make it accessible, then do so. 975 |
976 |
977 |
978 |

Use anchors for interactive elements which navigate somewhere

979 | 980 |
981 |
982 |

983 | 986 | The following HTML is uses an anchor element correctly 987 |

988 |
<a href="..." >Go here!</a>
989 |
990 |
991 |

992 | 995 | The following HTML is not a semantically correct anchor 996 |

997 |
Go here!
998 |
999 |
1000 |

1001 | 1004 | The following HTML is not a semantically correct anchor 1005 |

1006 |
<button>Go here!</button>
1007 | Buttons should not be used to navigate! 1008 |
1009 |
1010 | 1011 |
1012 |
1013 |

Collapsing/Expanding Content

1014 | 1015 |

1016 | For >IE11 use details element. 1017 |

1018 | 1019 |
1020 |
1021 |

1022 | 1025 | The following HTML is uses a details element correctly 1026 |

1027 |
<details>
1028 | 	<summary>Toggle Button</summary>
1029 | 	Content which will be expanded/collapsed
1030 | 	when clicking the summary element
1031 | </details>
1032 | 
1033 |
1034 |
1035 | 1036 |
1037 | Toggle Button 1038 | Content which will be expanded/collapsed when clicking the summary element 1039 |
1040 | 1041 |
1042 |
1043 |

Collapsing/Expanding Content

1044 | 1068 | 1069 |
<button is="toggle-button" data-target="#section2"
1070 | 		aria-expanded="false" hidden>Toggle Section 2</button>
1071 | 
1072 | 1073 |

aria-expanded is an attribute of the BUTTON telling assistive technologies if the content area is currently visible or not.

1074 |
1075 |
1076 |

1077 | Again: aria-expanded only tells assistive technology that it should collapse content. 1078 |

1079 |

You need JavaScript to add the actual functionality.

1080 | 1081 |
class ToggleButton extends HTMLButtonElement {
1082 | 	connectedCallback () {
1083 | 		this.removeAttribute("hidden");
1084 | 		if (this.getAttribute("aria-expanded") !== "true") {
1085 | 			this.setAttribute("aria-expanded", "false");
1086 | 			this.target.classList.add("hide");
1087 | 		}
1088 | 		this.addEventListener("click", this.toggle.bind(this));
1089 | 	}
1090 | 
1091 | 	toggle () {
1092 | 		let classList = this.target.classList;
1093 | 		if (classList.contains("hide")) {
1094 | 			classList.remove("hide");
1095 | 			this.setAttribute("aria-expanded", "true");
1096 | 		} else {
1097 | 			classList.add("hide");
1098 | 			this.setAttribute("aria-expanded", "false");
1099 | 		}
1100 | 	}
1101 | 
1102 | 	get target () {
1103 | 		return document.querySelector(this.getAttribute("data-target"));
1104 | 	}
1105 | }
1106 | customElements.define("toggle-button", ToggleButton, { extends: "button" });
1107 | 
1108 |
1109 |
1110 | 1111 |
1112 | Link to Demo 1113 |
1114 |
1115 |
1116 |

aria-label and aria-labelledby

1117 |

Add textual descriptions of an element

1118 | 1119 | 1135 | 1136 | 1137 |
1138 |
1139 |

1140 | 1143 | The following HTML uses aria-label correctly 1144 |

1145 |
<table
1146 | 	aria-label="Label for table">
1147 | ...
1148 | </table>
1149 | 
1150 |
1151 |
1152 |

1153 | 1156 | The following HTML uses aria-label correctly 1157 |

1158 |
<span id="info">
1159 | 	Label for table
1160 | <span>
1161 | <table aria-labelledby="info">
1162 | ...
1163 | </table>
1164 | 
1165 |
1166 |
1167 | 1168 |
1169 |
1170 |

Hiding content visually (but not from screenreaders)

1171 | 1172 |
.visually-hidden {
1173 | 	clip: rect(0 0 0 0);
1174 | 	clip-path: inset(50%);
1175 | 	height: 1px;
1176 | 	overflow: hidden;
1177 | 	position: absolute;
1178 | 	white-space: nowrap;
1179 | 	width: 1px;
1180 | }
1181 | 
1182 | 1183 | 1194 |
1195 |
1196 |

Hiding content from assistive technologies

1197 | 1198 |
1199 |
1200 |
<svg aria-hidden="true">
1201 | 	This icon is only decorative
1202 | <svg>
1203 | 
1204 |
1205 |
1206 | 1207 | 1210 |
1211 |
1212 |

Descriptive alt text for images

1213 | 1214 |
1215 |
1216 |
<img src="..." alt="A duck looking sad">
1217 |
1218 |
1219 | 1220 |

1221 | An alt-text should provide the same information conveyed visually. 1222 |

1223 | Alt-texts guide 1224 | 1225 |
1226 |
1227 |

Adding clear focus styles

1228 |

Never add remove focus styles without providing a good alternative.

1229 | 1242 |
1243 |
1244 |

Setting focus correctly

1245 |

If we dynamically add content, we need to think about the focus and move it as necessary

1246 | 1247 | 1268 |
1269 |
1270 |

ARIA live regions

1271 | Inform assistive technologies of dynamic changes to the content by add notifications 1272 | to a region with an 1273 | aria-live 1274 | attribute. 1275 | 1276 |
1277 |
1278 |
<div role="status" aria-live="polite">
1279 | 	An informative message
1280 | </div>
1281 |
1282 |
1283 | 1284 |

1285 | Incorporate these messages into your visual design to improve usability for all users! 1286 |

1287 |
1288 |
1289 |

Make it possible to disable animations

1290 |

Some people can react physically to animations (e.g. getting a seizure or migraine).

1291 | 1292 |
@media (prefers-reduced-motion: no-preference) {
1293 | 	/* Animation code comes here */
1294 | }
1295 | 
1296 | 1297 |
1298 |
1299 | 1300 |
1301 |
1302 |

Thank you very much!

1303 |

Any questions?

1304 | https://responsibleweb.app 1305 | 1306 | 1307 |
1308 |
1309 | 1310 | 1311 | 1312 | 1319 | 1320 | 1321 | 1322 | 1323 | 1326 | 1336 | 1337 | 1338 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Styles taken from https://tailwindcss.com/ */ 3 | --box-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 4 | 0 2px 4px -1px rgba(0, 0, 0, 0.06); 5 | --box-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 6 | 0 4px 6px -2px rgba(0, 0, 0, 0.05); 7 | 8 | --pink-100: #fff5f7; 9 | --pink-200: #fed7e2; 10 | --pink-300: #fbb6ce; 11 | --pink-400: #f687b3; 12 | --pink-500: #ed64a6; 13 | --pink-600: #d53f8c; 14 | --pink-700: #b83280; 15 | --gray-100: #f7fafc; 16 | --gray-200: #edf2f7; 17 | --gray-300: #e2e8f0; 18 | --gray-400: #cbd5e0; 19 | --gray-500: #a0aec0; 20 | --gray-600: #718096; 21 | --gray-700: #4a5568; 22 | --gray-800: #2d3748; 23 | --gray-900: #1a202c; 24 | --black: #000; 25 | --red-100: #fff5f5; 26 | --red-200: #fed7d7; 27 | --red-300: #feb2b2; 28 | --red-400: #fc8181; 29 | --red-500: #f56565; 30 | --red-600: #e53e3e; 31 | --red-700: #c53030; 32 | --orange-100: #fffaf0; 33 | --orange-200: #feebc8; 34 | --orange-300: #fbd38d; 35 | --orange-400: #f6ad55; 36 | --orange-500: #ed8936; 37 | --orange-600: #dd6b20; 38 | --orange-700: #c05621; 39 | --orange-800: #9c4221; 40 | --orange-900: #7b341e; 41 | --green-100: #f0fff4; 42 | --green-200: #c6f6d5; 43 | --green-300: #9ae6b4; 44 | --green-400: #68d391; 45 | --green-500: #48bb78; 46 | --green-600: #38a169; 47 | --green-700: #2f855a; 48 | --teal-100: #e6fffa; 49 | --teal-200: #b2f5ea; 50 | --teal-300: #81e6d9; 51 | --teal-400: #4fd1c5; 52 | --teal-500: #38b2ac; 53 | --teal-600: #319795; 54 | --teal-700: #2c7a7b; 55 | --blue-100: #ebf8ff; 56 | --blue-200: #bee3f8; 57 | --blue-300: #90cdf4; 58 | --blue-400: #63b3ed; 59 | --blue-500: #4299e1; 60 | --blue-600: #3182ce; 61 | --blue-700: #2b6cb0; 62 | --blue-800: #2c5282; 63 | --blue-900: #2a4365; 64 | --indigo-100: #ebf4ff; 65 | --indigo-200: #c3dafe; 66 | --indigo-300: #a3bffa; 67 | --indigo-400: #7f9cf5; 68 | --indigo-500: #667eea; 69 | --indigo-600: #5a67d8; 70 | --indigo-700: #4c51bf; 71 | --indigo-800: #434190; 72 | --indigo-900: #3c366b; 73 | --purple-100: #faf5ff; 74 | --purple-200: #e9d8fd; 75 | --purple-300: #d6bcfa; 76 | --purple-400: #b794f4; 77 | --purple-500: #9f7aea; 78 | --purple-600: #805ad5; 79 | --purple-700: #6b46c1; 80 | 81 | --spacer-xxxs: 0.125rem; 82 | --spacer-xxs: 0.25rem; 83 | --spacer-xs: 0.5rem; 84 | --spacer-sm: 0.75rem; 85 | --spacer-base: 1.25rem; 86 | --spacer-md: 2rem; 87 | --spacer-lg: 3.25rem; 88 | --spacer-xl: 5.25rem; 89 | 90 | --default-container-width: 50rem; 91 | --container-width: var(--default-container-width); 92 | 93 | --body-padding-x: var(--spacer-base); 94 | 95 | --font-family-sans-bold: FFMarkWebProBold, "Helvetica Neue", Helvetica, 96 | Arial, sans-serif; 97 | --font-family-sans: FFMarkWebProBook, "Helvetica Neue", Helvetica, Arial, 98 | sans-serif; 99 | --font-family-serif: FreightTextPro, Georgia, Times, "Times New Roman", 100 | serif; 101 | --font-family-mono: HackRegular, "Monaco", "Courier New", monospace; 102 | } 103 | 104 | h1, 105 | h2, 106 | h3, 107 | h4, 108 | h5, 109 | h6 { 110 | font-family: var(--font-family-sans-bold); 111 | } 112 | body { 113 | font-family: var(--font-family-serif); 114 | color: var(--gray-900); 115 | line-height: 1.6; 116 | margin: 0; 117 | font-size: 1.125rem; 118 | } 119 | body > header { 120 | padding: var(--spacer-base) var(--body-padding-x) 0; 121 | background-image: radial-gradient( 122 | circle at 50% -20%, 123 | var(--pink-100) 37%, 124 | #fff5f700 37.5% 125 | ), 126 | radial-gradient(circle at 30% 40%, var(--purple-100) 7%, #ebf4ff00 7.5%), 127 | radial-gradient( 128 | circle at 75% 50%, 129 | var(--purple-100) 10%, 130 | #ebf4ff00 10.5% 131 | ); 132 | background-repeat: no-repeat; 133 | font-family: var(--font-family-sans); 134 | } 135 | main > section { 136 | padding: var(--spacer-lg) var(--body-padding-x) var(--spacer-xl); 137 | margin: 0; 138 | } 139 | main > section:first-of-type { 140 | padding-top: 0; 141 | } 142 | header, 143 | main > section > * { 144 | max-width: var(--container-width); 145 | margin-left: auto; 146 | margin-right: auto; 147 | } 148 | * { 149 | margin-bottom: 0; 150 | } 151 | *, 152 | *::before, 153 | *::after { 154 | box-sizing: border-box; 155 | } 156 | * + * { 157 | margin-top: var(--spacer-sm); 158 | } 159 | h1, 160 | h2, 161 | h3, 162 | h4, 163 | h5, 164 | h6 { 165 | line-height: 1.15; 166 | } 167 | h2, 168 | h3, 169 | h4 { 170 | display: flex; 171 | } 172 | h2 a, 173 | h3 a, 174 | h4 a { 175 | margin-left: var(--spacer-xxs); 176 | } 177 | h2 a::before, 178 | h3 a::before, 179 | h4 a::before { 180 | display: block; 181 | content: "#"; 182 | } 183 | h2 { 184 | margin-top: var(--spacer-base); 185 | } 186 | h3, 187 | h4, 188 | h5 { 189 | margin-top: var(--spacer-md); 190 | } 191 | h5 + *, 192 | h6 + * { 193 | margin-top: var(--spacer-sm); 194 | } 195 | ol, 196 | ul { 197 | margin-top: var(--spacer-base); 198 | padding-left: var(--spacer-base); 199 | } 200 | 201 | pre, 202 | p code { 203 | font-family: var(--font-family-mono); 204 | } 205 | p code { 206 | display: inline-block; 207 | background-color: var(--gray-100); 208 | padding: 0 var(--spacer-xxs); 209 | margin-top: var(--spacer-xxxs); 210 | } 211 | 212 | pre { 213 | overflow: auto; 214 | padding: var(--spacer-sm) var(--body-padding-x); 215 | background-color: var(--gray-100); 216 | line-height: 1.25; 217 | box-shadow: var(--box-shadow-lg); 218 | } 219 | 220 | *:focus { 221 | padding: var(--spacer-xxs); 222 | outline: 2px dashed var(--blue-500); 223 | } 224 | 225 | .visually-hidden { 226 | max-height: 0; 227 | font-size: 0; 228 | margin: 0; 229 | } 230 | 231 | a { 232 | color: var(--blue-700); 233 | text-decoration-color: var(--blue-200); 234 | } 235 | a:hover, 236 | a:focus { 237 | color: var(--blue-500); 238 | text-decoration-color: var(--blue-300); 239 | } 240 | 241 | body.js .no-js { 242 | display: none; 243 | } 244 | body:not(.js) .js-only { 245 | display: none; 246 | } 247 | 248 | [hidden] { 249 | display: none; 250 | } 251 | 252 | .author { 253 | display: flex; 254 | align-items: center; 255 | position: relative; 256 | z-index: 1; 257 | } 258 | .author img { 259 | max-width: 5rem; 260 | border-radius: 50%; 261 | margin-right: var(--spacer-sm); 262 | box-shadow: var(--box-shadow-lg); 263 | } 264 | .author > * { 265 | margin: 0; 266 | } 267 | 268 | header > nav { 269 | background-color: var(--blue-100); 270 | padding: var(--spacer-base); 271 | box-shadow: var(--box-shadow-lg); 272 | } 273 | 274 | /* CIRCLES LAYOUT */ 275 | @media (min-width: 68rem) { 276 | .title { 277 | margin-left: 6ch; 278 | margin-bottom: var(--spacer-md); 279 | } 280 | header > nav { 281 | margin-left: -20rem; 282 | float: left; 283 | shape-outside: circle(21rem at 1.5rem 40%); 284 | padding: 3.5rem 6.5rem 3.5rem 9rem; 285 | margin-top: -5.5rem; 286 | margin-right: 3rem; 287 | margin-bottom: 3rem; 288 | border-radius: 50%; 289 | } 290 | header > nav > * { 291 | margin-left: 2rem; 292 | } 293 | } 294 | header > nav > ul { 295 | font-size: 1rem; 296 | margin-top: var(--spacer-xs); 297 | } 298 | header > nav * { 299 | margin-top: 0; 300 | } 301 | header > nav li { 302 | padding-top: var(--spacer-xs); 303 | } 304 | 305 | header > nav > h2 { 306 | font-size: 1rem; 307 | display: flex; 308 | } 309 | toggle-button { 310 | display: flex; 311 | align-items: center; 312 | position: relative; 313 | } 314 | toggle-button > button { 315 | background-color: transparent; 316 | border: 0; 317 | display: flex; 318 | align-items: center; 319 | } 320 | toggle-button > button::after { 321 | position: absolute; 322 | top: 0; 323 | left: 0; 324 | opacity: 0.5; 325 | width: 100%; 326 | height: 100%; 327 | content: ""; 328 | } 329 | toggle-button svg { 330 | transition: transform 200ms ease; 331 | } 332 | toggle-button [aria-expanded="true"] svg { 333 | transform: rotate(90deg); 334 | } 335 | .hide { 336 | display: none; 337 | } 338 | @media screen and (min-width: 68rem) { 339 | toggle-button button { 340 | display: none; 341 | } 342 | .hide { 343 | display: block; 344 | } 345 | header > nav li { 346 | padding-top: 0; 347 | } 348 | } 349 | 350 | body > footer { 351 | display: flex; 352 | flex-direction: column; 353 | align-items: center; 354 | padding: 0 var(--body-padding-x) var(--spacer-sm); 355 | background-color: var(--gray-100); 356 | font-family: var(--font-family-sans); 357 | } 358 | body > footer img { 359 | max-width: 3rem; 360 | } 361 | 362 | blockquote, 363 | pre, 364 | figure, 365 | .negative-padding { 366 | --container-width: calc( 367 | var(--default-container-width) + var(--body-padding-x) * 2 368 | ); 369 | } 370 | 371 | blockquote { 372 | display: flex; 373 | background-color: white; 374 | padding: var(--spacer-sm) var(--spacer-base); 375 | box-shadow: var(--box-shadow-md); 376 | align-items: center; 377 | } 378 | blockquote blockquote-quote { 379 | display: flex; 380 | justify-content: center; 381 | } 382 | blockquote blockquote-quote::before { 383 | content: "‟"; 384 | font-size: 3rem; 385 | font-family: var(--font-family-serif); 386 | color: var(--indigo-200); 387 | height: 3rem; 388 | margin-right: var(--spacer-sm); 389 | } 390 | blockquote > * { 391 | margin: 0; 392 | } 393 | 394 | .em-list { 395 | list-style-type: none; 396 | font-size: 2rem; 397 | background-color: white; 398 | box-shadow: var(--box-shadow-lg); 399 | padding-left: var(--body-padding-x); 400 | margin-bottom: var(--spacer-md); 401 | position: relative; 402 | display: grid; 403 | grid-template-columns: var(--spacer-md) 1fr; 404 | grid-template-rows: 1fr 1fr; 405 | grid-column-gap: var(--spacer-sm); 406 | } 407 | .em-list > * { 408 | line-height: 1.2; 409 | grid-column-start: 2; 410 | padding: var(--spacer-xs); 411 | margin: 0; 412 | display: flex; 413 | align-items: center; 414 | } 415 | .em-list [data-and] { 416 | font-size: 3rem; 417 | color: var(--indigo-500); 418 | grid-row: 1 / 3; 419 | grid-column-start: 1; 420 | align-self: center; 421 | justify-self: center; 422 | } 423 | .em-list a { 424 | text-decoration: none; 425 | color: var(--indigo-900); 426 | } 427 | .em-list li:not([data-and]):hover { 428 | background-color: var(--gray-100); 429 | } 430 | 431 | figure { 432 | background-color: white; 433 | box-shadow: var(--box-shadow-lg); 434 | margin: var(--spacer-base) calc(-1 * var(--body-padding-x)); 435 | } 436 | figcaption { 437 | padding: var(--spacer-base); 438 | background-color: var(--gray-100); 439 | } 440 | 441 | .horizontal-scroll { 442 | overflow-x: auto; 443 | } 444 | 445 | .no-grid-message { 446 | position: relative; 447 | } 448 | .no-grid-message::before { 449 | content: "Your browser does not support CSS Grid so this demo will not work correctly"; 450 | color: var(--gray-900); 451 | background-color: var(--gray-300); 452 | position: absolute; 453 | z-index: 1; 454 | top: 0; 455 | left: 0; 456 | width: 100%; 457 | height: 100%; 458 | padding: var(--spacer-md); 459 | } 460 | @supports (display: grid) { 461 | .no-grid-message::before { 462 | content: ""; 463 | display: none; 464 | } 465 | } 466 | 467 | .responsive-web-design { 468 | background-color: var(--indigo-100); 469 | } 470 | .accessible-web-design { 471 | background-color: var(--orange-100); 472 | } 473 | 474 | del { 475 | font-style: italic; 476 | } 477 | del code, 478 | del a { 479 | text-decoration: line-through; 480 | font-family: unset; 481 | } 482 | 483 | ins, 484 | ins code { 485 | text-decoration: none; 486 | } 487 | 488 | .insert { 489 | background-color: white; 490 | padding: var(--body-padding-x); 491 | box-shadow: var(--box-shadow-md); 492 | } 493 | --------------------------------------------------------------------------------