From 02c7b32602b722292241dc741b705cb89ecada20 Mon Sep 17 00:00:00 2001 From: Steve Kossouho Date: Fri, 4 Jul 2025 18:33:31 +0200 Subject: [PATCH] Initial commit --- .github/CONTRIBUTING.md | 21 + .github/FUNDING.yml | 1 + .github/workflows/js.yml | 27 + .gitignore | 142 + .npmignore | 7 + LICENSE | 19 + README.md | 50 + css/layout.scss | 69 + css/print/paper.scss | 166 + css/print/pdf.scss | 155 + css/reveal.scss | 1869 +++ css/theme/README.md | 21 + css/theme/source/beige.scss | 41 + css/theme/source/black-contrast.scss | 49 + css/theme/source/black.scss | 46 + css/theme/source/blood.scss | 87 + css/theme/source/dracula.scss | 132 + css/theme/source/league.scss | 36 + css/theme/source/moon.scss | 58 + css/theme/source/night.scss | 37 + css/theme/source/serif.scss | 38 + css/theme/source/simple.scss | 40 + css/theme/source/sky.scss | 49 + css/theme/source/solarized.scss | 63 + css/theme/source/training.scss | 157 + css/theme/source/white-contrast.scss | 49 + css/theme/source/white.scss | 46 + css/theme/template/exposer.scss | 28 + css/theme/template/mixins.scss | 45 + css/theme/template/settings.scss | 45 + css/theme/template/theme.scss | 331 + demo.html | 481 + examples/assets/beeping.txt | 2 + examples/assets/beeping.wav | Bin 0 -> 422472 bytes examples/assets/image1.png | Bin 0 -> 21991 bytes examples/assets/image2.png | Bin 0 -> 10237 bytes examples/auto-animate.html | 225 + examples/backgrounds.html | 141 + examples/barebones.html | 32 + examples/layout-helpers.html | 160 + examples/markdown.html | 142 + examples/markdown.md | 41 + examples/math.html | 206 + examples/media.html | 75 + examples/multiple-presentations.html | 102 + examples/transitions.html | 97 + gulpfile.js | 319 + index.html | 40 + js/components/playback.js | 165 + js/config.js | 300 + js/controllers/autoanimate.js | 640 + js/controllers/backgrounds.js | 406 + js/controllers/controls.js | 266 + js/controllers/focus.js | 103 + js/controllers/fragments.js | 376 + js/controllers/jumptoslide.js | 170 + js/controllers/keyboard.js | 399 + js/controllers/location.js | 245 + js/controllers/notes.js | 120 + js/controllers/overview.js | 255 + js/controllers/plugins.js | 254 + js/controllers/pointer.js | 129 + js/controllers/print.js | 237 + js/controllers/progress.js | 110 + js/controllers/slidecontent.js | 480 + js/controllers/slidenumber.js | 132 + js/controllers/touch.js | 263 + js/index.js | 58 + js/reveal.js | 2840 ++++ js/utils/color.js | 77 + js/utils/constants.js | 10 + js/utils/device.js | 8 + js/utils/loader.js | 46 + js/utils/util.js | 313 + package-lock.json | 17622 ++++++++++++++++++++++++ package.json | 104 + pandoc/monokai.theme | 211 + pandoc/monokaipdf.theme | 211 + plugin/highlight/highlight.esm.js | 5 + plugin/highlight/highlight.js | 5 + plugin/highlight/monokai.css | 71 + plugin/highlight/plugin.js | 439 + plugin/highlight/zenburn.css | 80 + plugin/markdown/markdown.esm.js | 7 + plugin/markdown/markdown.js | 7 + plugin/markdown/plugin.js | 475 + plugin/math/katex.js | 96 + plugin/math/math.esm.js | 6 + plugin/math/math.js | 1 + plugin/math/mathjax2.js | 89 + plugin/math/mathjax3.js | 77 + plugin/math/plugin.js | 15 + plugin/notes/notes.esm.js | 1 + plugin/notes/notes.js | 1 + plugin/notes/plugin.js | 261 + plugin/notes/speaker-view.html | 891 ++ plugin/search/plugin.js | 243 + plugin/search/search.esm.js | 7 + plugin/search/search.js | 7 + plugin/zoom/plugin.js | 264 + plugin/zoom/zoom.esm.js | 11 + plugin/zoom/zoom.js | 11 + test/assets/external-script-a.js | 1 + test/assets/external-script-b.js | 1 + test/assets/external-script-c.js | 1 + test/assets/external-script-d.js | 1 + test/simple.md | 12 + test/test-auto-animate.html | 167 + test/test-dependencies-async.html | 78 + test/test-dependencies.html | 55 + test/test-grid-navigation.html | 75 + test/test-iframe-backgrounds.html | 100 + test/test-iframes.html | 104 + test/test-markdown.html | 482 + test/test-multiple-instances-es5.html | 86 + test/test-multiple-instances.html | 104 + test/test-pdf.html | 97 + test/test-plugins.html | 108 + test/test-state.html | 134 + test/test.html | 900 ++ 120 files changed, 38113 insertions(+) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/js.yml create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 css/layout.scss create mode 100644 css/print/paper.scss create mode 100644 css/print/pdf.scss create mode 100644 css/reveal.scss create mode 100644 css/theme/README.md create mode 100644 css/theme/source/beige.scss create mode 100644 css/theme/source/black-contrast.scss create mode 100644 css/theme/source/black.scss create mode 100644 css/theme/source/blood.scss create mode 100644 css/theme/source/dracula.scss create mode 100644 css/theme/source/league.scss create mode 100644 css/theme/source/moon.scss create mode 100644 css/theme/source/night.scss create mode 100644 css/theme/source/serif.scss create mode 100644 css/theme/source/simple.scss create mode 100644 css/theme/source/sky.scss create mode 100644 css/theme/source/solarized.scss create mode 100644 css/theme/source/training.scss create mode 100644 css/theme/source/white-contrast.scss create mode 100644 css/theme/source/white.scss create mode 100644 css/theme/template/exposer.scss create mode 100644 css/theme/template/mixins.scss create mode 100644 css/theme/template/settings.scss create mode 100644 css/theme/template/theme.scss create mode 100644 demo.html create mode 100644 examples/assets/beeping.txt create mode 100644 examples/assets/beeping.wav create mode 100644 examples/assets/image1.png create mode 100644 examples/assets/image2.png create mode 100644 examples/auto-animate.html create mode 100644 examples/backgrounds.html create mode 100644 examples/barebones.html create mode 100644 examples/layout-helpers.html create mode 100644 examples/markdown.html create mode 100644 examples/markdown.md create mode 100644 examples/math.html create mode 100644 examples/media.html create mode 100644 examples/multiple-presentations.html create mode 100644 examples/transitions.html create mode 100644 gulpfile.js create mode 100644 index.html create mode 100644 js/components/playback.js create mode 100644 js/config.js create mode 100644 js/controllers/autoanimate.js create mode 100644 js/controllers/backgrounds.js create mode 100644 js/controllers/controls.js create mode 100644 js/controllers/focus.js create mode 100644 js/controllers/fragments.js create mode 100644 js/controllers/jumptoslide.js create mode 100644 js/controllers/keyboard.js create mode 100644 js/controllers/location.js create mode 100644 js/controllers/notes.js create mode 100644 js/controllers/overview.js create mode 100644 js/controllers/plugins.js create mode 100644 js/controllers/pointer.js create mode 100644 js/controllers/print.js create mode 100644 js/controllers/progress.js create mode 100644 js/controllers/slidecontent.js create mode 100644 js/controllers/slidenumber.js create mode 100644 js/controllers/touch.js create mode 100644 js/index.js create mode 100644 js/reveal.js create mode 100644 js/utils/color.js create mode 100644 js/utils/constants.js create mode 100644 js/utils/device.js create mode 100644 js/utils/loader.js create mode 100644 js/utils/util.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pandoc/monokai.theme create mode 100644 pandoc/monokaipdf.theme create mode 100644 plugin/highlight/highlight.esm.js create mode 100644 plugin/highlight/highlight.js create mode 100644 plugin/highlight/monokai.css create mode 100644 plugin/highlight/plugin.js create mode 100644 plugin/highlight/zenburn.css create mode 100644 plugin/markdown/markdown.esm.js create mode 100644 plugin/markdown/markdown.js create mode 100644 plugin/markdown/plugin.js create mode 100644 plugin/math/katex.js create mode 100644 plugin/math/math.esm.js create mode 100644 plugin/math/math.js create mode 100644 plugin/math/mathjax2.js create mode 100644 plugin/math/mathjax3.js create mode 100644 plugin/math/plugin.js create mode 100644 plugin/notes/notes.esm.js create mode 100644 plugin/notes/notes.js create mode 100644 plugin/notes/plugin.js create mode 100644 plugin/notes/speaker-view.html create mode 100644 plugin/search/plugin.js create mode 100644 plugin/search/search.esm.js create mode 100644 plugin/search/search.js create mode 100644 plugin/zoom/plugin.js create mode 100644 plugin/zoom/zoom.esm.js create mode 100644 plugin/zoom/zoom.js create mode 100644 test/assets/external-script-a.js create mode 100644 test/assets/external-script-b.js create mode 100644 test/assets/external-script-c.js create mode 100644 test/assets/external-script-d.js create mode 100644 test/simple.md create mode 100644 test/test-auto-animate.html create mode 100644 test/test-dependencies-async.html create mode 100644 test/test-dependencies.html create mode 100644 test/test-grid-navigation.html create mode 100644 test/test-iframe-backgrounds.html create mode 100644 test/test-iframes.html create mode 100644 test/test-markdown.html create mode 100644 test/test-multiple-instances-es5.html create mode 100644 test/test-multiple-instances.html create mode 100644 test/test-pdf.html create mode 100644 test/test-plugins.html create mode 100644 test/test-state.html create mode 100644 test/test.html diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..bd0fab2 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,21 @@ +## Contributing +Please keep the [issue tracker](https://github.com/hakimel/reveal.js/issues) limited to **bug reports**. + + +### General Questions and Support +If you have questions about how to use reveal.js the best place to ask is in the [Discussions](https://github.com/hakimel/reveal.js/discussions). Anything that isn't a bug report should be posted as a dicussion instead. + + +### Bug Reports +When reporting a bug make sure to include information about which browser and operating system you are on as well as the necessary steps to reproduce the issue. If possible please include a link to a sample presentation where the bug can be tested. + + +### Pull Requests +- Should be submitted from a feature/topic branch (not your master) +- Should follow the coding style of the file you work in, most importantly: + - Tabs to indent + - Single-quoted strings + + +### Plugins +Please do not submit plugins as pull requests. They should be maintained in their own separate repository. More information here: https://github.com/hakimel/reveal.js/wiki/Plugin-Guidelines diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..972831e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [hakimel] diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml new file mode 100644 index 0000000..af86bea --- /dev/null +++ b/.github/workflows/js.yml @@ -0,0 +1,27 @@ +name: tests + +on: [push] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run build --if-present + - run: npm test + env: + CI: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e68985 --- /dev/null +++ b/.gitignore @@ -0,0 +1,142 @@ +.idea/ +*.iml +*.iws +*.eml +out/ +.DS_Store +.svn +log/*.log +tmp/** +node_modules/ +.sass-cache### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..50c12b9 --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +/test +/examples +.github +.gulpfile +.sass-cache +gulpfile.js +CONTRIBUTING.md \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0de9fdd --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2011-2023 Hakim El Hattab, http://hakim.se, and reveal.js contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..db584dc --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +

+ + reveal.js + +

+ + Slides +

