├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── LICENSE ├── README.md ├── assets ├── favicon.ico ├── icons │ ├── icon-256x256.png │ └── icon-512x512.png └── logo.svg ├── css ├── print.css └── styles.css ├── index.html ├── js └── script.js ├── manifest.json └── sw.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Critical] title of my issue" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | The Blitz project is under an “End of Life process”, which means only critical bugs and security issues will still be addressed. 11 | 12 | Maintenance for critical bugs will end on May, 15th. 13 | 14 | Maintenance for security issues will end on July, 1st. 15 | 16 | If your issue is not critical or is a feature request, please reconsider opening it as it will be locked for conversation and labeled `wontfix`. Thanks for your understanding. 17 | 18 | Please check [the related Blitz issue](https://github.com/FriendsOfEpub/Blitz/issues/66) for more details. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: wontfix 6 | assignees: '' 7 | 8 | --- 9 | 10 | The Blitz project is under an “End of Life process”, which means only critical bugs and security issues will still be addressed. 11 | 12 | Maintenance for critical bugs will end on May, 15th. 13 | 14 | Maintenance for security issues will end on July, 1st. 15 | 16 | If your issue is not critical or is a feature request, please reconsider opening it as it will be locked for conversation and labeled `wontfix`. Thanks for your understanding. 17 | 18 | Please check [the related Blitz issue](https://github.com/FriendsOfEpub/Blitz/issues/66) for more details. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2017 Jiminy Panoz 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blitz eBook Tricks 2 | 3 | A collection of CSS snippets to do progressive enhancement and achieve better typography, layout and UX in eBooks. 4 | 5 | ## Important Note 6 | 7 | All the Blitz repositories reached End Of Life on July 1, 2020. The entire project is no longer maintained and its repositories are read-only. You can still fork them if they can be useful to you. 8 | 9 | ## How-to 10 | 11 | ### Use 12 | 13 | **If you’re using a mouse/touch:** 14 | 15 | - click the “Expand/Collapse button” to toggle all details 16 | - click the “Copy snippet” button to copy the code to your clipboard 17 | - click “Details” to display more information 18 | 19 | **If you’re using a keyboard:** 20 | 21 | - press “tab” to navigate menu, “copy snippet” buttons, details, and links 22 | - press “enter/spacebar” to copy the code snippet 23 | - press “enter/spacebar” to display details (including toggle button) 24 | 25 | ### Install 26 | 27 | If you’re on iOS/Android/Windows 8–10, you can actually install this web app. Just visit the page and “Add to homescreen”. 28 | 29 | Thanks to service workers, it will even work offline on Android, Chrome, Opera or Firefox (appcache fallback for iOS). 30 | 31 | ## Summary 32 | 33 | The Blitz eBook Tricks collection is part of the Blitz Project. It’s a progressive web app which aims is to help you do progressive enhancement. 34 | 35 | It is currently in v.1.0.0 and available [on this page](https://friendsofepub.github.io/eBookTricks/). 36 | 37 | ## Details 38 | 39 | This web app is just a glorified list of CSS snippets. 40 | 41 | - We’re using JS to toggle details. 42 | - We’re using Service Workers and appcache (fallback) to make it work offline. 43 | - Keyboard features are implemented via JS when needed. 44 | 45 | And that’s it. 46 | 47 | ## License 48 | 49 | Blitz eBook Tricks, a Blitz tool to help you do progressive enhancement in eBooks 50 | 51 | Copyright (C) 2017–present Jiminy Panoz 52 | 53 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 54 | 55 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 56 | 57 | You should have received a copy of the GNU Lesser General Public License along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). 58 | 59 | ### What LGPL3 actually implies 60 | 61 | You can fork this repo and make your own collection/web app. You can use it in commercial projets but your modifications to this specific part (consider this a library) should then be released with a LGPL3 license. Attribution is needed in any case. 62 | 63 | ### Why LGPL3 OMG it’s not MIT like the Blitz Framework! 64 | 65 | The eBook dev ecosystem is a nightmare, we need eBook tools. And fast! 66 | 67 | Very few people actually release those tools. Now it’s just a checklist so why would you want to keep this closed source, huh? 68 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfEpub/eBookTricks/fee9645ce8e026c80014b7fd3a207d1338b45fb3/assets/favicon.ico -------------------------------------------------------------------------------- /assets/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfEpub/eBookTricks/fee9645ce8e026c80014b7fd3a207d1338b45fb3/assets/icons/icon-256x256.png -------------------------------------------------------------------------------- /assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfEpub/eBookTricks/fee9645ce8e026c80014b7fd3a207d1338b45fb3/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /css/print.css: -------------------------------------------------------------------------------- 1 | @page { 2 | margin: 12pt; 3 | size: portrait; 4 | padding: 0; 5 | } 6 | 7 | html, 8 | body { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | * { 14 | background: none !important; 15 | color: #222 !important; 16 | } 17 | 18 | header { 19 | margin: 0; 20 | padding: 0; 21 | } 22 | 23 | .header-icon, 24 | .lead, 25 | .details, 26 | .summary, 27 | footer, 28 | button, 29 | .menu { 30 | display: none; 31 | } 32 | 33 | h1 { 34 | margin: 0; 35 | padding: 0; 36 | font-size: 16pt; 37 | text-align: left; 38 | position: absolute; 39 | top: -16pt; 40 | right: 0; 41 | transform-origin: 100% 100%; 42 | transform: rotate(270deg); 43 | display: inline-block; 44 | } 45 | 46 | h1:after { 47 | content: " (a blitz eBook framework tool)"; 48 | font-size: 10pt; 49 | display: block; 50 | opacity: 0.4; 51 | text-align: right; 52 | } 53 | 54 | h2 { 55 | font-size: 14pt; 56 | text-align: left; 57 | margin: 10pt 0 10pt 0; 58 | } 59 | 60 | h3 { 61 | font-size: 11pt; 62 | text-align: left; 63 | margin: 10pt 0 0 0; 64 | } 65 | 66 | .category { 67 | margin: 30pt 0 0 0; 68 | padding: 0; 69 | box-sizing: border-box; 70 | border-top: 1px solid currentColor 71 | } 72 | 73 | .tags { 74 | margin: 0; 75 | font-size: 6pt; 76 | } 77 | 78 | .tag { 79 | margin: 0 1pt; 80 | } 81 | 82 | .trick { 83 | margin: 20pt 0 0 0; 84 | padding: 0; 85 | box-sizing: border-box; 86 | position: relative; 87 | page-break-inside: avoid; 88 | } 89 | 90 | .category:first-child, 91 | .trick:first-child { 92 | margin: 0; 93 | border: none; 94 | } 95 | 96 | pre { 97 | word-wrap: break-word; 98 | white-space: pre-wrap; 99 | overflow: hidden; 100 | border: none; 101 | padding: 0; 102 | margin: 0 0 10pt 20pt; 103 | font-size: 10pt; 104 | } 105 | 106 | .wrapper { 107 | margin: 0; 108 | padding: 0; 109 | max-width: none; 110 | } -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /*! normalize.css v4.2.0 | MIT License | github.com/necolas/normalize.css */ 4 | 5 | html { 6 | font-family: sans-serif; 7 | line-height: 1.15; 8 | -ms-text-size-adjust: 100%; 9 | -moz-text-size-adjust: 100%; 10 | -webkit-text-size-adjust: 100%; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | } 16 | 17 | header, 18 | main, 19 | section, 20 | footer { 21 | display: block; 22 | } 23 | 24 | h1 { 25 | font-size: 2em; 26 | margin: 0.67em 0; 27 | } 28 | 29 | a { 30 | background-color: transparent; 31 | -webkit-text-decoration-skip: objects; 32 | } 33 | 34 | a:active, 35 | a:hover { 36 | outline-width: 0; 37 | } 38 | 39 | abbr[title] { 40 | border-bottom: none; 41 | text-decoration: none; 42 | cursor: help; 43 | } 44 | 45 | b, 46 | strong { 47 | font-weight: bolder; 48 | } 49 | 50 | code { 51 | font-family: monospace, monospace; 52 | font-size: 1em; 53 | } 54 | 55 | small { 56 | font-size: 80%; 57 | } 58 | 59 | sub, 60 | sup { 61 | font-size: 75%; 62 | line-height: 0; 63 | position: relative; 64 | vertical-align: baseline; 65 | } 66 | 67 | sub { 68 | bottom: -0.25em; 69 | } 70 | 71 | sup { 72 | top: -0.5em; 73 | } 74 | 75 | pre { 76 | font-family: monospace, monospace; 77 | font-size: 1em; 78 | } 79 | 80 | img { 81 | border-style: none; 82 | } 83 | 84 | svg:not(:root) { 85 | overflow: hidden; 86 | } 87 | 88 | button { 89 | margin: 0; 90 | border: 0; 91 | padding: 0; 92 | display: block; 93 | white-space: normal; 94 | background: none; 95 | line-height: 1.5; 96 | font-size: 1.8rem; 97 | font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", "Roboto", sans-serif; 98 | } 99 | 100 | button { 101 | -webkit-box-sizing: border-box; 102 | -moz-box-sizing: border-box; 103 | box-sizing: border-box; 104 | -webkit-user-select: none; 105 | -moz-user-select: none; 106 | -ms-user-select: none; 107 | user-select: none; 108 | } 109 | 110 | button { 111 | overflow: visible; 112 | width: auto; 113 | } 114 | 115 | button::-moz-focus-inner, 116 | [type="button"]::-moz-focus-inner, 117 | [type="reset"]::-moz-focus-inner, 118 | [type="submit"]::-moz-focus-inner { 119 | border-style: none; 120 | padding: 0; 121 | } 122 | 123 | button:-moz-focusring, 124 | [type="button"]:-moz-focusring, 125 | [type="reset"]:-moz-focusring, 126 | [type="submit"]:-moz-focusring { 127 | outline: 1px dotted ButtonText; 128 | } 129 | 130 | [hidden] { 131 | display: none; 132 | } 133 | 134 | /* Custom 135 | ========================================================================== */ 136 | 137 | html { 138 | font-size: 62.5%; 139 | background-color: #FDFDFD; 140 | } 141 | 142 | body { 143 | font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", "Roboto", sans-serif; 144 | font-size: 1.8rem; 145 | line-height: 1.65; 146 | margin: 12.8rem auto 3.6rem auto; 147 | } 148 | 149 | header { 150 | max-width: 42em; 151 | margin: 0 auto 7.2rem auto; 152 | padding: 0 2rem; 153 | } 154 | 155 | section { 156 | padding: 4.8rem 0; 157 | margin: 0; 158 | } 159 | 160 | .wrapper { 161 | max-width: 42em; 162 | margin: 0 auto; 163 | padding: 0 2rem; 164 | } 165 | 166 | .category { 167 | background-color: #111111; 168 | color: #FAFAFA; 169 | } 170 | 171 | .category+.category { 172 | padding-top: 0; 173 | } 174 | 175 | .category:last-child { 176 | padding-bottom: 9.6rem; 177 | } 178 | 179 | .trick:nth-child(odd) { 180 | background-color: #FDFDFD; 181 | color: #333; 182 | } 183 | 184 | .trick:nth-child(even) { 185 | background-color: #EFEFEF; 186 | color: #000; 187 | } 188 | 189 | .tags { 190 | font-size: 1.6rem; 191 | font-weight: 600; 192 | } 193 | 194 | .trick:nth-child(odd) .tag { 195 | color: rgba(0, 0, 0, 0.5); 196 | } 197 | 198 | .trick:nth-child(even) .tag { 199 | color: rgba(0, 0, 0, 0.55); 200 | } 201 | 202 | .tag { 203 | display: inline-block; 204 | margin: 0 0.5em; 205 | } 206 | 207 | .tag:first-child { 208 | margin-left: 0; 209 | } 210 | 211 | .tag:before { 212 | content: "[ "; 213 | position: relative; 214 | top: -0.0375em; 215 | } 216 | 217 | .tag:after { 218 | content: " ]"; 219 | position: relative; 220 | top: -0.0375em; 221 | } 222 | 223 | footer { 224 | max-width: 42em; 225 | margin: 7.2rem auto 0 auto; 226 | text-align: center; 227 | padding: 0 2rem; 228 | } 229 | 230 | .menu { 231 | width: 100%; 232 | margin: 0; 233 | position: fixed; 234 | top: 0; 235 | left: 0; 236 | background-color: rgba(0, 0, 0, 0.8); 237 | color: #FAFAFA; 238 | padding: 2.4rem 1rem; 239 | box-sizing: border-box; 240 | z-index: 10; 241 | } 242 | 243 | .menu ol { 244 | display: flex; 245 | flex-wrap: wrap; 246 | justify-content: center; 247 | margin: 0; 248 | padding: 0; 249 | } 250 | 251 | .menu li { 252 | list-style: none; 253 | } 254 | 255 | .menu li { 256 | margin: 0 0.5em; 257 | } 258 | 259 | .menu a { 260 | color: #FAFAFA; 261 | font-weight: 700; 262 | text-decoration: none; 263 | } 264 | 265 | .header-icon { 266 | margin: 7.2rem auto 3.6rem auto; 267 | width: 6.4rem; 268 | height: 6.4rem; 269 | display: block; 270 | } 271 | 272 | h1 { 273 | font-size: 3.6rem; 274 | line-height: 1.2; 275 | text-align: center; 276 | font-weight: 800; 277 | margin: 0 0 7.2rem 0; 278 | } 279 | 280 | h2 { 281 | font-size: 2.8rem; 282 | line-height: 1; 283 | text-align: center; 284 | margin: 0 0 4.8rem 0; 285 | } 286 | 287 | h3 { 288 | margin: 0; 289 | } 290 | 291 | h4 { 292 | margin: 32px 0 16px 0; 293 | } 294 | 295 | p {} 296 | 297 | .lead { 298 | font-size: 2.1rem; 299 | line-height: 1.4; 300 | } 301 | 302 | .banner { 303 | display: block; 304 | padding: 1.4rem 1rem; 305 | font-weight: 600; 306 | background-color: #DF0101; 307 | color: #FFFFFF; 308 | text-align: center; 309 | margin: 2.8rem 0; 310 | border-radius: 0.3rem; 311 | } 312 | 313 | .secondary { 314 | font-size: 1.6rem; 315 | margin: 0; 316 | } 317 | 318 | .code-snippet { 319 | margin: 3.6rem 0; 320 | } 321 | 322 | pre { 323 | margin: 0; 324 | overflow-x: auto; 325 | /* overflow-x: scroll; 326 | -webkit-overflow-scrolling: touch; */ 327 | border: 1px solid currentColor; 328 | padding: 1.2rem; 329 | } 330 | 331 | .copyButton { 332 | font-size: 1.2rem; 333 | font-weight: 600; 334 | padding: 0.6rem 1.8rem; 335 | background-color: #000000; 336 | color: #FAFAFA; 337 | margin: 0 0 0 auto; 338 | } 339 | 340 | .hidden { 341 | display: none; 342 | } 343 | 344 | .details-para { 345 | font-size: 1.6rem; 346 | line-height: 1.65; 347 | margin: 1.65rem 2rem; 348 | } 349 | 350 | code { 351 | font-family: "Andale Mono", Menlo, Consolas, monospace; 352 | } 353 | 354 | a { 355 | color: #DF0101; 356 | font-weight: 600; 357 | } 358 | 359 | a code { 360 | font-weight: 700; 361 | } 362 | 363 | .summary { 364 | font-weight: 700; 365 | margin: 3.3rem 0 1.65rem 2rem; 366 | position: relative; 367 | } 368 | 369 | .js-enabled .summary { 370 | cursor: pointer; 371 | } 372 | 373 | .js-enabled .summary:before { 374 | content: "+"; 375 | position: absolute; 376 | left: -20px; 377 | left: -2rem; 378 | color: #666; 379 | font-size: 1.8rem; 380 | font-weight: bold; 381 | margin: 0.5rem 0 0 0; 382 | padding: 0; 383 | line-height: 1; 384 | } 385 | 386 | .js-enabled .summary.open:before { 387 | content: "−"; 388 | } 389 | 390 | .checkAll { 391 | margin: 5.4rem auto; 392 | color: inherit; 393 | border: 0.2rem solid currentColor; 394 | border-radius: 0.3rem; 395 | padding: 1rem 1.5rem; 396 | transition: all 350ms; 397 | cursor: pointer; 398 | font-weight: 500; 399 | } 400 | 401 | .checkAll:active { 402 | color: inherit; 403 | transition: all 350ms; 404 | } 405 | 406 | @-ms-viewport { 407 | width: device-width; 408 | } 409 | 410 | @media screen and (max-width: 560px) { 411 | html { 412 | font-size: 50%; 413 | } 414 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BlitzTricks — CSS tricks to improve your eBooks 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 |

Blitz ebook Tricks

32 | 33 |

34 | 35 |

A collection of CSS snippets to do progressive enhancement and achieve better typography, layout and UX in eBooks.

36 | 37 |

Those snippets are primarily intended for EPUB3 though some will work in ePub2 and/or Kindle.

38 |
39 | 40 | 48 | 49 |
50 |
51 |

Typography

52 | 53 |
54 |
55 |

Add line-height to body

56 | 57 |
ePub2 EPUB3
58 | 59 |
60 |
body {
 61 |   line-height: 1.5;
 62 | }
 63 | 
 64 | body * {
 65 |   line-height: inherit;
 66 | }
 67 | 
68 |
69 | 70 |

Details

71 |
72 |

If you add an explicit line-height to elements, your CSS may break Kobo’s – and possibly others’ – user setting. By adding it to body and letting elements inherit from it, this problem is solved.

73 |

If you are targeting Kindle, please note the Publishing Guidelines recommends against setting a line-height on body.

74 |
75 |
76 |
77 | 78 |
79 |
80 |

Don’t define a line-height value which is less than 1.2

81 | 82 |
Kindle Format 8
83 | 84 |
85 |
element {
 86 |   line-height: 1.2;
 87 | }
 88 | 
89 |
90 | 91 |

Details

92 |
93 |

As stated in versions of the Kindle Publishing Guidelines prior to 2018.2, “to ensure pagination, the Kindle software does not honor line-height value less than 1.2em or 120%.”

94 | 95 |

In other words, elements for which line-height is less than 1.2 will be applied the default’s line-height, which is 1.75 on most Kindle devices.

96 |
97 |
98 |
99 | 100 |
101 |
102 |

Disable hyphens

103 | 104 |
ePub2 EPUB3 Kindle Format X
105 | 106 |
107 |
element {
108 |   adobe-hyphenate: none;
109 |   -webkit-hyphens: none;
110 |   -moz-hyphens: none;
111 |   -ms-hyphens: none;
112 |   -epub-hyphens: none;
113 |   hyphens: none;
114 | }
115 | 
116 |
117 | 118 |

Details

119 |
120 |

Usually, some elements like headings, centered text, etc. are not hyphenated.

121 |

Those declarations should cover all self-respecting Reading Systems.

122 |
123 |
124 |
125 | 126 |
127 |
128 |

Improve hyphenation

129 | 130 |
EPUB3
131 | 132 |
133 |
element {
134 |   -webkit-hyphenate-limit-before: 3;
135 |   -webkit-hyphenate-limit-after: 2;
136 |   -ms-hyphenate-limit-chars: 6 3 2;
137 |   hyphenate-limit-chars: 6 3 2;
138 |   -webkit-hyphenate-limit-lines: 2;
139 |   hyphenate-limit-lines: 2;
140 | }
141 | 
142 |
143 | 144 |

Details

145 |
146 |

When you set hyphens to auto, extra declarations can be set to improve hyphenation for your language.

147 |

You can indeed set the minimum number of letters a word must contain to be hyphenated, and the minimum number of letters which should be before and after the hyphen.

148 |

Finally, you can control the maximum number of consecutive lines for which hyphenation must happen. As Bringhurst advised, “avoid more than three consecutive hyphenated lines.”

149 |
150 |
151 |
152 | 153 |
154 |
155 |

Declare text-align for elements which should be left-aligned

156 | 157 |
ePub2 EPUB3 Kindle
158 | 159 |
160 |
element {
161 |   text-align: left;
162 | }
163 | 
164 |
165 | 166 |

Details

167 |
168 |

If some elements like headings should be left-aligned, make sure to declare text-align.

169 |

Indeed, those elements will be justified if you don’t and the user sets full justification. Word-spacing will then be adjusted so that the text falls flush with both margins, which can result in terrible typography.

170 |
171 |
172 |
173 | 174 |
175 |
176 |

Prevent sub- and superscript from affecting line-height

177 | 178 |
ePub2 EPUB3 Kindle
179 | 180 |
181 |
sub {
182 |   font-size: 0.675em;
183 |   line-height: 1.2;
184 |   vertical-align: sub;
185 |   vertical-align: -20%;
186 | }
187 | 
188 | sup {
189 |   font-size: 0.675em;
190 |   line-height: 1.2;
191 |   vertical-align: super;
192 |   vertical-align: 35%;
193 | }
194 | 
195 |
196 | 197 |

Details

198 |
199 |

Sub- and superscript will affect line-height if you just use their dedicated keyword for vertical-align.

200 |

By decreasing line-height to the minimum value Kindle supports (i.e. 1.2) and using % for vertical-align, we solve this problem and can vertically-align sub- and superscript more accurately.

201 |
202 |
203 |
204 | 205 |
206 |
207 |

Improve legibility

208 | 209 |
EPUB3
210 | 211 |
212 |
body {
213 |   font-kerning: normal;
214 |   font-variant: common-ligatures oldstyle-nums proportional-nums;
215 |   font-feature-settings: "kern", "liga", "clig", "onum", "pnum";
216 | }
217 | 
218 | h1, h2, h3 {
219 |   font-variant: common-ligatures lining-nums proportional-nums;
220 |   font-feature-settings: "kern", "liga", "clig", "lnum", "pnum";
221 | }
222 | 
223 | table {
224 |   font-variant-numeric: lining-nums tabular-nums;
225 |   font-feature-settings: "kern", "lnum", "tnum";
226 | }
227 | 
228 | code {
229 |   font-variant: no-common-ligatures lining-nums;
230 |   font-feature-settings: "kern" 0, "liga" 0, "clig" 0, "lnum";
231 | }
232 | 
233 | .fraction {
234 |   font-variant-numeric: diagonal-fractions;
235 |   font-feature-settings: "frac";
236 | }
237 | 
238 |
239 | 240 |

Details

241 |
242 |

OpenType features can dramatically improve the legibility of an eBook. Although not all default fonts provided by Reading Systems support all those features, you can still benefit from them.

243 |

The font-feature-settings property is a low-level feature designed to handle special cases where no other way to enable or access an OpenType font feature exists, which is why you should prefer font-variant and its associated longhand properties.

244 |

Please note font-feature-settings doesn’t inherit values from the parent element but resets them.

245 |

For a complete list of features, visit this Typekit help page.

246 |
247 |
248 |
249 | 250 |
251 |
252 |

Use real small capitals

253 | 254 |
EPUB3
255 | 256 |
257 |
element {
258 |   font-variant: small-caps;
259 | }
260 | 
261 | @supports not (font-variant-caps: small-caps) {
262 |   element {
263 |     font-variant: normal;
264 |     font-feature-settings: "smcp", "onum", "pnum";
265 |   }
266 | }
267 | 
268 | @supports (font-variant-caps: small-caps) {
269 |   element {
270 |     font-variant: normal;
271 |     font-variant-caps: small-caps;
272 |   }
273 | }
274 | 
275 |
276 | 277 |

Details

278 |
279 |

The font-variant:small-caps property creates fake small caps. This can turn an enjoyable book into a mediocre experience since you can tell they are fake in the blink of an eye.

280 |

By using OpenType Features, we can use real small caps the typeface designer took special care getting right.

281 |

Please note that if the font doesn’t support this feature, it will fall back to fake small caps.

282 |
283 |
284 |
285 | 286 |
287 |
288 |

Use semantic asterisms and make them reflowable

289 | 290 |
ePub2 EPUB3 Kindle
291 | 292 |
293 |
hr.asterism {
294 |   height: 1.5em;
295 |   background: transparent url("../Images/asterism.svg") no-repeat center;
296 |   background-size: 2.5em 1.25em;
297 |   overflow: hidden;
298 |   page-break-inside: avoid;
299 |   break-inside: avoid;
300 | }
301 | 
302 | @supports not ((page-break-inside: avoid) and (break-inside: avoid)) {
303 |   hr.asterism {
304 |     -webkit-column-break-inside: avoid;
305 |   }
306 | }
307 | 
308 |
309 | 310 |

Details

311 |
312 |

Asterisms are context changes, thus you should use hr.

313 |

Problem is hr can’t contain anything so the only solution is to use a background image.

314 |

Your best option is SVG since it manages transparency, reflow without a loss in quality, can be designed to be compatible with night modes, etc.

315 |

Reading Systems which don’t support background-size will fall back to the width, height and viewbox attributes in your SVG so don’t get rid of them.

316 |
317 |
318 |
319 | 320 |
321 |
322 |

Fake asterisms (context change)

323 | 324 |
EPUB3
325 | 326 |
327 |
<div class="asterism" role="separator" aria-label="Interlude">
328 |   * * *
329 | </div>
330 | 
331 |
332 | 333 |

Details

334 |
335 |

Sometimes you can’t use semantic asterisms (background-image for hr) and adding it with hr:before may prove to be problematic for accessibility.

336 |

In that case, the ARIA role and aria-label attributes may come in handy.

337 |

The separator role tells Reading Systems the div is intended to be a context change (hr) and the aria-label will override the text inside it for Text to Speech.

338 |

If you intend to mimick how hr will typically be handled for selection and copy, don’t forget to disable user’s selection though (see “Create an automated numbering system” trick).

339 |
340 |
341 |
342 | 343 | 364 |
365 | 366 |
367 |

Layout

368 | 369 |
370 |
371 |

Make HTML5 tags behave as expected in legacy RMSDK

372 | 373 |
ePub2 EPUB3 Kindle
374 | 375 |
376 |
article, aside, figure, figcaption, 
377 | footer, header, main, nav, section {
378 |   display: block;
379 | }
380 | 
381 |
382 | 383 |

Details

384 |
385 |

Did you know most of HTML elements have a default display value of inline? It’s up to browsers to set some elements to block.

386 |

Since the legacy RMSDK is not supposed to support HTML5, you must set grouping elements to block in your style sheet.

387 |
388 |
389 |
390 | 391 |
392 |
393 |

Prevent horizontal margins to reflow with font-size

394 | 395 |
ePub2 EPUB3 Kindle
396 | 397 |
398 |
element-1 {
399 |   margin-left: {number}%;
400 | }
401 | 
402 | element-2 {
403 |   padding-left: {number}%;
404 |   padding-right: {number}%;
405 | }
406 | 
407 |
408 | 409 |

Details

410 |
411 |

If you use em for horizontal margins, they will increase/decrease with the font-size user setting. This implies that the bigger the text is set, the smaller its container will be – it should be the opposite.

412 |

By using % for horizontal margins and paddings, Reading Systems will use the width of the page or parent container to compute them, not the current font-size.

413 |
414 |
415 |
416 | 417 |
418 |
419 |

Center a block element in legacy RMSDK

420 | 421 |
ePub2 EPUB3 Kindle
422 | 423 |
424 |
element {
425 |   width: {number}%;
426 |   margin-left: ((100 - {number}) / 2 )%;
427 |   margin-right: ((100 - {number}) / 2 )%;
428 | }
429 | 
430 |
431 | 432 |

Details

433 |
434 |

Legacy RMSDK doesn’t really support the auto value for margin. As a matter of fact, it maps auto to 0, which is allowed in a footnote of the ePub2 specification.

435 |

In other words, if you want to center an element, you should declare a width then substract it from 100 and divide it by 2 to get your horizontal margins.

436 |

If your element is 80% then each margin will be (100-80)/2 or 10%.

437 |
438 |
439 |
440 | 441 |
442 |
443 |

Do not rely on page-breaks

444 | 445 |
ePub2 EPUB3
446 | 447 |
448 |
element {
449 |   page-break-inside: avoid;
450 |   break-inside: avoid;
451 | }
452 |     
453 | @supports not ((page-break-inside: avoid) and (break-inside: avoid)) {
454 |   element {
455 |     -webkit-column-break-inside: avoid;
456 |   }
457 | }
458 | 
459 |
460 | 461 |

Details

462 |
463 |

CSS break is a strange beast. But what’s important is that we shouldn’t only rely on paged media.

464 |

Modern Reading Systems will typically use CSS multi-columns to fake pagination since browsers don’t implement paged media, which means page-break-* is not necessarily aliased to column-break-*. And page-break-* itself is destined to become an alias for break-* at some point. For maximum compatibility, we must therefore use all three.

465 |

The feature query (@supports) allows us to get around some bug in iBooks. At some point, declaring -webkit-column-break-inside in the same rule as page-break-inside would indeed result in both styles being ignored.

466 |
467 |
468 |
469 | 470 |
471 |
472 |

Prefer page-break-after to page-break-before

473 | 474 |
ePub2 EPUB3 Kindle
475 | 476 |
477 |
element {
478 |   page-break-after: always;
479 |   break-after: always;
480 | }
481 | 
482 | @supports not ((page-break-after: always) and (break-after: always)) {
483 |   element {
484 |     -webkit-column-break-after: always;
485 |   }
486 | }
487 | 
488 |
489 | 490 |

Details

491 |
492 |

For some reason, it looks like page-break-after:always has got slightly better support than page-break-before:always in some Reading Systems.

493 |

As stated in iBooks Asset Guide, “if you include page breaks to mark a chapter break, use page-break-after to create a break at the end of a chapter, not page-break-before to insert the break at the beginning of the chapter. This modification improves performance with the table of contents.”

494 |

In any case, don’t use both since it will create a blank page in some Reading Systems.

495 |
496 |
497 |
498 | 499 |
500 |
501 |

Fix the layout of tables

502 | 503 |
ePub2 EPUB3 Kindle
504 | 505 |
506 |
table {
507 |   table-layout: fixed;
508 | }
509 | 
510 | th.third {
511 |   width: 33%;
512 | }
513 | 
514 |
515 | 516 |

Details

517 |
518 |

By default, you can’t really tell how Reading Systems will compute the width of tables’ columns. All you know is that they will compute those widths depending on their cells’ contents.

519 |

You can force a width for each column by using table-layout:fixed then declaring a width for each element in the first line of the table.

520 |

Reading Systems will now use the width specified for those elements to compute the width of each column.

521 |
522 |
523 |
524 | 525 |
526 |
527 |

Create an automated numbering system

528 | 529 |
EPUB3 Kindle
530 | 531 |
532 |
parent {
533 |   counter-increment: elements;
534 | }
535 | 
536 | element:before {
537 |   content: counter(elements);
538 |   -webkit-user-select: none;
539 |   -moz-user-select: none;
540 |   -ms-user-select: none;
541 |   user-select: none;
542 | }
543 | 
544 |
545 | 546 |

Details

547 |
548 |

Although it won't work in legacy RMSDK, this CSS snippet can be useful for numbered lines of code, headings, poetry, etc.

549 |

Make sure to disable selection in some cases; nobody wants to copy-paste code with numbers breaking it.

550 |

Check this MDN tutorial for further details.

551 |
552 |
553 |
554 | 555 |
556 |
557 |

Build a super simple responsive grid

558 | 559 |
EPUB3
560 | 561 |
562 |
@supports (display: -webkit-flex) or (display: flex) {
563 |   ul.grid,
564 |   ol.grid {
565 |     display: -webkit-flex;
566 |     display: flex;
567 |     -webkit-flex-wrap: wrap;
568 |     flex-wrap: wrap;
569 |     -webkit-flex: 1 1 15em;
570 |     flex: 1 1 15em;
571 |   }
572 | 
573 |   ul.grid li,
574 |   ol.grid li {
575 |     page-break-inside: avoid;
576 |     break-inside: avoid;
577 |   }
578 | }
579 | 
580 |
581 | 582 |

Details

583 |
584 |

What if you could make the most of the page’s width for, say, an inline table of contents or a list of ingredients?

585 |

By using flexbox, you can create a responsive grid which should behave according to a preferred width (the third value in flex e.g. 15em).

586 |

Of course you may want to provide a fallback for Reading Systems which don't support flexbox.

587 |
588 |
589 |
590 | 591 |
592 |
593 |

Vertically-align elements on a page

594 | 595 |
EPUB3
596 | 597 |
598 |
@supports (display: -webkit-flex) or (display: flex) {
599 |   parent {
600 |     min-height: 95vh;
601 |     display: -webkit-flex;
602 |     display: flex;
603 |     -webkit-flex-direction: column;
604 |     flex-direction: column;
605 |     -webkit-justify-content: {value};
606 |     justify-content: {value};
607 |   }
608 | }
609 | 
610 |
611 | 612 |

Details

613 |
614 |

Pages of an eBook don’t have a middle or a bottom, right? With flexbox, they now do.

615 |

Make sure to use the min-height property so that the container’s height can grow in case the user sets a huge font-size… or else contents will collapse.

616 |

If you’re not familiar with flex properties and values, check CSS-Tricks’ complete guide to flexbox.

617 |

Finally, you should provide a fallback for Reading Systems which don't support flexbox.

618 |
619 |
620 |
621 | 622 |
623 |
624 |

Give text wraps a modern twist

625 | 626 |
EPUB3
627 | 628 |
629 |
element {
630 |   float: left;
631 | }
632 | 
633 | @supports (-webkit-shape-outside: {value}) or (shape-outside: {value}) {
634 |   element {
635 |     -webkit-shape-outside: {value};
636 |     shape-outside: {value};
637 |     -webkit-clip-path: {value};
638 |     clip-path: {value};
639 |   }
640 | }
641 | 
642 |
643 | 644 |

Details

645 |
646 |

For decades, text has wrapped around the floated element’s bouding box, which means it couldn’t wrap the object like it does in DTP software.

647 |

By using shape-outside and clip-path, you can define a shape around which the text should wrap.

648 |

And there’s even a Chrome extension to help you do that.

649 |
650 |
651 |
652 |
653 | 654 |
655 |

Images

656 | 657 |
658 |
659 |

Force images to keep their aspect ratio

660 | 661 |
EPUB3
662 | 663 |
664 |
img {
665 |   object-fit: contain;
666 | }
667 | 
668 |
669 | 670 |

Details

671 |
672 |

When sizing images based on width or height, you’ll probably end with distorted images in some contexts. It turns out there is a CSS property to manage that.

673 |

Get used to add object-fit:contain to img as this property prevent this distortion.

674 |
675 |
676 |
677 | 678 |
679 |
680 |

Keep an image with its caption

681 | 682 |
ePub2 EPUB3
683 | 684 |
685 |
figure {
686 |   page-break-inside: avoid;
687 |   break-inside: avoid;
688 | }
689 | 
690 | @supports not ((page-break-inside: avoid) and (break-inside: avoid)) {
691 |   figure {
692 |     -webkit-column-break-inside: avoid;
693 |   }
694 | }
695 | 
696 |
697 | 698 |

Details

699 |
700 |

Readability is as important as legibility and this is why you should take care of the relationship between elements.

701 |

For best comprehension, the image and its caption should be displayed on the same page so let’s avoid a page-break inside figures.

702 |
703 |
704 |
705 | 706 |
707 |
708 |

Make an image responsive to its caption’s font-size

709 | 710 |
EPUB3
711 | 712 |
713 |
@supports (height: calc(98vh - 5em)) {
714 |   img {
715 |     width: auto;
716 |     max-width: 100%;
717 |     min-height: 300px;
718 |     height: calc(98vh - 5em);
719 |     max-height: 95%;
720 |     object-fit: contain;
721 |   }
722 | }
723 | 
724 |
725 | 726 |

Details

727 |
728 |

For images with a portrait aspect ratio, you must go the extra mile so that the image and (part of) its caption are displayed on the same page.

729 |

This is where the CSS calc() function really shines. It allows you to dynamically compute the height of an image depending on the current font-size.

730 |

In this example, the image’s height should ideally be 98% of the page minus 3 lines of text (with a line-height of 1.5). Finally, min- and max-height provide a range for the image sizing.

731 |
732 |
733 |
734 |
735 | 736 |
737 |

Misc.

738 | 739 |
740 |
741 |

Use text color as a variable

742 | 743 |
ePub2 EPUB3 Kindle
744 | 745 |
746 |
element {
747 |   border: 1px solid currentColor;
748 | }
749 | 
750 | svg {
751 |   fill: currentColor;
752 | }
753 | 
754 |
755 | 756 |

Details

757 |
758 |

If you want borders, backgrounds or SVG’s fill to be the same color as text, including in night modes, currentColor is your best bet.

759 |

Indeed, currentColor sort of behaves like a variable: it inherits the current color of text.

760 |

In other words, when Reading Systems set a light color for text in night mode, currentColor will become this color.

761 |
762 |
763 |
764 | 765 |
766 |
767 |

Style epub:type

768 | 769 |
EPUB3
770 | 771 |
772 |
@namespace epub "http://www.idpf.org/2007/ops";
773 | 
774 | [epub|type~="toc"] {
775 |   /* styles */
776 | }
777 | 
778 |
779 | 780 |

Details

781 |
782 |

Problem with epub:type is that you have to escape the colon in CSS

783 |

Unless you declare a namespace at the top of your style sheet. You can now use epub|style instead of epub\:style, which is a lot more readable and maintainable.

784 |
785 |
786 |
787 | 788 |
789 |
790 |

Prevent unsupported styles from impacting legacy RMSDK

791 | 792 |
EPUB3
793 | 794 |
795 |
@supports ({property}: {value}) {
796 |   element {
797 |     {property}: {value};
798 |   }
799 | }
800 | 
801 |
802 | 803 |

Details

804 |
805 |

If you want to do progressive enhancement and don't want to risk your stylesheet being entierely ignored in legacy RMSDK, feature queries are the way to go.

806 |

Besides asking Reading Systems if they support a CSS declaration, it will basically protect the nested styles from being parsed by the legacy RMSDK. It can be pretty useful with the CSS calc() function for instance.

807 |

And @supports can protect media queries too.

808 |

The only downside is that Internet Explorer 11 doesn't support feature queries and Adobe Digital Editions 4.5 is using its rendering engine (Trident) on Windows. As a consequence, those styles won't be applied even though Internet Explorer supports them.

809 |
810 |
811 |
812 | 813 |
814 |
815 |

Declare specific styles for Kindle

816 | 817 |
Kindle
818 | 819 |
820 |
@media amzn-kf8 {
821 |   /* Specific KF8 styles (new format) */
822 | }
823 | 
824 | @media amzn-mobi {
825 |   /* Specific Mobi7 styles (old format) */
826 | }
827 | 
828 |
829 | 830 |

Details

831 |
832 |

If for some reason you must add or override styles for Kindle, those two media queries can help. KindleGen will indeed take them into account when converting your EPUB file.

833 |

Do not ever let them empty in your EPUB file since that would crash the legacy RMSDK and all the Reading Systems using it.

834 |
835 |
836 |
837 | 838 |
839 |
840 |

Style for monochrome

841 | 842 |
EPUB3
843 | 844 |
845 |
@media (monochrome) {
846 |   /* Styles */
847 | }
848 | 
849 |
850 | 851 |

Details

852 |
853 |

Although this media query won't work with eInk devices, it can help solve accessibility issues.

854 |

MacOS and iOS indeed ship with a monochrome switch in system preferences (accessibility panel) which, when enabled, will trigger the monochrome media.

855 |

In other words, you could adapt colors to improve contrast for this mode.

856 |

Make sure to protect this media query from legacy RMSDK’s parsing because your entire stylesheet could be ignored or, even worse, crash some apps and devices.

857 |
858 |
859 |
860 | 861 |
862 |
863 |

Style for touch or click input

864 | 865 |
EPUB3
866 | 867 |
868 |
@media (pointer: coarse) {
869 |   /* Styles for touch input */
870 | }
871 | 
872 | @media (pointer: fine) {
873 |   /* Styles for mouse input */
874 | }
875 | 
876 |
877 | 878 |

Details

879 |
880 |

Footnote references styled as in print (superscript) might provide users with a mediocre experience on touch devices. Fortunately, there is a media for such a case.

881 |

The pointer media returns the accuracy of the primary input mechanism of the device, which means touch will return coarse while mouse will return fine for instance.

882 |

In other words, you could increase the size of clickable elements for coarse.

883 |

Make sure to protect this media query from legacy RMSDK’s parsing because your entire stylesheet could be ignored or, even worse, crash some apps and devices.

884 |
885 |
886 |
887 |
888 |
889 | 890 | 895 | 896 | 897 | 902 | 903 | 904 | -------------------------------------------------------------------------------- /js/script.js: -------------------------------------------------------------------------------- 1 | r(function () { 2 | // VARIABLES 3 | 4 | var details = document.querySelectorAll(".details"); 5 | var main = document.querySelector("main"); 6 | var snippets = document.querySelectorAll(".code-snippet"); 7 | var toggle = document.createElement("button"); 8 | var header = document.querySelector("header"); 9 | var nav = document.querySelector("nav"); 10 | 11 | var mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); 12 | var noMotion = false; 13 | 14 | // FUNCTIONS 15 | 16 | function toggleAria(el) { 17 | if (el.getAttribute("aria-hidden") === "true") { 18 | el.setAttribute("aria-hidden", "false"); 19 | } else { 20 | el.setAttribute("aria-hidden", "true"); 21 | } 22 | }; 23 | 24 | function toggleDetails(trigger) { 25 | var detail = trigger.nextElementSibling; 26 | trigger.classList.toggle("open"); 27 | detail.classList.toggle("hidden"); 28 | toggleAria(detail); 29 | }; 30 | 31 | function scrollTo(element, to, duration) { 32 | if (duration <= 0) return; 33 | var difference = to - element.scrollTop; 34 | var perTick = difference / duration * 10; 35 | setTimeout(function () { 36 | element.scrollTop = element.scrollTop + perTick; 37 | if (element.scrollTop == to) return; 38 | scrollTo(element, to, duration - 10); 39 | }, 10); 40 | }; 41 | 42 | function focusNoScroll(el) { 43 | var x = window.scrollX; 44 | var y = window.scrollY; 45 | el.focus(); 46 | window.scrollTo(x, y); 47 | }; 48 | 49 | function getCodeSnippet(button) { 50 | var parentElement = button.parentElement; 51 | var codeSnippet = parentElement.querySelector("code"); 52 | return codeSnippet; 53 | }; 54 | 55 | function copyToClipboard(button) { 56 | var code = getCodeSnippet(button) 57 | var range = document.createRange(); 58 | range.selectNode(code); 59 | window.getSelection().addRange(range); 60 | 61 | try { 62 | document.execCommand("copy"); 63 | var originalLabel = button.textContent; 64 | button.textContent = "Copied!"; 65 | 66 | setTimeout(function () { 67 | button.textContent = originalLabel; 68 | }, 3500); 69 | } catch (err) { 70 | console.error(err); 71 | } 72 | 73 | window.getSelection().removeAllRanges(); 74 | }; 75 | 76 | function changeMotionHook() { 77 | if (mediaQuery.matches) { 78 | noMotion = true; 79 | } else { 80 | noMotion = false; 81 | } 82 | }; 83 | 84 | // INIT (run on doc Ready) 85 | 86 | (function initMotionHook() { 87 | changeMotionHook(); 88 | })(); 89 | 90 | (function hideDetails() { 91 | document.body.classList.add("js-enabled"); 92 | for (var i = 0; i < details.length; i++) { 93 | var detail = details[i]; 94 | detail.classList.add("hidden"); 95 | detail.setAttribute("aria-hidden", "true"); 96 | detail.setAttribute("aria-live", "polite"); 97 | detail.previousElementSibling.setAttribute("tabindex", "0"); 98 | }; 99 | })(); 100 | 101 | (function initToggle() { 102 | toggle.type = "button"; 103 | toggle.id = "toggle"; 104 | toggle.className = "checkAll"; 105 | toggle.innerHTML = "Expand all details"; 106 | header.appendChild(toggle); 107 | })(); 108 | 109 | (function initCopy() { 110 | for (var i = 0; i < snippets.length; i++) { 111 | var snippet = snippets[i]; 112 | var button = document.createElement("button"); 113 | button.type = button; 114 | button.textContent = "Copy snippet"; 115 | button.className = "copyButton"; 116 | snippet.appendChild(button); 117 | } 118 | })(); 119 | 120 | // Event Listeners 121 | 122 | mediaQuery.addListener(changeMotionHook); 123 | 124 | main.addEventListener("click", function (e) { 125 | var elt = e.target; 126 | var isSummary = elt.classList.contains("summary"); 127 | var isDetailsPara = elt.classList.contains("details-para"); 128 | var isCopyButton = elt.classList.contains("copyButton"); 129 | 130 | if (isSummary) { 131 | e.preventDefault(); 132 | toggleDetails(elt); 133 | } else if (isDetailsPara) { 134 | e.preventDefault(); 135 | } else if (isCopyButton) { 136 | copyToClipboard(elt); 137 | } else { 138 | return; 139 | } 140 | }); 141 | 142 | main.addEventListener("keydown", function (e) { 143 | var active = document.activeElement; 144 | var isSummary = active.classList.contains("summary"); 145 | var isCopyButton = active.classList.contains("copyButton"); 146 | var pressEnter = (e.key === "Enter" || e.keyCode === 13); 147 | var pressSpacebar = (e.key === "Spacebar" || e.keyCode === 32); 148 | 149 | if (isSummary && (pressEnter || pressSpacebar)) { 150 | e.preventDefault(); 151 | e.stopImmediatePropagation(); 152 | toggleDetails(active); 153 | } else if (isCopyButton && (pressEnter || pressSpacebar)) { 154 | e.preventDefault(); 155 | e.stopImmediatePropagation(); 156 | active.click(); 157 | } else { 158 | return; 159 | } 160 | }); 161 | 162 | nav.addEventListener("click", function (e) { 163 | var elt = e.target; 164 | var isLink = elt.tagName.toLowerCase() === "a"; 165 | if (isLink && elt.href) { 166 | if (!noMotion) { 167 | e.preventDefault(); 168 | var selector = "#" + elt.href.split("#")[1]; 169 | var section = document.querySelector(selector); 170 | var sectionPadding = window.getComputedStyle(nav).getPropertyValue("height"); 171 | var sectionTop = section.offsetTop - parseInt(sectionPadding, 10); 172 | scrollTo(document.scrollingElement, sectionTop, 600); 173 | history.replaceState(undefined, undefined, selector); 174 | section.setAttribute("tabindex", "-1"); 175 | focusNoScroll(section); 176 | } 177 | } 178 | }); 179 | 180 | toggle.addEventListener("click", function (e) { 181 | e.preventDefault(); 182 | this.classList.toggle("toggleActive"); 183 | if (this.classList.contains("toggleActive")) { 184 | this.innerHTML = "Collapse all details"; 185 | for (var i = 0; i < details.length; i++) { 186 | var detail = details[i]; 187 | detail.classList.remove("hidden"); 188 | detail.previousElementSibling.classList.add("open"); 189 | detail.setAttribute("aria-hidden", "false"); 190 | }; 191 | } else { 192 | this.innerHTML = "Expand all details"; 193 | for (var i = 0; i < details.length; i++) { 194 | var detail = details[i]; 195 | detail.classList.add("hidden"); 196 | detail.previousElementSibling.classList.remove("open"); 197 | detail.setAttribute("aria-hidden", "true"); 198 | }; 199 | }; 200 | }); 201 | 202 | }); 203 | function r(f) { /in/.test(document.readyState) ? setTimeout("r(" + f + ")", 9) : f() } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blitz eBook Tricks", 3 | "short_name": "BlitzTricks", 4 | "icons": [ 5 | { 6 | "src": "assets/icons/icon-256x256.png", 7 | "sizes": "256x256", 8 | "type": "image/png", 9 | "purpose": "maskable any" 10 | }, 11 | { 12 | "src": "assets/icons/icon-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "maskable any" 16 | } 17 | ], 18 | "start_url": "/eBookTricks/", 19 | "display": "standalone", 20 | "background_color": "#FAFAFA", 21 | "theme_color": "#FAFAFA" 22 | } -------------------------------------------------------------------------------- /sw.js: -------------------------------------------------------------------------------- 1 | /* https://gist.github.com/kosamari/7c5d1e8449b2fbc97d372675f16b566e */ 2 | 3 | var APP_PREFIX = "blitzTricks_" 4 | var VERSION = "v1_0_1" 5 | var CACHE_NAME = APP_PREFIX + VERSION 6 | var URLS = [ 7 | "/eBookTricks/", 8 | "/eBookTricks/index.html", 9 | "/eBookTricks/assets/logo.svg", 10 | "/eBookTricks/assets/favicon.ico", 11 | "/eBookTricks/css/styles.css", 12 | "/eBookTricks/css/print.css", 13 | "/eBookTricks/js/script.js" 14 | ] 15 | 16 | // Respond with cached resources 17 | self.addEventListener("fetch", function (e) { 18 | console.log("fetch request: " + e.request.url) 19 | e.respondWith( 20 | caches.match(e.request).then(function (request) { 21 | return request || fetch(e.request) 22 | }) 23 | ) 24 | }) 25 | 26 | // Cache resources 27 | self.addEventListener("install", function (e) { 28 | e.waitUntil( 29 | caches.open(CACHE_NAME).then(function (cache) { 30 | console.log("installing cache: " + CACHE_NAME) 31 | return cache.addAll(URLS) 32 | }) 33 | ) 34 | }) 35 | 36 | // Delete outdated caches 37 | self.addEventListener("activate", function (e) { 38 | e.waitUntil( 39 | caches.keys().then(function (keyList) { 40 | var cacheWhitelist = keyList.filter(function (key) { 41 | return key.indexOf(APP_PREFIX) 42 | }) 43 | cacheWhitelist.push(CACHE_NAME) 44 | 45 | return Promise.all(keyList.map(function (key, i) { 46 | if (cacheWhitelist.indexOf(key) === -1) { 47 | console.log("deleting cache: " + keyList[i] ) 48 | return caches.delete(keyList[i]) 49 | } 50 | })) 51 | }) 52 | ) 53 | }) --------------------------------------------------------------------------------