+ +reveal.js is an open source HTML presentation framework. It enables anyone with a web browser to create beautiful presentations for free. Check out the live demo at [revealjs.com](https://revealjs.com/). + +The framework comes with a powerful feature set including [nested slides](https://revealjs.com/vertical-slides/), [Markdown support](https://revealjs.com/markdown/), [Auto-Animate](https://revealjs.com/auto-animate/), [PDF export](https://revealjs.com/pdf-export/), [speaker notes](https://revealjs.com/speaker-view/), [LaTeX typesetting](https://revealjs.com/math/), [syntax highlighted code](https://revealjs.com/code/) and an [extensive API](https://revealjs.com/api/). + +--- + +Want to create reveal.js presentation in a graphical editor? Try . It's made by the same people behind reveal.js. + +--- + +### Sponsors +Hakim's open source work is supported by GitHub sponsors. Special thanks to: +
+ + +
+ +
+ WorkOS +
+ Your app, enterprise-ready. +
+ Start selling to enterprise customers with just a few lines of code. Add Single Sign-On (and more) in minutes instead of months. +
+
+
+
+ +--- + +### Getting started +- 🚀 [Install reveal.js](https://revealjs.com/installation) +- 👀 [View the demo presentation](https://revealjs.com/demo) +- 📖 [Read the documentation](https://revealjs.com/markup/) +- 🖌 [Try the visual editor for reveal.js at Slides.com](https://slides.com/) +- 🎬 [Watch the reveal.js video course (paid)](https://revealjs.com/course) + +--- +
+ MIT licensed | Copyright © 2011-2023 Hakim El Hattab, https://hakim.se +
diff --git a/css/layout.scss b/css/layout.scss new file mode 100644 index 0000000..f499fdd --- /dev/null +++ b/css/layout.scss @@ -0,0 +1,69 @@ +/** + * Layout helpers. + */ + +// Stretch an element vertically based on available space +.reveal .stretch, +.reveal .r-stretch { + max-width: none; + max-height: none; +} + +.reveal pre.stretch code, +.reveal pre.r-stretch code { + height: 100%; + max-height: 100%; + box-sizing: border-box; +} + +// Text that auto-fits its container +.reveal .r-fit-text { + display: inline-block; // https://github.com/rikschennink/fitty#performance + white-space: nowrap; +} + +// Stack multiple elements on top of each other +.reveal .r-stack { + display: grid; +} + +.reveal .r-stack > * { + grid-area: 1/1; + margin: auto; +} + +// Horizontal and vertical stacks +.reveal .r-vstack, +.reveal .r-hstack { + display: flex; + + img, video { + min-width: 0; + min-height: 0; + 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; +} + +// Naming based on tailwindcss +.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; } diff --git a/css/print/paper.scss b/css/print/paper.scss new file mode 100644 index 0000000..32fab8a --- /dev/null +++ b/css/print/paper.scss @@ -0,0 +1,166 @@ + +@media print { + html:not(.print-pdf) { + overflow: visible; + width: auto; + height: auto; + + body { + margin: 0; + padding: 0; + overflow: visible; + } + } + + html:not(.print-pdf) .reveal { + background: #fff; + font-size: 20pt; + + .controls, + .state-background, + .progress, + .backgrounds, + .slide-number { + display: none !important; + } + + p, td, li { + font-size: 20pt!important; + color: #000; + } + + h1,h2,h3,h4,h5,h6 { + color: #000!important; + height: auto; + line-height: normal; + text-align: left; + letter-spacing: normal; + } + + h1 { font-size: 28pt !important; } + h2 { font-size: 24pt !important; } + h3 { font-size: 22pt !important; } + h4 { font-size: 22pt !important; font-variant: small-caps; } + h5 { font-size: 21pt !important; } + h6 { font-size: 20pt !important; font-style: italic; } + + a:link, + a:visited { + color: #000 !important; + font-weight: bold; + text-decoration: underline; + } + + ul, ol, div, p { + visibility: visible; + position: static; + width: auto; + height: auto; + display: block; + overflow: visible; + margin: 0; + text-align: left !important; + } + pre, + table { + margin-left: 0; + margin-right: 0; + } + pre code { + padding: 20px; + } + blockquote { + margin: 20px 0; + } + + .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%; + } + .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; + } + .slides section.stack { + padding: 0 !important; + } + .slides section:last-of-type { + page-break-after: avoid !important; + } + .slides section .fragment { + opacity: 1 !important; + visibility: visible !important; + + transform: none !important; + } + + .r-fit-text { + white-space: normal !important; + } + + section img { + display: block; + margin: 15px 0px; + background: rgba(255,255,255,1); + border: 1px solid #666; + box-shadow: none; + } + + section small { + font-size: 0.8em; + } + + .hljs { + max-height: 100%; + white-space: pre-wrap; + word-wrap: break-word; + word-break: break-word; + font-size: 15pt; + } + + .hljs .hljs-ln-numbers { + white-space: nowrap; + } + + .hljs td { + font-size: inherit !important; + color: inherit !important; + } + } +} diff --git a/css/print/pdf.scss b/css/print/pdf.scss new file mode 100644 index 0000000..6113810 --- /dev/null +++ b/css/print/pdf.scss @@ -0,0 +1,155 @@ +/** + * This stylesheet is used to print reveal.js + * presentations to PDF. + * + * https://revealjs.com/pdf-export/ + */ + +html.print-pdf { + * { + -webkit-print-color-adjust: exact; + } + + & { + width: 100%; + height: 100%; + overflow: visible; + } + + body { + margin: 0 auto !important; + border: 0; + padding: 0; + float: none !important; + overflow: visible; + } + + /* Remove any elements not needed in print. */ + .nestedarrow, + .reveal .controls, + .reveal .progress, + .reveal .playback, + .reveal.overview, + .state-background { + display: none !important; + } + + .reveal pre code { + overflow: hidden !important; + font-family: Courier, 'Courier New', monospace !important; + } + + .reveal { + width: auto !important; + height: auto !important; + overflow: hidden !important; + } + .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%; + } + + .reveal .slides .pdf-page { + position: relative; + overflow: hidden; + z-index: 1; + + page-break-after: always; + } + + .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; + } + + .reveal section.stack { + position: relative !important; + margin: 0 !important; + padding: 0 !important; + page-break-after: avoid !important; + height: auto !important; + min-height: auto !important; + } + + .reveal img { + box-shadow: none; + } + + /* Slide backgrounds are placed inside of their slide when exporting to PDF */ + .reveal .backgrounds { + display: none; + } + .reveal .slide-background { + display: block !important; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: auto !important; + } + + /* Display slide speaker notes when 'showNotes' is enabled */ + .reveal.show-notes { + max-width: none; + max-height: none; + } + .reveal .speaker-notes-pdf { + display: block; + width: 100%; + height: auto; + max-height: none; + top: auto; + right: auto; + bottom: auto; + left: auto; + z-index: 100; + } + + /* Layout option which makes notes appear on a separate page */ + .reveal .speaker-notes-pdf[data-layout="separate-page"] { + position: relative; + color: inherit; + background-color: transparent; + padding: 20px; + page-break-after: always; + border: 0; + } + + /* Display slide numbers when 'slideNumber' is enabled */ + .reveal .slide-number-pdf { + display: block; + position: absolute; + font-size: 14px; + } + + /* This accessibility tool is not useful in PDF and breaks it visually */ + .aria-status { + display: none; + } +} diff --git a/css/reveal.scss b/css/reveal.scss new file mode 100644 index 0000000..6f741e0 --- /dev/null +++ b/css/reveal.scss @@ -0,0 +1,1869 @@ +@use "sass:math"; + +/** + * reveal.js + * http://revealjs.com + * MIT licensed + * + * Copyright (C) Hakim El Hattab, https://hakim.se + */ + +@import 'layout'; + +/********************************************* + * GLOBAL STYLES + *********************************************/ + +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; +} + +// Force the presentation to cover the full viewport when we +// enter fullscreen mode. Fixes sizing issues in Safari. +.reveal-viewport:fullscreen { + top: 0 !important; + left: 0 !important; + width: 100% !important; + height: 100% !important; + transform: none !important; +} + + +/********************************************* + * VIEW FRAGMENTS + *********************************************/ + +.reveal .fragment { + transition: all .2s ease; + + &:not(.custom) { + opacity: 0; + visibility: hidden; + will-change: opacity; + } + + &.visible { + opacity: 1; + visibility: inherit; + } + + &.disabled { + transition: none; + } +} + +.reveal .fragment.grow { + opacity: 1; + visibility: inherit; + + &.visible { + transform: scale( 1.3 ); + } +} + +.reveal .fragment.shrink { + opacity: 1; + visibility: inherit; + + &.visible { + transform: scale( 0.7 ); + } +} + +.reveal .fragment.zoom-in { + transform: scale( 0.1 ); + + &.visible { + transform: none; + } +} + +.reveal .fragment.fade-out { + opacity: 1; + visibility: inherit; + + &.visible { + opacity: 0; + visibility: hidden; + } +} + +.reveal .fragment.semi-fade-out { + opacity: 1; + visibility: inherit; + + &.visible { + opacity: 0.5; + visibility: inherit; + } +} + +.reveal .fragment.strike { + opacity: 1; + visibility: inherit; + + &.visible { + text-decoration: line-through; + } +} + +.reveal .fragment.fade-up { + transform: translate(0, 40px); + + &.visible { + transform: translate(0, 0); + } +} + +.reveal .fragment.fade-down { + transform: translate(0, -40px); + + &.visible { + transform: translate(0, 0); + } +} + +.reveal .fragment.fade-right { + transform: translate(-40px, 0); + + &.visible { + transform: translate(0, 0); + } +} + +.reveal .fragment.fade-left { + transform: translate(40px, 0); + + &.visible { + transform: translate(0, 0); + } +} + +.reveal .fragment.fade-in-then-out, +.reveal .fragment.current-visible { + opacity: 0; + visibility: hidden; + + &.current-fragment { + opacity: 1; + visibility: inherit; + } +} + +.reveal .fragment.fade-in-then-semi-out { + opacity: 0; + visibility: hidden; + + &.visible { + opacity: 0.5; + visibility: inherit; + } + + &.current-fragment { + opacity: 1; + visibility: inherit; + } +} + +.reveal .fragment.highlight-red, +.reveal .fragment.highlight-current-red, +.reveal .fragment.highlight-green, +.reveal .fragment.highlight-current-green, +.reveal .fragment.highlight-blue, +.reveal .fragment.highlight-current-blue { + opacity: 1; + visibility: inherit; +} + .reveal .fragment.highlight-red.visible { + color: #ff2c2d + } + .reveal .fragment.highlight-green.visible { + color: #17ff2e; + } + .reveal .fragment.highlight-blue.visible { + color: #1b91ff; + } + +.reveal .fragment.highlight-current-red.current-fragment { + color: #ff2c2d +} +.reveal .fragment.highlight-current-green.current-fragment { + color: #17ff2e; +} +.reveal .fragment.highlight-current-blue.current-fragment { + color: #1b91ff; +} + + +/********************************************* + * DEFAULT ELEMENT STYLES + *********************************************/ + +/* Fixes issue in Chrome where italic fonts did not appear when printing to PDF */ +.reveal:after { + content: ''; + font-style: italic; +} + +.reveal iframe { + z-index: 1; +} + +/** Prevents layering issues in certain browser/transition combinations */ +.reveal a { + position: relative; +} + + +/********************************************* + * CONTROLS + *********************************************/ + +@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);} +} + +$controlArrowSize: 3.6em; +$controlArrowSpacing: 1.4em; +$controlArrowLength: 2.6em; +$controlArrowThickness: 0.5em; +$controlsArrowAngle: 45deg; +$controlsArrowAngleHover: 40deg; +$controlsArrowAngleActive: 36deg; + +@mixin controlsArrowTransform( $angle ) { + &:before { + transform: translateX(($controlArrowSize - $controlArrowLength)*0.5) translateY(($controlArrowSize - $controlArrowThickness)*0.5) rotate( $angle ); + } + + &:after { + transform: translateX(($controlArrowSize - $controlArrowLength)*0.5) translateY(($controlArrowSize - $controlArrowThickness)*0.5) rotate( -$angle ); + } +} + +.reveal .controls { + $spacing: 12px; + + display: none; + position: absolute; + top: auto; + bottom: $spacing; + right: $spacing; + left: auto; + z-index: 11; + color: #000; + pointer-events: none; + font-size: 10px; + + button { + position: absolute; + padding: 0; + background-color: transparent; + border: 0; + outline: 0; + cursor: pointer; + color: currentColor; + transform: scale(.9999); + transition: color 0.2s ease, + opacity 0.2s ease, + transform 0.2s ease; + z-index: 2; // above slides + pointer-events: auto; + font-size: inherit; + + visibility: hidden; + opacity: 0; + + -webkit-appearance: none; + -webkit-tap-highlight-color: rgba( 0, 0, 0, 0 ); + } + + .controls-arrow:before, + .controls-arrow:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: $controlArrowLength; + height: $controlArrowThickness; + border-radius: $controlArrowThickness*0.5; + background-color: currentColor; + + transition: all 0.15s ease, background-color 0.8s ease; + transform-origin: math.div(floor(($controlArrowThickness*0.5)*10), 10) 50%; + will-change: transform; + } + + .controls-arrow { + position: relative; + width: $controlArrowSize; + height: $controlArrowSize; + + @include controlsArrowTransform( $controlsArrowAngle ); + + &:hover { + @include controlsArrowTransform( $controlsArrowAngleHover ); + } + + &:active { + @include controlsArrowTransform( $controlsArrowAngleActive ); + } + } + + .navigate-left { + right: $controlArrowSize + $controlArrowSpacing*2; + bottom: $controlArrowSpacing + $controlArrowSize*0.5; + transform: translateX( -10px ); + + &.highlight { + animation: bounce-left 2s 50 both ease-out; + } + } + + .navigate-right { + right: 0; + bottom: $controlArrowSpacing + $controlArrowSize*0.5; + transform: translateX( 10px ); + + .controls-arrow { + transform: rotate( 180deg ); + } + + &.highlight { + animation: bounce-right 2s 50 both ease-out; + } + } + + .navigate-up { + right: $controlArrowSpacing + $controlArrowSize*0.5; + bottom: $controlArrowSpacing*2 + $controlArrowSize; + transform: translateY( -10px ); + + .controls-arrow { + transform: rotate( 90deg ); + } + } + + .navigate-down { + right: $controlArrowSpacing + $controlArrowSize*0.5; + bottom: -$controlArrowSpacing; + padding-bottom: $controlArrowSpacing; + transform: translateY( 10px ); + + .controls-arrow { + transform: rotate( -90deg ); + } + + &.highlight { + animation: bounce-down 2s 50 both ease-out; + } + } + + // Back arrow style: "faded": + // Deemphasize backwards navigation arrows in favor of drawing + // attention to forwards navigation + &[data-controls-back-arrows="faded"] .navigate-up.enabled { + opacity: 0.3; + + &:hover { + opacity: 1; + } + } + + // Back arrow style: "hidden": + // Never show arrows for backwards navigation + &[data-controls-back-arrows="hidden"] .navigate-up.enabled { + opacity: 0; + visibility: hidden; + } + + // Any control button that can be clicked is "enabled" + .enabled { + visibility: visible; + opacity: 0.9; + cursor: pointer; + transform: none; + } + + // Any control button that leads to showing or hiding + // a fragment + .enabled.fragmented { + opacity: 0.5; + } + + .enabled:hover, + .enabled.fragmented:hover { + opacity: 1; + } +} + +.reveal:not(.rtl) .controls { + // Back arrow style: "faded": + // Deemphasize left arrow + &[data-controls-back-arrows="faded"] .navigate-left.enabled { + opacity: 0.3; + + &:hover { + opacity: 1; + } + } + + // Back arrow style: "hidden": + // Never show left arrow + &[data-controls-back-arrows="hidden"] .navigate-left.enabled { + opacity: 0; + visibility: hidden; + } +} + +.reveal.rtl .controls { + // Back arrow style: "faded": + // Deemphasize right arrow in RTL mode + &[data-controls-back-arrows="faded"] .navigate-right.enabled { + opacity: 0.3; + + &:hover { + opacity: 1; + } + } + + // Back arrow style: "hidden": + // Never show right arrow in RTL mode + &[data-controls-back-arrows="hidden"] .navigate-right.enabled { + opacity: 0; + visibility: hidden; + } +} + +.reveal[data-navigation-mode="linear"].has-horizontal-slides .navigate-up, +.reveal[data-navigation-mode="linear"].has-horizontal-slides .navigate-down { + display: none; +} + +// Adjust the layout when there are no vertical slides +.reveal[data-navigation-mode="linear"].has-horizontal-slides .navigate-left, +.reveal:not(.has-vertical-slides) .controls .navigate-left { + bottom: $controlArrowSpacing; + right: 0.5em + $controlArrowSpacing + $controlArrowSize; +} + +.reveal[data-navigation-mode="linear"].has-horizontal-slides .navigate-right, +.reveal:not(.has-vertical-slides) .controls .navigate-right { + bottom: $controlArrowSpacing; + right: 0.5em; +} + +// Adjust the layout when there are no horizontal slides +.reveal:not(.has-horizontal-slides) .controls .navigate-up { + right: $controlArrowSpacing; + bottom: $controlArrowSpacing + $controlArrowSize; +} +.reveal:not(.has-horizontal-slides) .controls .navigate-down { + right: $controlArrowSpacing; + bottom: 0.5em; +} + +// Invert arrows based on background color +.reveal.has-dark-background .controls { + color: #fff; +} +.reveal.has-light-background .controls { + color: #000; +} + +// Disable active states on touch devices +.reveal.no-hover .controls .controls-arrow:hover, +.reveal.no-hover .controls .controls-arrow:active { + @include controlsArrowTransform( $controlsArrowAngle ); +} + +// Edge aligned controls layout +@media screen and (min-width: 500px) { + + $spacing: 0.8em; + + .reveal .controls[data-controls-layout="edges"] { + & { + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .navigate-left, + .navigate-right, + .navigate-up, + .navigate-down { + bottom: auto; + right: auto; + } + + .navigate-left { + top: 50%; + left: $spacing; + margin-top: -$controlArrowSize*0.5; + } + + .navigate-right { + top: 50%; + right: $spacing; + margin-top: -$controlArrowSize*0.5; + } + + .navigate-up { + top: $spacing; + left: 50%; + margin-left: -$controlArrowSize*0.5; + } + + .navigate-down { + bottom: $spacing - $controlArrowSpacing + 0.3em; + left: 50%; + margin-left: -$controlArrowSize*0.5; + } + } + +} + + +/********************************************* + * PROGRESS BAR + *********************************************/ + +.reveal .progress { + position: absolute; + display: none; + height: 3px; + width: 100%; + bottom: 0; + left: 0; + z-index: 10; + + background-color: rgba( 0, 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 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985); + transform-origin: 0 0; + transform: scaleX(0); + } + +/********************************************* + * SLIDE NUMBER + *********************************************/ + +.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, 0.4 ); + padding: 5px; +} + +.reveal .slide-number a { + color: currentColor; +} + +.reveal .slide-number-delimiter { + margin: 0 3px; +} + +/********************************************* + * SLIDES + *********************************************/ + +.reveal { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + touch-action: pinch-zoom; +} + +// Swiping on an embedded deck should not block page scrolling +.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 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985), + transform 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985), + visibility 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985), + opacity 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985); +} + +/* Global transition speed settings */ +.reveal[data-transition-speed="fast"] .slides section { + transition-duration: 400ms; +} +.reveal[data-transition-speed="slow"] .slides section { + transition-duration: 1200ms; +} + +/* Slide-specific transition speed overrides */ +.reveal .slides section[data-transition-speed="fast"] { + transition-duration: 400ms; +} +.reveal .slides section[data-transition-speed="slow"] { + transition-duration: 1200ms; +} + +.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[data-background-interactive], +.reveal .slides>section>section[data-background-interactive] { + pointer-events: none; +} + +.reveal.center, +.reveal.center .slides, +.reveal.center .slides section { + min-height: 0 !important; +} + +/* Don't allow interaction with invisible slides */ +.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.past, +.reveal .slides>section.future, +.reveal .slides>section.past>section, +.reveal .slides>section.future>section, +.reveal .slides>section>section.past, +.reveal .slides>section>section.future { + opacity: 0; +} + + +/********************************************* + * Mixins for readability of transitions + *********************************************/ + +@mixin transition-global($style) { + .reveal .slides section[data-transition=#{$style}], + .reveal.#{$style} .slides section:not([data-transition]) { + @content; + } +} +@mixin transition-stack($style) { + .reveal .slides section[data-transition=#{$style}].stack, + .reveal.#{$style} .slides section.stack { + @content; + } +} +@mixin transition-horizontal-past($style) { + .reveal .slides>section[data-transition=#{$style}].past, + .reveal .slides>section[data-transition~=#{$style}-out].past, + .reveal.#{$style} .slides>section:not([data-transition]).past { + @content; + } +} +@mixin transition-horizontal-future($style) { + .reveal .slides>section[data-transition=#{$style}].future, + .reveal .slides>section[data-transition~=#{$style}-in].future, + .reveal.#{$style} .slides>section:not([data-transition]).future { + @content; + } +} + +@mixin transition-vertical-past($style) { + .reveal .slides>section>section[data-transition=#{$style}].past, + .reveal .slides>section>section[data-transition~=#{$style}-out].past, + .reveal.#{$style} .slides>section>section:not([data-transition]).past { + @content; + } +} +@mixin transition-vertical-future($style) { + .reveal .slides>section>section[data-transition=#{$style}].future, + .reveal .slides>section>section[data-transition~=#{$style}-in].future, + .reveal.#{$style} .slides>section>section:not([data-transition]).future { + @content; + } +} + +/********************************************* + * SLIDE TRANSITION + * Aliased 'linear' for backwards compatibility + *********************************************/ + +@each $stylename in slide, linear { + @include transition-horizontal-past(#{$stylename}) { + transform: translate(-150%, 0); + } + @include transition-horizontal-future(#{$stylename}) { + transform: translate(150%, 0); + } + @include transition-vertical-past(#{$stylename}) { + transform: translate(0, -150%); + } + @include transition-vertical-future(#{$stylename}) { + transform: translate(0, 150%); + } +} + +/********************************************* + * CONVEX TRANSITION + * Aliased 'default' for backwards compatibility + *********************************************/ + +@each $stylename in default, convex { + @include transition-stack(#{$stylename}) { + transform-style: preserve-3d; + } + + @include transition-horizontal-past(#{$stylename}) { + transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); + } + @include transition-horizontal-future(#{$stylename}) { + transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); + } + @include transition-vertical-past(#{$stylename}) { + transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0); + } + @include transition-vertical-future(#{$stylename}) { + transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0); + } +} + +/********************************************* + * CONCAVE TRANSITION + *********************************************/ + +@include transition-stack(concave) { + transform-style: preserve-3d; +} + +@include transition-horizontal-past(concave) { + transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); +} +@include transition-horizontal-future(concave) { + transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); +} +@include transition-vertical-past(concave) { + transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0); +} +@include transition-vertical-future(concave) { + transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0); +} + + +/********************************************* + * ZOOM TRANSITION + *********************************************/ + +@include transition-global(zoom) { + transition-timing-function: ease; +} +@include transition-horizontal-past(zoom) { + visibility: hidden; + transform: scale(16); +} +@include transition-horizontal-future(zoom) { + visibility: hidden; + transform: scale(0.2); +} +@include transition-vertical-past(zoom) { + transform: scale(16); +} +@include transition-vertical-future(zoom) { + transform: scale(0.2); +} + + +/********************************************* + * CUBE TRANSITION + * + * WARNING: + * this is deprecated and will be removed in a + * future version. + *********************************************/ + +.reveal.cube .slides { + perspective: 1300px; +} + +.reveal.cube .slides section { + padding: 30px; + min-height: 700px; + 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,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: none; + z-index: 1; + + border-radius: 4px; + box-shadow: 0px 95px 25px rgba(0,0,0,0.2); + transform: translateZ(-90px) rotateX( 65deg ); + } + +.reveal.cube .slides>section.stack { + padding: 0; + background: none; +} + +.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); +} + + +/********************************************* + * PAGE TRANSITION + * + * WARNING: + * this is deprecated and will be removed in a + * future version. + *********************************************/ + +.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,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: none; + z-index: 1; + + border-radius: 4px; + box-shadow: 0px 95px 25px rgba(0,0,0,0.2); + + -webkit-transform: translateZ(-90px) rotateX( 65deg ); + } + +.reveal.page .slides>section.stack { + padding: 0; + background: none; +} + +.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); +} + + +/********************************************* + * FADE TRANSITION + *********************************************/ + +.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 0.5s; +} + + +.reveal.fade.overview .slides section, +.reveal.fade.overview .slides>section>section { + transition: none; +} + + +/********************************************* + * NO TRANSITION + *********************************************/ + +@include transition-global(none) { + transform: none; + transition: none; +} + + +/********************************************* + * PAUSED MODE + *********************************************/ + +.reveal .pause-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: black; + 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: transparent; + cursor: pointer; + + &:hover { + color: #fff; + border-color: #fff; + } +} + +.reveal.paused .pause-overlay { + visibility: visible; + opacity: 1; +} + + +/********************************************* + * FALLBACK + *********************************************/ + +.reveal .no-transition, +.reveal .no-transition *, +.reveal .slides.disable-slide-transitions section { + transition: none !important; +} + +.reveal .slides.disable-slide-transitions section { + transform: none !important; +} + + +/********************************************* + * PER-SLIDE BACKGROUNDS + *********************************************/ + +.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 800ms cubic-bezier(0.260, 0.860, 0.440, 0.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; + } + +/* Video backgrounds */ +.reveal .slide-background video { + position: absolute; + width: 100%; + height: 100%; + max-width: none; + max-height: none; + top: 0; + left: 0; + object-fit: cover; +} + .reveal .slide-background[data-background-size="contain"] video { + object-fit: contain; + } + +/* Immediate transition style */ +.reveal[data-background-transition=none]>.backgrounds .slide-background:not([data-background-transition]), +.reveal>.backgrounds .slide-background[data-background-transition=none] { + transition: none; +} + +/* Slide */ +.reveal[data-background-transition=slide]>.backgrounds .slide-background:not([data-background-transition]), +.reveal>.backgrounds .slide-background[data-background-transition=slide] { + opacity: 1; +} + .reveal[data-background-transition=slide]>.backgrounds .slide-background.past:not([data-background-transition]), + .reveal>.backgrounds .slide-background.past[data-background-transition=slide] { + transform: translate(-100%, 0); + } + .reveal[data-background-transition=slide]>.backgrounds .slide-background.future:not([data-background-transition]), + .reveal>.backgrounds .slide-background.future[data-background-transition=slide] { + transform: translate(100%, 0); + } + + .reveal[data-background-transition=slide]>.backgrounds .slide-background>.slide-background.past:not([data-background-transition]), + .reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=slide] { + transform: translate(0, -100%); + } + .reveal[data-background-transition=slide]>.backgrounds .slide-background>.slide-background.future:not([data-background-transition]), + .reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=slide] { + transform: translate(0, 100%); + } + + +/* Convex */ +.reveal[data-background-transition=convex]>.backgrounds .slide-background.past:not([data-background-transition]), +.reveal>.backgrounds .slide-background.past[data-background-transition=convex] { + opacity: 0; + transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); +} +.reveal[data-background-transition=convex]>.backgrounds .slide-background.future:not([data-background-transition]), +.reveal>.backgrounds .slide-background.future[data-background-transition=convex] { + opacity: 0; + transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); +} + +.reveal[data-background-transition=convex]>.backgrounds .slide-background>.slide-background.past:not([data-background-transition]), +.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=convex] { + opacity: 0; + transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0); +} +.reveal[data-background-transition=convex]>.backgrounds .slide-background>.slide-background.future:not([data-background-transition]), +.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=convex] { + opacity: 0; + transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0); +} + + +/* Concave */ +.reveal[data-background-transition=concave]>.backgrounds .slide-background.past:not([data-background-transition]), +.reveal>.backgrounds .slide-background.past[data-background-transition=concave] { + opacity: 0; + transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); +} +.reveal[data-background-transition=concave]>.backgrounds .slide-background.future:not([data-background-transition]), +.reveal>.backgrounds .slide-background.future[data-background-transition=concave] { + opacity: 0; + transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); +} + +.reveal[data-background-transition=concave]>.backgrounds .slide-background>.slide-background.past:not([data-background-transition]), +.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=concave] { + opacity: 0; + transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0); +} +.reveal[data-background-transition=concave]>.backgrounds .slide-background>.slide-background.future:not([data-background-transition]), +.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=concave] { + opacity: 0; + transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0); +} + +/* Zoom */ +.reveal[data-background-transition=zoom]>.backgrounds .slide-background:not([data-background-transition]), +.reveal>.backgrounds .slide-background[data-background-transition=zoom] { + transition-timing-function: ease; +} + +.reveal[data-background-transition=zoom]>.backgrounds .slide-background.past:not([data-background-transition]), +.reveal>.backgrounds .slide-background.past[data-background-transition=zoom] { + opacity: 0; + visibility: hidden; + transform: scale(16); +} +.reveal[data-background-transition=zoom]>.backgrounds .slide-background.future:not([data-background-transition]), +.reveal>.backgrounds .slide-background.future[data-background-transition=zoom] { + opacity: 0; + visibility: hidden; + transform: scale(0.2); +} + +.reveal[data-background-transition=zoom]>.backgrounds .slide-background>.slide-background.past:not([data-background-transition]), +.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=zoom] { + opacity: 0; + visibility: hidden; + transform: scale(16); +} +.reveal[data-background-transition=zoom]>.backgrounds .slide-background>.slide-background.future:not([data-background-transition]), +.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=zoom] { + opacity: 0; + visibility: hidden; + transform: scale(0.2); +} + + +/* Global transition speed settings */ +.reveal[data-transition-speed="fast"]>.backgrounds .slide-background { + transition-duration: 400ms; +} +.reveal[data-transition-speed="slow"]>.backgrounds .slide-background { + transition-duration: 1200ms; +} + + +/********************************************* + * AUTO ANIMATE + *********************************************/ + +.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; +} + + +/********************************************* + * OVERVIEW + *********************************************/ + +.reveal.overview { + perspective-origin: 50% 50%; + perspective: 700px; + + .slides { + // Fixes overview rendering errors in FF48+, not applied to + // other browsers since it degrades performance + -moz-transform-style: preserve-3d; + } + + .slides section { + height: 100%; + top: 0 !important; + opacity: 1 !important; + overflow: hidden; + visibility: visible !important; + cursor: pointer; + box-sizing: border-box; + } + .slides section:hover, + .slides section.present { + outline: 10px solid rgba(150,150,150,0.4); + outline-offset: 10px; + } + .slides section .fragment { + opacity: 1; + transition: none; + } + .slides section:after, + .slides section:before { + display: none !important; + } + .slides>section.stack { + padding: 0; + top: 0 !important; + background: none; + outline: none; + overflow: visible; + } + + .backgrounds { + perspective: inherit; + + // Fixes overview rendering errors in FF48+, not applied to + // other browsers since it degrades performance + -moz-transform-style: preserve-3d; + } + + .backgrounds .slide-background { + opacity: 1; + visibility: visible; + + // This can't be applied to the slide itself in Safari + outline: 10px solid rgba(150,150,150,0.1); + outline-offset: 10px; + } + + .backgrounds .slide-background.stack { + overflow: visible; + } +} + +// Disable transitions transitions while we're activating +// or deactivating the overview mode. +.reveal.overview .slides section, +.reveal.overview-deactivating .slides section { + transition: none; +} + +.reveal.overview .backgrounds .slide-background, +.reveal.overview-deactivating .backgrounds .slide-background { + transition: none; +} + + +/********************************************* + * RTL SUPPORT + *********************************************/ + +.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 pre, +.reveal.rtl code { + direction: ltr; +} + +.reveal.rtl ol, +.reveal.rtl ul { + text-align: right; +} + +.reveal.rtl .progress span { + transform-origin: 100% 0; +} + +/********************************************* + * PARALLAX BACKGROUND + *********************************************/ + +.reveal.has-parallax-background .backgrounds { + transition: all 0.8s ease; +} + +/* Global transition speed settings */ +.reveal.has-parallax-background[data-transition-speed="fast"] .backgrounds { + transition-duration: 400ms; +} +.reveal.has-parallax-background[data-transition-speed="slow"] .backgrounds { + transition-duration: 1200ms; +} + + +/********************************************* + * OVERLAY FOR LINK PREVIEWS AND HELP + *********************************************/ + +$overlayHeaderHeight: 40px; +$overlayHeaderPadding: 5px; + +.reveal > .overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + background: rgba( 0, 0, 0, 0.9 ); + transition: all 0.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: 0.6; + transition: all 0.3s ease; + } + + .reveal > .overlay header { + position: absolute; + left: 0; + top: 0; + width: 100%; + padding: $overlayHeaderPadding; + z-index: 2; + box-sizing: border-box; + } + .reveal > .overlay header a { + display: inline-block; + width: $overlayHeaderHeight; + height: $overlayHeaderHeight; + line-height: 36px; + padding: 0 10px; + float: right; + opacity: 0.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: $overlayHeaderHeight + $overlayHeaderPadding*2; + 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 0.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 0.3s ease 0.3s; + } + .reveal > .overlay.overlay-preview.loaded .x-frame-error { + opacity: 1; + } + + .reveal > .overlay.overlay-preview.loaded .spinner { + opacity: 0; + visibility: hidden; + transform: scale(0.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 th, + .reveal > .overlay.overlay-help .viewport .viewport-inner table td { + 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; + } + + +/********************************************* + * PLAYBACK COMPONENT + *********************************************/ + +.reveal .playback { + position: absolute; + left: 15px; + bottom: 20px; + z-index: 30; + cursor: pointer; + transition: all 400ms ease; + -webkit-tap-highlight-color: rgba( 0, 0, 0, 0 ); +} + +.reveal.overview .playback { + opacity: 0; + visibility: hidden; +} + + +/********************************************* + * CODE HIGHLGIHTING + *********************************************/ + +.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: 0.6; + padding-right: 0.75em; + text-align: right; + vertical-align: top; +} + +.reveal .hljs.has-highlights tr:not(.highlight-line) { + opacity: 0.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%; +} + + +/********************************************* + * ROLLING LINKS + *********************************************/ + +.reveal .roll { + display: inline-block; + line-height: 1.2; + overflow: hidden; + + vertical-align: top; + perspective: 400px; + perspective-origin: 50% 50%; +} + .reveal .roll:hover { + background: none; + text-shadow: none; + } +.reveal .roll span { + display: block; + position: relative; + padding: 0 2px; + + pointer-events: none; + transition: all 400ms ease; + transform-origin: 50% 0%; + transform-style: preserve-3d; + backface-visibility: hidden; +} + .reveal .roll:hover span { + background: rgba(0,0,0,0.5); + transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg ); + } +.reveal .roll span:after { + content: attr(data-title); + + display: block; + position: absolute; + left: 0; + top: 0; + padding: 0 2px; + backface-visibility: hidden; + transform-origin: 50% 0%; + transform: translate3d( 0px, 110%, 0px ) rotateX( -90deg ); +} + + +/********************************************* + * SPEAKER NOTES + *********************************************/ + +$notesWidthPercent: 25%; + +// Hide on-page notes +.reveal aside.notes { + display: none; +} + +// An interface element that can optionally be used to show the +// speaker notes to all viewers, on top of the presentation +.reveal .speaker-notes { + display: none; + position: absolute; + width: math.div($notesWidthPercent, (1 - math.div($notesWidthPercent,100))) * 1%; + 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, 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; + + .notes-placeholder { + color: #ccc; + font-style: italic; + } + + &:focus { + outline: none; + } + + &:before { + content: 'Speaker notes'; + display: block; + margin-bottom: 10px; + opacity: 0.5; + } +} + + +.reveal.show-notes { + max-width: 100% - $notesWidthPercent; + 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: 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: 40vh; + } + + .reveal .speaker-notes { + font-size: 14px; + } +} + + +/********************************************* + * JUMP-TO-SLIDE COMPONENT + *********************************************/ + + .reveal .jump-to-slide { + position: absolute; + top: 15px; + left: 15px; + z-index: 30; + font-size: 32px; + -webkit-tap-highlight-color: rgba( 0, 0, 0, 0 ); +} + +.reveal .jump-to-slide-input { + background: transparent; + padding: 8px; + font-size: inherit; + color: currentColor; + border: 0; +} +.reveal .jump-to-slide-input::placeholder { + color: currentColor; + opacity: 0.5; +} + +.reveal.has-dark-background .jump-to-slide-input { + color: #fff; +} +.reveal.has-light-background .jump-to-slide-input { + color: #222; +} + +.reveal .jump-to-slide-input:focus { + outline: none; +} + + +/********************************************* + * ZOOM PLUGIN + *********************************************/ + +.zoomed .reveal *, +.zoomed .reveal *:before, +.zoomed .reveal *:after { + backface-visibility: visible !important; +} + +.zoomed .reveal .progress, +.zoomed .reveal .controls { + opacity: 0; +} + +.zoomed .reveal .roll span { + background: none; +} + +.zoomed .reveal .roll span:after { + visibility: hidden; +} + + +/********************************************* + * PRINT STYLES + *********************************************/ + +@import 'print/pdf.scss'; +@import 'print/paper.scss'; + diff --git a/css/theme/README.md b/css/theme/README.md new file mode 100644 index 0000000..30916c4 --- /dev/null +++ b/css/theme/README.md @@ -0,0 +1,21 @@ +## Dependencies + +Themes are written using Sass to keep things modular and reduce the need for repeated selectors across files. Make sure that you have the reveal.js development environment installed before proceeding: https://revealjs.com/installation/#full-setup + +## Creating a Theme + +To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled from Sass to CSS (see the [gulpfile](https://github.com/hakimel/reveal.js/blob/master/gulpfile.js)) when you run `npm run build -- css-themes`. + +Each theme file does four things in the following order: + +1. **Include [/css/theme/template/mixins.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/mixins.scss)** +Shared utility functions. + +2. **Include [/css/theme/template/settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss)** +Declares a set of custom variables that the template file (step 4) expects. Can be overridden in step 3. + +3. **Override** +This is where you override the default theme. Either by specifying variables (see [settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss) for reference) or by adding any selectors and styles you please. + +4. **Include [/css/theme/template/theme.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/theme.scss)** +The template theme file which will generate final CSS output based on the currently defined variables. diff --git a/css/theme/source/beige.scss b/css/theme/source/beige.scss new file mode 100644 index 0000000..1f60178 --- /dev/null +++ b/css/theme/source/beige.scss @@ -0,0 +1,41 @@ +/** + * Beige theme for reveal.js. + * + * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + + +// Include theme-specific fonts +@import url(./fonts/league-gothic/league-gothic.css); +@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); + + +// Override theme settings (see ../template/settings.scss) +$mainColor: #333; +$headingColor: #333; +$headingTextShadow: none; +$backgroundColor: #f7f3de; +$linkColor: #8b743d; +$linkColorHover: lighten( $linkColor, 20% ); +$selectionBackgroundColor: rgba(79, 64, 28, 0.99); +$heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); + +// Background generator +@mixin bodyBackground() { + @include radial-gradient( rgba(247,242,211,1), rgba(255,255,255,1) ); +} + +// Change text colors against dark slide backgrounds +@include dark-bg-text-color(#fff); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/black-contrast.scss b/css/theme/source/black-contrast.scss new file mode 100644 index 0000000..9e1a2ca --- /dev/null +++ b/css/theme/source/black-contrast.scss @@ -0,0 +1,49 @@ +/** + * Black compact & high contrast reveal.js theme, with headers not in capitals. + * + * By Peter Kehl. Based on black.(s)css by Hakim El Hattab, http://hakim.se + * + * - Keep the source similar to black.css - for easy comparison. + * - $mainFontSize controls code blocks, too (although under some ratio). + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + +// Include theme-specific fonts +@import url(./fonts/source-sans-pro/source-sans-pro.css); + + +// Override theme settings (see ../template/settings.scss) +$backgroundColor: #000000; + +$mainColor: #fff; +$headingColor: #fff; + +$mainFontSize: 42px; +$mainFont: 'Source Sans Pro', Helvetica, sans-serif; +$headingFont: 'Source Sans Pro', Helvetica, sans-serif; +$headingTextShadow: none; +$headingLetterSpacing: normal; +$headingTextTransform: uppercase; +$headingFontWeight: 600; +$linkColor: #42affa; +$linkColorHover: lighten( $linkColor, 15% ); +$selectionBackgroundColor: lighten( $linkColor, 25% ); + +$heading1Size: 2.5em; +$heading2Size: 1.6em; +$heading3Size: 1.3em; +$heading4Size: 1.0em; + +// Change text colors against light slide backgrounds +@include light-bg-text-color(#000); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/black.scss b/css/theme/source/black.scss new file mode 100644 index 0000000..7c655c4 --- /dev/null +++ b/css/theme/source/black.scss @@ -0,0 +1,46 @@ +/** + * Black theme for reveal.js. This is the opposite of the 'white' theme. + * + * By Hakim El Hattab, http://hakim.se + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + +// Include theme-specific fonts +@import url(./fonts/source-sans-pro/source-sans-pro.css); + + +// Override theme settings (see ../template/settings.scss) +$backgroundColor: #191919; + +$mainColor: #fff; +$headingColor: #fff; + +$mainFontSize: 42px; +$mainFont: 'Source Sans Pro', Helvetica, sans-serif; +$headingFont: 'Source Sans Pro', Helvetica, sans-serif; +$headingTextShadow: none; +$headingLetterSpacing: normal; +$headingTextTransform: uppercase; +$headingFontWeight: 600; +$linkColor: #42affa; +$linkColorHover: lighten( $linkColor, 15% ); +$selectionBackgroundColor: rgba( $linkColor, 0.75 ); + +$heading1Size: 2.5em; +$heading2Size: 1.6em; +$heading3Size: 1.3em; +$heading4Size: 1.0em; + +// Change text colors against light slide backgrounds +@include light-bg-text-color(#222); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/blood.scss b/css/theme/source/blood.scss new file mode 100644 index 0000000..b5a8679 --- /dev/null +++ b/css/theme/source/blood.scss @@ -0,0 +1,87 @@ +/** + * Blood theme for reveal.js + * Author: Walther http://github.com/Walther + * + * Designed to be used with highlight.js theme + * "monokai_sublime.css" available from + * https://github.com/isagalaev/highlight.js/ + * + * For other themes, change $codeBackground accordingly. + * + */ + + // Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + +// Include theme-specific fonts + +@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,700,300italic,700italic); + +// Colors used in the theme +$blood: #a23; +$coal: #222; +$codeBackground: #23241f; + +$backgroundColor: $coal; + +// Main text +$mainFont: Ubuntu, 'sans-serif'; +$mainColor: #eee; + +// Headings +$headingFont: Ubuntu, 'sans-serif'; +$headingTextShadow: 2px 2px 2px $coal; + +// h1 shadow, borrowed humbly from +// (c) Default theme by Hakim El Hattab +$heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); + +// Links +$linkColor: $blood; +$linkColorHover: lighten( $linkColor, 20% ); + +// Text selection +$selectionBackgroundColor: $blood; +$selectionColor: #fff; + +// Change text colors against dark slide backgrounds +@include light-bg-text-color(#222); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- + +// some overrides after theme template import + +.reveal p { + font-weight: 300; + text-shadow: 1px 1px $coal; +} + +section.has-light-background { + p, h1, h2, h3, h4 { + text-shadow: none; + } +} + +.reveal h1, +.reveal h2, +.reveal h3, +.reveal h4, +.reveal h5, +.reveal h6 { + font-weight: 700; +} + +.reveal p code { + background-color: $codeBackground; + display: inline-block; + border-radius: 7px; +} + +.reveal small code { + vertical-align: baseline; +} \ No newline at end of file diff --git a/css/theme/source/dracula.scss b/css/theme/source/dracula.scss new file mode 100644 index 0000000..67fb59c --- /dev/null +++ b/css/theme/source/dracula.scss @@ -0,0 +1,132 @@ +/** + * Dracula Dark theme for reveal.js. + * Based on https://draculatheme.com + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + + +// Include theme-specific fonts +$systemFontsSansSerif: -apple-system, + BlinkMacSystemFont, + avenir next, + avenir, + segoe ui, + helvetica neue, + helvetica, + Cantarell, + Ubuntu, + roboto, + noto, + arial, + sans-serif; +$systemFontsMono: Menlo, + Consolas, + Monaco, + Liberation Mono, + Lucida Console, + monospace; + +/** + * Dracula colors by Zeno Rocha + * https://draculatheme.com/contribute + */ +html * { + color-profile: sRGB; + rendering-intent: auto; +} + +$background: #282A36; +$foreground: #F8F8F2; +$selection: #44475A; +$comment: #6272A4; +$red: #FF5555; +$orange: #FFB86C; +$yellow: #F1FA8C; +$green: #50FA7B; +$purple: #BD93F9; +$cyan: #8BE9FD; +$pink: #FF79C6; + + + +// Override theme settings (see ../template/settings.scss) +$mainColor: $foreground; +$headingColor: $purple; +$headingTextShadow: none; +$headingTextTransform: none; +$backgroundColor: $background; +$linkColor: $pink; +$linkColorHover: $cyan; +$selectionBackgroundColor: $selection; +$inlineCodeColor: $green; +$listBulletColor: $cyan; + +$mainFont: $systemFontsSansSerif; +$codeFont: "Fira Code", $systemFontsMono; + +// Change text colors against light slide backgrounds +@include light-bg-text-color($background); + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- + +// Define additional color effects based on Dracula spec +// https://spec.draculatheme.com/ +:root { + --r-bold-color: #{$orange}; + --r-italic-color: #{$yellow}; + --r-inline-code-color: #{$inlineCodeColor}; + --r-list-bullet-color: #{$listBulletColor}; +} + +.reveal strong, .reveal b { + color: var(--r-bold-color); +} + +.reveal em, .reveal i, .reveal blockquote { + color: var(--r-italic-color); +} + +.reveal code { + color: var(--r-inline-code-color); +} + +// Dracula colored list bullets and numbers +.reveal ul { + list-style: none; +} + +.reveal ul li::before { + content: "•"; + color: var(--r-list-bullet-color); + display: inline-block; + width: 1em; + margin-left: -1em +} + +.reveal ol { + list-style: none; + counter-reset: li; +} + +.reveal ol li::before { + content: counter(li) "."; + color: var(--r-list-bullet-color); + display: inline-block; + width: 2em; + + margin-left: -2.5em; + margin-right: 0.5em; + text-align: right; +} + +.reveal ol li { + counter-increment: li +} diff --git a/css/theme/source/league.scss b/css/theme/source/league.scss new file mode 100644 index 0000000..ee01258 --- /dev/null +++ b/css/theme/source/league.scss @@ -0,0 +1,36 @@ +/** + * League theme for reveal.js. + * + * This was the default theme pre-3.0.0. + * + * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + + +// Include theme-specific fonts +@import url(./fonts/league-gothic/league-gothic.css); +@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); + +// Override theme settings (see ../template/settings.scss) +$headingTextShadow: 0px 0px 6px rgba(0,0,0,0.2); +$heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); + +// Background generator +@mixin bodyBackground() { + @include radial-gradient( rgba(28,30,32,1), rgba(85,90,95,1) ); +} + +// Change text colors against light slide backgrounds +@include light-bg-text-color(#222); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/moon.scss b/css/theme/source/moon.scss new file mode 100644 index 0000000..ff2074a --- /dev/null +++ b/css/theme/source/moon.scss @@ -0,0 +1,58 @@ +/** + * Solarized Dark theme for reveal.js. + * Author: Achim Staebler + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + + +// Include theme-specific fonts +@import url(./fonts/league-gothic/league-gothic.css); +@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); + +/** + * Solarized colors by Ethan Schoonover + */ +html * { + color-profile: sRGB; + rendering-intent: auto; +} + +// Solarized colors +$base03: #002b36; +$base02: #073642; +$base01: #586e75; +$base00: #657b83; +$base0: #839496; +$base1: #93a1a1; +$base2: #eee8d5; +$base3: #fdf6e3; +$yellow: #b58900; +$orange: #cb4b16; +$red: #dc322f; +$magenta: #d33682; +$violet: #6c71c4; +$blue: #268bd2; +$cyan: #2aa198; +$green: #859900; + +// Override theme settings (see ../template/settings.scss) +$mainColor: $base1; +$headingColor: $base2; +$headingTextShadow: none; +$backgroundColor: $base03; +$linkColor: $blue; +$linkColorHover: lighten( $linkColor, 20% ); +$selectionBackgroundColor: $magenta; + +// Change text colors against light slide backgrounds +@include light-bg-text-color(#222); + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/night.scss b/css/theme/source/night.scss new file mode 100644 index 0000000..98a2062 --- /dev/null +++ b/css/theme/source/night.scss @@ -0,0 +1,37 @@ +/** + * Black theme for reveal.js. + * + * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + +// Include theme-specific fonts +@import url(https://fonts.googleapis.com/css?family=Montserrat:700); +@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic); + + +// Override theme settings (see ../template/settings.scss) +$backgroundColor: #111; + +$mainFont: 'Open Sans', sans-serif; +$linkColor: #e7ad52; +$linkColorHover: lighten( $linkColor, 20% ); +$headingFont: 'Montserrat', Impact, sans-serif; +$headingTextShadow: none; +$headingLetterSpacing: -0.03em; +$headingTextTransform: none; +$selectionBackgroundColor: #e7ad52; + +// Change text colors against light slide backgrounds +@include light-bg-text-color(#222); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- \ No newline at end of file diff --git a/css/theme/source/serif.scss b/css/theme/source/serif.scss new file mode 100644 index 0000000..1c8d778 --- /dev/null +++ b/css/theme/source/serif.scss @@ -0,0 +1,38 @@ +/** + * A simple theme for reveal.js presentations, similar + * to the default theme. The accent color is brown. + * + * This theme is Copyright (C) 2012-2013 Owen Versteeg, http://owenversteeg.com - it is MIT licensed. + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + + +// Override theme settings (see ../template/settings.scss) +$mainFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; +$mainColor: #000; +$headingFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; +$headingColor: #383D3D; +$headingTextShadow: none; +$headingTextTransform: none; +$backgroundColor: #F0F1EB; +$linkColor: #51483D; +$linkColorHover: lighten( $linkColor, 20% ); +$selectionBackgroundColor: #26351C; + +.reveal a { + line-height: 1.3em; +} + +// Change text colors against dark slide backgrounds +@include dark-bg-text-color(#fff); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/simple.scss b/css/theme/source/simple.scss new file mode 100644 index 0000000..faf245f --- /dev/null +++ b/css/theme/source/simple.scss @@ -0,0 +1,40 @@ +/** + * A simple theme for reveal.js presentations, similar + * to the default theme. The accent color is darkblue. + * + * This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed. + * reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + + +// Include theme-specific fonts +@import url(https://fonts.googleapis.com/css?family=News+Cycle:400,700); +@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); + + +// Override theme settings (see ../template/settings.scss) +$mainFont: 'Lato', sans-serif; +$mainColor: #000; +$headingFont: 'News Cycle', Impact, sans-serif; +$headingColor: #000; +$headingTextShadow: none; +$headingTextTransform: none; +$backgroundColor: #fff; +$linkColor: #00008B; +$linkColorHover: lighten( $linkColor, 20% ); +$selectionBackgroundColor: rgba(0, 0, 0, 0.99); + +// Change text colors against dark slide backgrounds +@include dark-bg-text-color(#fff); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- \ No newline at end of file diff --git a/css/theme/source/sky.scss b/css/theme/source/sky.scss new file mode 100644 index 0000000..c83b9c0 --- /dev/null +++ b/css/theme/source/sky.scss @@ -0,0 +1,49 @@ +/** + * Sky theme for reveal.js. + * + * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + + +// Include theme-specific fonts +@import url(https://fonts.googleapis.com/css?family=Quicksand:400,700,400italic,700italic); +@import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); + + +// Override theme settings (see ../template/settings.scss) +$mainFont: 'Open Sans', sans-serif; +$mainColor: #333; +$headingFont: 'Quicksand', sans-serif; +$headingColor: #333; +$headingLetterSpacing: -0.08em; +$headingTextShadow: none; +$backgroundColor: #f7fbfc; +$linkColor: #3b759e; +$linkColorHover: lighten( $linkColor, 20% ); +$selectionBackgroundColor: #134674; + +// Fix links so they are not cut off +.reveal a { + line-height: 1.3em; +} + +// Background generator +@mixin bodyBackground() { + @include radial-gradient( #add9e4, #f7fbfc ); +} + +// Change text colors against dark slide backgrounds +@include dark-bg-text-color(#fff); + + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/solarized.scss b/css/theme/source/solarized.scss new file mode 100644 index 0000000..8bdf1eb --- /dev/null +++ b/css/theme/source/solarized.scss @@ -0,0 +1,63 @@ +/** + * Solarized Light theme for reveal.js. + * Author: Achim Staebler + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + + +// Include theme-specific fonts +@import url(./fonts/league-gothic/league-gothic.css); +@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); + + +/** + * Solarized colors by Ethan Schoonover + */ +html * { + color-profile: sRGB; + rendering-intent: auto; +} + +// Solarized colors +$base03: #002b36; +$base02: #073642; +$base01: #586e75; +$base00: #657b83; +$base0: #839496; +$base1: #93a1a1; +$base2: #eee8d5; +$base3: #fdf6e3; +$yellow: #b58900; +$orange: #cb4b16; +$red: #dc322f; +$magenta: #d33682; +$violet: #6c71c4; +$blue: #268bd2; +$cyan: #2aa198; +$green: #859900; + +// Override theme settings (see ../template/settings.scss) +$mainColor: $base00; +$headingColor: $base01; +$headingTextShadow: none; +$backgroundColor: $base3; +$linkColor: $blue; +$linkColorHover: lighten( $linkColor, 20% ); +$selectionBackgroundColor: $magenta; + +// Background generator +// @mixin bodyBackground() { +// @include radial-gradient( rgba($base3,1), rgba(lighten($base3, 20%),1) ); +// } + + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/training.scss b/css/theme/source/training.scss new file mode 100644 index 0000000..e2a4450 --- /dev/null +++ b/css/theme/source/training.scss @@ -0,0 +1,157 @@ +/** + * League theme for reveal.js. + * + * This was the default theme pre-3.0.0. + * + * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se + */ + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + +// Include theme-specific fonts +@import url(./fonts/league-gothic/league-gothic.css); + +// Override theme settings (see ../template/settings.scss) +$headingTextShadow: 0px 0px 6px rgba(0, 0, 0, 0.2); +$heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0, 0, 0, .1), 0 0 5px rgba(0, 0, 0, .1), 0 1px 3px rgba(0, 0, 0, .3), 0 3px 5px rgba(0, 0, 0, .2), 0 5px 10px rgba(0, 0, 0, .25), 0 20px 20px rgba(0, 0, 0, .15); +$mainFont: 'Cabin', sans-serif; +$headingFont: 'Montserrat', Impact, sans-serif; +$listColor: #7bf; + +// Background generator +@mixin bodyBackground() { + @include radial-gradient(rgba(28, 30, 32, 1), rgba(65, 70, 75, 1)); +} + +// Change text colors against light slide backgrounds +@include light-bg-text-color(#222); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- + +div.sourceCode { + border-radius: 0.2em; + border-bottom: rgba(0, 0, 0, 0.6) 9px solid; + padding-top: 0.1em; + padding-bottom: 0.1em; +} + +pre.sourceCode, code.sourceCode { + font-family: "JetBrains Mono", monospace; + box-shadow: none; + overflow: visible !important; +} + +:not(pre) > code { + font-size: 0.9em; + background-color: rgba(0, 0, 0, 0.25); + border-radius: 0.2em; + padding: 0.1em 0.5em; + border-bottom: rgba(0, 0, 0, 0.6) 3px solid; +} + +pre code { + max-height: 800px !important; +} + +pre.numberSource { + // *** REMOVED: *** + // margin-left: 3em; + // border-left: 1px solid red; + // [... more SCSS, omitted ...] +} + +// *** ADDED: *** Needed to preserve Line Numbers background-color +pre.numberSource { + margin-left: 4em; // Space for Line Numbers with 4 digits maximum! + padding-left: 4px; // Add some space to distance from line numbers + counter-reset: linecount; +} + +// *** LINE-NUMS BORDER: *** The border separating linenums from code +pre.numberSource a::before { + color: rgba(255, 255, 255, 0.25) !important; + text-decoration: none !important; + border-right: 1px solid #fff; + counter-increment: linecount; + content: counter(linecount) !important; +} + +body:after { + content: url(./image/python.png); + position: fixed; + top: 1em; + right: 1em; + transform: scale(0.5); +} + +div.progress { + height: 6px !important; +} + +ul, ol { + min-width: 66% !important; +} + +.reveal ol { + counter-reset: item; + + li { + display: block; + } + + li:before { + opacity: 0.6; + content: counter(item) "⋅ "; + counter-increment: item; + font-family: "JetBrains Mono", monospace; + } +} + + + + +figcaption { + font-size: 0.8em; + font-weight: 400; + opacity: 0.6; + font-style: italic; +} + +figure > img { + border-radius: 0.2em; + max-height: 75vh !important; +} + +table { + width: 100%; + + thead { + th { + border-bottom: 0.25em solid #000 !important; + padding-bottom: 0.3em !important; + } + } + + tbody { + tr:nth-of-type(2n) td { + background-color: rgba(0, 0, 0, 0.25); + } + + td { + border-bottom: transparent !important; + padding-bottom: 0.3em !important; + } + } +} + +.naming { + font-weight: bold; + font-style: normal; + color: #FC1; +} diff --git a/css/theme/source/white-contrast.scss b/css/theme/source/white-contrast.scss new file mode 100644 index 0000000..2a23ba4 --- /dev/null +++ b/css/theme/source/white-contrast.scss @@ -0,0 +1,49 @@ +/** + * White compact & high contrast reveal.js theme, with headers not in capitals. + * + * By Peter Kehl. Based on white.(s)css by Hakim El Hattab, http://hakim.se + * + * - Keep the source similar to black.css - for easy comparison. + * - $mainFontSize controls code blocks, too (although under some ratio). + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + +// Include theme-specific fonts +@import url(./fonts/source-sans-pro/source-sans-pro.css); + + +// Override theme settings (see ../template/settings.scss) +$backgroundColor: #fff; + +$mainColor: #000; +$headingColor: #000; + +$mainFontSize: 42px; +$mainFont: 'Source Sans Pro', Helvetica, sans-serif; +$headingFont: 'Source Sans Pro', Helvetica, sans-serif; +$headingTextShadow: none; +$headingLetterSpacing: normal; +$headingTextTransform: uppercase; +$headingFontWeight: 600; +$linkColor: #2a76dd; +$linkColorHover: lighten( $linkColor, 15% ); +$selectionBackgroundColor: lighten( $linkColor, 25% ); + +$heading1Size: 2.5em; +$heading2Size: 1.6em; +$heading3Size: 1.3em; +$heading4Size: 1.0em; + +// Change text colors against dark slide backgrounds +@include dark-bg-text-color(#fff); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/source/white.scss b/css/theme/source/white.scss new file mode 100644 index 0000000..443d30a --- /dev/null +++ b/css/theme/source/white.scss @@ -0,0 +1,46 @@ +/** + * White theme for reveal.js. This is the opposite of the 'black' theme. + * + * By Hakim El Hattab, http://hakim.se + */ + + +// Default mixins and settings ----------------- +@import "../template/mixins"; +@import "../template/settings"; +// --------------------------------------------- + + +// Include theme-specific fonts +@import url(./fonts/source-sans-pro/source-sans-pro.css); + + +// Override theme settings (see ../template/settings.scss) +$backgroundColor: #fff; + +$mainColor: #222; +$headingColor: #222; + +$mainFontSize: 42px; +$mainFont: 'Source Sans Pro', Helvetica, sans-serif; +$headingFont: 'Source Sans Pro', Helvetica, sans-serif; +$headingTextShadow: none; +$headingLetterSpacing: normal; +$headingTextTransform: uppercase; +$headingFontWeight: 600; +$linkColor: #2a76dd; +$linkColorHover: lighten( $linkColor, 15% ); +$selectionBackgroundColor: lighten( $linkColor, 25% ); + +$heading1Size: 2.5em; +$heading2Size: 1.6em; +$heading3Size: 1.3em; +$heading4Size: 1.0em; + +// Change text colors against dark slide backgrounds +@include dark-bg-text-color(#fff); + + +// Theme template ------------------------------ +@import "../template/theme"; +// --------------------------------------------- diff --git a/css/theme/template/exposer.scss b/css/theme/template/exposer.scss new file mode 100644 index 0000000..4aec3e8 --- /dev/null +++ b/css/theme/template/exposer.scss @@ -0,0 +1,28 @@ +// Exposes theme's variables for easy re-use in CSS for plugin authors + +:root { + --r-background-color: #{$backgroundColor}; + --r-main-font: #{$mainFont}; + --r-main-font-size: #{$mainFontSize}; + --r-main-color: #{$mainColor}; + --r-block-margin: #{$blockMargin}; + --r-heading-margin: #{$headingMargin}; + --r-heading-font: #{$headingFont}; + --r-heading-color: #{$headingColor}; + --r-heading-line-height: #{$headingLineHeight}; + --r-heading-letter-spacing: #{$headingLetterSpacing}; + --r-heading-text-transform: #{$headingTextTransform}; + --r-heading-text-shadow: #{$headingTextShadow}; + --r-heading-font-weight: #{$headingFontWeight}; + --r-heading1-text-shadow: #{$heading1TextShadow}; + --r-heading1-size: #{$heading1Size}; + --r-heading2-size: #{$heading2Size}; + --r-heading3-size: #{$heading3Size}; + --r-heading4-size: #{$heading4Size}; + --r-code-font: #{$codeFont}; + --r-link-color: #{$linkColor}; + --r-link-color-dark: #{darken($linkColor , 15% )}; + --r-link-color-hover: #{$linkColorHover}; + --r-selection-background-color: #{$selectionBackgroundColor}; + --r-selection-color: #{$selectionColor}; +} diff --git a/css/theme/template/mixins.scss b/css/theme/template/mixins.scss new file mode 100644 index 0000000..17a3db5 --- /dev/null +++ b/css/theme/template/mixins.scss @@ -0,0 +1,45 @@ +@mixin vertical-gradient( $top, $bottom ) { + background: $top; + background: -moz-linear-gradient( top, $top 0%, $bottom 100% ); + background: -webkit-gradient( linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom) ); + background: -webkit-linear-gradient( top, $top 0%, $bottom 100% ); + background: -o-linear-gradient( top, $top 0%, $bottom 100% ); + background: -ms-linear-gradient( top, $top 0%, $bottom 100% ); + background: linear-gradient( top, $top 0%, $bottom 100% ); +} + +@mixin horizontal-gradient( $top, $bottom ) { + background: $top; + background: -moz-linear-gradient( left, $top 0%, $bottom 100% ); + background: -webkit-gradient( linear, left top, right top, color-stop(0%,$top), color-stop(100%,$bottom) ); + background: -webkit-linear-gradient( left, $top 0%, $bottom 100% ); + background: -o-linear-gradient( left, $top 0%, $bottom 100% ); + background: -ms-linear-gradient( left, $top 0%, $bottom 100% ); + background: linear-gradient( left, $top 0%, $bottom 100% ); +} + +@mixin radial-gradient( $outer, $inner, $type: circle ) { + background: $outer; + background: -moz-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); + background: -webkit-gradient( radial, center center, 0px, center center, 100%, color-stop(0%,$inner), color-stop(100%,$outer) ); + background: -webkit-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); + background: -o-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); + background: -ms-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); + background: radial-gradient( center, $type cover, $inner 0%, $outer 100% ); +} + +@mixin light-bg-text-color( $color ) { + section.has-light-background { + &, h1, h2, h3, h4, h5, h6 { + color: $color; + } + } +} + +@mixin dark-bg-text-color( $color ) { + section.has-dark-background { + &, h1, h2, h3, h4, h5, h6 { + color: $color; + } + } +} \ No newline at end of file diff --git a/css/theme/template/settings.scss b/css/theme/template/settings.scss new file mode 100644 index 0000000..5a917f8 --- /dev/null +++ b/css/theme/template/settings.scss @@ -0,0 +1,45 @@ +// Base settings for all themes that can optionally be +// overridden by the super-theme + +// Background of the presentation +$backgroundColor: #2b2b2b; + +// Primary/body text +$mainFont: 'Lato', sans-serif; +$mainFontSize: 40px; +$mainColor: #eee; + +// Vertical spacing between blocks of text +$blockMargin: 20px; + +// Headings +$headingMargin: 0 0 $blockMargin 0; +$headingFont: 'League Gothic', Impact, sans-serif; +$headingColor: #eee; +$headingLineHeight: 1.2; +$headingLetterSpacing: normal; +$headingTextTransform: uppercase; +$headingTextShadow: none; +$headingFontWeight: normal; +$heading1TextShadow: $headingTextShadow; + +$heading1Size: 3.77em; +$heading2Size: 2.11em; +$heading3Size: 1.55em; +$heading4Size: 1.00em; + +$codeFont: monospace; + +// Links and actions +$linkColor: #13DAEC; +$linkColorHover: lighten( $linkColor, 20% ); + +// Text selection +$selectionBackgroundColor: #FF5E99; +$selectionColor: #fff; + +// Generates the presentation background, can be overridden +// to return a background image or gradient +@mixin bodyBackground() { + background: $backgroundColor; +} diff --git a/css/theme/template/theme.scss b/css/theme/template/theme.scss new file mode 100644 index 0000000..bc377d3 --- /dev/null +++ b/css/theme/template/theme.scss @@ -0,0 +1,331 @@ +// Base theme template for reveal.js + +/********************************************* + * GLOBAL STYLES + *********************************************/ + +@import "./exposer"; + +.reveal-viewport { + @include bodyBackground(); + background-color: var(--r-background-color); +} + +.reveal { + font-family: var(--r-main-font); + font-size: var(--r-main-font-size); + font-weight: normal; + color: var(--r-main-color); +} + +.reveal ::selection { + color: var(--r-selection-color); + background: var(--r-selection-background-color); + text-shadow: none; +} + +.reveal ::-moz-selection { + color: var(--r-selection-color); + background: var(--r-selection-background-color); + text-shadow: none; +} + +.reveal .slides section, +.reveal .slides section>section { + line-height: 1.3; + font-weight: inherit; +} + +/********************************************* + * HEADERS + *********************************************/ + +.reveal h1, +.reveal h2, +.reveal h3, +.reveal h4, +.reveal h5, +.reveal h6 { + margin: var(--r-heading-margin); + color: var(--r-heading-color); + + font-family: var(--r-heading-font); + font-weight: var(--r-heading-font-weight); + line-height: var(--r-heading-line-height); + letter-spacing: var(--r-heading-letter-spacing); + + text-transform: var(--r-heading-text-transform); + text-shadow: var(--r-heading-text-shadow); + + word-wrap: break-word; +} + +.reveal h1 {font-size: var(--r-heading1-size); } +.reveal h2 {font-size: var(--r-heading2-size); } +.reveal h3 {font-size: var(--r-heading3-size); } +.reveal h4 {font-size: var(--r-heading4-size); } + +.reveal h1 { + text-shadow: var(--r-heading1-text-shadow); +} + + +/********************************************* + * OTHER + *********************************************/ + +.reveal p { + margin: var(--r-block-margin) 0; + line-height: 1.3; +} + +/* Remove trailing margins after titles */ +.reveal h1:last-child, +.reveal h2:last-child, +.reveal h3:last-child, +.reveal h4:last-child, +.reveal h5:last-child, +.reveal h6:last-child { + margin-bottom: 0; +} + +/* Ensure certain elements are never larger than the slide itself */ +.reveal img, +.reveal video, +.reveal iframe { + max-width: 95%; + max-height: 95%; +} +.reveal strong, +.reveal b { + font-weight: bold; +} + +.reveal em { + font-style: italic; +} + +.reveal ol, +.reveal dl, +.reveal ul { + display: inline-block; + + text-align: left; + margin: 0 0 0 1em; +} + +.reveal ol { + list-style-type: decimal; +} + +.reveal ul { + list-style-type: disc; +} + +.reveal ul ul { + list-style-type: square; +} + +.reveal ul ul ul { + list-style-type: circle; +} + +.reveal ul ul, +.reveal ul ol, +.reveal ol ol, +.reveal ol ul { + display: block; + margin-left: 40px; +} + +.reveal dt { + font-weight: bold; +} + +.reveal dd { + margin-left: 40px; +} + +.reveal blockquote { + display: block; + position: relative; + width: 70%; + margin: var(--r-block-margin) auto; + padding: 5px; + + font-style: italic; + background: rgba(255, 255, 255, 0.05); + box-shadow: 0px 0px 2px rgba(0,0,0,0.2); +} + .reveal blockquote p:first-child, + .reveal blockquote p:last-child { + display: inline-block; + } + +.reveal q { + font-style: italic; +} + +.reveal pre { + display: block; + position: relative; + width: 90%; + margin: var(--r-block-margin) auto; + + text-align: left; + font-size: 0.55em; + font-family: var(--r-code-font); + line-height: 1.2em; + + word-wrap: break-word; + + box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); +} + +.reveal code { + font-family: var(--r-code-font); + text-transform: none; + tab-size: 2; +} + +.reveal pre code { + display: block; + padding: 5px; + overflow: auto; + max-height: 400px; + word-wrap: normal; +} + +.reveal .code-wrapper { + white-space: normal; +} + +.reveal .code-wrapper code { + white-space: pre; +} + +.reveal table { + margin: auto; + border-collapse: collapse; + border-spacing: 0; +} + +.reveal table th { + font-weight: bold; +} + +.reveal table th, +.reveal table td { + text-align: left; + padding: 0.2em 0.5em 0.2em 0.5em; + border-bottom: 1px solid; +} + +.reveal table th[align="center"], +.reveal table td[align="center"] { + text-align: center; +} + +.reveal table th[align="right"], +.reveal table td[align="right"] { + text-align: right; +} + +.reveal table tbody tr:last-child th, +.reveal table tbody tr:last-child td { + border-bottom: none; +} + +.reveal sup { + vertical-align: super; + font-size: smaller; +} +.reveal sub { + vertical-align: sub; + font-size: smaller; +} + +.reveal small { + display: inline-block; + font-size: 0.6em; + line-height: 1.2em; + vertical-align: top; +} + +.reveal small * { + vertical-align: top; +} + +.reveal img { + margin: var(--r-block-margin) 0; +} + + +/********************************************* + * LINKS + *********************************************/ + +.reveal a { + color: var(--r-link-color); + text-decoration: none; + transition: color .15s ease; +} + .reveal a:hover { + color: var(--r-link-color-hover); + text-shadow: none; + border: none; + } + +.reveal .roll span:after { + color: #fff; + // background: darken( var(--r-link-color), 15% ); + background: var(--r-link-color-dark); + +} + + +/********************************************* + * Frame helper + *********************************************/ + +.reveal .r-frame { + border: 4px solid var(--r-main-color); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); +} + +.reveal a .r-frame { + transition: all .15s linear; +} + +.reveal a:hover .r-frame { + border-color: var(--r-link-color); + box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); +} + + +/********************************************* + * NAVIGATION CONTROLS + *********************************************/ + +.reveal .controls { + color: var(--r-link-color); +} + + +/********************************************* + * PROGRESS BAR + *********************************************/ + +.reveal .progress { + background: rgba(0,0,0,0.2); + color: var(--r-link-color); +} + +/********************************************* + * PRINT BACKGROUND + *********************************************/ + @media print { + .backgrounds { + background-color: var(--r-background-color); + } +} diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..d4787ea --- /dev/null +++ b/demo.html @@ -0,0 +1,481 @@ + + + + + + + reveal.js – The HTML Presentation Framework + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + +

The HTML Presentation Framework

+

+ Created by Hakim El Hattab and contributors +

+
+ +
+

Hello There

+

+ reveal.js enables you to create beautiful interactive slide decks using HTML. This presentation will show you examples of what it can do. +

+
+ + +
+
+

Vertical Slides

+

Slides can be nested inside of each other.

+

Use the Space key to navigate through all slides.

+
+ + Down arrow + +
+
+

Basement Level 1

+

Nested slides are useful for adding additional detail underneath a high level horizontal slide.

+
+
+

Basement Level 2

+

That's it, time to go back up.

+
+ + Up arrow + +
+
+ +
+

Slides

+

+ Not a coder? Not a problem. There's a fully-featured visual editor for authoring these, try it out at https://slides.com. +

+
+ +
+

Hidden Slides

+

+ This slide is visible in the source, but hidden when the presentation is viewed. You can show all hidden slides by setting the `showHiddenSlides` config option to `true`. +

+
+ +
+

Pretty Code

+

+						import React, { useState } from 'react';
+
+						function Example() {
+						  const [count, setCount] = useState(0);
+
+						  return (
+						    ...
+						  );
+						}
+					
+

Code syntax highlighting courtesy of highlight.js.

+
+ +
+

With Animations

+
+
+ +
+

Point of View

+

+ Press ESC to enter the slide overview. +

+

+ Hold down the alt key (ctrl in Linux) and click on any element to zoom towards it using zoom.js. Click again to zoom back out. +

+

+ (NOTE: Use ctrl + click in Linux.) +

+
+ +
+

Auto-Animate

+

Automatically animate matching elements across slides with Auto-Animate.

+
+
+
+
+
+
+
+
+
+
+
+
+

Auto-Animate

+
+
+
+
+
+
+
+

Auto-Animate

+
+ +
+

Touch Optimized

+

+ Presentations look great on touch devices, like mobile phones and tablets. Simply swipe through your slides. +

+
+ +
+ +
+ +
+

Add the r-fit-text class to auto-size text

+

FIT TEXT

+
+ +
+
+

Fragments

+

Hit the next arrow...

+

... to step through ...

+

... a fragmented slide.

+ + +
+
+

Fragment Styles

+

There's different types of fragments, like:

+

grow

+

shrink

+

fade-out

+

+ fade-right, + up, + down, + left +

+

fade-in-then-out

+

fade-in-then-semi-out

+

Highlight red blue green

+
+
+ +
+

Transition Styles

+

+ You can select from different transitions, like:
+ None - + Fade - + Slide - + Convex - + Concave - + Zoom +

+
+ +
+

Themes

+

+ reveal.js comes with a few themes built in:
+ + Black (default) - + White - + League - + Sky - + Beige - + Simple
+ Serif - + Blood - + Night - + Moon - + Solarized +

+
+ +
+
+

Slide Backgrounds

+

+ Set data-background="#dddddd" on a slide to change the background color. All CSS color formats are supported. +

+ + Down arrow + +
+
+

Gradient Backgrounds

+
<section data-background-gradient=
+							"linear-gradient(to bottom, #ddd, #191919)">
+
+
+

Image Backgrounds

+
<section data-background="image.png">
+
+
+

Tiled Backgrounds

+
<section data-background="image.png" data-background-repeat="repeat" data-background-size="100px">
+
+
+
+

Video Backgrounds

+
<section data-background-video="video.mp4,video.webm">
+
+
+
+

... and GIFs!

+
+
+ +
+

Background Transitions

+

+ Different background transitions are available via the backgroundTransition option. This one's called "zoom". +

+
Reveal.configure({ backgroundTransition: 'zoom' })
+
+ +
+

Background Transitions

+

+ You can override background transitions per-slide. +

+
<section data-background-transition="zoom">
+
+ +
+
+

Iframe Backgrounds

+

Since reveal.js runs on the web, you can easily embed other web content. Try interacting with the page in the background.

+
+
+ +
+

Marvelous List

+
    +
  • No order here
  • +
  • Or here
  • +
  • Or here
  • +
  • Or here
  • +
+
+ +
+

Fantastic Ordered List

+
    +
  1. One is smaller than...
  2. +
  3. Two is smaller than...
  4. +
  5. Three!
  6. +
+
+ +
+

Tabular Tables

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ItemValueQuantity
Apples$17
Lemonade$218
Bread$32
+
+ +
+

Clever Quotes

+

+ These guys come in two forms, inline: The nice thing about standards is that there are so many to choose from and block: +

+
+ “For years there has been a theory that millions of monkeys typing at random on millions of typewriters would + reproduce the entire works of Shakespeare. The Internet has proven this theory to be untrue.” +
+
+ +
+

Intergalactic Interconnections

+

+ You can link between slides internally, + like this. +

+
+ +
+

Speaker View

+

There's a speaker view. It includes a timer, preview of the upcoming slide as well as your speaker notes.

+

Press the S key to try it out.

+ + +
+ +
+

Export to PDF

+

Presentations can be exported to PDF, here's an example:

+ +
+ +
+

Global State

+

+ Set data-state="something" on a slide and "something" + will be added as a class to the document element when the slide is open. This lets you + apply broader style changes, like switching the page background. +

+
+ +
+

State Events

+

+ Additionally custom events can be triggered on a per slide basis by binding to the data-state name. +

+

+Reveal.on( 'customevent', function() {
+	console.log( '"customevent" has fired' );
+} );
+					
+
+ +
+

Take a Moment

+

+ Press B or . on your keyboard to pause the presentation. This is helpful when you're on stage and want to take distracting slides off the screen. +

+
+ +
+

Much more

+ +
+ +
+

THE END

+

+ - Try the online editor
+ - Source code & documentation +

+
+ +
+ +
+ + + + + + + + + + + diff --git a/examples/assets/beeping.txt b/examples/assets/beeping.txt new file mode 100644 index 0000000..bf41997 --- /dev/null +++ b/examples/assets/beeping.txt @@ -0,0 +1,2 @@ +Source: https://freesound.org/people/fennelliott/sounds/379419/ +License: CC0 (public domain) \ No newline at end of file diff --git a/examples/assets/beeping.wav b/examples/assets/beeping.wav new file mode 100644 index 0000000000000000000000000000000000000000..38747a533a78497c52134dc0ea13ba197eec9e85 GIT binary patch literal 422472 zcmeFaWtbedvo9>Edw6GOnG9=Ywi8p#Oo^G96Enn)A!cTdnK_2U%#5*PW{PRoUf$tp zkaSC)DpmbdDwU*CZR;it8`d6aL>=n1Z_t0p=zL)aA&dyB zzYn3XKM9gi80y<=bgz3OjBqFCCkKHX1ac6_K_CZ#90YO@$Uz_nfgA*K5XeCw2Z0;} zauCQtAP0dQ1ac6_K_CZ#90YO@$Uz_nfgA*K5XeCw2Z0;}auCQtAP0dQ1ac6_K_CZ# z90YO@$Uz_nfgA*K5XeCw2Z0;}auCQtAP0dQ1ac6_K_CZ#90YO@$Uz_nfgA*K5XeCw z2Z8@TA;9^+(*EanaTR~LC4L9Lefjf${QA!_;`@I-iBa(G*K1$zi_hS9&e?yL;qXI{ z1a}yGk}tLDT;mw7XIWJRGsDHKY^ARA&l zz*Tr60MEL(lN*o`Xi;v0r(;lFpc)QO$Dwo{hCXmVSK!%fp7!ON8RZ6w)-TljaK*zP zavORNoIQf8S-c?1gF;XQibti910@1|6>14bsZjnie*=^#&*C3}V^N<4soV{SAW#Z@i}C}dP*e&eP2*LNpY1}M`8E`Z?!)z0 zs5{!gKk=RB4`h9K3wD54=M&J6s3u;-xALW^8k$Lpksas+P@c(#;Y`4_ zDKE>MtP3m0kFtHNl>S`H<#&3o`-*#ZD`S0ss7(Xgv5t}*H^YV{Ov>Fa*Xq3G=M6cK- zd|2zi+W0Ce&9d@3Ns{!@|6%YuIpuJ&H7zP-xpPBy3;u}hv+cGV3sr43?BTXv5lF7*K@9i3rH99cPKMv310}MyV2)UBf&a}z6DD0IZB4J&^+`K5?(Oi#m z&yU#?iy~vAkC-Rg_9^?Q-CJIL=IgGSJxzVfvd%aQr2UdoEXDfaSjMO16z8S111@jo zYj0)WQ!SERL<6K}c!;5~w9I(GwmYO(?5dbHiCgp5OWd2cU%W3aEQZ7!bo?9|f)Db> zN=N0KdPL^uJF-(6Z5Xk#o}+oH;jsku4>wADMQbZ;+hqwDPFqIv)F$p z^=+E+F6Qmfcl}@2{8agUt@I9`3SSmgQLe_Jb#FSEXeCCqL#OVvz|9o-#D9z!li z!Bcr|B{quxDOa(Wb}^kJir8OT^N~!_RFC6}HI2Rqyi^xEr?|I%+Wx7<`$2DWe{v*k zNq3}H&4^8J=`7@V7;wx4H6DKVbtQjtv@ z*&%o2@1>jc5v@$G>Swis>Yv_RzAb6n)3QGdes4;)Cml&kN%d!(�H}cLh9$l+Oxd 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 xew|VLM&3s#=50$LtvH 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≫3ZEarH~xrHBVj{4GFnEEY@`rc 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 fM=9{qE4H}`h;0{c+MZu1)J4X&|qom@(L8(bu8 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뫶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 literal 0 HcmV?d00001 diff --git a/examples/assets/image2.png b/examples/assets/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..6c403a0d20be97d7f4ce000487fac783764dbaf7 GIT binary patch literal 10237 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 Gg#Hhj!X@AU literal 0 HcmV?d00001 diff --git a/examples/auto-animate.html b/examples/auto-animate.html new file mode 100644 index 0000000..199810e --- /dev/null +++ b/examples/auto-animate.html @@ -0,0 +1,225 @@ + + + + + + + reveal.js - Auto Animate + + + + + + + + + + +
+ +
+
+

Auto-Animate Example

+

This will fade out

+ +

+						function Example() {
+						  const [count, setCount] = useState(0);
+						}
+					
+
+
+

Auto-Animate Example

+

This will fade out

+

This element is unmatched

+ +

+						function Example() {
+						  New line!
+						  const [count, setCount] = useState(0);
+						}
+					
+
+ +
+

Line Height & Letter Spacing

+
+
+

Line Height & Letter Spacing

+
+ +
+
+

+							import React, { useState } from 'react';
+
+							function Example() {
+							  const [count, setCount] = useState(0);
+
+							  return (
+							    ...
+							  );
+							}
+						
+
+
+

+							function Example() {
+							  const [count, setCount] = useState(0);
+
+							  return (
+							    <div>
+							      <p>You clicked {count} times</p>
+							      <button onClick={() => setCount(count + 1)}>
+							        Click me
+							      </button>
+							    </div>
+							  );
+							}
+						
+
+
+

+							function Example() {
+							  // A comment!
+							  const [count, setCount] = useState(0);
+
+							  return (
+							    <div>
+							      <p>You clicked {count} times</p>
+							      <button onClick={() => setCount(count + 1)}>
+							        Click me
+							      </button>
+							    </div>
+							  );
+							}
+						
+
+
+ +
+
+

Swapping list items

+
    +
  • One
  • +
  • Two
  • +
  • Three
  • +
+
+
+

Swapping list items

+
    +
  • Two
  • +
  • One
  • +
  • Three
  • +
+
+
+

Swapping list items

+
    +
  • Two
  • +
  • Three
  • +
  • One
  • +
+
+
+ +
+

SLIDE 1

+

Animate Anything

+
+
+
+
+
+
+

SLIDE 2

+

With Auto Animate

+
+
+
+
+
+
+

SLIDE 3

+

With Auto Animate

+
+
+
+
+
+
+

SLIDE 3

+

With Auto Animate

+
+
+
+
+
+ +
+

data-auto-animate-id="a"

+

A1

+
+
+

data-auto-animate-id="a"

+

A1

+

A2

+
+
+

data-auto-animate-id="b"

+

B1

+
+
+

data-auto-animate-id="b"

+

B1

+

B2

+
+ +
+
+ Slide 1
+ Slide 2
+ Slide 3
+ Slide 4
+
A
+
+
+ Slide 1
+ Slide 2
+ Slide 3
+ Slide 4
+
A
+
+
+ Slide 1
+ Slide 2
+ Slide 3
+ Slide 4
+
A
+
+
+ Slide 1
+ Slide 2
+ Slide 3
+ Slide 4
+
A
+
+
+ +
+ +
+ + + + + + + diff --git a/examples/backgrounds.html b/examples/backgrounds.html new file mode 100644 index 0000000..19d40c3 --- /dev/null +++ b/examples/backgrounds.html @@ -0,0 +1,141 @@ + + + + + + + reveal.js - Slide Backgrounds + + + + + + + + + + +
+ +
+ +
+

data-background: #00ffff

+
+ +
+

data-background: #bb00bb

+
+ +
+

data-background: lightblue

+
+ +
+
+

data-background: #ff0000

+
+
+

data-background: rgba(0, 0, 0, 0.2)

+
+
+

data-background: salmon

+
+
+ +
+
+

Background applied to stack

+
+
+

Background applied to stack

+
+
+

Background applied to slide inside of stack

+
+
+ +
+

Background image

+
+ +
+
+

Background image

+
+
+

Background image

+
+
+ +
+

Background image

+
data-background-size="100px" data-background-repeat="repeat" data-background-color="#111"
+
+ +
+

Same background twice (1/2)

+
+
+

Same background twice (2/2)

+
+ +
+

Video background

+
+ +
+

Iframe background

+
+ +
+
+

Same background twice vertical (1/2)

+
+
+

Same background twice vertical (2/2)

+
+
+ +
+

Same background from horizontal to vertical (1/3)

+
+
+
+

Same background from horizontal to vertical (2/3)

+
+
+

Same background from horizontal to vertical (3/3)

+
+
+ +
+ +
+ + + + + + diff --git a/examples/barebones.html b/examples/barebones.html new file mode 100644 index 0000000..50adcb8 --- /dev/null +++ b/examples/barebones.html @@ -0,0 +1,32 @@ + + + + + reveal.js - Barebones + + + + +
+
+ +
+

Barebones Presentation

+

This example contains the bare minimum includes and markup required to run a reveal.js presentation.

+
+ +
+

No Theme

+

There's no theme included, so it will fall back on browser defaults.

+
+ +
+
+ + + + + + diff --git a/examples/layout-helpers.html b/examples/layout-helpers.html new file mode 100644 index 0000000..a129811 --- /dev/null +++ b/examples/layout-helpers.html @@ -0,0 +1,160 @@ + + + + + + + reveal.js - Layout Helpers + + + + + + + + + + +
+ +
+ +
+

Layout Helper Examples

+ +
+ +
+

Fit Text

+

Resizes text to be as large as possible within its container.

+

+					  

FIT

+
+
+ +
+

FIT

+
+ +
+

HELLO WORLD

+

BOTH THESE TITLES USE FIT-TEXT

+
+ +
+

Stretch

+

Makes an element as tall as possible while remaining within the slide bounds.

+

+					  

Stretch Example

+ +

Image byline

+
+
+ +
+

Stretch Example

+ +

Image byline

+
+ +
+

Stack

+

Stacks multiple elements on top of each other, for use with fragments.

+

+					  
+ <img class="fragment" width="450" height="300" src="..."> + <img class="fragment" width="300" height="450" src="..."> + <img class="fragment" width="400" height="400" src="..."> +
+
+
+ +
+

Stack Example

+
+

One

+

Two

+

Three

+

Four

+
+
+ + + +
+
+ +
+

Stack Example

+

fade-in-then-out fragments

+
+ + + +
+
+ +
+

HStack

+

Stacks multiple elements horizontally.

+

+					  
+ <img width="450" height="300" src="..."> + <img width="300" height="450" src="..."> + <img width="400" height="400" src="..."> +
+
+
+ +
+

HStack Example

+
+

One

+

Two

+

Three

+
+
+ +
+

VStack

+

Stacks multiple elements vertically.

+

+					  
+ <img width="450" height="300" src="..."> + <img width="300" height="450" src="..."> + <img width="400" height="400" src="..."> +
+
+
+ +
+

VStack Example

+
+

One

+

Two

+

Three

+
+
+ +
+ +
+ + + + + + + diff --git a/examples/markdown.html b/examples/markdown.html new file mode 100644 index 0000000..e7294bc --- /dev/null +++ b/examples/markdown.html @@ -0,0 +1,142 @@ + + + + + + + reveal.js - Markdown Example + + + + + + + + + +
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ ## The Lorenz Equations + `\[\begin{aligned} + \dot{x} & = \sigma(y-x) \\ + \dot{y} & = \rho x - y - xz \\ + \dot{z} & = -\beta z + xy + \end{aligned} \]` +
+ +
+
+ + + + + + + + + + + diff --git a/examples/markdown.md b/examples/markdown.md new file mode 100644 index 0000000..1315172 --- /dev/null +++ b/examples/markdown.md @@ -0,0 +1,41 @@ +# Markdown Demo + + + +## External 1.1 + +Content 1.1 + +Note: This will only appear in the speaker notes window. + + +## External 1.2 + +Content 1.2 + + + +## External 2 + +Content 2.1 + + + +## External 3.1 + +Content 3.1 + + +## External 3.2 + +Content 3.2 + + +## External 3.3 (Image) + +![External Image](https://s3.amazonaws.com/static.slid.es/logo/v2/slides-symbol-512x512.png) + + +## External 3.4 (Math) + +`\[ J(\theta_0,\theta_1) = \sum_{i=0} \]` diff --git a/examples/math.html b/examples/math.html new file mode 100644 index 0000000..bd2e75a --- /dev/null +++ b/examples/math.html @@ -0,0 +1,206 @@ + + + + + + + reveal.js - Math Plugin + + + + + + + + + +
+ +
+ +
+

reveal.js Math Plugin

+

Render math with KaTeX, MathJax 2 or MathJax 3

+
+ +
+

The Lorenz Equations

+ + \[\begin{aligned} + \dot{x} & = \sigma(y-x) \\ + \dot{y} & = \rho x - y - xz \\ + \dot{z} & = -\beta z + xy + \end{aligned} \] +
+ +
+

The Cauchy-Schwarz Inequality

+ + +
+ +
+

A Cross Product Formula

+ + \[\mathbf{V}_1 \times \mathbf{V}_2 = \begin{vmatrix} + \mathbf{i} & \mathbf{j} & \mathbf{k} \\ + \frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\ + \frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0 + \end{vmatrix} \] +
+ +
+

The probability of getting \(k\) heads when flipping \(n\) coins is

+ + \[P(E) = {n \choose k} p^k (1-p)^{ n-k} \] +
+ +
+

An Identity of Ramanujan

+ + \[ \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = + 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} + {1+\frac{e^{-8\pi}} {1+\ldots} } } } \] +
+ +
+

A Rogers-Ramanujan Identity

+ + \[ 1 + \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots = + \prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})}\] +
+ +
+

Maxwell’s Equations

+ + \[ \begin{aligned} + \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\ \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\ + \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\ + \nabla \cdot \vec{\mathbf{B}} & = 0 \end{aligned} + \] +
+ +
+

TeX Macros

+ + Here is a common vector space: + \[L^2(\R) = \set{u : \R \to \R}{\int_\R |u|^2 < +\infty}\] + used in functional analysis. +
+ +
+
+

The Lorenz Equations

+ +
+ \[\begin{aligned} + \dot{x} & = \sigma(y-x) \\ + \dot{y} & = \rho x - y - xz \\ + \dot{z} & = -\beta z + xy + \end{aligned} \] +
+
+ +
+

The Cauchy-Schwarz Inequality

+ +
+ \[ \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) \] +
+
+ +
+

A Cross Product Formula

+ +
+ \[\mathbf{V}_1 \times \mathbf{V}_2 = \begin{vmatrix} + \mathbf{i} & \mathbf{j} & \mathbf{k} \\ + \frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\ + \frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0 + \end{vmatrix} \] +
+
+ +
+

The probability of getting \(k\) heads when flipping \(n\) coins is

+ +
+ \[P(E) = {n \choose k} p^k (1-p)^{ n-k} \] +
+
+ +
+

An Identity of Ramanujan

+ +
+ \[ \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = + 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} + {1+\frac{e^{-8\pi}} {1+\ldots} } } } \] +
+
+ +
+

A Rogers-Ramanujan Identity

+ +
+ \[ 1 + \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots = + \prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})}\] +
+
+ +
+

Maxwell’s Equations

+ +
+ \[ \begin{aligned} + \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\ \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\ + \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\ + \nabla \cdot \vec{\mathbf{B}} & = 0 \end{aligned} + \] +
+
+ +
+

TeX Macros

+ + Here is a common vector space: + \[L^2(\R) = \set{u : \R \to \R}{\int_\R |u|^2 < +\infty}\] + used in functional analysis. +
+
+ +
+ +
+ + + + + + + diff --git a/examples/media.html b/examples/media.html new file mode 100644 index 0000000..388208f --- /dev/null +++ b/examples/media.html @@ -0,0 +1,75 @@ + + + + + + + reveal.js - Video, Audio and Iframes + + + + + + + + + +
+ +
+ +
+

Examples of embedded Video, Audio and Iframes

+
+ +
+

Iframe

+ +
+ +
+

Iframe Background

+
+ +
+

Video

+ +
+ +
+

Background Video

+
+ +
+

Auto-playing audio

+ +
+ +
+

Audio inside slide fragments

+
+ Beep 1 + +
+
+ Beep 2 + +
+
+ +
+

Audio with controls

+ +
+ +
+ +
+ + + + + + diff --git a/examples/multiple-presentations.html b/examples/multiple-presentations.html new file mode 100644 index 0000000..e5347d4 --- /dev/null +++ b/examples/multiple-presentations.html @@ -0,0 +1,102 @@ + + + + + + + reveal.js - Multiple Presentations + + + + + + + + + + +
+
+
+
Deck 1, Slide 1
+
Deck 1, Slide 2
+
+

+							import React, { useState } from 'react';
+							function Example() {
+							  const [count, setCount] = useState(0);
+							}
+						
+
+
+
+ +
+
+
Deck 2, Slide 1
+
Deck 2, Slide 2
+
+ +
+
+

The Lorenz Equations

+ + \[\begin{aligned} + \dot{x} & = \sigma(y-x) \\ + \dot{y} & = \rho x - y - xz \\ + \dot{z} & = -\beta z + xy + \end{aligned} \] +
+
+
+
+ + + + + + + + + + + diff --git a/examples/transitions.html b/examples/transitions.html new file mode 100644 index 0000000..adbfd15 --- /dev/null +++ b/examples/transitions.html @@ -0,0 +1,97 @@ + + + + + + + reveal.js - Slide Transitions + + + + + + + + +
+ +
+ +
+

Default

+
+ +
+

Default

+
+ +
+

data-transition: zoom

+
+ +
+

data-transition: zoom-in fade-out

+
+ +
+

Default

+
+ +
+

data-transition: convex

+
+ +
+

data-transition: convex-in concave-out

+
+ +
+
+

Default

+
+
+

data-transition: concave

+
+
+

data-transition: convex-in fade-out

+
+
+

Default

+
+
+ +
+

data-transition: none

+
+ +
+

Default

+
+ +
+ +
+ + + + + + diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..6818e8b --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,319 @@ +const pkg = require('./package.json') +const path = require('path') +const glob = require('glob') +const yargs = require('yargs') +const colors = require('colors') +const through = require('through2'); +const qunit = require('node-qunit-puppeteer') + +const {rollup} = require('rollup') +const {terser} = require('rollup-plugin-terser') +const babel = require('@rollup/plugin-babel').default +const commonjs = require('@rollup/plugin-commonjs') +const resolve = require('@rollup/plugin-node-resolve').default +const sass = require('sass') + +const gulp = require('gulp') +const tap = require('gulp-tap') +const zip = require('gulp-zip') +const header = require('gulp-header') +const eslint = require('gulp-eslint') +const minify = require('gulp-clean-css') +const connect = require('gulp-connect') +const autoprefixer = require('gulp-autoprefixer') + +const root = yargs.argv.root || '.' +const port = yargs.argv.port || 8000 +const host = yargs.argv.host || 'localhost' + +const banner = `/*! +* reveal.js ${pkg.version} +* ${pkg.homepage} +* MIT licensed +* +* Copyright (C) 2011-2023 Hakim El Hattab, https://hakim.se +*/\n` + +// Prevents warnings from opening too many test pages +process.setMaxListeners(20); + +const babelConfig = { + babelHelpers: 'bundled', + ignore: ['node_modules'], + compact: false, + extensions: ['.js', '.html'], + plugins: [ + 'transform-html-import-to-string' + ], + presets: [[ + '@babel/preset-env', + { + corejs: 3, + useBuiltIns: 'usage', + modules: false + } + ]] +}; + +// Our ES module bundle only targets newer browsers with +// module support. Browsers are targeted explicitly instead +// of using the "esmodule: true" target since that leads to +// polyfilling older browsers and a larger bundle. +const babelConfigESM = JSON.parse( JSON.stringify( babelConfig ) ); +babelConfigESM.presets[0][1].targets = { browsers: [ + 'last 2 Chrome versions', + 'last 2 Safari versions', + 'last 2 iOS versions', + 'last 2 Firefox versions', + 'last 2 Edge versions', +] }; + +let cache = {}; + +// Creates a bundle with broad browser support, exposed +// as UMD +gulp.task('js-es5', () => { + return rollup({ + cache: cache.umd, + input: 'js/index.js', + plugins: [ + resolve(), + commonjs(), + babel( babelConfig ), + terser() + ] + }).then( bundle => { + cache.umd = bundle.cache; + return bundle.write({ + name: 'Reveal', + file: './dist/reveal.js', + format: 'umd', + banner: banner, + sourcemap: true + }); + }); +}) + +// Creates an ES module bundle +gulp.task('js-es6', () => { + return rollup({ + cache: cache.esm, + input: 'js/index.js', + plugins: [ + resolve(), + commonjs(), + babel( babelConfigESM ), + terser() + ] + }).then( bundle => { + cache.esm = bundle.cache; + return bundle.write({ + file: './dist/reveal.esm.js', + format: 'es', + banner: banner, + sourcemap: true + }); + }); +}) +gulp.task('js', gulp.parallel('js-es5', 'js-es6')); + +// Creates a UMD and ES module bundle for each of our +// built-in plugins +gulp.task('plugins', () => { + return Promise.all([ + { name: 'RevealHighlight', input: './plugin/highlight/plugin.js', output: './plugin/highlight/highlight' }, + { name: 'RevealMarkdown', input: './plugin/markdown/plugin.js', output: './plugin/markdown/markdown' }, + { name: 'RevealSearch', input: './plugin/search/plugin.js', output: './plugin/search/search' }, + { name: 'RevealNotes', input: './plugin/notes/plugin.js', output: './plugin/notes/notes' }, + { name: 'RevealZoom', input: './plugin/zoom/plugin.js', output: './plugin/zoom/zoom' }, + { name: 'RevealMath', input: './plugin/math/plugin.js', output: './plugin/math/math' }, + ].map( plugin => { + return rollup({ + cache: cache[plugin.input], + input: plugin.input, + plugins: [ + resolve(), + commonjs(), + babel({ + ...babelConfig, + ignore: [/node_modules\/(?!(highlight\.js|marked)\/).*/], + }), + terser() + ] + }).then( bundle => { + cache[plugin.input] = bundle.cache; + bundle.write({ + file: plugin.output + '.esm.js', + name: plugin.name, + format: 'es' + }) + + bundle.write({ + file: plugin.output + '.js', + name: plugin.name, + format: 'umd' + }) + }); + } )); +}) + +// a custom pipeable step to transform Sass to CSS +function compileSass() { + return through.obj( ( vinylFile, encoding, callback ) => { + const transformedFile = vinylFile.clone(); + + sass.render({ + data: transformedFile.contents.toString(), + includePaths: ['css/', 'css/theme/template'] + }, ( err, result ) => { + if( err ) { + console.log( vinylFile.path ); + console.log( err.formatted ); + } + else { + transformedFile.extname = '.css'; + transformedFile.contents = result.css; + callback( null, transformedFile ); + } + }); + }); +} + +gulp.task('css-themes', () => gulp.src(['./css/theme/source/*.{sass,scss}']) + .pipe(compileSass()) + .pipe(gulp.dest('./dist/theme'))) + +gulp.task('css-core', () => gulp.src(['css/reveal.scss']) + .pipe(compileSass()) + .pipe(autoprefixer()) + .pipe(minify({compatibility: 'ie9'})) + .pipe(header(banner)) + .pipe(gulp.dest('./dist'))) + +gulp.task('css', gulp.parallel('css-themes', 'css-core')) + +gulp.task('qunit', () => { + + let serverConfig = { + root, + port: 8009, + host: 'localhost', + name: 'test-server' + } + + let server = connect.server( serverConfig ) + + let testFiles = glob.sync('test/*.html' ) + + let totalTests = 0; + let failingTests = 0; + + let tests = Promise.all( testFiles.map( filename => { + return new Promise( ( resolve, reject ) => { + qunit.runQunitPuppeteer({ + targetUrl: `http://${serverConfig.host}:${serverConfig.port}/${filename}`, + timeout: 20000, + redirectConsole: false, + puppeteerArgs: ['--allow-file-access-from-files'] + }) + .then(result => { + if( result.stats.failed > 0 ) { + console.log(`${'!'} ${filename} [${result.stats.passed}/${result.stats.total}] in ${result.stats.runtime}ms`.red); + // qunit.printResultSummary(result, console); + qunit.printFailedTests(result, console); + } + else { + console.log(`${'✔'} ${filename} [${result.stats.passed}/${result.stats.total}] in ${result.stats.runtime}ms`.green); + } + + totalTests += result.stats.total; + failingTests += result.stats.failed; + + resolve(); + }) + .catch(error => { + console.error(error); + reject(); + }); + } ) + } ) ); + + return new Promise( ( resolve, reject ) => { + + tests.then( () => { + if( failingTests > 0 ) { + reject( new Error(`${failingTests}/${totalTests} tests failed`.red) ); + } + else { + console.log(`${'✔'} Passed ${totalTests} tests`.green.bold); + resolve(); + } + } ) + .catch( () => { + reject(); + } ) + .finally( () => { + server.close(); + } ); + + } ); +} ) + +gulp.task('eslint', () => gulp.src(['./js/**', 'gulpfile.js']) + .pipe(eslint()) + .pipe(eslint.format())) + +gulp.task('test', gulp.series( 'eslint', 'qunit' )) + +gulp.task('default', gulp.series(gulp.parallel('js', 'css', 'plugins'), 'test')) + +gulp.task('build', gulp.parallel('js', 'css', 'plugins')) + +gulp.task('package', gulp.series(() => + + gulp.src( + [ + './index.html', + './dist/**', + './lib/**', + './images/**', + './plugin/**', + './**/*.md' + ], + { base: './' } + ) + .pipe(zip('reveal-js-presentation.zip')).pipe(gulp.dest('./')) + +)) + +gulp.task('reload', () => gulp.src(['**/*.html', '**/*.md']) + .pipe(connect.reload())); + +gulp.task('serve', () => { + + connect.server({ + root: root, + port: port, + host: host, + livereload: true + }) + + gulp.watch(['**/*.html', '**/*.md'], gulp.series('reload')) + + gulp.watch(['js/**'], gulp.series('js', 'reload', 'eslint')) + + gulp.watch(['plugin/**/plugin.js', 'plugin/**/*.html'], gulp.series('plugins', 'reload')) + + gulp.watch([ + 'css/theme/source/*.{sass,scss}', + 'css/theme/template/*.{sass,scss}', + ], gulp.series('css-themes', 'reload')) + + gulp.watch([ + 'css/*.scss', + 'css/print/*.{sass,scss,css}' + ], gulp.series('css-core', 'reload')) + + gulp.watch(['test/*.html'], gulp.series('test')) + +}) diff --git a/index.html b/index.html new file mode 100644 index 0000000..2097df3 --- /dev/null +++ b/index.html @@ -0,0 +1,40 @@ + + + + + + + reveal.js + + + + + + + + + +
+
+
Slide 1
+
Slide 2
+
+
+ + + + + + + + diff --git a/js/components/playback.js b/js/components/playback.js new file mode 100644 index 0000000..06fa7ba --- /dev/null +++ b/js/components/playback.js @@ -0,0 +1,165 @@ +/** + * UI component that lets the use control auto-slide + * playback via play/pause. + */ +export default class Playback { + + /** + * @param {HTMLElement} container The component will append + * itself to this + * @param {function} progressCheck A method which will be + * called frequently to get the current playback progress on + * a range of 0-1 + */ + constructor( container, progressCheck ) { + + // Cosmetics + this.diameter = 100; + this.diameter2 = this.diameter/2; + this.thickness = 6; + + // Flags if we are currently playing + this.playing = false; + + // Current progress on a 0-1 range + this.progress = 0; + + // Used to loop the animation smoothly + this.progressOffset = 1; + + this.container = container; + this.progressCheck = progressCheck; + + this.canvas = document.createElement( 'canvas' ); + this.canvas.className = 'playback'; + this.canvas.width = this.diameter; + this.canvas.height = this.diameter; + this.canvas.style.width = this.diameter2 + 'px'; + this.canvas.style.height = this.diameter2 + 'px'; + this.context = this.canvas.getContext( '2d' ); + + this.container.appendChild( this.canvas ); + + this.render(); + + } + + setPlaying( value ) { + + const wasPlaying = this.playing; + + this.playing = value; + + // Start repainting if we weren't already + if( !wasPlaying && this.playing ) { + this.animate(); + } + else { + this.render(); + } + + } + + animate() { + + const progressBefore = this.progress; + + this.progress = this.progressCheck(); + + // When we loop, offset the progress so that it eases + // smoothly rather than immediately resetting + if( progressBefore > 0.8 && this.progress < 0.2 ) { + this.progressOffset = this.progress; + } + + this.render(); + + if( this.playing ) { + requestAnimationFrame( this.animate.bind( this ) ); + } + + } + + /** + * Renders the current progress and playback state. + */ + render() { + + let progress = this.playing ? this.progress : 0, + radius = ( this.diameter2 ) - this.thickness, + x = this.diameter2, + y = this.diameter2, + iconSize = 28; + + // Ease towards 1 + this.progressOffset += ( 1 - this.progressOffset ) * 0.1; + + const endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) ); + const startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) ); + + this.context.save(); + this.context.clearRect( 0, 0, this.diameter, this.diameter ); + + // Solid background color + this.context.beginPath(); + this.context.arc( x, y, radius + 4, 0, Math.PI * 2, false ); + this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )'; + this.context.fill(); + + // Draw progress track + this.context.beginPath(); + this.context.arc( x, y, radius, 0, Math.PI * 2, false ); + this.context.lineWidth = this.thickness; + this.context.strokeStyle = 'rgba( 255, 255, 255, 0.2 )'; + this.context.stroke(); + + if( this.playing ) { + // Draw progress on top of track + this.context.beginPath(); + this.context.arc( x, y, radius, startAngle, endAngle, false ); + this.context.lineWidth = this.thickness; + this.context.strokeStyle = '#fff'; + this.context.stroke(); + } + + this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) ); + + // Draw play/pause icons + if( this.playing ) { + this.context.fillStyle = '#fff'; + this.context.fillRect( 0, 0, iconSize / 2 - 4, iconSize ); + this.context.fillRect( iconSize / 2 + 4, 0, iconSize / 2 - 4, iconSize ); + } + else { + this.context.beginPath(); + this.context.translate( 4, 0 ); + this.context.moveTo( 0, 0 ); + this.context.lineTo( iconSize - 4, iconSize / 2 ); + this.context.lineTo( 0, iconSize ); + this.context.fillStyle = '#fff'; + this.context.fill(); + } + + this.context.restore(); + + } + + on( type, listener ) { + this.canvas.addEventListener( type, listener, false ); + } + + off( type, listener ) { + this.canvas.removeEventListener( type, listener, false ); + } + + destroy() { + + this.playing = false; + + if( this.canvas.parentNode ) { + this.container.removeChild( this.canvas ); + } + + } + +} \ No newline at end of file diff --git a/js/config.js b/js/config.js new file mode 100644 index 0000000..8b7a883 --- /dev/null +++ b/js/config.js @@ -0,0 +1,300 @@ +/** + * The default reveal.js config object. + */ +export default { + + // The "normal" size of the presentation, aspect ratio will be preserved + // when the presentation is scaled to fit different resolutions + width: 960, + height: 700, + + // Factor of the display size that should remain empty around the content + margin: 0.04, + + // Bounds for smallest/largest possible scale to apply to content + minScale: 0.2, + maxScale: 2.0, + + // Display presentation control arrows + controls: true, + + // Help the user learn the controls by providing hints, for example by + // bouncing the down arrow when they first encounter a vertical slide + controlsTutorial: true, + + // Determines where controls appear, "edges" or "bottom-right" + controlsLayout: 'bottom-right', + + // Visibility rule for backwards navigation arrows; "faded", "hidden" + // or "visible" + controlsBackArrows: 'faded', + + // Display a presentation progress bar + progress: true, + + // Display the page number of the current slide + // - true: Show slide number + // - false: Hide slide number + // + // Can optionally be set as a string that specifies the number formatting: + // - "h.v": Horizontal . vertical slide number (default) + // - "h/v": Horizontal / vertical slide number + // - "c": Flattened slide number + // - "c/t": Flattened slide number / total slides + // + // Alternatively, you can provide a function that returns the slide + // number for the current slide. The function should take in a slide + // object and return an array with one string [slideNumber] or + // three strings [n1,delimiter,n2]. See #formatSlideNumber(). + slideNumber: false, + + // Can be used to limit the contexts in which the slide number appears + // - "all": Always show the slide number + // - "print": Only when printing to PDF + // - "speaker": Only in the speaker view + showSlideNumber: 'all', + + // Use 1 based indexing for # links to match slide number (default is zero + // based) + hashOneBasedIndex: false, + + // Add the current slide number to the URL hash so that reloading the + // page/copying the URL will return you to the same slide + hash: false, + + // Flags if we should monitor the hash and change slides accordingly + respondToHashChanges: true, + + // Enable support for jump-to-slide navigation shortcuts + jumpToSlide: true, + + // Push each slide change to the browser history. Implies `hash: true` + history: false, + + // Enable keyboard shortcuts for navigation + keyboard: true, + + // Optional function that blocks keyboard events when retuning false + // + // If you set this to 'focused', we will only capture keyboard events + // for embedded decks when they are in focus + keyboardCondition: null, + + // Disables the default reveal.js slide layout (scaling and centering) + // so that you can use custom CSS layout + disableLayout: false, + + // Enable the slide overview mode + overview: true, + + // Vertical centering of slides + center: true, + + // Enables touch navigation on devices with touch input + touch: true, + + // Loop the presentation + loop: false, + + // Change the presentation direction to be RTL + rtl: false, + + // Changes the behavior of our navigation directions. + // + // "default" + // Left/right arrow keys step between horizontal slides, up/down + // arrow keys step between vertical slides. Space key steps through + // all slides (both horizontal and vertical). + // + // "linear" + // Removes the up/down arrows. Left/right arrows step through all + // slides (both horizontal and vertical). + // + // "grid" + // When this is enabled, stepping left/right from a vertical stack + // to an adjacent vertical stack will land you at the same vertical + // index. + // + // Consider a deck with six slides ordered in two vertical stacks: + // 1.1 2.1 + // 1.2 2.2 + // 1.3 2.3 + // + // If you're on slide 1.3 and navigate right, you will normally move + // from 1.3 -> 2.1. If "grid" is used, the same navigation takes you + // from 1.3 -> 2.3. + navigationMode: 'default', + + // Randomizes the order of slides each time the presentation loads + shuffle: false, + + // Turns fragments on and off globally + fragments: true, + + // Flags whether to include the current fragment in the URL, + // so that reloading brings you to the same fragment position + fragmentInURL: true, + + // Flags if the presentation is running in an embedded mode, + // i.e. contained within a limited portion of the screen + embedded: false, + + // Flags if we should show a help overlay when the question-mark + // key is pressed + help: true, + + // Flags if it should be possible to pause the presentation (blackout) + pause: true, + + // Flags if speaker notes should be visible to all viewers + showNotes: false, + + // Flags if slides with data-visibility="hidden" should be kep visible + showHiddenSlides: false, + + // Global override for autoplaying embedded media (video/audio/iframe) + // - null: Media will only autoplay if data-autoplay is present + // - true: All media will autoplay, regardless of individual setting + // - false: No media will autoplay, regardless of individual setting + autoPlayMedia: null, + + // Global override for preloading lazy-loaded iframes + // - null: Iframes with data-src AND data-preload will be loaded when within + // the viewDistance, iframes with only data-src will be loaded when visible + // - true: All iframes with data-src will be loaded when within the viewDistance + // - false: All iframes with data-src will be loaded only when visible + preloadIframes: null, + + // Can be used to globally disable auto-animation + autoAnimate: true, + + // Optionally provide a custom element matcher that will be + // used to dictate which elements we can animate between. + autoAnimateMatcher: null, + + // Default settings for our auto-animate transitions, can be + // overridden per-slide or per-element via data arguments + autoAnimateEasing: 'ease', + autoAnimateDuration: 1.0, + autoAnimateUnmatched: true, + + // CSS properties that can be auto-animated. Position & scale + // is matched separately so there's no need to include styles + // like top/right/bottom/left, width/height or margin. + autoAnimateStyles: [ + 'opacity', + 'color', + 'background-color', + 'padding', + 'font-size', + 'line-height', + 'letter-spacing', + 'border-width', + 'border-color', + 'border-radius', + 'outline', + 'outline-offset' + ], + + // Controls automatic progression to the next slide + // - 0: Auto-sliding only happens if the data-autoslide HTML attribute + // is present on the current slide or fragment + // - 1+: All slides will progress automatically at the given interval + // - false: No auto-sliding, even if data-autoslide is present + autoSlide: 0, + + // Stop auto-sliding after user input + autoSlideStoppable: true, + + // Use this method for navigation when auto-sliding (defaults to navigateNext) + autoSlideMethod: null, + + // Specify the average time in seconds that you think you will spend + // presenting each slide. This is used to show a pacing timer in the + // speaker view + defaultTiming: null, + + // Enable slide navigation via mouse wheel + mouseWheel: false, + + // Opens links in an iframe preview overlay + // Add `data-preview-link` and `data-preview-link="false"` to customise each link + // individually + previewLinks: false, + + // Exposes the reveal.js API through window.postMessage + postMessage: true, + + // Dispatches all reveal.js events to the parent window through postMessage + postMessageEvents: false, + + // Focuses body when page changes visibility to ensure keyboard shortcuts work + focusBodyOnPageVisibilityChange: true, + + // Transition style + transition: 'slide', // none/fade/slide/convex/concave/zoom + + // Transition speed + transitionSpeed: 'default', // default/fast/slow + + // Transition style for full page slide backgrounds + backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom + + // Parallax background image + parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg" + + // Parallax background size + parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px" + + // Parallax background repeat + parallaxBackgroundRepeat: '', // repeat/repeat-x/repeat-y/no-repeat/initial/inherit + + // Parallax background position + parallaxBackgroundPosition: '', // CSS syntax, e.g. "top left" + + // Amount of pixels to move the parallax background per slide step + parallaxBackgroundHorizontal: null, + parallaxBackgroundVertical: null, + + // The maximum number of pages a single slide can expand onto when printing + // to PDF, unlimited by default + pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY, + + // Prints each fragment on a separate slide + pdfSeparateFragments: true, + + // Offset used to reduce the height of content within exported PDF pages. + // This exists to account for environment differences based on how you + // print to PDF. CLI printing options, like phantomjs and wkpdf, can end + // on precisely the total height of the document whereas in-browser + // printing has to end one pixel before. + pdfPageHeightOffset: -1, + + // Number of slides away from the current that are visible + viewDistance: 3, + + // Number of slides away from the current that are visible on mobile + // devices. It is advisable to set this to a lower number than + // viewDistance in order to save resources. + mobileViewDistance: 2, + + // The display mode that will be used to show slides + display: 'block', + + // Hide cursor if inactive + hideInactiveCursor: true, + + // Time before the cursor is hidden (in ms) + hideCursorTime: 5000, + + // Should we automatmically sort and set indices for fragments + // at each sync? (See Reveal.sync) + sortFragmentsOnSync: true, + + // Script dependencies to load + dependencies: [], + + // Plugin objects to register and use for this presentation + plugins: [] + +} \ No newline at end of file diff --git a/js/controllers/autoanimate.js b/js/controllers/autoanimate.js new file mode 100644 index 0000000..24eba32 --- /dev/null +++ b/js/controllers/autoanimate.js @@ -0,0 +1,640 @@ +import { queryAll, extend, createStyleSheet, matches, closest } from '../utils/util.js' +import { FRAGMENT_STYLE_REGEX } from '../utils/constants.js' + +// Counter used to generate unique IDs for auto-animated elements +let autoAnimateCounter = 0; + +/** + * Automatically animates matching elements across + * slides with the [data-auto-animate] attribute. + */ +export default class AutoAnimate { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + } + + /** + * Runs an auto-animation between the given slides. + * + * @param {HTMLElement} fromSlide + * @param {HTMLElement} toSlide + */ + run( fromSlide, toSlide ) { + + // Clean up after prior animations + this.reset(); + + let allSlides = this.Reveal.getSlides(); + let toSlideIndex = allSlides.indexOf( toSlide ); + let fromSlideIndex = allSlides.indexOf( fromSlide ); + + // Ensure that both slides are auto-animate targets with the same data-auto-animate-id value + // (including null if absent on both) and that data-auto-animate-restart isn't set on the + // physically latter slide (independent of slide direction) + if( fromSlide.hasAttribute( 'data-auto-animate' ) && toSlide.hasAttribute( 'data-auto-animate' ) + && fromSlide.getAttribute( 'data-auto-animate-id' ) === toSlide.getAttribute( 'data-auto-animate-id' ) + && !( toSlideIndex > fromSlideIndex ? toSlide : fromSlide ).hasAttribute( 'data-auto-animate-restart' ) ) { + + // Create a new auto-animate sheet + this.autoAnimateStyleSheet = this.autoAnimateStyleSheet || createStyleSheet(); + + let animationOptions = this.getAutoAnimateOptions( toSlide ); + + // Set our starting state + fromSlide.dataset.autoAnimate = 'pending'; + toSlide.dataset.autoAnimate = 'pending'; + + // Flag the navigation direction, needed for fragment buildup + animationOptions.slideDirection = toSlideIndex > fromSlideIndex ? 'forward' : 'backward'; + + // If the from-slide is hidden because it has moved outside + // the view distance, we need to temporarily show it while + // measuring + let fromSlideIsHidden = fromSlide.style.display === 'none'; + if( fromSlideIsHidden ) fromSlide.style.display = this.Reveal.getConfig().display; + + // Inject our auto-animate styles for this transition + let css = this.getAutoAnimatableElements( fromSlide, toSlide ).map( elements => { + return this.autoAnimateElements( elements.from, elements.to, elements.options || {}, animationOptions, autoAnimateCounter++ ); + } ); + + if( fromSlideIsHidden ) fromSlide.style.display = 'none'; + + // Animate unmatched elements, if enabled + if( toSlide.dataset.autoAnimateUnmatched !== 'false' && this.Reveal.getConfig().autoAnimateUnmatched === true ) { + + // Our default timings for unmatched elements + let defaultUnmatchedDuration = animationOptions.duration * 0.8, + defaultUnmatchedDelay = animationOptions.duration * 0.2; + + this.getUnmatchedAutoAnimateElements( toSlide ).forEach( unmatchedElement => { + + let unmatchedOptions = this.getAutoAnimateOptions( unmatchedElement, animationOptions ); + let id = 'unmatched'; + + // If there is a duration or delay set specifically for this + // element our unmatched elements should adhere to those + if( unmatchedOptions.duration !== animationOptions.duration || unmatchedOptions.delay !== animationOptions.delay ) { + id = 'unmatched-' + autoAnimateCounter++; + css.push( `[data-auto-animate="running"] [data-auto-animate-target="${id}"] { transition: opacity ${unmatchedOptions.duration}s ease ${unmatchedOptions.delay}s; }` ); + } + + unmatchedElement.dataset.autoAnimateTarget = id; + + }, this ); + + // Our default transition for unmatched elements + css.push( `[data-auto-animate="running"] [data-auto-animate-target="unmatched"] { transition: opacity ${defaultUnmatchedDuration}s ease ${defaultUnmatchedDelay}s; }` ); + + } + + // Setting the whole chunk of CSS at once is the most + // efficient way to do this. Using sheet.insertRule + // is multiple factors slower. + this.autoAnimateStyleSheet.innerHTML = css.join( '' ); + + // Start the animation next cycle + requestAnimationFrame( () => { + if( this.autoAnimateStyleSheet ) { + // This forces our newly injected styles to be applied in Firefox + getComputedStyle( this.autoAnimateStyleSheet ).fontWeight; + + toSlide.dataset.autoAnimate = 'running'; + } + } ); + + this.Reveal.dispatchEvent({ + type: 'autoanimate', + data: { + fromSlide, + toSlide, + sheet: this.autoAnimateStyleSheet + } + }); + + } + + } + + /** + * Rolls back all changes that we've made to the DOM so + * that as part of animating. + */ + reset() { + + // Reset slides + queryAll( this.Reveal.getRevealElement(), '[data-auto-animate]:not([data-auto-animate=""])' ).forEach( element => { + element.dataset.autoAnimate = ''; + } ); + + // Reset elements + queryAll( this.Reveal.getRevealElement(), '[data-auto-animate-target]' ).forEach( element => { + delete element.dataset.autoAnimateTarget; + } ); + + // Remove the animation sheet + if( this.autoAnimateStyleSheet && this.autoAnimateStyleSheet.parentNode ) { + this.autoAnimateStyleSheet.parentNode.removeChild( this.autoAnimateStyleSheet ); + this.autoAnimateStyleSheet = null; + } + + } + + /** + * Creates a FLIP animation where the `to` element starts out + * in the `from` element position and animates to its original + * state. + * + * @param {HTMLElement} from + * @param {HTMLElement} to + * @param {Object} elementOptions Options for this element pair + * @param {Object} animationOptions Options set at the slide level + * @param {String} id Unique ID that we can use to identify this + * auto-animate element in the DOM + */ + autoAnimateElements( from, to, elementOptions, animationOptions, id ) { + + // 'from' elements are given a data-auto-animate-target with no value, + // 'to' elements are are given a data-auto-animate-target with an ID + from.dataset.autoAnimateTarget = ''; + to.dataset.autoAnimateTarget = id; + + // Each element may override any of the auto-animate options + // like transition easing, duration and delay via data-attributes + let options = this.getAutoAnimateOptions( to, animationOptions ); + + // If we're using a custom element matcher the element options + // may contain additional transition overrides + if( typeof elementOptions.delay !== 'undefined' ) options.delay = elementOptions.delay; + if( typeof elementOptions.duration !== 'undefined' ) options.duration = elementOptions.duration; + if( typeof elementOptions.easing !== 'undefined' ) options.easing = elementOptions.easing; + + let fromProps = this.getAutoAnimatableProperties( 'from', from, elementOptions ), + toProps = this.getAutoAnimatableProperties( 'to', to, elementOptions ); + + // Maintain fragment visibility for matching elements when + // we're navigating forwards, this way the viewer won't need + // to step through the same fragments twice + if( to.classList.contains( 'fragment' ) ) { + + // Don't auto-animate the opacity of fragments to avoid + // conflicts with fragment animations + delete toProps.styles['opacity']; + + if( from.classList.contains( 'fragment' ) ) { + + let fromFragmentStyle = ( from.className.match( FRAGMENT_STYLE_REGEX ) || [''] )[0]; + let toFragmentStyle = ( to.className.match( FRAGMENT_STYLE_REGEX ) || [''] )[0]; + + // Only skip the fragment if the fragment animation style + // remains unchanged + if( fromFragmentStyle === toFragmentStyle && animationOptions.slideDirection === 'forward' ) { + to.classList.add( 'visible', 'disabled' ); + } + + } + + } + + // If translation and/or scaling are enabled, css transform + // the 'to' element so that it matches the position and size + // of the 'from' element + if( elementOptions.translate !== false || elementOptions.scale !== false ) { + + let presentationScale = this.Reveal.getScale(); + + let delta = { + x: ( fromProps.x - toProps.x ) / presentationScale, + y: ( fromProps.y - toProps.y ) / presentationScale, + scaleX: fromProps.width / toProps.width, + scaleY: fromProps.height / toProps.height + }; + + // Limit decimal points to avoid 0.0001px blur and stutter + delta.x = Math.round( delta.x * 1000 ) / 1000; + delta.y = Math.round( delta.y * 1000 ) / 1000; + delta.scaleX = Math.round( delta.scaleX * 1000 ) / 1000; + delta.scaleX = Math.round( delta.scaleX * 1000 ) / 1000; + + let translate = elementOptions.translate !== false && ( delta.x !== 0 || delta.y !== 0 ), + scale = elementOptions.scale !== false && ( delta.scaleX !== 0 || delta.scaleY !== 0 ); + + // No need to transform if nothing's changed + if( translate || scale ) { + + let transform = []; + + if( translate ) transform.push( `translate(${delta.x}px, ${delta.y}px)` ); + if( scale ) transform.push( `scale(${delta.scaleX}, ${delta.scaleY})` ); + + fromProps.styles['transform'] = transform.join( ' ' ); + fromProps.styles['transform-origin'] = 'top left'; + + toProps.styles['transform'] = 'none'; + + } + + } + + // Delete all unchanged 'to' styles + for( let propertyName in toProps.styles ) { + const toValue = toProps.styles[propertyName]; + const fromValue = fromProps.styles[propertyName]; + + if( toValue === fromValue ) { + delete toProps.styles[propertyName]; + } + else { + // If these property values were set via a custom matcher providing + // an explicit 'from' and/or 'to' value, we always inject those values. + if( toValue.explicitValue === true ) { + toProps.styles[propertyName] = toValue.value; + } + + if( fromValue.explicitValue === true ) { + fromProps.styles[propertyName] = fromValue.value; + } + } + } + + let css = ''; + + let toStyleProperties = Object.keys( toProps.styles ); + + // Only create animate this element IF at least one style + // property has changed + if( toStyleProperties.length > 0 ) { + + // Instantly move to the 'from' state + fromProps.styles['transition'] = 'none'; + + // Animate towards the 'to' state + toProps.styles['transition'] = `all ${options.duration}s ${options.easing} ${options.delay}s`; + toProps.styles['transition-property'] = toStyleProperties.join( ', ' ); + toProps.styles['will-change'] = toStyleProperties.join( ', ' ); + + // Build up our custom CSS. We need to override inline styles + // so we need to make our styles vErY IMPORTANT!1!! + let fromCSS = Object.keys( fromProps.styles ).map( propertyName => { + return propertyName + ': ' + fromProps.styles[propertyName] + ' !important;'; + } ).join( '' ); + + let toCSS = Object.keys( toProps.styles ).map( propertyName => { + return propertyName + ': ' + toProps.styles[propertyName] + ' !important;'; + } ).join( '' ); + + css = '[data-auto-animate-target="'+ id +'"] {'+ fromCSS +'}' + + '[data-auto-animate="running"] [data-auto-animate-target="'+ id +'"] {'+ toCSS +'}'; + + } + + return css; + + } + + /** + * Returns the auto-animate options for the given element. + * + * @param {HTMLElement} element Element to pick up options + * from, either a slide or an animation target + * @param {Object} [inheritedOptions] Optional set of existing + * options + */ + getAutoAnimateOptions( element, inheritedOptions ) { + + let options = { + easing: this.Reveal.getConfig().autoAnimateEasing, + duration: this.Reveal.getConfig().autoAnimateDuration, + delay: 0 + }; + + options = extend( options, inheritedOptions ); + + // Inherit options from parent elements + if( element.parentNode ) { + let autoAnimatedParent = closest( element.parentNode, '[data-auto-animate-target]' ); + if( autoAnimatedParent ) { + options = this.getAutoAnimateOptions( autoAnimatedParent, options ); + } + } + + if( element.dataset.autoAnimateEasing ) { + options.easing = element.dataset.autoAnimateEasing; + } + + if( element.dataset.autoAnimateDuration ) { + options.duration = parseFloat( element.dataset.autoAnimateDuration ); + } + + if( element.dataset.autoAnimateDelay ) { + options.delay = parseFloat( element.dataset.autoAnimateDelay ); + } + + return options; + + } + + /** + * Returns an object containing all of the properties + * that can be auto-animated for the given element and + * their current computed values. + * + * @param {String} direction 'from' or 'to' + */ + getAutoAnimatableProperties( direction, element, elementOptions ) { + + let config = this.Reveal.getConfig(); + + let properties = { styles: [] }; + + // Position and size + if( elementOptions.translate !== false || elementOptions.scale !== false ) { + let bounds; + + // Custom auto-animate may optionally return a custom tailored + // measurement function + if( typeof elementOptions.measure === 'function' ) { + bounds = elementOptions.measure( element ); + } + else { + if( config.center ) { + // More precise, but breaks when used in combination + // with zoom for scaling the deck ¯\_(ツ)_/¯ + bounds = element.getBoundingClientRect(); + } + else { + let scale = this.Reveal.getScale(); + bounds = { + x: element.offsetLeft * scale, + y: element.offsetTop * scale, + width: element.offsetWidth * scale, + height: element.offsetHeight * scale + }; + } + } + + properties.x = bounds.x; + properties.y = bounds.y; + properties.width = bounds.width; + properties.height = bounds.height; + } + + const computedStyles = getComputedStyle( element ); + + // CSS styles + ( elementOptions.styles || config.autoAnimateStyles ).forEach( style => { + let value; + + // `style` is either the property name directly, or an object + // definition of a style property + if( typeof style === 'string' ) style = { property: style }; + + if( typeof style.from !== 'undefined' && direction === 'from' ) { + value = { value: style.from, explicitValue: true }; + } + else if( typeof style.to !== 'undefined' && direction === 'to' ) { + value = { value: style.to, explicitValue: true }; + } + else { + // Use a unitless value for line-height so that it inherits properly + if( style.property === 'line-height' ) { + value = parseFloat( computedStyles['line-height'] ) / parseFloat( computedStyles['font-size'] ); + } + + if( isNaN(value) ) { + value = computedStyles[style.property]; + } + } + + if( value !== '' ) { + properties.styles[style.property] = value; + } + } ); + + return properties; + + } + + /** + * Get a list of all element pairs that we can animate + * between the given slides. + * + * @param {HTMLElement} fromSlide + * @param {HTMLElement} toSlide + * + * @return {Array} Each value is an array where [0] is + * the element we're animating from and [1] is the + * element we're animating to + */ + getAutoAnimatableElements( fromSlide, toSlide ) { + + let matcher = typeof this.Reveal.getConfig().autoAnimateMatcher === 'function' ? this.Reveal.getConfig().autoAnimateMatcher : this.getAutoAnimatePairs; + + let pairs = matcher.call( this, fromSlide, toSlide ); + + let reserved = []; + + // Remove duplicate pairs + return pairs.filter( ( pair, index ) => { + if( reserved.indexOf( pair.to ) === -1 ) { + reserved.push( pair.to ); + return true; + } + } ); + + } + + /** + * Identifies matching elements between slides. + * + * You can specify a custom matcher function by using + * the `autoAnimateMatcher` config option. + */ + getAutoAnimatePairs( fromSlide, toSlide ) { + + let pairs = []; + + const codeNodes = 'pre'; + const textNodes = 'h1, h2, h3, h4, h5, h6, p, li'; + const mediaNodes = 'img, video, iframe'; + + // Explicit matches via data-id + this.findAutoAnimateMatches( pairs, fromSlide, toSlide, '[data-id]', node => { + return node.nodeName + ':::' + node.getAttribute( 'data-id' ); + } ); + + // Text + this.findAutoAnimateMatches( pairs, fromSlide, toSlide, textNodes, node => { + return node.nodeName + ':::' + node.innerText; + } ); + + // Media + this.findAutoAnimateMatches( pairs, fromSlide, toSlide, mediaNodes, node => { + return node.nodeName + ':::' + ( node.getAttribute( 'src' ) || node.getAttribute( 'data-src' ) ); + } ); + + // Code + this.findAutoAnimateMatches( pairs, fromSlide, toSlide, codeNodes, node => { + return node.nodeName + ':::' + node.innerText; + } ); + + pairs.forEach( pair => { + // Disable scale transformations on text nodes, we transition + // each individual text property instead + if( matches( pair.from, textNodes ) ) { + pair.options = { scale: false }; + } + // Animate individual lines of code + else if( matches( pair.from, codeNodes ) ) { + + // Transition the code block's width and height instead of scaling + // to prevent its content from being squished + pair.options = { scale: false, styles: [ 'width', 'height' ] }; + + // Lines of code + this.findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-code', node => { + return node.textContent; + }, { + scale: false, + styles: [], + measure: this.getLocalBoundingBox.bind( this ) + } ); + + // Line numbers + this.findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-line[data-line-number]', node => { + return node.getAttribute( 'data-line-number' ); + }, { + scale: false, + styles: [ 'width' ], + measure: this.getLocalBoundingBox.bind( this ) + } ); + + } + + }, this ); + + return pairs; + + } + + /** + * Helper method which returns a bounding box based on + * the given elements offset coordinates. + * + * @param {HTMLElement} element + * @return {Object} x, y, width, height + */ + getLocalBoundingBox( element ) { + + const presentationScale = this.Reveal.getScale(); + + return { + x: Math.round( ( element.offsetLeft * presentationScale ) * 100 ) / 100, + y: Math.round( ( element.offsetTop * presentationScale ) * 100 ) / 100, + width: Math.round( ( element.offsetWidth * presentationScale ) * 100 ) / 100, + height: Math.round( ( element.offsetHeight * presentationScale ) * 100 ) / 100 + }; + + } + + /** + * Finds matching elements between two slides. + * + * @param {Array} pairs List of pairs to push matches to + * @param {HTMLElement} fromScope Scope within the from element exists + * @param {HTMLElement} toScope Scope within the to element exists + * @param {String} selector CSS selector of the element to match + * @param {Function} serializer A function that accepts an element and returns + * a stringified ID based on its contents + * @param {Object} animationOptions Optional config options for this pair + */ + findAutoAnimateMatches( pairs, fromScope, toScope, selector, serializer, animationOptions ) { + + let fromMatches = {}; + let toMatches = {}; + + [].slice.call( fromScope.querySelectorAll( selector ) ).forEach( ( element, i ) => { + const key = serializer( element ); + if( typeof key === 'string' && key.length ) { + fromMatches[key] = fromMatches[key] || []; + fromMatches[key].push( element ); + } + } ); + + [].slice.call( toScope.querySelectorAll( selector ) ).forEach( ( element, i ) => { + const key = serializer( element ); + toMatches[key] = toMatches[key] || []; + toMatches[key].push( element ); + + let fromElement; + + // Retrieve the 'from' element + if( fromMatches[key] ) { + const primaryIndex = toMatches[key].length - 1; + const secondaryIndex = fromMatches[key].length - 1; + + // If there are multiple identical from elements, retrieve + // the one at the same index as our to-element. + if( fromMatches[key][ primaryIndex ] ) { + fromElement = fromMatches[key][ primaryIndex ]; + fromMatches[key][ primaryIndex ] = null; + } + // If there are no matching from-elements at the same index, + // use the last one. + else if( fromMatches[key][ secondaryIndex ] ) { + fromElement = fromMatches[key][ secondaryIndex ]; + fromMatches[key][ secondaryIndex ] = null; + } + } + + // If we've got a matching pair, push it to the list of pairs + if( fromElement ) { + pairs.push({ + from: fromElement, + to: element, + options: animationOptions + }); + } + } ); + + } + + /** + * Returns a all elements within the given scope that should + * be considered unmatched in an auto-animate transition. If + * fading of unmatched elements is turned on, these elements + * will fade when going between auto-animate slides. + * + * Note that parents of auto-animate targets are NOT considered + * unmatched since fading them would break the auto-animation. + * + * @param {HTMLElement} rootElement + * @return {Array} + */ + getUnmatchedAutoAnimateElements( rootElement ) { + + return [].slice.call( rootElement.children ).reduce( ( result, element ) => { + + const containsAnimatedElements = element.querySelector( '[data-auto-animate-target]' ); + + // The element is unmatched if + // - It is not an auto-animate target + // - It does not contain any auto-animate targets + if( !element.hasAttribute( 'data-auto-animate-target' ) && !containsAnimatedElements ) { + result.push( element ); + } + + if( element.querySelector( '[data-auto-animate-target]' ) ) { + result = result.concat( this.getUnmatchedAutoAnimateElements( element ) ); + } + + return result; + + }, [] ); + + } + +} diff --git a/js/controllers/backgrounds.js b/js/controllers/backgrounds.js new file mode 100644 index 0000000..f906269 --- /dev/null +++ b/js/controllers/backgrounds.js @@ -0,0 +1,406 @@ +import { queryAll } from '../utils/util.js' +import { colorToRgb, colorBrightness } from '../utils/color.js' + +/** + * Creates and updates slide backgrounds. + */ +export default class Backgrounds { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + } + + render() { + + this.element = document.createElement( 'div' ); + this.element.className = 'backgrounds'; + this.Reveal.getRevealElement().appendChild( this.element ); + + } + + /** + * Creates the slide background elements and appends them + * to the background container. One element is created per + * slide no matter if the given slide has visible background. + */ + create() { + + // Clear prior backgrounds + this.element.innerHTML = ''; + this.element.classList.add( 'no-transition' ); + + // Iterate over all horizontal slides + this.Reveal.getHorizontalSlides().forEach( slideh => { + + let backgroundStack = this.createBackground( slideh, this.element ); + + // Iterate over all vertical slides + queryAll( slideh, 'section' ).forEach( slidev => { + + this.createBackground( slidev, backgroundStack ); + + backgroundStack.classList.add( 'stack' ); + + } ); + + } ); + + // Add parallax background if specified + if( this.Reveal.getConfig().parallaxBackgroundImage ) { + + this.element.style.backgroundImage = 'url("' + this.Reveal.getConfig().parallaxBackgroundImage + '")'; + this.element.style.backgroundSize = this.Reveal.getConfig().parallaxBackgroundSize; + this.element.style.backgroundRepeat = this.Reveal.getConfig().parallaxBackgroundRepeat; + this.element.style.backgroundPosition = this.Reveal.getConfig().parallaxBackgroundPosition; + + // Make sure the below properties are set on the element - these properties are + // needed for proper transitions to be set on the element via CSS. To remove + // annoying background slide-in effect when the presentation starts, apply + // these properties after short time delay + setTimeout( () => { + this.Reveal.getRevealElement().classList.add( 'has-parallax-background' ); + }, 1 ); + + } + else { + + this.element.style.backgroundImage = ''; + this.Reveal.getRevealElement().classList.remove( 'has-parallax-background' ); + + } + + } + + /** + * Creates a background for the given slide. + * + * @param {HTMLElement} slide + * @param {HTMLElement} container The element that the background + * should be appended to + * @return {HTMLElement} New background div + */ + createBackground( slide, container ) { + + // Main slide background element + let element = document.createElement( 'div' ); + element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' ); + + // Inner background element that wraps images/videos/iframes + let contentElement = document.createElement( 'div' ); + contentElement.className = 'slide-background-content'; + + element.appendChild( contentElement ); + container.appendChild( element ); + + slide.slideBackgroundElement = element; + slide.slideBackgroundContentElement = contentElement; + + // Syncs the background to reflect all current background settings + this.sync( slide ); + + return element; + + } + + /** + * Renders all of the visual properties of a slide background + * based on the various background attributes. + * + * @param {HTMLElement} slide + */ + sync( slide ) { + + const element = slide.slideBackgroundElement, + contentElement = slide.slideBackgroundContentElement; + + const data = { + background: slide.getAttribute( 'data-background' ), + backgroundSize: slide.getAttribute( 'data-background-size' ), + backgroundImage: slide.getAttribute( 'data-background-image' ), + backgroundVideo: slide.getAttribute( 'data-background-video' ), + backgroundIframe: slide.getAttribute( 'data-background-iframe' ), + backgroundColor: slide.getAttribute( 'data-background-color' ), + backgroundGradient: slide.getAttribute( 'data-background-gradient' ), + backgroundRepeat: slide.getAttribute( 'data-background-repeat' ), + backgroundPosition: slide.getAttribute( 'data-background-position' ), + backgroundTransition: slide.getAttribute( 'data-background-transition' ), + backgroundOpacity: slide.getAttribute( 'data-background-opacity' ), + }; + + const dataPreload = slide.hasAttribute( 'data-preload' ); + + // Reset the prior background state in case this is not the + // initial sync + slide.classList.remove( 'has-dark-background' ); + slide.classList.remove( 'has-light-background' ); + + element.removeAttribute( 'data-loaded' ); + element.removeAttribute( 'data-background-hash' ); + element.removeAttribute( 'data-background-size' ); + element.removeAttribute( 'data-background-transition' ); + element.style.backgroundColor = ''; + + contentElement.style.backgroundSize = ''; + contentElement.style.backgroundRepeat = ''; + contentElement.style.backgroundPosition = ''; + contentElement.style.backgroundImage = ''; + contentElement.style.opacity = ''; + contentElement.innerHTML = ''; + + if( data.background ) { + // Auto-wrap image urls in url(...) + if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp|webp)([?#\s]|$)/gi.test( data.background ) ) { + slide.setAttribute( 'data-background-image', data.background ); + } + else { + element.style.background = data.background; + } + } + + // Create a hash for this combination of background settings. + // This is used to determine when two slide backgrounds are + // the same. + if( data.background || data.backgroundColor || data.backgroundGradient || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) { + element.setAttribute( 'data-background-hash', data.background + + data.backgroundSize + + data.backgroundImage + + data.backgroundVideo + + data.backgroundIframe + + data.backgroundColor + + data.backgroundGradient + + data.backgroundRepeat + + data.backgroundPosition + + data.backgroundTransition + + data.backgroundOpacity ); + } + + // Additional and optional background properties + if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize ); + if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor; + if( data.backgroundGradient ) element.style.backgroundImage = data.backgroundGradient; + if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition ); + + if( dataPreload ) element.setAttribute( 'data-preload', '' ); + + // Background image options are set on the content wrapper + if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize; + if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat; + if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition; + if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity; + + // If this slide has a background color, we add a class that + // signals if it is light or dark. If the slide has no background + // color, no class will be added + let contrastColor = data.backgroundColor; + + // If no bg color was found, or it cannot be converted by colorToRgb, check the computed background + if( !contrastColor || !colorToRgb( contrastColor ) ) { + let computedBackgroundStyle = window.getComputedStyle( element ); + if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) { + contrastColor = computedBackgroundStyle.backgroundColor; + } + } + + if( contrastColor ) { + const rgb = colorToRgb( contrastColor ); + + // Ignore fully transparent backgrounds. Some browsers return + // rgba(0,0,0,0) when reading the computed background color of + // an element with no background + if( rgb && rgb.a !== 0 ) { + if( colorBrightness( contrastColor ) < 128 ) { + slide.classList.add( 'has-dark-background' ); + } + else { + slide.classList.add( 'has-light-background' ); + } + } + } + + } + + /** + * Updates the background elements to reflect the current + * slide. + * + * @param {boolean} includeAll If true, the backgrounds of + * all vertical slides (not just the present) will be updated. + */ + update( includeAll = false ) { + + let currentSlide = this.Reveal.getCurrentSlide(); + let indices = this.Reveal.getIndices(); + + let currentBackground = null; + + // Reverse past/future classes when in RTL mode + let horizontalPast = this.Reveal.getConfig().rtl ? 'future' : 'past', + horizontalFuture = this.Reveal.getConfig().rtl ? 'past' : 'future'; + + // Update the classes of all backgrounds to match the + // states of their slides (past/present/future) + Array.from( this.element.childNodes ).forEach( ( backgroundh, h ) => { + + backgroundh.classList.remove( 'past', 'present', 'future' ); + + if( h < indices.h ) { + backgroundh.classList.add( horizontalPast ); + } + else if ( h > indices.h ) { + backgroundh.classList.add( horizontalFuture ); + } + else { + backgroundh.classList.add( 'present' ); + + // Store a reference to the current background element + currentBackground = backgroundh; + } + + if( includeAll || h === indices.h ) { + queryAll( backgroundh, '.slide-background' ).forEach( ( backgroundv, v ) => { + + backgroundv.classList.remove( 'past', 'present', 'future' ); + + if( v < indices.v ) { + backgroundv.classList.add( 'past' ); + } + else if ( v > indices.v ) { + backgroundv.classList.add( 'future' ); + } + else { + backgroundv.classList.add( 'present' ); + + // Only if this is the present horizontal and vertical slide + if( h === indices.h ) currentBackground = backgroundv; + } + + } ); + } + + } ); + + // Stop content inside of previous backgrounds + if( this.previousBackground ) { + + this.Reveal.slideContent.stopEmbeddedContent( this.previousBackground, { unloadIframes: !this.Reveal.slideContent.shouldPreload( this.previousBackground ) } ); + + } + + // Start content in the current background + if( currentBackground ) { + + this.Reveal.slideContent.startEmbeddedContent( currentBackground ); + + let currentBackgroundContent = currentBackground.querySelector( '.slide-background-content' ); + if( currentBackgroundContent ) { + + let backgroundImageURL = currentBackgroundContent.style.backgroundImage || ''; + + // Restart GIFs (doesn't work in Firefox) + if( /\.gif/i.test( backgroundImageURL ) ) { + currentBackgroundContent.style.backgroundImage = ''; + window.getComputedStyle( currentBackgroundContent ).opacity; + currentBackgroundContent.style.backgroundImage = backgroundImageURL; + } + + } + + // Don't transition between identical backgrounds. This + // prevents unwanted flicker. + let previousBackgroundHash = this.previousBackground ? this.previousBackground.getAttribute( 'data-background-hash' ) : null; + let currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' ); + if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== this.previousBackground ) { + this.element.classList.add( 'no-transition' ); + } + + this.previousBackground = currentBackground; + + } + + // If there's a background brightness flag for this slide, + // bubble it to the .reveal container + if( currentSlide ) { + [ 'has-light-background', 'has-dark-background' ].forEach( classToBubble => { + if( currentSlide.classList.contains( classToBubble ) ) { + this.Reveal.getRevealElement().classList.add( classToBubble ); + } + else { + this.Reveal.getRevealElement().classList.remove( classToBubble ); + } + }, this ); + } + + // Allow the first background to apply without transition + setTimeout( () => { + this.element.classList.remove( 'no-transition' ); + }, 1 ); + + } + + /** + * Updates the position of the parallax background based + * on the current slide index. + */ + updateParallax() { + + let indices = this.Reveal.getIndices(); + + if( this.Reveal.getConfig().parallaxBackgroundImage ) { + + let horizontalSlides = this.Reveal.getHorizontalSlides(), + verticalSlides = this.Reveal.getVerticalSlides(); + + let backgroundSize = this.element.style.backgroundSize.split( ' ' ), + backgroundWidth, backgroundHeight; + + if( backgroundSize.length === 1 ) { + backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 ); + } + else { + backgroundWidth = parseInt( backgroundSize[0], 10 ); + backgroundHeight = parseInt( backgroundSize[1], 10 ); + } + + let slideWidth = this.element.offsetWidth, + horizontalSlideCount = horizontalSlides.length, + horizontalOffsetMultiplier, + horizontalOffset; + + if( typeof this.Reveal.getConfig().parallaxBackgroundHorizontal === 'number' ) { + horizontalOffsetMultiplier = this.Reveal.getConfig().parallaxBackgroundHorizontal; + } + else { + horizontalOffsetMultiplier = horizontalSlideCount > 1 ? ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) : 0; + } + + horizontalOffset = horizontalOffsetMultiplier * indices.h * -1; + + let slideHeight = this.element.offsetHeight, + verticalSlideCount = verticalSlides.length, + verticalOffsetMultiplier, + verticalOffset; + + if( typeof this.Reveal.getConfig().parallaxBackgroundVertical === 'number' ) { + verticalOffsetMultiplier = this.Reveal.getConfig().parallaxBackgroundVertical; + } + else { + verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ); + } + + verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indices.v : 0; + + this.element.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px'; + + } + + } + + destroy() { + + this.element.remove(); + + } + +} diff --git a/js/controllers/controls.js b/js/controllers/controls.js new file mode 100644 index 0000000..734eb17 --- /dev/null +++ b/js/controllers/controls.js @@ -0,0 +1,266 @@ +import { queryAll } from '../utils/util.js' +import { isAndroid } from '../utils/device.js' + +/** + * Manages our presentation controls. This includes both + * the built-in control arrows as well as event monitoring + * of any elements within the presentation with either of the + * following helper classes: + * - .navigate-up + * - .navigate-right + * - .navigate-down + * - .navigate-left + * - .navigate-next + * - .navigate-prev + */ +export default class Controls { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + this.onNavigateLeftClicked = this.onNavigateLeftClicked.bind( this ); + this.onNavigateRightClicked = this.onNavigateRightClicked.bind( this ); + this.onNavigateUpClicked = this.onNavigateUpClicked.bind( this ); + this.onNavigateDownClicked = this.onNavigateDownClicked.bind( this ); + this.onNavigatePrevClicked = this.onNavigatePrevClicked.bind( this ); + this.onNavigateNextClicked = this.onNavigateNextClicked.bind( this ); + + } + + render() { + + const rtl = this.Reveal.getConfig().rtl; + const revealElement = this.Reveal.getRevealElement(); + + this.element = document.createElement( 'aside' ); + this.element.className = 'controls'; + this.element.innerHTML = + ` + + + `; + + this.Reveal.getRevealElement().appendChild( this.element ); + + // There can be multiple instances of controls throughout the page + this.controlsLeft = queryAll( revealElement, '.navigate-left' ); + this.controlsRight = queryAll( revealElement, '.navigate-right' ); + this.controlsUp = queryAll( revealElement, '.navigate-up' ); + this.controlsDown = queryAll( revealElement, '.navigate-down' ); + this.controlsPrev = queryAll( revealElement, '.navigate-prev' ); + this.controlsNext = queryAll( revealElement, '.navigate-next' ); + + // The left, right and down arrows in the standard reveal.js controls + this.controlsRightArrow = this.element.querySelector( '.navigate-right' ); + this.controlsLeftArrow = this.element.querySelector( '.navigate-left' ); + this.controlsDownArrow = this.element.querySelector( '.navigate-down' ); + + } + + /** + * Called when the reveal.js config is updated. + */ + configure( config, oldConfig ) { + + this.element.style.display = config.controls ? 'block' : 'none'; + + this.element.setAttribute( 'data-controls-layout', config.controlsLayout ); + this.element.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows ); + + } + + bind() { + + // Listen to both touch and click events, in case the device + // supports both + let pointerEvents = [ 'touchstart', 'click' ]; + + // Only support touch for Android, fixes double navigations in + // stock browser + if( isAndroid ) { + pointerEvents = [ 'touchstart' ]; + } + + pointerEvents.forEach( eventName => { + this.controlsLeft.forEach( el => el.addEventListener( eventName, this.onNavigateLeftClicked, false ) ); + this.controlsRight.forEach( el => el.addEventListener( eventName, this.onNavigateRightClicked, false ) ); + this.controlsUp.forEach( el => el.addEventListener( eventName, this.onNavigateUpClicked, false ) ); + this.controlsDown.forEach( el => el.addEventListener( eventName, this.onNavigateDownClicked, false ) ); + this.controlsPrev.forEach( el => el.addEventListener( eventName, this.onNavigatePrevClicked, false ) ); + this.controlsNext.forEach( el => el.addEventListener( eventName, this.onNavigateNextClicked, false ) ); + } ); + + } + + unbind() { + + [ 'touchstart', 'click' ].forEach( eventName => { + this.controlsLeft.forEach( el => el.removeEventListener( eventName, this.onNavigateLeftClicked, false ) ); + this.controlsRight.forEach( el => el.removeEventListener( eventName, this.onNavigateRightClicked, false ) ); + this.controlsUp.forEach( el => el.removeEventListener( eventName, this.onNavigateUpClicked, false ) ); + this.controlsDown.forEach( el => el.removeEventListener( eventName, this.onNavigateDownClicked, false ) ); + this.controlsPrev.forEach( el => el.removeEventListener( eventName, this.onNavigatePrevClicked, false ) ); + this.controlsNext.forEach( el => el.removeEventListener( eventName, this.onNavigateNextClicked, false ) ); + } ); + + } + + /** + * Updates the state of all control/navigation arrows. + */ + update() { + + let routes = this.Reveal.availableRoutes(); + + // Remove the 'enabled' class from all directions + [...this.controlsLeft, ...this.controlsRight, ...this.controlsUp, ...this.controlsDown, ...this.controlsPrev, ...this.controlsNext].forEach( node => { + node.classList.remove( 'enabled', 'fragmented' ); + + // Set 'disabled' attribute on all directions + node.setAttribute( 'disabled', 'disabled' ); + } ); + + // Add the 'enabled' class to the available routes; remove 'disabled' attribute to enable buttons + if( routes.left ) this.controlsLeft.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } ); + if( routes.right ) this.controlsRight.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } ); + if( routes.up ) this.controlsUp.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } ); + if( routes.down ) this.controlsDown.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } ); + + // Prev/next buttons + if( routes.left || routes.up ) this.controlsPrev.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } ); + if( routes.right || routes.down ) this.controlsNext.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } ); + + // Highlight fragment directions + let currentSlide = this.Reveal.getCurrentSlide(); + if( currentSlide ) { + + let fragmentsRoutes = this.Reveal.fragments.availableRoutes(); + + // Always apply fragment decorator to prev/next buttons + if( fragmentsRoutes.prev ) this.controlsPrev.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } ); + if( fragmentsRoutes.next ) this.controlsNext.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } ); + + // Apply fragment decorators to directional buttons based on + // what slide axis they are in + if( this.Reveal.isVerticalSlide( currentSlide ) ) { + if( fragmentsRoutes.prev ) this.controlsUp.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } ); + if( fragmentsRoutes.next ) this.controlsDown.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } ); + } + else { + if( fragmentsRoutes.prev ) this.controlsLeft.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } ); + if( fragmentsRoutes.next ) this.controlsRight.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } ); + } + + } + + if( this.Reveal.getConfig().controlsTutorial ) { + + let indices = this.Reveal.getIndices(); + + // Highlight control arrows with an animation to ensure + // that the viewer knows how to navigate + if( !this.Reveal.hasNavigatedVertically() && routes.down ) { + this.controlsDownArrow.classList.add( 'highlight' ); + } + else { + this.controlsDownArrow.classList.remove( 'highlight' ); + + if( this.Reveal.getConfig().rtl ) { + + if( !this.Reveal.hasNavigatedHorizontally() && routes.left && indices.v === 0 ) { + this.controlsLeftArrow.classList.add( 'highlight' ); + } + else { + this.controlsLeftArrow.classList.remove( 'highlight' ); + } + + } else { + + if( !this.Reveal.hasNavigatedHorizontally() && routes.right && indices.v === 0 ) { + this.controlsRightArrow.classList.add( 'highlight' ); + } + else { + this.controlsRightArrow.classList.remove( 'highlight' ); + } + } + } + } + } + + destroy() { + + this.unbind(); + this.element.remove(); + + } + + /** + * Event handlers for navigation control buttons. + */ + onNavigateLeftClicked( event ) { + + event.preventDefault(); + this.Reveal.onUserInput(); + + if( this.Reveal.getConfig().navigationMode === 'linear' ) { + this.Reveal.prev(); + } + else { + this.Reveal.left(); + } + + } + + onNavigateRightClicked( event ) { + + event.preventDefault(); + this.Reveal.onUserInput(); + + if( this.Reveal.getConfig().navigationMode === 'linear' ) { + this.Reveal.next(); + } + else { + this.Reveal.right(); + } + + } + + onNavigateUpClicked( event ) { + + event.preventDefault(); + this.Reveal.onUserInput(); + + this.Reveal.up(); + + } + + onNavigateDownClicked( event ) { + + event.preventDefault(); + this.Reveal.onUserInput(); + + this.Reveal.down(); + + } + + onNavigatePrevClicked( event ) { + + event.preventDefault(); + this.Reveal.onUserInput(); + + this.Reveal.prev(); + + } + + onNavigateNextClicked( event ) { + + event.preventDefault(); + this.Reveal.onUserInput(); + + this.Reveal.next(); + + } + + +} \ No newline at end of file diff --git a/js/controllers/focus.js b/js/controllers/focus.js new file mode 100644 index 0000000..3e68c3f --- /dev/null +++ b/js/controllers/focus.js @@ -0,0 +1,103 @@ +import { closest } from '../utils/util.js' + +/** + * Manages focus when a presentation is embedded. This + * helps us only capture keyboard from the presentation + * a user is currently interacting with in a page where + * multiple presentations are embedded. + */ + +const STATE_FOCUS = 'focus'; +const STATE_BLUR = 'blur'; + +export default class Focus { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + this.onRevealPointerDown = this.onRevealPointerDown.bind( this ); + this.onDocumentPointerDown = this.onDocumentPointerDown.bind( this ); + + } + + /** + * Called when the reveal.js config is updated. + */ + configure( config, oldConfig ) { + + if( config.embedded ) { + this.blur(); + } + else { + this.focus(); + this.unbind(); + } + + } + + bind() { + + if( this.Reveal.getConfig().embedded ) { + this.Reveal.getRevealElement().addEventListener( 'pointerdown', this.onRevealPointerDown, false ); + } + + } + + unbind() { + + this.Reveal.getRevealElement().removeEventListener( 'pointerdown', this.onRevealPointerDown, false ); + document.removeEventListener( 'pointerdown', this.onDocumentPointerDown, false ); + + } + + focus() { + + if( this.state !== STATE_FOCUS ) { + this.Reveal.getRevealElement().classList.add( 'focused' ); + document.addEventListener( 'pointerdown', this.onDocumentPointerDown, false ); + } + + this.state = STATE_FOCUS; + + } + + blur() { + + if( this.state !== STATE_BLUR ) { + this.Reveal.getRevealElement().classList.remove( 'focused' ); + document.removeEventListener( 'pointerdown', this.onDocumentPointerDown, false ); + } + + this.state = STATE_BLUR; + + } + + isFocused() { + + return this.state === STATE_FOCUS; + + } + + destroy() { + + this.Reveal.getRevealElement().classList.remove( 'focused' ); + + } + + onRevealPointerDown( event ) { + + this.focus(); + + } + + onDocumentPointerDown( event ) { + + let revealElement = closest( event.target, '.reveal' ); + if( !revealElement || revealElement !== this.Reveal.getRevealElement() ) { + this.blur(); + } + + } + +} \ No newline at end of file diff --git a/js/controllers/fragments.js b/js/controllers/fragments.js new file mode 100644 index 0000000..796c168 --- /dev/null +++ b/js/controllers/fragments.js @@ -0,0 +1,376 @@ +import { extend, queryAll } from '../utils/util.js' + +/** + * Handles sorting and navigation of slide fragments. + * Fragments are elements within a slide that are + * revealed/animated incrementally. + */ +export default class Fragments { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + } + + /** + * Called when the reveal.js config is updated. + */ + configure( config, oldConfig ) { + + if( config.fragments === false ) { + this.disable(); + } + else if( oldConfig.fragments === false ) { + this.enable(); + } + + } + + /** + * If fragments are disabled in the deck, they should all be + * visible rather than stepped through. + */ + disable() { + + queryAll( this.Reveal.getSlidesElement(), '.fragment' ).forEach( element => { + element.classList.add( 'visible' ); + element.classList.remove( 'current-fragment' ); + } ); + + } + + /** + * Reverse of #disable(). Only called if fragments have + * previously been disabled. + */ + enable() { + + queryAll( this.Reveal.getSlidesElement(), '.fragment' ).forEach( element => { + element.classList.remove( 'visible' ); + element.classList.remove( 'current-fragment' ); + } ); + + } + + /** + * Returns an object describing the available fragment + * directions. + * + * @return {{prev: boolean, next: boolean}} + */ + availableRoutes() { + + let currentSlide = this.Reveal.getCurrentSlide(); + if( currentSlide && this.Reveal.getConfig().fragments ) { + let fragments = currentSlide.querySelectorAll( '.fragment:not(.disabled)' ); + let hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.disabled):not(.visible)' ); + + return { + prev: fragments.length - hiddenFragments.length > 0, + next: !!hiddenFragments.length + }; + } + else { + return { prev: false, next: false }; + } + + } + + /** + * Return a sorted fragments list, ordered by an increasing + * "data-fragment-index" attribute. + * + * Fragments will be revealed in the order that they are returned by + * this function, so you can use the index attributes to control the + * order of fragment appearance. + * + * To maintain a sensible default fragment order, fragments are presumed + * to be passed in document order. This function adds a "fragment-index" + * attribute to each node if such an attribute is not already present, + * and sets that attribute to an integer value which is the position of + * the fragment within the fragments list. + * + * @param {object[]|*} fragments + * @param {boolean} grouped If true the returned array will contain + * nested arrays for all fragments with the same index + * @return {object[]} sorted Sorted array of fragments + */ + sort( fragments, grouped = false ) { + + fragments = Array.from( fragments ); + + let ordered = [], + unordered = [], + sorted = []; + + // Group ordered and unordered elements + fragments.forEach( fragment => { + if( fragment.hasAttribute( 'data-fragment-index' ) ) { + let index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 ); + + if( !ordered[index] ) { + ordered[index] = []; + } + + ordered[index].push( fragment ); + } + else { + unordered.push( [ fragment ] ); + } + } ); + + // Append fragments without explicit indices in their + // DOM order + ordered = ordered.concat( unordered ); + + // Manually count the index up per group to ensure there + // are no gaps + let index = 0; + + // Push all fragments in their sorted order to an array, + // this flattens the groups + ordered.forEach( group => { + group.forEach( fragment => { + sorted.push( fragment ); + fragment.setAttribute( 'data-fragment-index', index ); + } ); + + index ++; + } ); + + return grouped === true ? ordered : sorted; + + } + + /** + * Sorts and formats all of fragments in the + * presentation. + */ + sortAll() { + + this.Reveal.getHorizontalSlides().forEach( horizontalSlide => { + + let verticalSlides = queryAll( horizontalSlide, 'section' ); + verticalSlides.forEach( ( verticalSlide, y ) => { + + this.sort( verticalSlide.querySelectorAll( '.fragment' ) ); + + }, this ); + + if( verticalSlides.length === 0 ) this.sort( horizontalSlide.querySelectorAll( '.fragment' ) ); + + } ); + + } + + /** + * Refreshes the fragments on the current slide so that they + * have the appropriate classes (.visible + .current-fragment). + * + * @param {number} [index] The index of the current fragment + * @param {array} [fragments] Array containing all fragments + * in the current slide + * + * @return {{shown: array, hidden: array}} + */ + update( index, fragments ) { + + let changedFragments = { + shown: [], + hidden: [] + }; + + let currentSlide = this.Reveal.getCurrentSlide(); + if( currentSlide && this.Reveal.getConfig().fragments ) { + + fragments = fragments || this.sort( currentSlide.querySelectorAll( '.fragment' ) ); + + if( fragments.length ) { + + let maxIndex = 0; + + if( typeof index !== 'number' ) { + let currentFragment = this.sort( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop(); + if( currentFragment ) { + index = parseInt( currentFragment.getAttribute( 'data-fragment-index' ) || 0, 10 ); + } + } + + Array.from( fragments ).forEach( ( el, i ) => { + + if( el.hasAttribute( 'data-fragment-index' ) ) { + i = parseInt( el.getAttribute( 'data-fragment-index' ), 10 ); + } + + maxIndex = Math.max( maxIndex, i ); + + // Visible fragments + if( i <= index ) { + let wasVisible = el.classList.contains( 'visible' ) + el.classList.add( 'visible' ); + el.classList.remove( 'current-fragment' ); + + if( i === index ) { + // Announce the fragments one by one to the Screen Reader + this.Reveal.announceStatus( this.Reveal.getStatusText( el ) ); + + el.classList.add( 'current-fragment' ); + this.Reveal.slideContent.startEmbeddedContent( el ); + } + + if( !wasVisible ) { + changedFragments.shown.push( el ) + this.Reveal.dispatchEvent({ + target: el, + type: 'visible', + bubbles: false + }); + } + } + // Hidden fragments + else { + let wasVisible = el.classList.contains( 'visible' ) + el.classList.remove( 'visible' ); + el.classList.remove( 'current-fragment' ); + + if( wasVisible ) { + this.Reveal.slideContent.stopEmbeddedContent( el ); + changedFragments.hidden.push( el ); + this.Reveal.dispatchEvent({ + target: el, + type: 'hidden', + bubbles: false + }); + } + } + + } ); + + // Write the current fragment index to the slide
. + // This can be used by end users to apply styles based on + // the current fragment index. + index = typeof index === 'number' ? index : -1; + index = Math.max( Math.min( index, maxIndex ), -1 ); + currentSlide.setAttribute( 'data-fragment', index ); + + } + + } + + return changedFragments; + + } + + /** + * Formats the fragments on the given slide so that they have + * valid indices. Call this if fragments are changed in the DOM + * after reveal.js has already initialized. + * + * @param {HTMLElement} slide + * @return {Array} a list of the HTML fragments that were synced + */ + sync( slide = this.Reveal.getCurrentSlide() ) { + + return this.sort( slide.querySelectorAll( '.fragment' ) ); + + } + + /** + * Navigate to the specified slide fragment. + * + * @param {?number} index The index of the fragment that + * should be shown, -1 means all are invisible + * @param {number} offset Integer offset to apply to the + * fragment index + * + * @return {boolean} true if a change was made in any + * fragments visibility as part of this call + */ + goto( index, offset = 0 ) { + + let currentSlide = this.Reveal.getCurrentSlide(); + if( currentSlide && this.Reveal.getConfig().fragments ) { + + let fragments = this.sort( currentSlide.querySelectorAll( '.fragment:not(.disabled)' ) ); + if( fragments.length ) { + + // If no index is specified, find the current + if( typeof index !== 'number' ) { + let lastVisibleFragment = this.sort( currentSlide.querySelectorAll( '.fragment:not(.disabled).visible' ) ).pop(); + + if( lastVisibleFragment ) { + index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 ); + } + else { + index = -1; + } + } + + // Apply the offset if there is one + index += offset; + + let changedFragments = this.update( index, fragments ); + + if( changedFragments.hidden.length ) { + this.Reveal.dispatchEvent({ + type: 'fragmenthidden', + data: { + fragment: changedFragments.hidden[0], + fragments: changedFragments.hidden + } + }); + } + + if( changedFragments.shown.length ) { + this.Reveal.dispatchEvent({ + type: 'fragmentshown', + data: { + fragment: changedFragments.shown[0], + fragments: changedFragments.shown + } + }); + } + + this.Reveal.controls.update(); + this.Reveal.progress.update(); + + if( this.Reveal.getConfig().fragmentInURL ) { + this.Reveal.location.writeURL(); + } + + return !!( changedFragments.shown.length || changedFragments.hidden.length ); + + } + + } + + return false; + + } + + /** + * Navigate to the next slide fragment. + * + * @return {boolean} true if there was a next fragment, + * false otherwise + */ + next() { + + return this.goto( null, 1 ); + + } + + /** + * Navigate to the previous slide fragment. + * + * @return {boolean} true if there was a previous fragment, + * false otherwise + */ + prev() { + + return this.goto( null, -1 ); + + } + +} \ No newline at end of file diff --git a/js/controllers/jumptoslide.js b/js/controllers/jumptoslide.js new file mode 100644 index 0000000..cf2de99 --- /dev/null +++ b/js/controllers/jumptoslide.js @@ -0,0 +1,170 @@ +/** + * Makes it possible to jump to a slide by entering its + * slide number or id. + */ +export default class JumpToSlide { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + this.onInput = this.onInput.bind( this ); + this.onBlur = this.onBlur.bind( this ); + this.onKeyDown = this.onKeyDown.bind( this ); + + } + + render() { + + this.element = document.createElement( 'div' ); + this.element.className = 'jump-to-slide'; + + this.jumpInput = document.createElement( 'input' ); + this.jumpInput.type = 'text'; + this.jumpInput.className = 'jump-to-slide-input'; + this.jumpInput.placeholder = 'Jump to slide'; + this.jumpInput.addEventListener( 'input', this.onInput ); + this.jumpInput.addEventListener( 'keydown', this.onKeyDown ); + this.jumpInput.addEventListener( 'blur', this.onBlur ); + + this.element.appendChild( this.jumpInput ); + + } + + show() { + + this.indicesOnShow = this.Reveal.getIndices(); + + this.Reveal.getRevealElement().appendChild( this.element ); + this.jumpInput.focus(); + + } + + hide() { + + if( this.isVisible() ) { + this.element.remove(); + this.jumpInput.value = ''; + + clearTimeout( this.jumpTimeout ); + delete this.jumpTimeout; + } + + } + + isVisible() { + + return !!this.element.parentNode; + + } + + /** + * Parses the current input and jumps to the given slide. + */ + jump() { + + clearTimeout( this.jumpTimeout ); + delete this.jumpTimeout; + + const query = this.jumpInput.value.trim( '' ); + let indices = this.Reveal.location.getIndicesFromHash( query, { oneBasedIndex: true } ); + + // If no valid index was found and the input query is a + // string, fall back on a simple search + if( !indices && /\S+/i.test( query ) && query.length > 1 ) { + indices = this.search( query ); + } + + if( indices && query !== '' ) { + this.Reveal.slide( indices.h, indices.v, indices.f ); + return true; + } + else { + this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f ); + return false; + } + + } + + jumpAfter( delay ) { + + clearTimeout( this.jumpTimeout ); + this.jumpTimeout = setTimeout( () => this.jump(), delay ); + + } + + /** + * A lofi search that looks for the given query in all + * of our slides and returns the first match. + */ + search( query ) { + + const regex = new RegExp( '\\b' + query.trim() + '\\b', 'i' ); + + const slide = this.Reveal.getSlides().find( ( slide ) => { + return regex.test( slide.innerText ); + } ); + + if( slide ) { + return this.Reveal.getIndices( slide ); + } + else { + return null; + } + + } + + /** + * Reverts back to the slide we were on when jump to slide was + * invoked. + */ + cancel() { + + this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f ); + this.hide(); + + } + + confirm() { + + this.jump(); + this.hide(); + + } + + destroy() { + + this.jumpInput.removeEventListener( 'input', this.onInput ); + this.jumpInput.removeEventListener( 'keydown', this.onKeyDown ); + this.jumpInput.removeEventListener( 'blur', this.onBlur ); + + this.element.remove(); + + } + + onKeyDown( event ) { + + if( event.keyCode === 13 ) { + this.confirm(); + } + else if( event.keyCode === 27 ) { + this.cancel(); + + event.stopImmediatePropagation(); + } + + } + + onInput( event ) { + + this.jumpAfter( 200 ); + + } + + onBlur() { + + setTimeout( () => this.hide(), 1 ); + + } + +} \ No newline at end of file diff --git a/js/controllers/keyboard.js b/js/controllers/keyboard.js new file mode 100644 index 0000000..e3bff7a --- /dev/null +++ b/js/controllers/keyboard.js @@ -0,0 +1,399 @@ +import { enterFullscreen } from '../utils/util.js' + +/** + * Handles all reveal.js keyboard interactions. + */ +export default class Keyboard { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + // A key:value map of keyboard keys and descriptions of + // the actions they trigger + this.shortcuts = {}; + + // Holds custom key code mappings + this.bindings = {}; + + this.onDocumentKeyDown = this.onDocumentKeyDown.bind( this ); + this.onDocumentKeyPress = this.onDocumentKeyPress.bind( this ); + + } + + /** + * Called when the reveal.js config is updated. + */ + configure( config, oldConfig ) { + + if( config.navigationMode === 'linear' ) { + this.shortcuts['→ , ↓ , SPACE , N , L , J'] = 'Next slide'; + this.shortcuts['← , ↑ , P , H , K'] = 'Previous slide'; + } + else { + this.shortcuts['N , SPACE'] = 'Next slide'; + this.shortcuts['P , Shift SPACE'] = 'Previous slide'; + this.shortcuts['← , H'] = 'Navigate left'; + this.shortcuts['→ , L'] = 'Navigate right'; + this.shortcuts['↑ , K'] = 'Navigate up'; + this.shortcuts['↓ , J'] = 'Navigate down'; + } + + this.shortcuts['Alt + ←/↑/→/↓'] = 'Navigate without fragments'; + this.shortcuts['Shift + ←/↑/→/↓'] = 'Jump to first/last slide'; + this.shortcuts['B , .'] = 'Pause'; + this.shortcuts['F'] = 'Fullscreen'; + this.shortcuts['G'] = 'Jump to slide'; + this.shortcuts['ESC, O'] = 'Slide overview'; + + } + + /** + * Starts listening for keyboard events. + */ + bind() { + + document.addEventListener( 'keydown', this.onDocumentKeyDown, false ); + document.addEventListener( 'keypress', this.onDocumentKeyPress, false ); + + } + + /** + * Stops listening for keyboard events. + */ + unbind() { + + document.removeEventListener( 'keydown', this.onDocumentKeyDown, false ); + document.removeEventListener( 'keypress', this.onDocumentKeyPress, false ); + + } + + /** + * Add a custom key binding with optional description to + * be added to the help screen. + */ + addKeyBinding( binding, callback ) { + + if( typeof binding === 'object' && binding.keyCode ) { + this.bindings[binding.keyCode] = { + callback: callback, + key: binding.key, + description: binding.description + }; + } + else { + this.bindings[binding] = { + callback: callback, + key: null, + description: null + }; + } + + } + + /** + * Removes the specified custom key binding. + */ + removeKeyBinding( keyCode ) { + + delete this.bindings[keyCode]; + + } + + /** + * Programmatically triggers a keyboard event + * + * @param {int} keyCode + */ + triggerKey( keyCode ) { + + this.onDocumentKeyDown( { keyCode } ); + + } + + /** + * Registers a new shortcut to include in the help overlay + * + * @param {String} key + * @param {String} value + */ + registerKeyboardShortcut( key, value ) { + + this.shortcuts[key] = value; + + } + + getShortcuts() { + + return this.shortcuts; + + } + + getBindings() { + + return this.bindings; + + } + + /** + * Handler for the document level 'keypress' event. + * + * @param {object} event + */ + onDocumentKeyPress( event ) { + + // Check if the pressed key is question mark + if( event.shiftKey && event.charCode === 63 ) { + this.Reveal.toggleHelp(); + } + + } + + /** + * Handler for the document level 'keydown' event. + * + * @param {object} event + */ + onDocumentKeyDown( event ) { + + let config = this.Reveal.getConfig(); + + // If there's a condition specified and it returns false, + // ignore this event + if( typeof config.keyboardCondition === 'function' && config.keyboardCondition(event) === false ) { + return true; + } + + // If keyboardCondition is set, only capture keyboard events + // for embedded decks when they are focused + if( config.keyboardCondition === 'focused' && !this.Reveal.isFocused() ) { + return true; + } + + // Shorthand + let keyCode = event.keyCode; + + // Remember if auto-sliding was paused so we can toggle it + let autoSlideWasPaused = !this.Reveal.isAutoSliding(); + + this.Reveal.onUserInput( event ); + + // Is there a focused element that could be using the keyboard? + let activeElementIsCE = document.activeElement && document.activeElement.isContentEditable === true; + let activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName ); + let activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className); + + // Whitelist certain modifiers for slide navigation shortcuts + let isNavigationKey = [32, 37, 38, 39, 40, 78, 80].indexOf( event.keyCode ) !== -1; + + // Prevent all other events when a modifier is pressed + let unusedModifier = !( isNavigationKey && event.shiftKey || event.altKey ) && + ( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ); + + // Disregard the event if there's a focused element or a + // keyboard modifier key is present + if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || unusedModifier ) return; + + // While paused only allow resume keyboard events; 'b', 'v', '.' + let resumeKeyCodes = [66,86,190,191]; + let key; + + // Custom key bindings for togglePause should be able to resume + if( typeof config.keyboard === 'object' ) { + for( key in config.keyboard ) { + if( config.keyboard[key] === 'togglePause' ) { + resumeKeyCodes.push( parseInt( key, 10 ) ); + } + } + } + + if( this.Reveal.isPaused() && resumeKeyCodes.indexOf( keyCode ) === -1 ) { + return false; + } + + // Use linear navigation if we're configured to OR if + // the presentation is one-dimensional + let useLinearMode = config.navigationMode === 'linear' || !this.Reveal.hasHorizontalSlides() || !this.Reveal.hasVerticalSlides(); + + let triggered = false; + + // 1. User defined key bindings + if( typeof config.keyboard === 'object' ) { + + for( key in config.keyboard ) { + + // Check if this binding matches the pressed key + if( parseInt( key, 10 ) === keyCode ) { + + let value = config.keyboard[ key ]; + + // Callback function + if( typeof value === 'function' ) { + value.apply( null, [ event ] ); + } + // String shortcuts to reveal.js API + else if( typeof value === 'string' && typeof this.Reveal[ value ] === 'function' ) { + this.Reveal[ value ].call(); + } + + triggered = true; + + } + + } + + } + + // 2. Registered custom key bindings + if( triggered === false ) { + + for( key in this.bindings ) { + + // Check if this binding matches the pressed key + if( parseInt( key, 10 ) === keyCode ) { + + let action = this.bindings[ key ].callback; + + // Callback function + if( typeof action === 'function' ) { + action.apply( null, [ event ] ); + } + // String shortcuts to reveal.js API + else if( typeof action === 'string' && typeof this.Reveal[ action ] === 'function' ) { + this.Reveal[ action ].call(); + } + + triggered = true; + } + } + } + + // 3. System defined key bindings + if( triggered === false ) { + + // Assume true and try to prove false + triggered = true; + + // P, PAGE UP + if( keyCode === 80 || keyCode === 33 ) { + this.Reveal.prev({skipFragments: event.altKey}); + } + // N, PAGE DOWN + else if( keyCode === 78 || keyCode === 34 ) { + this.Reveal.next({skipFragments: event.altKey}); + } + // H, LEFT + else if( keyCode === 72 || keyCode === 37 ) { + if( event.shiftKey ) { + this.Reveal.slide( 0 ); + } + else if( !this.Reveal.overview.isActive() && useLinearMode ) { + this.Reveal.prev({skipFragments: event.altKey}); + } + else { + this.Reveal.left({skipFragments: event.altKey}); + } + } + // L, RIGHT + else if( keyCode === 76 || keyCode === 39 ) { + if( event.shiftKey ) { + this.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 ); + } + else if( !this.Reveal.overview.isActive() && useLinearMode ) { + this.Reveal.next({skipFragments: event.altKey}); + } + else { + this.Reveal.right({skipFragments: event.altKey}); + } + } + // K, UP + else if( keyCode === 75 || keyCode === 38 ) { + if( event.shiftKey ) { + this.Reveal.slide( undefined, 0 ); + } + else if( !this.Reveal.overview.isActive() && useLinearMode ) { + this.Reveal.prev({skipFragments: event.altKey}); + } + else { + this.Reveal.up({skipFragments: event.altKey}); + } + } + // J, DOWN + else if( keyCode === 74 || keyCode === 40 ) { + if( event.shiftKey ) { + this.Reveal.slide( undefined, Number.MAX_VALUE ); + } + else if( !this.Reveal.overview.isActive() && useLinearMode ) { + this.Reveal.next({skipFragments: event.altKey}); + } + else { + this.Reveal.down({skipFragments: event.altKey}); + } + } + // HOME + else if( keyCode === 36 ) { + this.Reveal.slide( 0 ); + } + // END + else if( keyCode === 35 ) { + this.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 ); + } + // SPACE + else if( keyCode === 32 ) { + if( this.Reveal.overview.isActive() ) { + this.Reveal.overview.deactivate(); + } + if( event.shiftKey ) { + this.Reveal.prev({skipFragments: event.altKey}); + } + else { + this.Reveal.next({skipFragments: event.altKey}); + } + } + // TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS "BLACK SCREEN" BUTTON + else if( keyCode === 58 || keyCode === 59 || keyCode === 66 || keyCode === 86 || keyCode === 190 || keyCode === 191 ) { + this.Reveal.togglePause(); + } + // F + else if( keyCode === 70 ) { + enterFullscreen( config.embedded ? this.Reveal.getViewportElement() : document.documentElement ); + } + // A + else if( keyCode === 65 ) { + if ( config.autoSlideStoppable ) { + this.Reveal.toggleAutoSlide( autoSlideWasPaused ); + } + } + // G + else if( keyCode === 71 ) { + if ( config.jumpToSlide ) { + this.Reveal.toggleJumpToSlide(); + } + } + else { + triggered = false; + } + + } + + // If the input resulted in a triggered action we should prevent + // the browsers default behavior + if( triggered ) { + event.preventDefault && event.preventDefault(); + } + // ESC or O key + else if( keyCode === 27 || keyCode === 79 ) { + if( this.Reveal.closeOverlay() === false ) { + this.Reveal.overview.toggle(); + } + + event.preventDefault && event.preventDefault(); + } + + // If auto-sliding is enabled we need to cue up + // another timeout + this.Reveal.cueAutoSlide(); + + } + +} \ No newline at end of file diff --git a/js/controllers/location.js b/js/controllers/location.js new file mode 100644 index 0000000..6fa4829 --- /dev/null +++ b/js/controllers/location.js @@ -0,0 +1,245 @@ +/** + * Reads and writes the URL based on reveal.js' current state. + */ +export default class Location { + + // The minimum number of milliseconds that must pass between + // calls to history.replaceState + MAX_REPLACE_STATE_FREQUENCY = 1000 + + constructor( Reveal ) { + + this.Reveal = Reveal; + + // Delays updates to the URL due to a Chrome thumbnailer bug + this.writeURLTimeout = 0; + + this.replaceStateTimestamp = 0; + + this.onWindowHashChange = this.onWindowHashChange.bind( this ); + + } + + bind() { + + window.addEventListener( 'hashchange', this.onWindowHashChange, false ); + + } + + unbind() { + + window.removeEventListener( 'hashchange', this.onWindowHashChange, false ); + + } + + /** + * Returns the slide indices for the given hash link. + * + * @param {string} [hash] the hash string that we want to + * find the indices for + * + * @returns slide indices or null + */ + getIndicesFromHash( hash=window.location.hash, options={} ) { + + // Attempt to parse the hash as either an index or name + let name = hash.replace( /^#\/?/, '' ); + let bits = name.split( '/' ); + + // If the first bit is not fully numeric and there is a name we + // can assume that this is a named link + if( !/^[0-9]*$/.test( bits[0] ) && name.length ) { + let element; + + let f; + + // Parse named links with fragments (#/named-link/2) + if( /\/[-\d]+$/g.test( name ) ) { + f = parseInt( name.split( '/' ).pop(), 10 ); + f = isNaN(f) ? undefined : f; + name = name.split( '/' ).shift(); + } + + // Ensure the named link is a valid HTML ID attribute + try { + element = document.getElementById( decodeURIComponent( name ) ); + } + catch ( error ) { } + + if( element ) { + return { ...this.Reveal.getIndices( element ), f }; + } + } + else { + const config = this.Reveal.getConfig(); + let hashIndexBase = config.hashOneBasedIndex || options.oneBasedIndex ? 1 : 0; + + // Read the index components of the hash + let h = ( parseInt( bits[0], 10 ) - hashIndexBase ) || 0, + v = ( parseInt( bits[1], 10 ) - hashIndexBase ) || 0, + f; + + if( config.fragmentInURL ) { + f = parseInt( bits[2], 10 ); + if( isNaN( f ) ) { + f = undefined; + } + } + + return { h, v, f }; + } + + // The hash couldn't be parsed or no matching named link was found + return null + + } + + /** + * Reads the current URL (hash) and navigates accordingly. + */ + readURL() { + + const currentIndices = this.Reveal.getIndices(); + const newIndices = this.getIndicesFromHash(); + + if( newIndices ) { + if( ( newIndices.h !== currentIndices.h || newIndices.v !== currentIndices.v || newIndices.f !== undefined ) ) { + this.Reveal.slide( newIndices.h, newIndices.v, newIndices.f ); + } + } + // If no new indices are available, we're trying to navigate to + // a slide hash that does not exist + else { + this.Reveal.slide( currentIndices.h || 0, currentIndices.v || 0 ); + } + + } + + /** + * Updates the page URL (hash) to reflect the current + * state. + * + * @param {number} delay The time in ms to wait before + * writing the hash + */ + writeURL( delay ) { + + let config = this.Reveal.getConfig(); + let currentSlide = this.Reveal.getCurrentSlide(); + + // Make sure there's never more than one timeout running + clearTimeout( this.writeURLTimeout ); + + // If a delay is specified, timeout this call + if( typeof delay === 'number' ) { + this.writeURLTimeout = setTimeout( this.writeURL, delay ); + } + else if( currentSlide ) { + + let hash = this.getHash(); + + // If we're configured to push to history OR the history + // API is not available. + if( config.history ) { + window.location.hash = hash; + } + // If we're configured to reflect the current slide in the + // URL without pushing to history. + else if( config.hash ) { + // If the hash is empty, don't add it to the URL + if( hash === '/' ) { + this.debouncedReplaceState( window.location.pathname + window.location.search ); + } + else { + this.debouncedReplaceState( '#' + hash ); + } + } + // UPDATE: The below nuking of all hash changes breaks + // anchors on pages where reveal.js is running. Removed + // in 4.0. Why was it here in the first place? ¯\_(ツ)_/¯ + // + // If history and hash are both disabled, a hash may still + // be added to the URL by clicking on a href with a hash + // target. Counter this by always removing the hash. + // else { + // window.history.replaceState( null, null, window.location.pathname + window.location.search ); + // } + + } + + } + + replaceState( url ) { + + window.history.replaceState( null, null, url ); + this.replaceStateTimestamp = Date.now(); + + } + + debouncedReplaceState( url ) { + + clearTimeout( this.replaceStateTimeout ); + + if( Date.now() - this.replaceStateTimestamp > this.MAX_REPLACE_STATE_FREQUENCY ) { + this.replaceState( url ); + } + else { + this.replaceStateTimeout = setTimeout( () => this.replaceState( url ), this.MAX_REPLACE_STATE_FREQUENCY ); + } + + } + + /** + * Return a hash URL that will resolve to the given slide location. + * + * @param {HTMLElement} [slide=currentSlide] The slide to link to + */ + getHash( slide ) { + + let url = '/'; + + // Attempt to create a named link based on the slide's ID + let s = slide || this.Reveal.getCurrentSlide(); + let id = s ? s.getAttribute( 'id' ) : null; + if( id ) { + id = encodeURIComponent( id ); + } + + let index = this.Reveal.getIndices( slide ); + if( !this.Reveal.getConfig().fragmentInURL ) { + index.f = undefined; + } + + // If the current slide has an ID, use that as a named link, + // but we don't support named links with a fragment index + if( typeof id === 'string' && id.length ) { + url = '/' + id; + + // If there is also a fragment, append that at the end + // of the named link, like: #/named-link/2 + if( index.f >= 0 ) url += '/' + index.f; + } + // Otherwise use the /h/v index + else { + let hashIndexBase = this.Reveal.getConfig().hashOneBasedIndex ? 1 : 0; + if( index.h > 0 || index.v > 0 || index.f >= 0 ) url += index.h + hashIndexBase; + if( index.v > 0 || index.f >= 0 ) url += '/' + (index.v + hashIndexBase ); + if( index.f >= 0 ) url += '/' + index.f; + } + + return url; + + } + + /** + * Handler for the window level 'hashchange' event. + * + * @param {object} [event] + */ + onWindowHashChange( event ) { + + this.readURL(); + + } + +} \ No newline at end of file diff --git a/js/controllers/notes.js b/js/controllers/notes.js new file mode 100644 index 0000000..7256425 --- /dev/null +++ b/js/controllers/notes.js @@ -0,0 +1,120 @@ +/** + * Handles the showing of speaker notes + */ +export default class Notes { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + } + + render() { + + this.element = document.createElement( 'div' ); + this.element.className = 'speaker-notes'; + this.element.setAttribute( 'data-prevent-swipe', '' ); + this.element.setAttribute( 'tabindex', '0' ); + this.Reveal.getRevealElement().appendChild( this.element ); + + } + + /** + * Called when the reveal.js config is updated. + */ + configure( config, oldConfig ) { + + if( config.showNotes ) { + this.element.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' ); + } + + } + + /** + * Pick up notes from the current slide and display them + * to the viewer. + * + * @see {@link config.showNotes} + */ + update() { + + if( this.Reveal.getConfig().showNotes && this.element && this.Reveal.getCurrentSlide() && !this.Reveal.print.isPrintingPDF() ) { + + this.element.innerHTML = this.getSlideNotes() || 'No notes on this slide.'; + + } + + } + + /** + * Updates the visibility of the speaker notes sidebar that + * is used to share annotated slides. The notes sidebar is + * only visible if showNotes is true and there are notes on + * one or more slides in the deck. + */ + updateVisibility() { + + if( this.Reveal.getConfig().showNotes && this.hasNotes() && !this.Reveal.print.isPrintingPDF() ) { + this.Reveal.getRevealElement().classList.add( 'show-notes' ); + } + else { + this.Reveal.getRevealElement().classList.remove( 'show-notes' ); + } + + } + + /** + * Checks if there are speaker notes for ANY slide in the + * presentation. + */ + hasNotes() { + + return this.Reveal.getSlidesElement().querySelectorAll( '[data-notes], aside.notes' ).length > 0; + + } + + /** + * Checks if this presentation is running inside of the + * speaker notes window. + * + * @return {boolean} + */ + isSpeakerNotesWindow() { + + return !!window.location.search.match( /receiver/gi ); + + } + + /** + * Retrieves the speaker notes from a slide. Notes can be + * defined in two ways: + * 1. As a data-notes attribute on the slide
+ * 2. With