Merge lp:~openlp-dev/openlp/webengine-migrate into lp:openlp

Proposed by Tomas Groth
Status: Superseded
Proposed branch: lp:~openlp-dev/openlp/webengine-migrate
Merge into: lp:openlp
Diff against target: 33194 lines (+15184/-5325)
370 files modified
.bzrignore (+7/-0)
karma.conf.js (+77/-0)
nose2.cfg (+8/-8)
openlp/.version (+1/-1)
openlp/core/api/deploy.py (+1/-1)
openlp/core/api/endpoint/controller.py (+8/-6)
openlp/core/api/endpoint/core.py (+2/-2)
openlp/core/api/endpoint/remote.py (+1/-0)
openlp/core/api/endpoint/service.py (+1/-0)
openlp/core/api/http/server.py (+6/-7)
openlp/core/api/http/wsgiapp.py (+1/-0)
openlp/core/api/tab.py (+4/-3)
openlp/core/api/websockets.py (+1/-0)
openlp/core/app.py (+10/-7)
openlp/core/common/actions.py (+1/-1)
openlp/core/common/applocation.py (+2/-1)
openlp/core/common/db.py (+1/-0)
openlp/core/common/httputils.py (+1/-0)
openlp/core/common/i18n.py (+2/-1)
openlp/core/common/mixins.py (+1/-0)
openlp/core/common/path.py (+1/-0)
openlp/core/common/registry.py (+2/-0)
openlp/core/common/settings.py (+40/-3)
openlp/core/display/html/black.css (+292/-0)
openlp/core/display/html/display.html (+39/-0)
openlp/core/display/html/display.js (+789/-0)
openlp/core/display/html/reveal.css (+1591/-0)
openlp/core/display/html/reveal.js (+5586/-0)
openlp/core/display/html/textFit.js (+237/-0)
openlp/core/display/render.py (+746/-0)
openlp/core/display/renderer.py (+0/-586)
openlp/core/display/screens.py (+283/-181)
openlp/core/display/webengine.py (+93/-0)
openlp/core/display/window.py (+402/-0)
openlp/core/lib/__init__.py (+97/-307)
openlp/core/lib/db.py (+4/-3)
openlp/core/lib/htmlbuilder.py (+3/-14)
openlp/core/lib/imagemanager.py (+6/-5)
openlp/core/lib/mediamanageritem.py (+6/-5)
openlp/core/lib/plugin.py (+1/-0)
openlp/core/lib/pluginmanager.py (+2/-0)
openlp/core/lib/serviceitem.py (+105/-193)
openlp/core/lib/settingstab.py (+4/-4)
openlp/core/lib/theme.py (+9/-7)
openlp/core/lib/ui.py (+1/-0)
openlp/core/loader.py (+1/-1)
openlp/core/projectors/constants.py (+1/-0)
openlp/core/projectors/db.py (+6/-3)
openlp/core/projectors/editform.py (+6/-5)
openlp/core/projectors/manager.py (+6/-5)
openlp/core/projectors/pjlink.py (+6/-5)
openlp/core/projectors/sourceselectform.py (+2/-1)
openlp/core/projectors/tab.py (+5/-4)
openlp/core/projectors/upgrade.py (+2/-1)
openlp/core/resources.py (+1/-1)
openlp/core/server.py (+1/-1)
openlp/core/ui/aboutform.py (+1/-0)
openlp/core/ui/advancedtab.py (+5/-4)
openlp/core/ui/exceptiondialog.py (+4/-4)
openlp/core/ui/exceptionform.py (+7/-46)
openlp/core/ui/filerenamedialog.py (+3/-3)
openlp/core/ui/filerenameform.py (+1/-1)
openlp/core/ui/firsttimeform.py (+42/-39)
openlp/core/ui/firsttimelanguagedialog.py (+3/-3)
openlp/core/ui/firsttimelanguageform.py (+2/-1)
openlp/core/ui/firsttimewizard.py (+30/-32)
openlp/core/ui/formattingtagdialog.py (+4/-4)
openlp/core/ui/formattingtagform.py (+1/-1)
openlp/core/ui/generaltab.py (+8/-144)
openlp/core/ui/icons.py (+2/-1)
openlp/core/ui/maindisplay.py (+0/-602)
openlp/core/ui/mainwindow.py (+20/-27)
openlp/core/ui/media/endpoint.py (+1/-0)
openlp/core/ui/media/mediacontroller.py (+6/-5)
openlp/core/ui/media/playertab.py (+4/-4)
openlp/core/ui/media/systemplayer.py (+2/-1)
openlp/core/ui/media/vendor/vlc.py (+1197/-892)
openlp/core/ui/media/vlcplayer.py (+2/-1)
openlp/core/ui/media/webkitplayer.py (+0/-312)
openlp/core/ui/plugindialog.py (+3/-3)
openlp/core/ui/pluginform.py (+2/-1)
openlp/core/ui/printservicedialog.py (+4/-4)
openlp/core/ui/printserviceform.py (+3/-2)
openlp/core/ui/screenstab.py (+97/-0)
openlp/core/ui/serviceitemeditdialog.py (+4/-4)
openlp/core/ui/serviceitemeditform.py (+3/-3)
openlp/core/ui/servicemanager.py (+19/-15)
openlp/core/ui/servicenoteform.py (+4/-4)
openlp/core/ui/settingsdialog.py (+3/-3)
openlp/core/ui/settingsform.py (+21/-13)
openlp/core/ui/shortcutlistdialog.py (+3/-3)
openlp/core/ui/shortcutlistform.py (+2/-1)
openlp/core/ui/slidecontroller.py (+162/-167)
openlp/core/ui/splashscreen.py (+2/-2)
openlp/core/ui/starttimedialog.py (+3/-3)
openlp/core/ui/starttimeform.py (+1/-1)
openlp/core/ui/style.py (+1/-0)
openlp/core/ui/themeform.py (+14/-8)
openlp/core/ui/themelayoutdialog.py (+3/-3)
openlp/core/ui/themelayoutform.py (+1/-1)
openlp/core/ui/thememanager.py (+11/-11)
openlp/core/ui/themestab.py (+5/-5)
openlp/core/ui/themewizard.py (+4/-4)
openlp/core/version.py (+54/-2)
openlp/core/widgets/docks.py (+2/-1)
openlp/core/widgets/edits.py (+2/-1)
openlp/core/widgets/layouts.py (+185/-0)
openlp/core/widgets/toolbar.py (+1/-0)
openlp/core/widgets/views.py (+40/-38)
openlp/core/widgets/widgets.py (+267/-1)
openlp/core/widgets/wizard.py (+5/-4)
openlp/plugins/alerts/alertsplugin.py (+8/-7)
openlp/plugins/alerts/endpoint.py (+1/-0)
openlp/plugins/alerts/forms/__init__.py (+1/-1)
openlp/plugins/alerts/forms/alertdialog.py (+4/-4)
openlp/plugins/alerts/forms/alertform.py (+2/-1)
openlp/plugins/alerts/lib/alertsmanager.py (+3/-5)
openlp/plugins/alerts/lib/alertstab.py (+3/-3)
openlp/plugins/bibles/bibleplugin.py (+1/-0)
openlp/plugins/bibles/endpoint.py (+2/-1)
openlp/plugins/bibles/forms/bibleimportform.py (+7/-6)
openlp/plugins/bibles/forms/booknamedialog.py (+3/-3)
openlp/plugins/bibles/forms/booknameform.py (+2/-1)
openlp/plugins/bibles/forms/editbibledialog.py (+5/-5)
openlp/plugins/bibles/forms/editbibleform.py (+3/-1)
openlp/plugins/bibles/forms/languagedialog.py (+3/-3)
openlp/plugins/bibles/forms/languageform.py (+2/-1)
openlp/plugins/bibles/lib/biblestab.py (+6/-5)
openlp/plugins/bibles/lib/db.py (+5/-4)
openlp/plugins/bibles/lib/importers/csvbible.py (+1/-0)
openlp/plugins/bibles/lib/importers/http.py (+1/-0)
openlp/plugins/bibles/lib/importers/osis.py (+1/-0)
openlp/plugins/bibles/lib/importers/sword.py (+1/-0)
openlp/plugins/bibles/lib/importers/wordproject.py (+2/-1)
openlp/plugins/bibles/lib/importers/zefania.py (+1/-0)
openlp/plugins/bibles/lib/manager.py (+2/-0)
openlp/plugins/bibles/lib/mediaitem.py (+9/-9)
openlp/plugins/bibles/lib/upgrade.py (+1/-0)
openlp/plugins/custom/customplugin.py (+3/-2)
openlp/plugins/custom/endpoint.py (+2/-1)
openlp/plugins/custom/forms/editcustomdialog.py (+4/-4)
openlp/plugins/custom/forms/editcustomform.py (+1/-1)
openlp/plugins/custom/forms/editcustomslidedialog.py (+3/-3)
openlp/plugins/custom/forms/editcustomslideform.py (+2/-1)
openlp/plugins/custom/lib/customtab.py (+3/-3)
openlp/plugins/custom/lib/customxmlhandler.py (+1/-0)
openlp/plugins/custom/lib/mediaitem.py (+5/-4)
openlp/plugins/images/endpoint.py (+2/-1)
openlp/plugins/images/forms/__init__.py (+1/-1)
openlp/plugins/images/forms/addgroupdialog.py (+3/-3)
openlp/plugins/images/forms/addgroupform.py (+1/-1)
openlp/plugins/images/forms/choosegroupdialog.py (+3/-3)
openlp/plugins/images/forms/choosegroupform.py (+1/-1)
openlp/plugins/images/imageplugin.py (+3/-2)
openlp/plugins/images/lib/imagetab.py (+3/-3)
openlp/plugins/images/lib/mediaitem.py (+5/-5)
openlp/plugins/images/lib/upgrade.py (+1/-0)
openlp/plugins/media/endpoint.py (+2/-1)
openlp/plugins/media/forms/mediaclipselectordialog.py (+3/-3)
openlp/plugins/media/forms/mediaclipselectorform.py (+3/-2)
openlp/plugins/media/lib/mediaitem.py (+4/-3)
openlp/plugins/media/lib/mediatab.py (+3/-3)
openlp/plugins/media/mediaplugin.py (+3/-0)
openlp/plugins/presentations/endpoint.py (+2/-1)
openlp/plugins/presentations/lib/impresscontroller.py (+5/-3)
openlp/plugins/presentations/lib/mediaitem.py (+5/-5)
openlp/plugins/presentations/lib/messagelistener.py (+1/-0)
openlp/plugins/presentations/lib/pdfcontroller.py (+3/-2)
openlp/plugins/presentations/lib/powerpointcontroller.py (+21/-10)
openlp/plugins/presentations/lib/presentationcontroller.py (+1/-0)
openlp/plugins/presentations/lib/presentationtab.py (+3/-3)
openlp/plugins/presentations/presentationplugin.py (+2/-1)
openlp/plugins/songs/endpoint.py (+2/-1)
openlp/plugins/songs/forms/__init__.py (+1/-1)
openlp/plugins/songs/forms/authorsdialog.py (+3/-3)
openlp/plugins/songs/forms/authorsform.py (+1/-1)
openlp/plugins/songs/forms/duplicatesongremovalform.py (+2/-1)
openlp/plugins/songs/forms/editsongdialog.py (+4/-4)
openlp/plugins/songs/forms/editsongform.py (+6/-5)
openlp/plugins/songs/forms/editversedialog.py (+3/-3)
openlp/plugins/songs/forms/editverseform.py (+2/-1)
openlp/plugins/songs/forms/mediafilesdialog.py (+3/-3)
openlp/plugins/songs/forms/mediafilesform.py (+2/-1)
openlp/plugins/songs/forms/songbookdialog.py (+3/-3)
openlp/plugins/songs/forms/songbookform.py (+1/-1)
openlp/plugins/songs/forms/songexportform.py (+4/-3)
openlp/plugins/songs/forms/songimportform.py (+4/-3)
openlp/plugins/songs/forms/songmaintenancedialog.py (+3/-3)
openlp/plugins/songs/forms/songmaintenanceform.py (+5/-3)
openlp/plugins/songs/forms/songreviewwidget.py (+4/-4)
openlp/plugins/songs/forms/songselectform.py (+1/-0)
openlp/plugins/songs/forms/topicsdialog.py (+3/-3)
openlp/plugins/songs/forms/topicsform.py (+1/-1)
openlp/plugins/songs/lib/__init__.py (+2/-2)
openlp/plugins/songs/lib/db.py (+2/-2)
openlp/plugins/songs/lib/importer.py (+2/-0)
openlp/plugins/songs/lib/importers/cclifile.py (+2/-0)
openlp/plugins/songs/lib/importers/chordpro.py (+1/-0)
openlp/plugins/songs/lib/importers/dreambeam.py (+1/-0)
openlp/plugins/songs/lib/importers/easyslides.py (+1/-0)
openlp/plugins/songs/lib/importers/easyworship.py (+3/-2)
openlp/plugins/songs/lib/importers/foilpresenter.py (+2/-1)
openlp/plugins/songs/lib/importers/lyrix.py (+1/-0)
openlp/plugins/songs/lib/importers/mediashout.py (+1/-0)
openlp/plugins/songs/lib/importers/openlp.py (+4/-2)
openlp/plugins/songs/lib/importers/openlyrics.py (+1/-0)
openlp/plugins/songs/lib/importers/openoffice.py (+2/-0)
openlp/plugins/songs/lib/importers/opensong.py (+1/-0)
openlp/plugins/songs/lib/importers/opspro.py (+1/-0)
openlp/plugins/songs/lib/importers/powersong.py (+1/-0)
openlp/plugins/songs/lib/importers/presentationmanager.py (+1/-1)
openlp/plugins/songs/lib/importers/propresenter.py (+1/-0)
openlp/plugins/songs/lib/importers/songbeamer.py (+2/-1)
openlp/plugins/songs/lib/importers/songimport.py (+3/-2)
openlp/plugins/songs/lib/importers/songshowplus.py (+1/-0)
openlp/plugins/songs/lib/importers/songsoffellowship.py (+2/-0)
openlp/plugins/songs/lib/importers/sundayplus.py (+2/-2)
openlp/plugins/songs/lib/importers/videopsalm.py (+1/-0)
openlp/plugins/songs/lib/importers/wordsofworship.py (+1/-0)
openlp/plugins/songs/lib/importers/worshipassistant.py (+1/-0)
openlp/plugins/songs/lib/importers/worshipcenterpro.py (+1/-0)
openlp/plugins/songs/lib/importers/zionworx.py (+1/-0)
openlp/plugins/songs/lib/mediaitem.py (+5/-4)
openlp/plugins/songs/lib/openlyricsexport.py (+1/-0)
openlp/plugins/songs/lib/openlyricsxml.py (+1/-0)
openlp/plugins/songs/lib/songselect.py (+1/-0)
openlp/plugins/songs/lib/songstab.py (+3/-3)
openlp/plugins/songs/lib/upgrade.py (+3/-2)
openlp/plugins/songs/reporting.py (+1/-0)
openlp/plugins/songs/songsplugin.py (+5/-5)
openlp/plugins/songusage/forms/songusagedeletedialog.py (+3/-3)
openlp/plugins/songusage/forms/songusagedeleteform.py (+1/-1)
openlp/plugins/songusage/forms/songusagedetaildialog.py (+3/-3)
openlp/plugins/songusage/forms/songusagedetailform.py (+3/-1)
openlp/plugins/songusage/lib/upgrade.py (+2/-1)
openlp/plugins/songusage/songusageplugin.py (+3/-2)
package.json (+25/-0)
run_openlp.py (+3/-1)
scripts/appveyor-webhook.py (+5/-4)
scripts/appveyor.yml (+4/-10)
scripts/check_dependencies.py (+1/-2)
scripts/clean_up_resources.py (+1/-0)
scripts/jenkins_script.py (+2/-1)
scripts/lp-merge.py (+5/-4)
scripts/mp_update.py (+3/-1)
scripts/reveal-js.patch (+25/-0)
scripts/translation_utils.py (+3/-3)
scripts/websocket_client.py (+2/-1)
setup.cfg (+1/-2)
setup.py (+2/-1)
tests/functional/openlp_core/api/endpoint/test_controller.py (+44/-16)
tests/functional/openlp_core/api/endpoint/test_remote.py (+1/-1)
tests/functional/openlp_core/api/http/test_init.py (+1/-2)
tests/functional/openlp_core/api/http/test_wsgiapp.py (+1/-0)
tests/functional/openlp_core/api/test_deploy.py (+7/-4)
tests/functional/openlp_core/api/test_tab.py (+1/-0)
tests/functional/openlp_core/api/test_websockets.py (+1/-0)
tests/functional/openlp_core/common/test_actions.py (+123/-128)
tests/functional/openlp_core/common/test_applocation.py (+1/-0)
tests/functional/openlp_core/common/test_common.py (+2/-2)
tests/functional/openlp_core/common/test_db.py (+1/-1)
tests/functional/openlp_core/common/test_path.py (+2/-2)
tests/functional/openlp_core/display/test_render.py (+210/-0)
tests/functional/openlp_core/display/test_renderer.py (+0/-208)
tests/functional/openlp_core/display/test_screens.py (+66/-18)
tests/functional/openlp_core/lib/test_db.py (+2/-2)
tests/functional/openlp_core/lib/test_formattingtags.py (+1/-0)
tests/functional/openlp_core/lib/test_htmlbuilder.py (+4/-13)
tests/functional/openlp_core/lib/test_image_manager.py (+2/-1)
tests/functional/openlp_core/lib/test_lib.py (+3/-177)
tests/functional/openlp_core/lib/test_serviceitem.py (+43/-60)
tests/functional/openlp_core/lib/test_ui.py (+4/-4)
tests/functional/openlp_core/test_app.py (+3/-0)
tests/functional/openlp_core/test_server.py (+6/-5)
tests/functional/openlp_core/test_threading.py (+1/-1)
tests/functional/openlp_core/ui/media/test_systemplayer.py (+1/-1)
tests/functional/openlp_core/ui/media/test_vlcplayer.py (+1/-1)
tests/functional/openlp_core/ui/media/test_webkitplayer.py (+0/-66)
tests/functional/openlp_core/ui/test_exceptionform.py (+42/-56)
tests/functional/openlp_core/ui/test_firsttimeform.py (+63/-38)
tests/functional/openlp_core/ui/test_formattingtagsform.py (+1/-1)
tests/functional/openlp_core/ui/test_icons.py (+0/-1)
tests/functional/openlp_core/ui/test_maindisplay.py (+0/-283)
tests/functional/openlp_core/ui/test_mainwindow.py (+20/-14)
tests/functional/openlp_core/ui/test_servicemanager.py (+15/-12)
tests/functional/openlp_core/ui/test_slidecontroller.py (+17/-21)
tests/functional/openlp_core/widgets/test_views.py (+15/-16)
tests/functional/openlp_core/widgets/test_widgets.py (+199/-0)
tests/functional/openlp_plugins/bibles/test_bibleimport.py (+1/-1)
tests/functional/openlp_plugins/bibles/test_bibleserver.py (+1/-1)
tests/functional/openlp_plugins/bibles/test_csvimport.py (+1/-0)
tests/functional/openlp_plugins/bibles/test_mediaitem.py (+2/-2)
tests/functional/openlp_plugins/bibles/test_opensongimport.py (+2/-1)
tests/functional/openlp_plugins/bibles/test_osisimport.py (+2/-1)
tests/functional/openlp_plugins/bibles/test_swordimport.py (+5/-4)
tests/functional/openlp_plugins/bibles/test_wordprojectimport.py (+2/-1)
tests/functional/openlp_plugins/bibles/test_zefaniaimport.py (+1/-0)
tests/functional/openlp_plugins/custom/test_mediaitem.py (+2/-1)
tests/functional/openlp_plugins/images/test_imagetab.py (+1/-0)
tests/functional/openlp_plugins/images/test_upgrade.py (+1/-0)
tests/functional/openlp_plugins/media/test_mediaitem.py (+1/-0)
tests/functional/openlp_plugins/presentations/test_impresscontroller.py (+3/-3)
tests/functional/openlp_plugins/presentations/test_mediaitem.py (+1/-1)
tests/functional/openlp_plugins/presentations/test_messagelistener.py (+1/-1)
tests/functional/openlp_plugins/presentations/test_pdfcontroller.py (+5/-5)
tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py (+3/-2)
tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py.THIS (+224/-0)
tests/functional/openlp_plugins/presentations/test_presentationcontroller.py (+1/-0)
tests/functional/openlp_plugins/songs/test_chordproimport.py (+2/-1)
tests/functional/openlp_plugins/songs/test_db.py (+1/-1)
tests/functional/openlp_plugins/songs/test_easyslidesimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_editsongform.py (+1/-1)
tests/functional/openlp_plugins/songs/test_editverseform.py (+1/-0)
tests/functional/openlp_plugins/songs/test_ewimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_foilpresenterimport.py (+1/-1)
tests/functional/openlp_plugins/songs/test_lib.py (+2/-2)
tests/functional/openlp_plugins/songs/test_lyriximport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_mediashout.py (+2/-1)
tests/functional/openlp_plugins/songs/test_openlpimporter.py (+1/-1)
tests/functional/openlp_plugins/songs/test_openlyricsimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_openoffice.py (+1/-0)
tests/functional/openlp_plugins/songs/test_opensongimport.py (+2/-1)
tests/functional/openlp_plugins/songs/test_opsproimport.py (+5/-3)
tests/functional/openlp_plugins/songs/test_powerpraiseimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_presentationmanagerimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_propresenterimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_songbeamerimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_songproimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_songselect.py (+4/-3)
tests/functional/openlp_plugins/songs/test_songshowplusimport.py (+2/-1)
tests/functional/openlp_plugins/songs/test_sundayplusimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_videopsalm.py (+2/-1)
tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_worshipassistantimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py (+3/-2)
tests/functional/openlp_plugins/songs/test_zionworximport.py (+1/-0)
tests/helpers/songfileimport.py (+2/-1)
tests/interfaces/openlp_core/common/test_utils.py (+0/-1)
tests/interfaces/openlp_core/ui/test_servicemanager.py (+3/-3)
tests/interfaces/openlp_core/ui/test_settings_form.py (+1/-0)
tests/interfaces/openlp_core/ui/test_thememanager.py (+1/-1)
tests/interfaces/openlp_core/widgets/test_edits.py (+1/-1)
tests/interfaces/openlp_core/widgets/test_views.py (+5/-0)
tests/interfaces/openlp_plugins/bibles/test_lib_http.py (+1/-1)
tests/interfaces/openlp_plugins/custom/forms/test_customform.py (+1/-1)
tests/interfaces/openlp_plugins/media/forms/test_mediaclipselectorform.py (+7/-8)
tests/interfaces/openlp_plugins/songs/forms/test_editsongform.py (+1/-0)
tests/interfaces/openlp_plugins/songs/forms/test_editverseform.py (+1/-0)
tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py (+1/-1)
tests/js/fake_webchannel.js (+5/-0)
tests/js/polyfill.js (+84/-0)
tests/js/test_display.js (+632/-0)
tests/openlp_core/common/test_network_interfaces.py (+0/-1)
tests/openlp_core/projectors/test_projector_bugfixes_01.py (+1/-0)
tests/openlp_core/projectors/test_projector_constants.py (+1/-0)
tests/openlp_core/projectors/test_projector_db.py (+1/-1)
tests/openlp_core/projectors/test_projector_editform.py (+1/-1)
tests/openlp_core/projectors/test_projector_pjlink_base_01.py (+3/-5)
tests/openlp_core/projectors/test_projector_pjlink_base_02.py (+0/-1)
tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py (+2/-3)
tests/openlp_core/projectors/test_projector_pjlink_commands_01.py (+2/-12)
tests/openlp_core/projectors/test_projector_pjlink_commands_02.py (+2/-2)
tests/openlp_core/projectors/test_projector_pjlink_udp.py (+1/-1)
tests/openlp_core/projectors/test_projector_sourceform.py (+3/-3)
tests/openlp_core/projectors/test_projector_utilities.py (+3/-2)
tests/openlp_core/projectors/test_projectormanager.py (+1/-1)
tests/utils/__init__.py (+7/-10)
tests/utils/constants.py (+1/-0)
tests/utils/test_bzr_tags.py (+2/-1)
tests/utils/test_pylint.py (+4/-2)
To merge this branch: bzr merge lp:~openlp-dev/openlp/webengine-migrate
Reviewer Review Type Date Requested Status
OpenLP Core Pending
Review via email: mp+363155@code.launchpad.net

This proposal supersedes a proposal from 2019-02-12.

This proposal has been superseded by a proposal from 2019-02-13.

Commit message

Migration from WebKit to Webengine. Also introduced reveal.js for slide rendering, new screen setup dialogs and many other changes.

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/73/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/75/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/76/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/77/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/31/ for more details

2880. By Tomas Groth

pep8 fixes

2881. By Tomas Groth

More pep8 fixes

2882. By Tomas Groth

Even more pep8 fixes and removed the old htmlbuilder.

2883. By Raoul Snyman

Fix an unused import and some incorrect patch()s

2884. By Raoul Snyman

HEAD

2885. By Tomas Groth

trunk

Unmerged revisions

2885. By Tomas Groth

trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2018-06-26 17:10:34 +0000
3+++ .bzrignore 2019-02-13 20:54:40 +0000
4@@ -45,4 +45,11 @@
5 resources/windows/warnOpenLP.txt
6 *.ropeproject
7 tags
8+output
9+htmlcov
10+node_modules
11+openlp-test-projectordb.sqlite
12+package-lock.json
13+.cache
14+test
15 tests.kdev4
16
17=== added file 'karma.conf.js'
18--- karma.conf.js 1970-01-01 00:00:00 +0000
19+++ karma.conf.js 2019-02-13 20:54:40 +0000
20@@ -0,0 +1,77 @@
21+module.exports = function(config) {
22+ config.set({
23+ // base path that will be used to resolve all patterns (eg. files, exclude)
24+ basePath: "",
25+
26+ // frameworks to use
27+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
28+ frameworks: ["jasmine"],
29+
30+ // list of files / patterns to load in the browser
31+ files: [
32+ "tests/js/polyfill.js",
33+ "tests/js/fake_webchannel.js",
34+ "openlp/core/display/html/reveal.js",
35+ "openlp/core/display/html/display.js",
36+ "tests/js/test_*.js"
37+ ],
38+
39+ // list of files to exclude
40+ exclude: [
41+ ],
42+
43+ // preprocess matching files before serving them to the browser
44+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
45+ preprocessors: {
46+ // source files, that you wanna generate coverage for
47+ // do not include tests or libraries
48+ // (these files will be instrumented by Istanbul)
49+ "display.js": ["coverage"]
50+ },
51+
52+ // test results reporter to use
53+ // possible values: "dots", "progress"
54+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
55+ reporters: ["progress", "coverage"],
56+
57+ // configure the coverateReporter
58+ coverageReporter: {
59+ type : "html",
60+ dir : "htmlcov/"
61+ },
62+
63+ // web server port
64+ port: 9876,
65+
66+ // enable / disable colors in the output (reporters and logs)
67+ colors: true,
68+
69+ // level of logging
70+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
71+ logLevel: config.LOG_DEBUG,
72+
73+ // loggers
74+ /* loggers: [
75+ {"type": "file", "filename": "karma.log"}
76+ ],*/
77+
78+ // enable / disable watching file and executing tests whenever any file changes
79+ autoWatch: true,
80+
81+ // start these browsers
82+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
83+ browsers: ["PhantomJS"],
84+
85+ // Continuous Integration mode
86+ // if true, Karma captures browsers, runs the tests and exits
87+ singleRun: false,
88+
89+ // Concurrency level
90+ // how many browser should be started simultaneous
91+ concurrency: Infinity,
92+
93+ client: {
94+ captureConsole: true
95+ }
96+ })
97+}
98
99=== modified file 'nose2.cfg'
100--- nose2.cfg 2017-11-16 05:03:19 +0000
101+++ nose2.cfg 2019-02-13 20:54:40 +0000
102@@ -1,5 +1,5 @@
103 [unittest]
104-verbose = True
105+verbose = true
106 plugins = nose2.plugins.mp
107
108 [log-capture]
109@@ -9,19 +9,19 @@
110 log-level = ERROR
111
112 [test-result]
113-always-on = True
114-descriptions = True
115+always-on = true
116+descriptions = true
117
118 [coverage]
119-always-on = True
120+always-on = true
121 coverage = openlp
122 coverage-report = html
123
124 [multiprocess]
125-always-on = False
126+always-on = false
127 processes = 4
128
129 [output-buffer]
130-always-on = True
131-stderr = True
132-stdout = False
133+always-on = true
134+stderr = true
135+stdout = true
136
137=== modified file 'openlp/.version'
138--- openlp/.version 2019-01-11 20:23:24 +0000
139+++ openlp/.version 2019-02-13 20:54:40 +0000
140@@ -1,1 +1,1 @@
141-2.9.0
142+2.5.dev2856
143\ No newline at end of file
144
145=== modified file 'openlp/core/api/deploy.py'
146--- openlp/core/api/deploy.py 2018-01-04 07:00:55 +0000
147+++ openlp/core/api/deploy.py 2019-02-13 20:54:40 +0000
148@@ -25,7 +25,7 @@
149 from zipfile import ZipFile
150
151 from openlp.core.common.applocation import AppLocation
152-from openlp.core.common.httputils import download_file, get_web_page, get_url_file_size
153+from openlp.core.common.httputils import download_file, get_url_file_size, get_web_page
154 from openlp.core.common.registry import Registry
155
156
157
158=== modified file 'openlp/core/api/endpoint/controller.py'
159--- openlp/core/api/endpoint/controller.py 2018-08-25 14:08:19 +0000
160+++ openlp/core/api/endpoint/controller.py 2019-02-13 20:54:40 +0000
161@@ -34,6 +34,7 @@
162 from openlp.core.lib import create_thumb
163 from openlp.core.lib.serviceitem import ItemCapabilities
164
165+
166 log = logging.getLogger(__name__)
167
168 controller_endpoint = Endpoint('controller')
169@@ -48,7 +49,7 @@
170
171 :param request: the http request - not used
172 """
173- log.debug("controller_text ")
174+ log.debug('controller_text')
175 live_controller = Registry().get('live_controller')
176 current_item = live_controller.service_item
177 data = []
178@@ -57,13 +58,14 @@
179 item = {}
180 # Handle text (songs, custom, bibles)
181 if current_item.is_text():
182- if frame['verseTag']:
183- item['tag'] = str(frame['verseTag'])
184+ if frame['verse']:
185+ item['tag'] = str(frame['verse'])
186 else:
187 item['tag'] = str(index + 1)
188- item['chords_text'] = str(frame['chords_text'])
189- item['text'] = str(frame['text'])
190- item['html'] = str(frame['html'])
191+ # TODO: Figure out rendering chords
192+ item['chords_text'] = str(frame.get('chords_text', ''))
193+ item['text'] = frame['text']
194+ item['html'] = current_item.get_rendered_frame(index)
195 # Handle images, unless a custom thumbnail is given or if thumbnails is disabled
196 elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'):
197 item['tag'] = str(index + 1)
198
199=== modified file 'openlp/core/api/endpoint/core.py'
200--- openlp/core/api/endpoint/core.py 2018-08-25 14:08:19 +0000
201+++ openlp/core/api/endpoint/core.py 2019-02-13 20:54:40 +0000
202@@ -30,8 +30,8 @@
203 from openlp.core.common.i18n import UiStrings, translate
204 from openlp.core.common.registry import Registry
205 from openlp.core.lib import image_to_byte
206-from openlp.core.lib.plugin import StringContent
207-from openlp.core.lib.plugin import PluginStatus
208+from openlp.core.lib.plugin import PluginStatus, StringContent
209+
210
211 template_dir = 'templates'
212 static_dir = 'static'
213
214=== modified file 'openlp/core/api/endpoint/remote.py'
215--- openlp/core/api/endpoint/remote.py 2017-12-29 09:15:48 +0000
216+++ openlp/core/api/endpoint/remote.py 2019-02-13 20:54:40 +0000
217@@ -24,6 +24,7 @@
218 from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
219 from openlp.core.api.http.endpoint import Endpoint
220
221+
222 log = logging.getLogger(__name__)
223
224 remote_endpoint = Endpoint('remote', template_dir='remotes')
225
226=== modified file 'openlp/core/api/endpoint/service.py'
227--- openlp/core/api/endpoint/service.py 2017-12-29 09:15:48 +0000
228+++ openlp/core/api/endpoint/service.py 2019-02-13 20:54:40 +0000
229@@ -26,6 +26,7 @@
230 from openlp.core.api.http.endpoint import Endpoint
231 from openlp.core.common.registry import Registry
232
233+
234 log = logging.getLogger(__name__)
235
236 service_endpoint = Endpoint('service')
237
238=== modified file 'openlp/core/api/http/server.py'
239--- openlp/core/api/http/server.py 2018-01-07 04:36:45 +0000
240+++ openlp/core/api/http/server.py 2019-02-13 20:54:40 +0000
241@@ -30,22 +30,21 @@
242 from waitress.server import create_server
243
244 from openlp.core.api.deploy import download_and_check, download_sha256
245-from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint
246-from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint
247+from openlp.core.api.endpoint.controller import api_controller_endpoint, controller_endpoint
248+from openlp.core.api.endpoint.core import blank_endpoint, chords_endpoint, main_endpoint, stage_endpoint
249 from openlp.core.api.endpoint.remote import remote_endpoint
250-from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint
251-from openlp.core.api.http import application
252-from openlp.core.api.http import register_endpoint
253+from openlp.core.api.endpoint.service import api_service_endpoint, service_endpoint
254+from openlp.core.api.http import application, register_endpoint
255 from openlp.core.api.poll import Poller
256 from openlp.core.common.applocation import AppLocation
257-from openlp.core.common.i18n import UiStrings
258-from openlp.core.common.i18n import translate
259+from openlp.core.common.i18n import UiStrings, translate
260 from openlp.core.common.mixins import LogMixin, RegistryProperties
261 from openlp.core.common.path import create_paths
262 from openlp.core.common.registry import Registry, RegistryBase
263 from openlp.core.common.settings import Settings
264 from openlp.core.threading import ThreadWorker, run_thread
265
266+
267 log = logging.getLogger(__name__)
268
269
270
271=== modified file 'openlp/core/api/http/wsgiapp.py'
272--- openlp/core/api/http/wsgiapp.py 2018-07-04 20:42:55 +0000
273+++ openlp/core/api/http/wsgiapp.py 2019-02-13 20:54:40 +0000
274@@ -33,6 +33,7 @@
275 from openlp.core.api.http.errors import HttpError, NotFound, ServerError
276 from openlp.core.common.applocation import AppLocation
277
278+
279 ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE)
280
281 log = logging.getLogger(__name__)
282
283=== modified file 'openlp/core/api/tab.py'
284--- openlp/core/api/tab.py 2018-08-25 14:08:19 +0000
285+++ openlp/core/api/tab.py 2019-02-13 20:54:40 +0000
286@@ -31,6 +31,7 @@
287 from openlp.core.lib.settingstab import SettingsTab
288 from openlp.core.ui.icons import UiIcons
289
290+
291 ZERO_URL = '0.0.0.0'
292
293
294@@ -43,9 +44,9 @@
295 advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
296 super(ApiTab, self).__init__(parent, 'api', advanced_translated)
297
298- def setupUi(self):
299+ def setup_ui(self):
300 self.setObjectName('ApiTab')
301- super(ApiTab, self).setupUi()
302+ super(ApiTab, self).setup_ui()
303 self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column)
304 self.server_settings_group_box.setObjectName('server_settings_group_box')
305 self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box)
306@@ -154,7 +155,7 @@
307 self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
308 self.address_edit.textChanged.connect(self.set_urls)
309
310- def retranslateUi(self):
311+ def retranslate_ui(self):
312 self.tab_title_visible = translate('RemotePlugin.RemoteTab', 'Remote Interface')
313 self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
314 self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:'))
315
316=== modified file 'openlp/core/api/websockets.py'
317--- openlp/core/api/websockets.py 2018-01-07 17:50:29 +0000
318+++ openlp/core/api/websockets.py 2019-02-13 20:54:40 +0000
319@@ -35,6 +35,7 @@
320 from openlp.core.common.settings import Settings
321 from openlp.core.threading import ThreadWorker, run_thread
322
323+
324 log = logging.getLogger(__name__)
325
326
327
328=== modified file 'openlp/core/app.py'
329--- openlp/core/app.py 2018-10-25 16:38:39 +0000
330+++ openlp/core/app.py 2019-02-13 20:54:40 +0000
331@@ -33,27 +33,28 @@
332 from datetime import datetime
333 from traceback import format_exception
334
335-from PyQt5 import QtCore, QtWidgets
336+from PyQt5 import QtCore, QtWebEngineWidgets, QtWidgets # noqa
337
338 from openlp.core.state import State
339 from openlp.core.common import is_macosx, is_win
340 from openlp.core.common.applocation import AppLocation
341 from openlp.core.loader import loader
342 from openlp.core.common.i18n import LanguageManager, UiStrings, translate
343-from openlp.core.common.path import create_paths, copytree
344+from openlp.core.common.path import copytree, create_paths
345 from openlp.core.common.registry import Registry
346 from openlp.core.common.settings import Settings
347 from openlp.core.display.screens import ScreenList
348 from openlp.core.resources import qInitResources
349-from openlp.core.ui.splashscreen import SplashScreen
350+from openlp.core.server import Server
351 from openlp.core.ui.exceptionform import ExceptionForm
352 from openlp.core.ui.firsttimeform import FirstTimeForm
353 from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
354 from openlp.core.ui.mainwindow import MainWindow
355+from openlp.core.ui.splashscreen import SplashScreen
356 from openlp.core.ui.style import get_application_stylesheet
357-from openlp.core.server import Server
358 from openlp.core.version import check_for_update, get_version
359
360+
361 __all__ = ['OpenLP', 'main']
362
363
364@@ -74,7 +75,8 @@
365 """
366 self.is_event_loop_active = True
367 result = QtWidgets.QApplication.exec()
368- self.server.close_server()
369+ if hasattr(self, 'server'):
370+ self.server.close_server()
371 return result
372
373 def run(self, args):
374@@ -317,7 +319,7 @@
375 file_path = log_path / 'openlp.log'
376 # TODO: FileHandler accepts a Path object in Py3.6
377 logfile = logging.FileHandler(str(file_path), 'w', encoding='UTF-8')
378- logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
379+ logfile.setFormatter(logging.Formatter('%(asctime)s %(threadName)s %(name)-55s %(levelname)-8s %(message)s'))
380 log.addHandler(logfile)
381 if log.isEnabledFor(logging.DEBUG):
382 print('Logging to: {name}'.format(name=file_path))
383@@ -330,7 +332,8 @@
384 :param args: Some args
385 """
386 args = parse_options(args)
387- qt_args = []
388+ qt_args = ['--disable-web-security']
389+ # qt_args = []
390 if args and args.loglevel.lower() in ['d', 'debug']:
391 log.setLevel(logging.DEBUG)
392 elif args and args.loglevel.lower() in ['w', 'warning']:
393
394=== modified file 'openlp/core/common/actions.py'
395--- openlp/core/common/actions.py 2018-10-13 10:24:01 +0000
396+++ openlp/core/common/actions.py 2019-02-13 20:54:40 +0000
397@@ -29,6 +29,7 @@
398
399 from openlp.core.common.settings import Settings
400
401+
402 log = logging.getLogger(__name__)
403
404
405@@ -113,7 +114,6 @@
406 if item[1] == action:
407 self.actions.remove(item)
408 return
409- log.warning('Action "{action}" does not exist.'.format(action=action))
410
411
412 class CategoryList(object):
413
414=== modified file 'openlp/core/common/applocation.py'
415--- openlp/core/common/applocation.py 2018-10-07 23:34:00 +0000
416+++ openlp/core/common/applocation.py 2019-02-13 20:54:40 +0000
417@@ -29,10 +29,11 @@
418 import appdirs
419
420 import openlp
421-from openlp.core.common import get_frozen_path, is_win, is_macosx
422+from openlp.core.common import get_frozen_path, is_macosx, is_win
423 from openlp.core.common.path import Path, create_paths
424 from openlp.core.common.settings import Settings
425
426+
427 log = logging.getLogger(__name__)
428
429 FROZEN_APP_PATH = Path(sys.argv[0]).parent
430
431=== modified file 'openlp/core/common/db.py'
432--- openlp/core/common/db.py 2017-12-29 09:15:48 +0000
433+++ openlp/core/common/db.py 2019-02-13 20:54:40 +0000
434@@ -27,6 +27,7 @@
435
436 import sqlalchemy
437
438+
439 log = logging.getLogger(__name__)
440
441
442
443=== modified file 'openlp/core/common/httputils.py'
444--- openlp/core/common/httputils.py 2018-06-08 20:55:20 +0000
445+++ openlp/core/common/httputils.py 2019-02-13 20:54:40 +0000
446@@ -34,6 +34,7 @@
447 from openlp.core.common.registry import Registry
448 from openlp.core.common.settings import ProxyMode, Settings
449
450+
451 log = logging.getLogger(__name__ + '.__init__')
452
453 USER_AGENTS = {
454
455=== modified file 'openlp/core/common/i18n.py'
456--- openlp/core/common/i18n.py 2018-10-27 11:05:41 +0000
457+++ openlp/core/common/i18n.py 2019-02-13 20:54:40 +0000
458@@ -29,10 +29,11 @@
459
460 from PyQt5 import QtCore, QtWidgets
461
462-from openlp.core.common import is_win, is_macosx
463+from openlp.core.common import is_macosx, is_win
464 from openlp.core.common.applocation import AppLocation
465 from openlp.core.common.settings import Settings
466
467+
468 log = logging.getLogger(__name__)
469
470
471
472=== modified file 'openlp/core/common/mixins.py'
473--- openlp/core/common/mixins.py 2018-11-18 17:29:47 +0000
474+++ openlp/core/common/mixins.py 2019-02-13 20:54:40 +0000
475@@ -28,6 +28,7 @@
476 from openlp.core.common import is_win, trace_error_handler
477 from openlp.core.common.registry import Registry
478
479+
480 DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent', 'drag_enter_event', 'drop_event', 'on_controller_size_changed',
481 'preview_size_changed', 'resizeEvent']
482
483
484=== modified file 'openlp/core/common/path.py'
485--- openlp/core/common/path.py 2018-08-12 11:14:47 +0000
486+++ openlp/core/common/path.py 2019-02-13 20:54:40 +0000
487@@ -25,6 +25,7 @@
488
489 from openlp.core.common import is_win
490
491+
492 if is_win():
493 from pathlib import WindowsPath as PathVariant # pragma: nocover
494 else:
495
496=== modified file 'openlp/core/common/registry.py'
497--- openlp/core/common/registry.py 2018-10-25 16:38:39 +0000
498+++ openlp/core/common/registry.py 2019-02-13 20:54:40 +0000
499@@ -27,6 +27,7 @@
500
501 from openlp.core.common import de_hump, trace_error_handler
502
503+
504 log = logging.getLogger(__name__)
505
506
507@@ -143,6 +144,7 @@
508 if event in self.functions_list:
509 for function in self.functions_list[event]:
510 try:
511+ log.debug('Running function {} for {}'.format(function, event))
512 result = function(*args, **kwargs)
513 if result:
514 results.append(result)
515
516=== modified file 'openlp/core/common/settings.py'
517--- openlp/core/common/settings.py 2018-10-24 19:35:22 +0000
518+++ openlp/core/common/settings.py 2019-02-13 20:54:40 +0000
519@@ -33,7 +33,8 @@
520
521 from openlp.core.common import SlideLimits, ThemeLevel, is_linux, is_win
522 from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder
523-from openlp.core.common.path import Path, str_to_path, files_to_paths
524+from openlp.core.common.path import Path, files_to_paths, str_to_path
525+
526
527 log = logging.getLogger(__name__)
528
529@@ -70,6 +71,34 @@
530 return string
531
532
533+def upgrade_screens(number, x_position, y_position, height, width, can_override, is_display_screen):
534+ """
535+ Upgrade them monitor setting from a few single entries to a composite JSON entry
536+
537+ :param int number: The old monitor number
538+ :param int x_position: The X position
539+ :param int y_position: The Y position
540+ :param bool can_override: Are the screen positions overridden
541+ :param bool is_display_screen: Is this a display screen
542+ :returns dict: Dictionary with the new value
543+ """
544+ geometry_key = 'geometry'
545+ if can_override:
546+ geometry_key = 'custom_geometry'
547+ return {
548+ number: {
549+ 'number': number,
550+ geometry_key: {
551+ 'x': x_position,
552+ 'y': y_position,
553+ 'height': height,
554+ 'width': width
555+ },
556+ 'is_display': is_display_screen
557+ }
558+ }
559+
560+
561 class Settings(QtCore.QSettings):
562 """
563 Class to wrap QSettings.
564@@ -175,6 +204,7 @@
565 # circular dependency.
566 'core/display on monitor': True,
567 'core/override position': False,
568+ 'core/monitor': {},
569 'core/application version': '0.0',
570 'images/background color': '#000000',
571 'media/players': 'system,webkit',
572@@ -276,6 +306,8 @@
573 ('songuasge/db hostname', 'songusage/db hostname', []),
574 ('songuasge/db database', 'songusage/db database', []),
575 ('presentations / Powerpoint Viewer', '', []),
576+ (['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override',
577+ 'core/display on monitor'], 'core/screens', [(upgrade_screens, [1, 0, 0, None, None, False, False])]),
578 ('bibles/proxy name', '', []), # Just remove these bible proxy settings. They weren't used in 2.4!
579 ('bibles/proxy address', '', []),
580 ('bibles/proxy username', '', []),
581@@ -545,7 +577,7 @@
582 :param value: The value to save
583 :rtype: None
584 """
585- if isinstance(value, Path) or (isinstance(value, list) and value and isinstance(value[0], Path)):
586+ if isinstance(value, (Path, dict)) or (isinstance(value, list) and value and isinstance(value[0], Path)):
587 value = json.dumps(value, cls=OpenLPJsonEncoder)
588 super().setValue(key, value)
589
590@@ -568,8 +600,11 @@
591 # An empty list saved to the settings results in a None type being returned.
592 elif isinstance(default_value, list):
593 return []
594+ # An empty dictionary saved to the settings results in a None type being returned.
595+ elif isinstance(default_value, dict):
596+ return {}
597 elif isinstance(setting, str):
598- if '__Path__' in setting:
599+ if '__Path__' in setting or setting.startswith('{'):
600 return json.loads(setting, cls=OpenLPJsonDecoder)
601 # Convert the setting to the correct type.
602 if isinstance(default_value, bool):
603@@ -578,6 +613,8 @@
604 # Sometimes setting is string instead of a boolean.
605 return setting == 'true'
606 if isinstance(default_value, int):
607+ if setting is None:
608+ return 0
609 return int(setting)
610 return setting
611
612
613=== added directory 'openlp/core/display/html'
614=== added file 'openlp/core/display/html/black.css'
615--- openlp/core/display/html/black.css 1970-01-01 00:00:00 +0000
616+++ openlp/core/display/html/black.css 2019-02-13 20:54:40 +0000
617@@ -0,0 +1,292 @@
618+/**
619+ * Black theme for reveal.js. This is the opposite of the 'white' theme.
620+ *
621+ * By Hakim El Hattab, http://hakim.se
622+ */
623+@import url(../../lib/font/source-sans-pro/source-sans-pro.css);
624+section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
625+ color: #222; }
626+
627+/*********************************************
628+ * GLOBAL STYLES
629+ *********************************************/
630+body {
631+ background: #222;
632+ background-color: #222; }
633+
634+.reveal {
635+ font-family: "Source Sans Pro", Helvetica, sans-serif;
636+ font-size: 42px;
637+ font-weight: normal;
638+ color: #fff; }
639+
640+::selection {
641+ color: #fff;
642+ background: #bee4fd;
643+ text-shadow: none; }
644+
645+::-moz-selection {
646+ color: #fff;
647+ background: #bee4fd;
648+ text-shadow: none; }
649+
650+.reveal .slides > section,
651+.reveal .slides > section > section {
652+ line-height: 1.3;
653+ font-weight: inherit; }
654+
655+/*********************************************
656+ * HEADERS
657+ *********************************************/
658+.reveal h1,
659+.reveal h2,
660+.reveal h3,
661+.reveal h4,
662+.reveal h5,
663+.reveal h6 {
664+ margin: 0 0 20px 0;
665+ color: #fff;
666+ font-family: "Source Sans Pro", Helvetica, sans-serif;
667+ font-weight: 600;
668+ line-height: 1.2;
669+ letter-spacing: normal;
670+ text-transform: uppercase;
671+ text-shadow: none;
672+ word-wrap: break-word; }
673+
674+.reveal h1 {
675+ font-size: 2.5em; }
676+
677+.reveal h2 {
678+ font-size: 1.6em; }
679+
680+.reveal h3 {
681+ font-size: 1.3em; }
682+
683+.reveal h4 {
684+ font-size: 1em; }
685+
686+.reveal h1 {
687+ text-shadow: none; }
688+
689+/*********************************************
690+ * OTHER
691+ *********************************************/
692+.reveal p {
693+ margin: 20px 0;
694+ line-height: 1.3; }
695+
696+/* Ensure certain elements are never larger than the slide itself */
697+.reveal img,
698+.reveal video,
699+.reveal iframe {
700+ max-width: 95%;
701+ max-height: 95%; }
702+
703+.reveal strong,
704+.reveal b {
705+ font-weight: bold; }
706+
707+.reveal em {
708+ font-style: italic; }
709+
710+.reveal ol,
711+.reveal dl,
712+.reveal ul {
713+ display: inline-block;
714+ text-align: left;
715+ margin: 0 0 0 1em; }
716+
717+.reveal ol {
718+ list-style-type: decimal; }
719+
720+.reveal ul {
721+ list-style-type: disc; }
722+
723+.reveal ul ul {
724+ list-style-type: square; }
725+
726+.reveal ul ul ul {
727+ list-style-type: circle; }
728+
729+.reveal ul ul,
730+.reveal ul ol,
731+.reveal ol ol,
732+.reveal ol ul {
733+ display: block;
734+ margin-left: 40px; }
735+
736+.reveal dt {
737+ font-weight: bold; }
738+
739+.reveal dd {
740+ margin-left: 40px; }
741+
742+.reveal q,
743+.reveal blockquote {
744+ quotes: none; }
745+
746+.reveal blockquote {
747+ display: block;
748+ position: relative;
749+ width: 70%;
750+ margin: 20px auto;
751+ padding: 5px;
752+ font-style: italic;
753+ background: rgba(255, 255, 255, 0.05);
754+ box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); }
755+
756+.reveal blockquote p:first-child,
757+.reveal blockquote p:last-child {
758+ display: inline-block; }
759+
760+.reveal q {
761+ font-style: italic; }
762+
763+.reveal pre {
764+ display: block;
765+ position: relative;
766+ width: 90%;
767+ margin: 20px auto;
768+ text-align: left;
769+ font-size: 0.55em;
770+ font-family: monospace;
771+ line-height: 1.2em;
772+ word-wrap: break-word;
773+ box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
774+
775+.reveal code {
776+ font-family: monospace; }
777+
778+.reveal pre code {
779+ display: block;
780+ padding: 5px;
781+ overflow: auto;
782+ max-height: 400px;
783+ word-wrap: normal; }
784+
785+.reveal table {
786+ margin: auto;
787+ border-collapse: collapse;
788+ border-spacing: 0; }
789+
790+.reveal table th {
791+ font-weight: bold; }
792+
793+.reveal table th,
794+.reveal table td {
795+ text-align: left;
796+ padding: 0.2em 0.5em 0.2em 0.5em;
797+ border-bottom: 1px solid; }
798+
799+.reveal table th[align="center"],
800+.reveal table td[align="center"] {
801+ text-align: center; }
802+
803+.reveal table th[align="right"],
804+.reveal table td[align="right"] {
805+ text-align: right; }
806+
807+.reveal table tbody tr:last-child th,
808+.reveal table tbody tr:last-child td {
809+ border-bottom: none; }
810+
811+.reveal sup {
812+ vertical-align: super; }
813+
814+.reveal sub {
815+ vertical-align: sub; }
816+
817+.reveal small {
818+ display: inline-block;
819+ font-size: 0.6em;
820+ line-height: 1.2em;
821+ vertical-align: top; }
822+
823+.reveal small * {
824+ vertical-align: top; }
825+
826+/*********************************************
827+ * LINKS
828+ *********************************************/
829+.reveal a {
830+ color: #42affa;
831+ text-decoration: none;
832+ -webkit-transition: color .15s ease;
833+ -moz-transition: color .15s ease;
834+ transition: color .15s ease; }
835+
836+.reveal a:hover {
837+ color: #8dcffc;
838+ text-shadow: none;
839+ border: none; }
840+
841+.reveal .roll span:after {
842+ color: #fff;
843+ background: #068de9; }
844+
845+/*********************************************
846+ * IMAGES
847+ *********************************************/
848+.reveal section img {
849+ margin: 15px 0px;
850+ background: rgba(255, 255, 255, 0.12);
851+ border: 4px solid #fff;
852+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); }
853+
854+.reveal section img.plain {
855+ border: 0;
856+ box-shadow: none; }
857+
858+.reveal a img {
859+ -webkit-transition: all .15s linear;
860+ -moz-transition: all .15s linear;
861+ transition: all .15s linear; }
862+
863+.reveal a:hover img {
864+ background: rgba(255, 255, 255, 0.2);
865+ border-color: #42affa;
866+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); }
867+
868+/*********************************************
869+ * NAVIGATION CONTROLS
870+ *********************************************/
871+.reveal .controls .navigate-left,
872+.reveal .controls .navigate-left.enabled {
873+ border-right-color: #42affa; }
874+
875+.reveal .controls .navigate-right,
876+.reveal .controls .navigate-right.enabled {
877+ border-left-color: #42affa; }
878+
879+.reveal .controls .navigate-up,
880+.reveal .controls .navigate-up.enabled {
881+ border-bottom-color: #42affa; }
882+
883+.reveal .controls .navigate-down,
884+.reveal .controls .navigate-down.enabled {
885+ border-top-color: #42affa; }
886+
887+.reveal .controls .navigate-left.enabled:hover {
888+ border-right-color: #8dcffc; }
889+
890+.reveal .controls .navigate-right.enabled:hover {
891+ border-left-color: #8dcffc; }
892+
893+.reveal .controls .navigate-up.enabled:hover {
894+ border-bottom-color: #8dcffc; }
895+
896+.reveal .controls .navigate-down.enabled:hover {
897+ border-top-color: #8dcffc; }
898+
899+/*********************************************
900+ * PROGRESS BAR
901+ *********************************************/
902+.reveal .progress {
903+ background: rgba(0, 0, 0, 0.2); }
904+
905+.reveal .progress span {
906+ background: #42affa;
907+ -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
908+ -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
909+ transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
910
911=== added file 'openlp/core/display/html/checkerboard.png'
912Binary files openlp/core/display/html/checkerboard.png 1970-01-01 00:00:00 +0000 and openlp/core/display/html/checkerboard.png 2019-02-13 20:54:40 +0000 differ
913=== added file 'openlp/core/display/html/display.html'
914--- openlp/core/display/html/display.html 1970-01-01 00:00:00 +0000
915+++ openlp/core/display/html/display.html 2019-02-13 20:54:40 +0000
916@@ -0,0 +1,39 @@
917+<!DOCTYPE html>
918+<html>
919+ <head>
920+ <title>Display Window</title>
921+ <link href="reveal.css" rel="stylesheet">
922+ <style type="text/css">
923+ body {
924+ background: transparent !important;
925+ color: #fff !important;
926+ }
927+ sup {
928+ vertical-align: super !important;
929+ font-size: smaller !important;
930+ }
931+ .reveal .slides > section,
932+ .reveal .slides > section > section {
933+ padding: 0;
934+ }
935+ .reveal > .backgrounds > .present {
936+ visibility: hidden !important;
937+ }
938+ #global-background {
939+ display: block;
940+ visibility: visible;
941+ z-index: -1;
942+ }
943+ </style>
944+ <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
945+ <script type="text/javascript" src="reveal.js"></script>
946+ <script type="text/javascript" src="display.js"></script>
947+ </head>
948+ <body>
949+ <div class="reveal">
950+ <div id="global-background" class="slide-background present" data-loaded="true"></div>
951+ <div class="slides"></div>
952+ <div class="footer"></div>
953+ </div>
954+ </body>
955+</html>
956
957=== added file 'openlp/core/display/html/display.js'
958--- openlp/core/display/html/display.js 1970-01-01 00:00:00 +0000
959+++ openlp/core/display/html/display.js 2019-02-13 20:54:40 +0000
960@@ -0,0 +1,789 @@
961+/**
962+ * display.js is the main Javascript file that is used to drive the display.
963+ */
964+
965+/**
966+ * Background type enumeration
967+ */
968+var BackgroundType = {
969+ Transparent: "transparent",
970+ Solid: "solid",
971+ Gradient: "gradient",
972+ Video: "video",
973+ Image: "image"
974+};
975+
976+/**
977+ * Gradient type enumeration
978+ */
979+var GradientType = {
980+ Horizontal: "horizontal",
981+ LeftTop: "leftTop",
982+ LeftBottom: "leftBottom",
983+ Vertical: "vertical",
984+ Circular: "circular"
985+};
986+
987+/**
988+ * Horizontal alignment enumeration
989+ */
990+var HorizontalAlign = {
991+ Left: "left",
992+ Right: "right",
993+ Center: "center",
994+ Justify: "justify"
995+};
996+
997+/**
998+ * Vertical alignment enumeration
999+ */
1000+var VerticalAlign = {
1001+ Top: "top",
1002+ Middle: "middle",
1003+ Bottom: "bottom"
1004+};
1005+
1006+/**
1007+ * Audio state enumeration
1008+ */
1009+var AudioState = {
1010+ Playing: "playing",
1011+ Paused: "paused",
1012+ Stopped: "stopped"
1013+};
1014+
1015+/**
1016+ * Return an array of elements based on the selector query
1017+ * @param {string} selector - The selector to find elements
1018+ * @returns {array} An array of matching elements
1019+ */
1020+function $(selector) {
1021+ return Array.from(document.querySelectorAll(selector));
1022+}
1023+
1024+/**
1025+ * Build linear gradient CSS
1026+ * @private
1027+ * @param {string} startDir - Starting direction
1028+ * @param {string} endDir - Ending direction
1029+ * @param {string} startColor - The starting color
1030+ * @param {string} endColor - The ending color
1031+ * @returns {string} A string of the gradient CSS
1032+ */
1033+function _buildLinearGradient(startDir, endDir, startColor, endColor) {
1034+ return "-webkit-gradient(linear, " + startDir + ", " + endDir + ", from(" + startColor + "), to(" + endColor + ")) fixed";
1035+}
1036+
1037+/**
1038+ * Build radial gradient CSS
1039+ * @private
1040+ * @param {string} width - Width of the gradient
1041+ * @param {string} startColor - The starting color
1042+ * @param {string} endColor - The ending color
1043+ * @returns {string} A string of the gradient CSS
1044+ */
1045+function _buildRadialGradient(width, startColor, endColor) {
1046+ return "-webkit-gradient(radial, " + width + " 50%, 100, " + width + " 50%, " + width + ", from(" + startColor + "), to(" + endColor + ")) fixed";
1047+}
1048+
1049+/**
1050+ * Get a style value from an element (computed or manual)
1051+ * @private
1052+ * @param {Object} element - The element whose style we want
1053+ * @param {string} style - The name of the style we want
1054+ * @returns {(Number|string)} The style value (type depends on the style)
1055+ */
1056+function _getStyle(element, style) {
1057+ return document.defaultView.getComputedStyle(element).getPropertyValue(style);
1058+}
1059+
1060+/**
1061+ * Convert newlines to <br> tags
1062+ * @private
1063+ * @param {string} text - The text to parse
1064+ * @returns {string} The text now with <br> tags
1065+ */
1066+function _nl2br(text) {
1067+ return text.replace("\r\n", "\n").replace("\n", "<br>");
1068+}
1069+
1070+/**
1071+ * Prepare text by creating paragraphs and calling _nl2br to convert newlines to <br> tags
1072+ * @private
1073+ * @param {string} text - The text to parse
1074+ * @returns {string} The text now with <p> and <br> tags
1075+ */
1076+function _prepareText(text) {
1077+ return "<p>" + _nl2br(text) + "</p>";
1078+}
1079+
1080+/**
1081+ * The paths we get are JSON versions of Python Path objects, so let's just fix that.
1082+ * @private
1083+ * @param {object} path - The Path object
1084+ * @returns {string} The actual file path
1085+ */
1086+function _pathToString(path) {
1087+ var filename = path.__Path__.join("/").replace("//", "/");
1088+ if (!filename.startsWith("/")) {
1089+ filename = "/" + filename;
1090+ }
1091+ return filename;
1092+}
1093+
1094+/**
1095+ * An audio player with a play list
1096+ */
1097+var AudioPlayer = function (audioElement) {
1098+ this._audioElement = null;
1099+ this._eventListeners = {};
1100+ this._playlist = [];
1101+ this._currentTrack = null;
1102+ this._canRepeat = false;
1103+ this._state = AudioState.Stopped;
1104+ this.createAudioElement();
1105+};
1106+
1107+/**
1108+ * Call all listeners associated with this event
1109+ * @private
1110+ * @param {object} event - The event that was emitted
1111+ */
1112+AudioPlayer.prototype._callListener = function (event) {
1113+ if (this._eventListeners.hasOwnProperty(event.type)) {
1114+ this._eventListeners[event.type].forEach(function (listener) {
1115+ listener(event);
1116+ });
1117+ }
1118+ else {
1119+ console.warn("Received unknown event \"" + event.type + "\", doing nothing.");
1120+ }
1121+};
1122+
1123+/**
1124+ * Create the <audio> element that is used to play the audio
1125+ */
1126+AudioPlayer.prototype.createAudioElement = function () {
1127+ this._audioElement = document.createElement("audio");
1128+ this._audioElement.addEventListener("ended", this.onEnded);
1129+ this._audioElement.addEventListener("ended", this._callListener);
1130+ this._audioElement.addEventListener("timeupdate", this._callListener);
1131+ this._audioElement.addEventListener("volumechange", this._callListener);
1132+ this._audioElement.addEventListener("durationchange", this._callListener);
1133+ this._audioElement.addEventListener("loadeddata", this._callListener);
1134+ document.addEventListener("complete", function(event) {
1135+ document.body.appendChild(this._audioElement);
1136+ });
1137+};
1138+AudioPlayer.prototype.addEventListener = function (eventType, listener) {
1139+ this._eventListeners[eventType] = this._eventListeners[eventType] || [];
1140+ this._eventListeners[eventType].push(listener);
1141+};
1142+AudioPlayer.prototype.onEnded = function (event) {
1143+ this.nextTrack();
1144+};
1145+AudioPlayer.prototype.setCanRepeat = function (canRepeat) {
1146+ this._canRepeat = canRepeat;
1147+};
1148+AudioPlayer.prototype.clearTracks = function () {
1149+ this._playlist = [];
1150+};
1151+AudioPlayer.prototype.addTrack = function (track) {
1152+ this._playlist.push(track);
1153+};
1154+AudioPlayer.prototype.nextTrack = function () {
1155+ if (!!this._currentTrack) {
1156+ var trackIndex = this._playlist.indexOf(this._currentTrack);
1157+ if ((trackIndex + 1 >= this._playlist.length) && this._canRepeat) {
1158+ this.play(this._playlist[0]);
1159+ }
1160+ else if (trackIndex + 1 < this._playlist.length) {
1161+ this.play(this._playlist[trackIndex + 1]);
1162+ }
1163+ else {
1164+ this.stop();
1165+ }
1166+ }
1167+ else if (this._playlist.length > 0) {
1168+ this.play(this._playlist[0]);
1169+ }
1170+ else {
1171+ console.warn("No tracks in playlist, doing nothing.");
1172+ }
1173+};
1174+AudioPlayer.prototype.play = function () {
1175+ if (arguments.length > 0) {
1176+ this._currentTrack = arguments[0];
1177+ this._audioElement.src = this._currentTrack;
1178+ this._audioElement.play();
1179+ this._state = AudioState.Playing;
1180+ }
1181+ else if (this._state == AudioState.Paused) {
1182+ this._audioElement.play();
1183+ this._state = AudioState.Playing;
1184+ }
1185+ else {
1186+ console.warn("No track currently paused and no track specified, doing nothing.");
1187+ }
1188+};
1189+
1190+/**
1191+ * Pause
1192+ */
1193+AudioPlayer.prototype.pause = function () {
1194+ this._audioElement.pause();
1195+ this._state = AudioState.Paused;
1196+};
1197+
1198+/**
1199+ * Stop playing
1200+ */
1201+AudioPlayer.prototype.stop = function () {
1202+ this._audioElement.pause();
1203+ this._audioElement.src = "";
1204+ this._state = AudioState.Stopped;
1205+};
1206+
1207+/**
1208+ * The Display object is what we use from OpenLP
1209+ */
1210+var Display = {
1211+ _slides: {},
1212+ _revealConfig: {
1213+ margin: 0.0,
1214+ minScale: 1.0,
1215+ maxScale: 1.0,
1216+ controls: false,
1217+ progress: false,
1218+ history: false,
1219+ overview: false,
1220+ center: false,
1221+ help: false,
1222+ transition: "none",
1223+ backgroundTransition: "none",
1224+ viewDistance: 9999,
1225+ width: "100%",
1226+ height: "100%"
1227+ },
1228+ /**
1229+ * Start up reveal and do any other initialisation
1230+ */
1231+ init: function () {
1232+ Reveal.initialize(this._revealConfig);
1233+ },
1234+ /**
1235+ * Reinitialise Reveal
1236+ */
1237+ reinit: function () {
1238+ Reveal.reinitialize();
1239+ },
1240+ /**
1241+ * Set the transition type
1242+ * @param {string} transitionType - Can be one of "none", "fade", "slide", "convex", "concave", "zoom"
1243+ */
1244+ setTransition: function (transitionType) {
1245+ Reveal.configure({"transition": transitionType});
1246+ },
1247+ /**
1248+ * Clear the current list of slides
1249+ */
1250+ clearSlides: function () {
1251+ $(".slides")[0].innerHTML = "";
1252+ this._slides = {};
1253+ },
1254+ /**
1255+ * Checks if the present slide content fits within the slide
1256+ */
1257+ doesContentFit: function () {
1258+ console.debug("scrollHeight: " + $(".slides")[0].scrollHeight + ", clientHeight: " + $(".slides")[0].clientHeight);
1259+ return $(".slides")[0].clientHeight >= $(".slides")[0].scrollHeight;
1260+ },
1261+ /**
1262+ * Generate the OpenLP startup splashscreen
1263+ * @param {string} bg_color - The background color
1264+ * @param {string} image - Path to the splash image
1265+ */
1266+ setStartupSplashScreen: function(bg_color, image) {
1267+ Display.clearSlides();
1268+ var globalBackground = $("#global-background")[0];
1269+ globalBackground.style.cssText = "";
1270+ globalBackground.style.setProperty("background", bg_color);
1271+ var slidesDiv = $(".slides")[0];
1272+ var section = document.createElement("section");
1273+ section.setAttribute("id", 0);
1274+ section.setAttribute("data-background", bg_color);
1275+ section.setAttribute("style", "height: 100%; width: 100%; position: relative;");
1276+ var img = document.createElement('img');
1277+ img.src = image;
1278+ img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto;");
1279+ section.appendChild(img);
1280+ slidesDiv.appendChild(section);
1281+ Display._slides['0'] = 0;
1282+ Display.reinit();
1283+ },
1284+ /**
1285+ * Set fullscreen image from path
1286+ * @param {string} bg_color - The background color
1287+ * @param {string} image - Path to the image
1288+ */
1289+ setFullscreenImage: function(bg_color, image) {
1290+ Display.clearSlides();
1291+ var globalBackground = $("#global-background")[0];
1292+ globalBackground.style.cssText = "";
1293+ globalBackground.style.setProperty("background", bg_color);
1294+ var slidesDiv = $(".slides")[0];
1295+ var section = document.createElement("section");
1296+ section.setAttribute("id", 0);
1297+ section.setAttribute("data-background", bg_color);
1298+ section.setAttribute("style", "height: 100%; width: 100%;");
1299+ var img = document.createElement('img');
1300+ img.src = image;
1301+ img.setAttribute("style", "height: 100%; width: 100%");
1302+ section.appendChild(img);
1303+ slidesDiv.appendChild(section);
1304+ Display._slides['0'] = 0;
1305+ Display.reinit();
1306+ },
1307+ /**
1308+ * Set fullscreen image from base64 data
1309+ * @param {string} bg_color - The background color
1310+ * @param {string} image - Path to the image
1311+ */
1312+ setFullscreenImageFromData: function(bg_color, image_data) {
1313+ Display.clearSlides();
1314+ var globalBackground = $("#global-background")[0];
1315+ globalBackground.style.cssText = "";
1316+ globalBackground.style.setProperty("background", bg_color);
1317+ var slidesDiv = $(".slides")[0];
1318+ var section = document.createElement("section");
1319+ section.setAttribute("id", 0);
1320+ section.setAttribute("data-background", bg_color);
1321+ section.setAttribute("style", "height: 100%; width: 100%;");
1322+ var img = document.createElement('img');
1323+ img.src = 'data:image/png;base64,' + image_data;
1324+ img.setAttribute("style", "height: 100%; width: 100%");
1325+ section.appendChild(img);
1326+ slidesDiv.appendChild(section);
1327+ Display._slides['0'] = 0;
1328+ Display.reinit();
1329+ },
1330+ /**
1331+ * Display an alert
1332+ * @param {string} text - The alert text
1333+ * @param {int} location - The location of the text (top, middle or bottom)
1334+ */
1335+ alert: function (text, location) {
1336+ console.debug(" alert text: " + text, ", location: " + location);
1337+ /*
1338+ * The implementation should show an alert.
1339+ * It should be able to handle receiving a new alert before a previous one is "finished", basically queueing it.
1340+ */
1341+ return;
1342+},
1343+
1344+ /**
1345+ * Add a slides. If the slide exists but the HTML is different, update the slide.
1346+ * @param {string} verse - The verse number, e.g. "v1"
1347+ * @param {string} text - The HTML for the verse, e.g. "line1<br>line2"
1348+ * @param {string} footer_text - The HTML for the footer"
1349+ * @param {bool} [reinit=true] - Re-initialize Reveal. Defaults to true.
1350+ */
1351+ addTextSlide: function (verse, text, footer_text) {
1352+ var html = _prepareText(text);
1353+ if (this._slides.hasOwnProperty(verse)) {
1354+ var slide = $("#" + verse)[0];
1355+ if (slide.innerHTML != html) {
1356+ slide.innerHTML = html;
1357+ }
1358+ }
1359+ else {
1360+ var slidesDiv = $(".slides")[0];
1361+ var slide = document.createElement("section");
1362+ slide.setAttribute("id", verse);
1363+ slide.innerHTML = html;
1364+ slidesDiv.appendChild(slide);
1365+ var slides = $(".slides > section");
1366+ this._slides[verse] = slides.length - 1;
1367+
1368+ console.debug(" footer_text: " + footer_text);
1369+
1370+ var footerDiv = $(".footer")[0];
1371+ footerDiv.innerHTML = footer_text;
1372+ }
1373+ if ((arguments.length > 3) && (arguments[3] === true)) {
1374+ this.reinit();
1375+ }
1376+ else if (arguments.length == 3) {
1377+ this.reinit();
1378+ }
1379+ },
1380+ /**
1381+ * Set text slides.
1382+ * @param {Object[]} slides - A list of slides to add as JS objects: {"verse": "v1", "text": "line 1\nline2"}
1383+ */
1384+ setTextSlides: function (slides) {
1385+ Display.clearSlides();
1386+ slides.forEach(function (slide) {
1387+ Display.addTextSlide(slide.verse, slide.text, slide.footer, false);
1388+ });
1389+ Display.reinit();
1390+ Display.goToSlide(0);
1391+ },
1392+ /**
1393+ * Set image slides
1394+ * @param {Object[]} slides - A list of images to add as JS objects [{"path": "url/to/file"}]
1395+ */
1396+ setImageSlides: function (slides) {
1397+ Display.clearSlides();
1398+ var slidesDiv = $(".slides")[0];
1399+ slides.forEach(function (slide, index) {
1400+ var section = document.createElement("section");
1401+ section.setAttribute("id", index);
1402+ section.setAttribute("data-background", "#000");
1403+ var img = document.createElement('img');
1404+ img.src = slide["path"];
1405+ img.setAttribute("style", "height: 100%; width: 100%;");
1406+ section.appendChild(img);
1407+ slidesDiv.appendChild(section);
1408+ Display._slides[index.toString()] = index;
1409+ });
1410+ Display.reinit();
1411+ },
1412+ /**
1413+ * Set a video
1414+ * @param {Object} video - The video to show as a JS object: {"path": "url/to/file"}
1415+ */
1416+ setVideo: function (video) {
1417+ this.clearSlides();
1418+ var section = document.createElement("section");
1419+ section.setAttribute("data-background", "#000");
1420+ var videoElement = document.createElement("video");
1421+ videoElement.src = video["path"];
1422+ videoElement.preload = "auto";
1423+ videoElement.setAttribute("id", "video");
1424+ videoElement.setAttribute("style", "height: 100%; width: 100%;");
1425+ videoElement.autoplay = false;
1426+ // All the update methods below are Python functions, hence not camelCase
1427+ videoElement.addEventListener("durationchange", function (event) {
1428+ mediaWatcher.update_duration(event.target.duration);
1429+ });
1430+ videoElement.addEventListener("timeupdate", function (event) {
1431+ mediaWatcher.update_progress(event.target.currentTime);
1432+ });
1433+ videoElement.addEventListener("volumeupdate", function (event) {
1434+ mediaWatcher.update_volume(event.target.volume);
1435+ });
1436+ videoElement.addEventListener("ratechange", function (event) {
1437+ mediaWatcher.update_playback_rate(event.target.playbackRate);
1438+ });
1439+ videoElement.addEventListener("ended", function (event) {
1440+ mediaWatcher.has_ended(event.target.ended);
1441+ });
1442+ videoElement.addEventListener("muted", function (event) {
1443+ mediaWatcher.has_muted(event.target.muted);
1444+ });
1445+ section.appendChild(videoElement);
1446+ $(".slides")[0].appendChild(section);
1447+ this.reinit();
1448+ },
1449+ /**
1450+ * Play a video
1451+ */
1452+ playVideo: function () {
1453+ if ($("#video").length == 1) {
1454+ $("#video")[0].play();
1455+ }
1456+ },
1457+ /**
1458+ * Pause a video
1459+ */
1460+ pauseVideo: function () {
1461+ if ($("#video").length == 1) {
1462+ $("#video")[0].pause();
1463+ }
1464+ },
1465+ /**
1466+ * Stop a video
1467+ */
1468+ stopVideo: function () {
1469+ if ($("#video").length == 1) {
1470+ $("#video")[0].pause();
1471+ $("#video")[0].currentTime = 0.0;
1472+ }
1473+ },
1474+ /**
1475+ * Go to a particular time in a video
1476+ * @param seconds The position in seconds to seek to
1477+ */
1478+ seekVideo: function (seconds) {
1479+ if ($("#video").length == 1) {
1480+ $("#video")[0].currentTime = seconds;
1481+ }
1482+ },
1483+ /**
1484+ * Set the playback rate of a video
1485+ * @param rate A Double of the rate. 1.0 => 100% speed, 0.75 => 75% speed, 1.25 => 125% speed, etc.
1486+ */
1487+ setPlaybackRate: function (rate) {
1488+ if ($("#video").length == 1) {
1489+ $("#video")[0].playbackRate = rate;
1490+ }
1491+ },
1492+ /**
1493+ * Set the volume
1494+ * @param level The volume level from 0 to 100.
1495+ */
1496+ setVideoVolume: function (level) {
1497+ if ($("#video").length == 1) {
1498+ $("#video")[0].volume = level / 100.0;
1499+ }
1500+ },
1501+ /**
1502+ * Mute the volume
1503+ */
1504+ toggleVideoMute: function () {
1505+ if ($("#video").length == 1) {
1506+ $("#video")[0].muted = !$("#video")[0].muted;
1507+ }
1508+ },
1509+ /**
1510+ * Clear the background audio playlist
1511+ */
1512+ clearPlaylist: function () {
1513+ if ($("#background-audio").length == 1) {
1514+ var audio = $("#background-audio")[0];
1515+ /* audio.playList */
1516+ }
1517+ },
1518+ /**
1519+ * Add background audio
1520+ * @param files The list of files as objects in an array
1521+ */
1522+ addBackgroundAudio: function (files) {
1523+ },
1524+ /**
1525+ * Go to a slide.
1526+ * @param slide The slide number or name, e.g. "v1", 0
1527+ */
1528+ goToSlide: function (slide) {
1529+ if (this._slides.hasOwnProperty(slide)) {
1530+ Reveal.slide(this._slides[slide]);
1531+ }
1532+ else {
1533+ Reveal.slide(slide);
1534+ }
1535+ },
1536+ /**
1537+ * Go to the next slide in the list
1538+ */
1539+ next: Reveal.next,
1540+ /**
1541+ * Go to the previous slide in the list
1542+ */
1543+ prev: Reveal.prev,
1544+ /**
1545+ * Blank the screen
1546+ */
1547+ blankToBlack: function () {
1548+ if (!Reveal.isPaused()) {
1549+ Reveal.togglePause();
1550+ }
1551+ // var slidesDiv = $(".slides")[0];
1552+ },
1553+ /**
1554+ * Blank to theme
1555+ */
1556+ blankToTheme: function () {
1557+ var slidesDiv = $(".slides")[0];
1558+ slidesDiv.style.visibility = "hidden";
1559+ var footerDiv = $(".footer")[0];
1560+ footerDiv.style.visibility = "hidden";
1561+ if (Reveal.isPaused()) {
1562+ Reveal.togglePause();
1563+ }
1564+ },
1565+ /**
1566+ * Show the screen
1567+ */
1568+ show: function () {
1569+ var slidesDiv = $(".slides")[0];
1570+ slidesDiv.style.visibility = "visible";
1571+ var footerDiv = $(".footer")[0];
1572+ footerDiv.style.visibility = "visible";
1573+ if (Reveal.isPaused()) {
1574+ Reveal.togglePause();
1575+ }
1576+ },
1577+ /**
1578+ * Figure out how many lines can fit on a slide given the font size
1579+ * @param fontSize The font size in pts
1580+ */
1581+ calculateLineCount: function (fontSize) {
1582+ var p = $(".slides > section > p");
1583+ if (p.length == 0) {
1584+ this.addSlide("v1", "Arky arky");
1585+ p = $(".slides > section > p");
1586+ }
1587+ p = p[0];
1588+ p.style.fontSize = "" + fontSize + "pt";
1589+ var d = $(".slides")[0];
1590+ var lh = parseFloat(_getStyle(p, "line-height"));
1591+ var dh = parseFloat(_getStyle(d, "height"));
1592+ return Math.floor(dh / lh);
1593+ },
1594+ setTheme: function (theme) {
1595+ this._theme = theme;
1596+ var slidesDiv = $(".slides")
1597+ // Set the background
1598+ var globalBackground = $("#global-background")[0];
1599+ var backgroundStyle = {};
1600+ var backgroundHtml = "";
1601+ switch (theme.background_type) {
1602+ case BackgroundType.Transparent:
1603+ backgroundStyle["background"] = "transparent";
1604+ break;
1605+ case BackgroundType.Solid:
1606+ backgroundStyle["background"] = theme.background_color;
1607+ break;
1608+ case BackgroundType.Gradient:
1609+ switch (theme.background_direction) {
1610+ case GradientType.Horizontal:
1611+ backgroundStyle["background"] = _buildLinearGradient("left top", "left bottom",
1612+ theme.background_start_color,
1613+ theme.background_end_color);
1614+ break;
1615+ case GradientType.Vertical:
1616+ backgroundStyle["background"] = _buildLinearGradient("left top", "right top",
1617+ theme.background_start_color,
1618+ theme.background_end_color);
1619+ break;
1620+ case GradientType.LeftTop:
1621+ backgroundStyle["background"] = _buildLinearGradient("left top", "right bottom",
1622+ theme.background_start_color,
1623+ theme.background_end_color);
1624+ break;
1625+ case GradientType.LeftBottom:
1626+ backgroundStyle["background"] = _buildLinearGradient("left bottom", "right top",
1627+ theme.background_start_color,
1628+ theme.background_end_color);
1629+ break;
1630+ case GradientType.Circular:
1631+ backgroundStyle["background"] = _buildRadialGradient(window.innerWidth / 2, theme.background_start_color,
1632+ theme.background_end_color);
1633+ break;
1634+ default:
1635+ backgroundStyle["background"] = "#000";
1636+ }
1637+ break;
1638+ case BackgroundType.Image:
1639+ background_filename = _pathToString(theme.background_filename);
1640+ backgroundStyle["background-image"] = "url('file://" + background_filename + "')";
1641+ break;
1642+ case BackgroundType.Video:
1643+ background_filename = _pathToString(theme.background_filename);
1644+ backgroundStyle["background-color"] = theme.background_border_color;
1645+ backgroundHtml = "<video loop autoplay muted><source src='file://" + background_filename + "'></video>";
1646+ break;
1647+ default:
1648+ backgroundStyle["background"] = "#000";
1649+ }
1650+ globalBackground.style.cssText = "";
1651+ for (var key in backgroundStyle) {
1652+ if (backgroundStyle.hasOwnProperty(key)) {
1653+ globalBackground.style.setProperty(key, backgroundStyle[key]);
1654+ }
1655+ }
1656+ if (!!backgroundHtml) {
1657+ globalBackground.innerHTML = backgroundHtml;
1658+ }
1659+ // set up the main area
1660+ mainStyle = {
1661+ "word-wrap": "break-word",
1662+ /*"margin": "0",
1663+ "padding": "0"*/
1664+ };
1665+ if (!!theme.font_main_outline) {
1666+ mainStyle["-webkit-text-stroke"] = "" + theme.font_main_outline_size + "pt " +
1667+ theme.font_main_outline_color;
1668+ mainStyle["-webkit-text-fill-color"] = theme.font_main_color;
1669+ }
1670+ mainStyle["font-family"] = theme.font_main_name;
1671+ mainStyle["font-size"] = "" + theme.font_main_size + "pt";
1672+ mainStyle["font-style"] = !!theme.font_main_italics ? "italic" : "";
1673+ mainStyle["font-weight"] = !!theme.font_main_bold ? "bold" : "";
1674+ mainStyle["color"] = theme.font_main_color;
1675+ mainStyle["line-height"] = "" + (100 + theme.font_main_line_adjustment) + "%";
1676+ mainStyle["text-align"] = theme.display_horizontal_align;
1677+ if (theme.display_horizontal_align != HorizontalAlign.Justify) {
1678+ mainStyle["white-space"] = "pre-wrap";
1679+ }
1680+ mainStyle["vertical-align"] = theme.display_vertical_align;
1681+ if (theme.hasOwnProperty('font_main_shadow_size')) {
1682+ mainStyle["text-shadow"] = theme.font_main_shadow_color + " " + theme.font_main_shadow_size + "px " +
1683+ theme.font_main_shadow_size + "px";
1684+ }
1685+ mainStyle["padding-bottom"] = theme.display_vertical_align == VerticalAlign.Bottom ? "0.5em" : "0";
1686+ mainStyle["padding-left"] = !!theme.font_main_outline ? "" + (theme.font_main_outline_size * 2) + "pt" : "0";
1687+ // These need to be fixed, in the Python they use a width passed in as a parameter
1688+ mainStyle["position"] = "absolute";
1689+ mainStyle["width"] = "" + (window.innerWidth - (theme.font_main_outline_size * 4)) + "px";
1690+ mainStyle["height"] = "" + (window.innerHeight - (theme.font_main_outline_size * 4)) + "px";
1691+ mainStyle["left"] = "" + theme.font_main_x + "px";
1692+ mainStyle["top"] = "" + theme.font_main_y + "px";
1693+ var slidesDiv = $(".slides")[0];
1694+ slidesDiv.style.cssText = "";
1695+ for (var key in mainStyle) {
1696+ if (mainStyle.hasOwnProperty(key)) {
1697+ slidesDiv.style.setProperty(key, mainStyle[key]);
1698+ }
1699+ }
1700+ // Set up the footer
1701+ footerStyle = {
1702+ "text-align": "left"
1703+ };
1704+ footerStyle["position"] = "absolute";
1705+ footerStyle["left"] = "" + theme.font_footer_x + "px";
1706+ footerStyle["top"] = "" + theme.font_footer_y + "px";
1707+ footerStyle["bottom"] = "" + (window.innerHeight - theme.font_footer_y - theme.font_footer_height) + "px";
1708+ footerStyle["width"] = "" + theme.font_footer_width + "px";
1709+ footerStyle["font-family"] = theme.font_footer_name;
1710+ footerStyle["font-size"] = "" + theme.font_footer_size + "pt";
1711+ footerStyle["color"] = theme.font_footer_color;
1712+ footerStyle["white-space"] = theme.font_footer_wrap ? "normal" : "nowrap";
1713+ var footer = $(".footer")[0];
1714+ footer.style.cssText = "";
1715+ for (var key in footerStyle) {
1716+ if (footerStyle.hasOwnProperty(key)) {
1717+ footer.style.setProperty(key, footerStyle[key]);
1718+ }
1719+ }
1720+ },
1721+ /**
1722+ * Return the video types supported by the video tag
1723+ */
1724+ getVideoTypes: function () {
1725+ var videoElement = document.createElement('video');
1726+ var videoTypes = [];
1727+ if (videoElement.canPlayType('video/mp4; codecs="mp4v.20.8"') == "probably" ||
1728+ videoElement.canPlayType('video/mp4; codecs="avc1.42E01E"') == "pobably" ||
1729+ videoElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"') == "probably") {
1730+ videoTypes.push(['video/mp4', '*.mp4']);
1731+ }
1732+ if (videoElement.canPlayType('video/ogg; codecs="theora"') == "probably") {
1733+ videoTypes.push(['video/ogg', '*.ogv']);
1734+ }
1735+ if (videoElement.canPlayType('video/webm; codecs="vp8, vorbis"') == "probably") {
1736+ videoTypes.push(['video/webm', '*.webm']);
1737+ }
1738+ return videoTypes;
1739+ },
1740+ /**
1741+ * Sets the scale of the page - used to make preview widgets scale
1742+ */
1743+ setScale: function(scale) {
1744+ document.body.style.zoom = scale+"%";
1745+ }
1746+};
1747+new QWebChannel(qt.webChannelTransport, function (channel) {
1748+ window.mediaWatcher = channel.objects.mediaWatcher;
1749+});
1750
1751=== added file 'openlp/core/display/html/openlp-splash-screen.png'
1752Binary files openlp/core/display/html/openlp-splash-screen.png 1970-01-01 00:00:00 +0000 and openlp/core/display/html/openlp-splash-screen.png 2019-02-13 20:54:40 +0000 differ
1753=== added file 'openlp/core/display/html/reveal.css'
1754--- openlp/core/display/html/reveal.css 1970-01-01 00:00:00 +0000
1755+++ openlp/core/display/html/reveal.css 2019-02-13 20:54:40 +0000
1756@@ -0,0 +1,1591 @@
1757+/*!
1758+ * reveal.js
1759+ * http://revealjs.com
1760+ * MIT licensed
1761+ *
1762+ * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
1763+ */
1764+/*********************************************
1765+ * RESET STYLES
1766+ *********************************************/
1767+html, body, .reveal div, .reveal span, .reveal applet, .reveal object, .reveal iframe,
1768+.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal p, .reveal blockquote, .reveal pre,
1769+.reveal a, .reveal abbr, .reveal acronym, .reveal address, .reveal big, .reveal cite, .reveal code,
1770+.reveal del, .reveal dfn, .reveal em, .reveal img, .reveal ins, .reveal kbd, .reveal q, .reveal s, .reveal samp,
1771+.reveal small, .reveal strike, .reveal strong, .reveal sub, .reveal sup, .reveal tt, .reveal var,
1772+.reveal b, .reveal u, .reveal center,
1773+.reveal dl, .reveal dt, .reveal dd, .reveal ol, .reveal ul, .reveal li,
1774+.reveal fieldset, .reveal form, .reveal label, .reveal legend,
1775+.reveal table, .reveal caption, .reveal tbody, .reveal tfoot, .reveal thead, .reveal tr, .reveal th, .reveal td,
1776+.reveal article, .reveal aside, .reveal canvas, .reveal details, .reveal embed,
1777+.reveal figure, .reveal figcaption, .reveal footer, .reveal header, .reveal hgroup,
1778+.reveal menu, .reveal nav, .reveal output, .reveal ruby, .reveal section, .reveal summary,
1779+.reveal time, .reveal mark, .reveal audio, .reveal video {
1780+ margin: 0;
1781+ padding: 0;
1782+ border: 0;
1783+ font-size: 100%;
1784+ font: inherit;
1785+ vertical-align: baseline; }
1786+
1787+.reveal article, .reveal aside, .reveal details, .reveal figcaption, .reveal figure,
1788+.reveal footer, .reveal header, .reveal hgroup, .reveal menu, .reveal nav, .reveal section {
1789+ display: block; }
1790+
1791+/*********************************************
1792+ * GLOBAL STYLES
1793+ *********************************************/
1794+html,
1795+body {
1796+ width: 100%;
1797+ height: 100%;
1798+ overflow: hidden; }
1799+
1800+body {
1801+ position: relative;
1802+ line-height: 1;
1803+ background-color: #fff;
1804+ color: #000; }
1805+
1806+/*********************************************
1807+ * VIEW FRAGMENTS
1808+ *********************************************/
1809+.reveal .slides section .fragment {
1810+ opacity: 0;
1811+ visibility: hidden;
1812+ transition: all .2s ease; }
1813+ .reveal .slides section .fragment.visible {
1814+ opacity: 1;
1815+ visibility: inherit; }
1816+
1817+.reveal .slides section .fragment.grow {
1818+ opacity: 1;
1819+ visibility: inherit; }
1820+ .reveal .slides section .fragment.grow.visible {
1821+ -webkit-transform: scale(1.3);
1822+ transform: scale(1.3); }
1823+
1824+.reveal .slides section .fragment.shrink {
1825+ opacity: 1;
1826+ visibility: inherit; }
1827+ .reveal .slides section .fragment.shrink.visible {
1828+ -webkit-transform: scale(0.7);
1829+ transform: scale(0.7); }
1830+
1831+.reveal .slides section .fragment.zoom-in {
1832+ -webkit-transform: scale(0.1);
1833+ transform: scale(0.1); }
1834+ .reveal .slides section .fragment.zoom-in.visible {
1835+ -webkit-transform: none;
1836+ transform: none; }
1837+
1838+.reveal .slides section .fragment.fade-out {
1839+ opacity: 1;
1840+ visibility: inherit; }
1841+ .reveal .slides section .fragment.fade-out.visible {
1842+ opacity: 0;
1843+ visibility: hidden; }
1844+
1845+.reveal .slides section .fragment.semi-fade-out {
1846+ opacity: 1;
1847+ visibility: inherit; }
1848+ .reveal .slides section .fragment.semi-fade-out.visible {
1849+ opacity: 0.5;
1850+ visibility: inherit; }
1851+
1852+.reveal .slides section .fragment.strike {
1853+ opacity: 1;
1854+ visibility: inherit; }
1855+ .reveal .slides section .fragment.strike.visible {
1856+ text-decoration: line-through; }
1857+
1858+.reveal .slides section .fragment.fade-up {
1859+ -webkit-transform: translate(0, 20%);
1860+ transform: translate(0, 20%); }
1861+ .reveal .slides section .fragment.fade-up.visible {
1862+ -webkit-transform: translate(0, 0);
1863+ transform: translate(0, 0); }
1864+
1865+.reveal .slides section .fragment.fade-down {
1866+ -webkit-transform: translate(0, -20%);
1867+ transform: translate(0, -20%); }
1868+ .reveal .slides section .fragment.fade-down.visible {
1869+ -webkit-transform: translate(0, 0);
1870+ transform: translate(0, 0); }
1871+
1872+.reveal .slides section .fragment.fade-right {
1873+ -webkit-transform: translate(-20%, 0);
1874+ transform: translate(-20%, 0); }
1875+ .reveal .slides section .fragment.fade-right.visible {
1876+ -webkit-transform: translate(0, 0);
1877+ transform: translate(0, 0); }
1878+
1879+.reveal .slides section .fragment.fade-left {
1880+ -webkit-transform: translate(20%, 0);
1881+ transform: translate(20%, 0); }
1882+ .reveal .slides section .fragment.fade-left.visible {
1883+ -webkit-transform: translate(0, 0);
1884+ transform: translate(0, 0); }
1885+
1886+.reveal .slides section .fragment.fade-in-then-out,
1887+.reveal .slides section .fragment.current-visible {
1888+ opacity: 0;
1889+ visibility: hidden; }
1890+ .reveal .slides section .fragment.fade-in-then-out.current-fragment,
1891+ .reveal .slides section .fragment.current-visible.current-fragment {
1892+ opacity: 1;
1893+ visibility: inherit; }
1894+
1895+.reveal .slides section .fragment.fade-in-then-semi-out {
1896+ opacity: 0;
1897+ visibility: hidden; }
1898+ .reveal .slides section .fragment.fade-in-then-semi-out.visible {
1899+ opacity: 0.5;
1900+ visibility: inherit; }
1901+ .reveal .slides section .fragment.fade-in-then-semi-out.current-fragment {
1902+ opacity: 1;
1903+ visibility: inherit; }
1904+
1905+.reveal .slides section .fragment.highlight-red,
1906+.reveal .slides section .fragment.highlight-current-red,
1907+.reveal .slides section .fragment.highlight-green,
1908+.reveal .slides section .fragment.highlight-current-green,
1909+.reveal .slides section .fragment.highlight-blue,
1910+.reveal .slides section .fragment.highlight-current-blue {
1911+ opacity: 1;
1912+ visibility: inherit; }
1913+
1914+.reveal .slides section .fragment.highlight-red.visible {
1915+ color: #ff2c2d; }
1916+
1917+.reveal .slides section .fragment.highlight-green.visible {
1918+ color: #17ff2e; }
1919+
1920+.reveal .slides section .fragment.highlight-blue.visible {
1921+ color: #1b91ff; }
1922+
1923+.reveal .slides section .fragment.highlight-current-red.current-fragment {
1924+ color: #ff2c2d; }
1925+
1926+.reveal .slides section .fragment.highlight-current-green.current-fragment {
1927+ color: #17ff2e; }
1928+
1929+.reveal .slides section .fragment.highlight-current-blue.current-fragment {
1930+ color: #1b91ff; }
1931+
1932+/*********************************************
1933+ * DEFAULT ELEMENT STYLES
1934+ *********************************************/
1935+/* Fixes issue in Chrome where italic fonts did not appear when printing to PDF */
1936+.reveal:after {
1937+ content: '';
1938+ font-style: italic; }
1939+
1940+.reveal iframe {
1941+ z-index: 1; }
1942+
1943+/** Prevents layering issues in certain browser/transition combinations */
1944+.reveal a {
1945+ position: relative; }
1946+
1947+.reveal .stretch {
1948+ max-width: none;
1949+ max-height: none; }
1950+
1951+.reveal pre.stretch code {
1952+ height: 100%;
1953+ max-height: 100%;
1954+ box-sizing: border-box; }
1955+
1956+/*********************************************
1957+ * CONTROLS
1958+ *********************************************/
1959+@-webkit-keyframes bounce-right {
1960+ 0%, 10%, 25%, 40%, 50% {
1961+ -webkit-transform: translateX(0);
1962+ transform: translateX(0); }
1963+ 20% {
1964+ -webkit-transform: translateX(10px);
1965+ transform: translateX(10px); }
1966+ 30% {
1967+ -webkit-transform: translateX(-5px);
1968+ transform: translateX(-5px); } }
1969+@keyframes bounce-right {
1970+ 0%, 10%, 25%, 40%, 50% {
1971+ -webkit-transform: translateX(0);
1972+ transform: translateX(0); }
1973+ 20% {
1974+ -webkit-transform: translateX(10px);
1975+ transform: translateX(10px); }
1976+ 30% {
1977+ -webkit-transform: translateX(-5px);
1978+ transform: translateX(-5px); } }
1979+
1980+@-webkit-keyframes bounce-down {
1981+ 0%, 10%, 25%, 40%, 50% {
1982+ -webkit-transform: translateY(0);
1983+ transform: translateY(0); }
1984+ 20% {
1985+ -webkit-transform: translateY(10px);
1986+ transform: translateY(10px); }
1987+ 30% {
1988+ -webkit-transform: translateY(-5px);
1989+ transform: translateY(-5px); } }
1990+
1991+@keyframes bounce-down {
1992+ 0%, 10%, 25%, 40%, 50% {
1993+ -webkit-transform: translateY(0);
1994+ transform: translateY(0); }
1995+ 20% {
1996+ -webkit-transform: translateY(10px);
1997+ transform: translateY(10px); }
1998+ 30% {
1999+ -webkit-transform: translateY(-5px);
2000+ transform: translateY(-5px); } }
2001+
2002+.reveal .controls {
2003+ display: none;
2004+ position: absolute;
2005+ top: auto;
2006+ bottom: 12px;
2007+ right: 12px;
2008+ left: auto;
2009+ z-index: 1;
2010+ color: #000;
2011+ pointer-events: none;
2012+ font-size: 10px; }
2013+ .reveal .controls button {
2014+ position: absolute;
2015+ padding: 0;
2016+ background-color: transparent;
2017+ border: 0;
2018+ outline: 0;
2019+ cursor: pointer;
2020+ color: currentColor;
2021+ -webkit-transform: scale(0.9999);
2022+ transform: scale(0.9999);
2023+ transition: color 0.2s ease, opacity 0.2s ease, -webkit-transform 0.2s ease;
2024+ transition: color 0.2s ease, opacity 0.2s ease, transform 0.2s ease;
2025+ z-index: 2;
2026+ pointer-events: auto;
2027+ font-size: inherit;
2028+ visibility: hidden;
2029+ opacity: 0;
2030+ -webkit-appearance: none;
2031+ -webkit-tap-highlight-color: transparent; }
2032+ .reveal .controls .controls-arrow:before,
2033+ .reveal .controls .controls-arrow:after {
2034+ content: '';
2035+ position: absolute;
2036+ top: 0;
2037+ left: 0;
2038+ width: 2.6em;
2039+ height: 0.5em;
2040+ border-radius: 0.25em;
2041+ background-color: currentColor;
2042+ transition: all 0.15s ease, background-color 0.8s ease;
2043+ -webkit-transform-origin: 0.2em 50%;
2044+ transform-origin: 0.2em 50%;
2045+ will-change: transform; }
2046+ .reveal .controls .controls-arrow {
2047+ position: relative;
2048+ width: 3.6em;
2049+ height: 3.6em; }
2050+ .reveal .controls .controls-arrow:before {
2051+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
2052+ transform: translateX(0.5em) translateY(1.55em) rotate(45deg); }
2053+ .reveal .controls .controls-arrow:after {
2054+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
2055+ transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); }
2056+ .reveal .controls .controls-arrow:hover:before {
2057+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(40deg);
2058+ transform: translateX(0.5em) translateY(1.55em) rotate(40deg); }
2059+ .reveal .controls .controls-arrow:hover:after {
2060+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-40deg);
2061+ transform: translateX(0.5em) translateY(1.55em) rotate(-40deg); }
2062+ .reveal .controls .controls-arrow:active:before {
2063+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(36deg);
2064+ transform: translateX(0.5em) translateY(1.55em) rotate(36deg); }
2065+ .reveal .controls .controls-arrow:active:after {
2066+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-36deg);
2067+ transform: translateX(0.5em) translateY(1.55em) rotate(-36deg); }
2068+ .reveal .controls .navigate-left {
2069+ right: 6.4em;
2070+ bottom: 3.2em;
2071+ -webkit-transform: translateX(-10px);
2072+ transform: translateX(-10px); }
2073+ .reveal .controls .navigate-right {
2074+ right: 0;
2075+ bottom: 3.2em;
2076+ -webkit-transform: translateX(10px);
2077+ transform: translateX(10px); }
2078+ .reveal .controls .navigate-right .controls-arrow {
2079+ -webkit-transform: rotate(180deg);
2080+ transform: rotate(180deg); }
2081+ .reveal .controls .navigate-right.highlight {
2082+ -webkit-animation: bounce-right 2s 50 both ease-out;
2083+ animation: bounce-right 2s 50 both ease-out; }
2084+ .reveal .controls .navigate-up {
2085+ right: 3.2em;
2086+ bottom: 6.4em;
2087+ -webkit-transform: translateY(-10px);
2088+ transform: translateY(-10px); }
2089+ .reveal .controls .navigate-up .controls-arrow {
2090+ -webkit-transform: rotate(90deg);
2091+ transform: rotate(90deg); }
2092+ .reveal .controls .navigate-down {
2093+ right: 3.2em;
2094+ bottom: 0;
2095+ -webkit-transform: translateY(10px);
2096+ transform: translateY(10px); }
2097+ .reveal .controls .navigate-down .controls-arrow {
2098+ -webkit-transform: rotate(-90deg);
2099+ transform: rotate(-90deg); }
2100+ .reveal .controls .navigate-down.highlight {
2101+ -webkit-animation: bounce-down 2s 50 both ease-out;
2102+ animation: bounce-down 2s 50 both ease-out; }
2103+ .reveal .controls[data-controls-back-arrows="faded"] .navigate-left.enabled,
2104+ .reveal .controls[data-controls-back-arrows="faded"] .navigate-up.enabled {
2105+ opacity: 0.3; }
2106+ .reveal .controls[data-controls-back-arrows="faded"] .navigate-left.enabled:hover,
2107+ .reveal .controls[data-controls-back-arrows="faded"] .navigate-up.enabled:hover {
2108+ opacity: 1; }
2109+ .reveal .controls[data-controls-back-arrows="hidden"] .navigate-left.enabled,
2110+ .reveal .controls[data-controls-back-arrows="hidden"] .navigate-up.enabled {
2111+ opacity: 0;
2112+ visibility: hidden; }
2113+ .reveal .controls .enabled {
2114+ visibility: visible;
2115+ opacity: 0.9;
2116+ cursor: pointer;
2117+ -webkit-transform: none;
2118+ transform: none; }
2119+ .reveal .controls .enabled.fragmented {
2120+ opacity: 0.5; }
2121+ .reveal .controls .enabled:hover,
2122+ .reveal .controls .enabled.fragmented:hover {
2123+ opacity: 1; }
2124+
2125+.reveal:not(.has-vertical-slides) .controls .navigate-left {
2126+ bottom: 1.4em;
2127+ right: 5.5em; }
2128+
2129+.reveal:not(.has-vertical-slides) .controls .navigate-right {
2130+ bottom: 1.4em;
2131+ right: 0.5em; }
2132+
2133+.reveal:not(.has-horizontal-slides) .controls .navigate-up {
2134+ right: 1.4em;
2135+ bottom: 5em; }
2136+
2137+.reveal:not(.has-horizontal-slides) .controls .navigate-down {
2138+ right: 1.4em;
2139+ bottom: 0.5em; }
2140+
2141+.reveal.has-dark-background .controls {
2142+ color: #fff; }
2143+
2144+.reveal.has-light-background .controls {
2145+ color: #000; }
2146+
2147+.reveal.no-hover .controls .controls-arrow:hover:before,
2148+.reveal.no-hover .controls .controls-arrow:active:before {
2149+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
2150+ transform: translateX(0.5em) translateY(1.55em) rotate(45deg); }
2151+
2152+.reveal.no-hover .controls .controls-arrow:hover:after,
2153+.reveal.no-hover .controls .controls-arrow:active:after {
2154+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
2155+ transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); }
2156+
2157+@media screen and (min-width: 500px) {
2158+ .reveal .controls[data-controls-layout="edges"] {
2159+ top: 0;
2160+ right: 0;
2161+ bottom: 0;
2162+ left: 0; }
2163+ .reveal .controls[data-controls-layout="edges"] .navigate-left,
2164+ .reveal .controls[data-controls-layout="edges"] .navigate-right,
2165+ .reveal .controls[data-controls-layout="edges"] .navigate-up,
2166+ .reveal .controls[data-controls-layout="edges"] .navigate-down {
2167+ bottom: auto;
2168+ right: auto; }
2169+ .reveal .controls[data-controls-layout="edges"] .navigate-left {
2170+ top: 50%;
2171+ left: 8px;
2172+ margin-top: -1.8em; }
2173+ .reveal .controls[data-controls-layout="edges"] .navigate-right {
2174+ top: 50%;
2175+ right: 8px;
2176+ margin-top: -1.8em; }
2177+ .reveal .controls[data-controls-layout="edges"] .navigate-up {
2178+ top: 8px;
2179+ left: 50%;
2180+ margin-left: -1.8em; }
2181+ .reveal .controls[data-controls-layout="edges"] .navigate-down {
2182+ bottom: 8px;
2183+ left: 50%;
2184+ margin-left: -1.8em; } }
2185+
2186+/*********************************************
2187+ * PROGRESS BAR
2188+ *********************************************/
2189+.reveal .progress {
2190+ position: absolute;
2191+ display: none;
2192+ height: 3px;
2193+ width: 100%;
2194+ bottom: 0;
2195+ left: 0;
2196+ z-index: 10;
2197+ background-color: rgba(0, 0, 0, 0.2);
2198+ color: #fff; }
2199+
2200+.reveal .progress:after {
2201+ content: '';
2202+ display: block;
2203+ position: absolute;
2204+ height: 10px;
2205+ width: 100%;
2206+ top: -10px; }
2207+
2208+.reveal .progress span {
2209+ display: block;
2210+ height: 100%;
2211+ width: 0px;
2212+ background-color: currentColor;
2213+ transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
2214+
2215+/*********************************************
2216+ * SLIDE NUMBER
2217+ *********************************************/
2218+.reveal .slide-number {
2219+ position: absolute;
2220+ display: block;
2221+ right: 8px;
2222+ bottom: 8px;
2223+ z-index: 31;
2224+ font-family: Helvetica, sans-serif;
2225+ font-size: 12px;
2226+ line-height: 1;
2227+ color: #fff;
2228+ background-color: rgba(0, 0, 0, 0.4);
2229+ padding: 5px; }
2230+
2231+.reveal .slide-number a {
2232+ color: currentColor; }
2233+
2234+.reveal .slide-number-delimiter {
2235+ margin: 0 3px; }
2236+
2237+/*********************************************
2238+ * SLIDES
2239+ *********************************************/
2240+.reveal {
2241+ position: relative;
2242+ width: 100%;
2243+ height: 100%;
2244+ overflow: hidden;
2245+ -ms-touch-action: none;
2246+ touch-action: none; }
2247+
2248+@media only screen and (orientation: landscape) {
2249+ .reveal.ua-iphone {
2250+ position: fixed; } }
2251+
2252+.reveal .slides {
2253+ position: absolute;
2254+ width: 100%;
2255+ height: 100%;
2256+ top: 0;
2257+ right: 0;
2258+ bottom: 0;
2259+ left: 0;
2260+ margin: auto;
2261+ pointer-events: none;
2262+ overflow: visible;
2263+ z-index: 1;
2264+ text-align: center;
2265+ -webkit-perspective: 600px;
2266+ perspective: 600px;
2267+ -webkit-perspective-origin: 50% 40%;
2268+ perspective-origin: 50% 40%; }
2269+
2270+.reveal .slides > section {
2271+ -ms-perspective: 600px; }
2272+
2273+.reveal .slides > section,
2274+.reveal .slides > section > section {
2275+ display: none;
2276+ position: absolute;
2277+ width: 100%;
2278+ padding: 20px 0px;
2279+ pointer-events: auto;
2280+ z-index: 10;
2281+ -webkit-transform-style: flat;
2282+ transform-style: flat;
2283+ transition: -webkit-transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), -webkit-transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
2284+ transition: transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
2285+
2286+/* Global transition speed settings */
2287+.reveal[data-transition-speed="fast"] .slides section {
2288+ transition-duration: 400ms; }
2289+
2290+.reveal[data-transition-speed="slow"] .slides section {
2291+ transition-duration: 1200ms; }
2292+
2293+/* Slide-specific transition speed overrides */
2294+.reveal .slides section[data-transition-speed="fast"] {
2295+ transition-duration: 400ms; }
2296+
2297+.reveal .slides section[data-transition-speed="slow"] {
2298+ transition-duration: 1200ms; }
2299+
2300+.reveal .slides > section.stack {
2301+ padding-top: 0;
2302+ padding-bottom: 0;
2303+ pointer-events: none; }
2304+
2305+.reveal .slides > section.present,
2306+.reveal .slides > section > section.present {
2307+ display: block;
2308+ z-index: 11;
2309+ opacity: 1; }
2310+
2311+.reveal .slides > section:empty,
2312+.reveal .slides > section > section:empty,
2313+.reveal .slides > section[data-background-interactive],
2314+.reveal .slides > section > section[data-background-interactive] {
2315+ pointer-events: none; }
2316+
2317+.reveal.center,
2318+.reveal.center .slides,
2319+.reveal.center .slides section {
2320+ min-height: 0 !important; }
2321+
2322+/* Don't allow interaction with invisible slides */
2323+.reveal .slides > section.future,
2324+.reveal .slides > section > section.future,
2325+.reveal .slides > section.past,
2326+.reveal .slides > section > section.past {
2327+ pointer-events: none; }
2328+
2329+.reveal.overview .slides > section,
2330+.reveal.overview .slides > section > section {
2331+ pointer-events: auto; }
2332+
2333+.reveal .slides > section.past,
2334+.reveal .slides > section.future,
2335+.reveal .slides > section > section.past,
2336+.reveal .slides > section > section.future {
2337+ opacity: 0; }
2338+
2339+/*********************************************
2340+ * Mixins for readability of transitions
2341+ *********************************************/
2342+/*********************************************
2343+ * SLIDE TRANSITION
2344+ * Aliased 'linear' for backwards compatibility
2345+ *********************************************/
2346+.reveal.slide section {
2347+ -webkit-backface-visibility: hidden;
2348+ backface-visibility: hidden; }
2349+
2350+.reveal .slides > section[data-transition=slide].past,
2351+.reveal .slides > section[data-transition~=slide-out].past,
2352+.reveal.slide .slides > section:not([data-transition]).past {
2353+ -webkit-transform: translate(-150%, 0);
2354+ transform: translate(-150%, 0); }
2355+
2356+.reveal .slides > section[data-transition=slide].future,
2357+.reveal .slides > section[data-transition~=slide-in].future,
2358+.reveal.slide .slides > section:not([data-transition]).future {
2359+ -webkit-transform: translate(150%, 0);
2360+ transform: translate(150%, 0); }
2361+
2362+.reveal .slides > section > section[data-transition=slide].past,
2363+.reveal .slides > section > section[data-transition~=slide-out].past,
2364+.reveal.slide .slides > section > section:not([data-transition]).past {
2365+ -webkit-transform: translate(0, -150%);
2366+ transform: translate(0, -150%); }
2367+
2368+.reveal .slides > section > section[data-transition=slide].future,
2369+.reveal .slides > section > section[data-transition~=slide-in].future,
2370+.reveal.slide .slides > section > section:not([data-transition]).future {
2371+ -webkit-transform: translate(0, 150%);
2372+ transform: translate(0, 150%); }
2373+
2374+.reveal.linear section {
2375+ -webkit-backface-visibility: hidden;
2376+ backface-visibility: hidden; }
2377+
2378+.reveal .slides > section[data-transition=linear].past,
2379+.reveal .slides > section[data-transition~=linear-out].past,
2380+.reveal.linear .slides > section:not([data-transition]).past {
2381+ -webkit-transform: translate(-150%, 0);
2382+ transform: translate(-150%, 0); }
2383+
2384+.reveal .slides > section[data-transition=linear].future,
2385+.reveal .slides > section[data-transition~=linear-in].future,
2386+.reveal.linear .slides > section:not([data-transition]).future {
2387+ -webkit-transform: translate(150%, 0);
2388+ transform: translate(150%, 0); }
2389+
2390+.reveal .slides > section > section[data-transition=linear].past,
2391+.reveal .slides > section > section[data-transition~=linear-out].past,
2392+.reveal.linear .slides > section > section:not([data-transition]).past {
2393+ -webkit-transform: translate(0, -150%);
2394+ transform: translate(0, -150%); }
2395+
2396+.reveal .slides > section > section[data-transition=linear].future,
2397+.reveal .slides > section > section[data-transition~=linear-in].future,
2398+.reveal.linear .slides > section > section:not([data-transition]).future {
2399+ -webkit-transform: translate(0, 150%);
2400+ transform: translate(0, 150%); }
2401+
2402+/*********************************************
2403+ * CONVEX TRANSITION
2404+ * Aliased 'default' for backwards compatibility
2405+ *********************************************/
2406+.reveal .slides section[data-transition=default].stack,
2407+.reveal.default .slides section.stack {
2408+ -webkit-transform-style: preserve-3d;
2409+ transform-style: preserve-3d; }
2410+
2411+.reveal .slides > section[data-transition=default].past,
2412+.reveal .slides > section[data-transition~=default-out].past,
2413+.reveal.default .slides > section:not([data-transition]).past {
2414+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
2415+ transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
2416+
2417+.reveal .slides > section[data-transition=default].future,
2418+.reveal .slides > section[data-transition~=default-in].future,
2419+.reveal.default .slides > section:not([data-transition]).future {
2420+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
2421+ transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
2422+
2423+.reveal .slides > section > section[data-transition=default].past,
2424+.reveal .slides > section > section[data-transition~=default-out].past,
2425+.reveal.default .slides > section > section:not([data-transition]).past {
2426+ -webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
2427+ transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0); }
2428+
2429+.reveal .slides > section > section[data-transition=default].future,
2430+.reveal .slides > section > section[data-transition~=default-in].future,
2431+.reveal.default .slides > section > section:not([data-transition]).future {
2432+ -webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
2433+ transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0); }
2434+
2435+.reveal .slides section[data-transition=convex].stack,
2436+.reveal.convex .slides section.stack {
2437+ -webkit-transform-style: preserve-3d;
2438+ transform-style: preserve-3d; }
2439+
2440+.reveal .slides > section[data-transition=convex].past,
2441+.reveal .slides > section[data-transition~=convex-out].past,
2442+.reveal.convex .slides > section:not([data-transition]).past {
2443+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
2444+ transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
2445+
2446+.reveal .slides > section[data-transition=convex].future,
2447+.reveal .slides > section[data-transition~=convex-in].future,
2448+.reveal.convex .slides > section:not([data-transition]).future {
2449+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
2450+ transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
2451+
2452+.reveal .slides > section > section[data-transition=convex].past,
2453+.reveal .slides > section > section[data-transition~=convex-out].past,
2454+.reveal.convex .slides > section > section:not([data-transition]).past {
2455+ -webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
2456+ transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0); }
2457+
2458+.reveal .slides > section > section[data-transition=convex].future,
2459+.reveal .slides > section > section[data-transition~=convex-in].future,
2460+.reveal.convex .slides > section > section:not([data-transition]).future {
2461+ -webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
2462+ transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0); }
2463+
2464+/*********************************************
2465+ * CONCAVE TRANSITION
2466+ *********************************************/
2467+.reveal .slides section[data-transition=concave].stack,
2468+.reveal.concave .slides section.stack {
2469+ -webkit-transform-style: preserve-3d;
2470+ transform-style: preserve-3d; }
2471+
2472+.reveal .slides > section[data-transition=concave].past,
2473+.reveal .slides > section[data-transition~=concave-out].past,
2474+.reveal.concave .slides > section:not([data-transition]).past {
2475+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
2476+ transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); }
2477+
2478+.reveal .slides > section[data-transition=concave].future,
2479+.reveal .slides > section[data-transition~=concave-in].future,
2480+.reveal.concave .slides > section:not([data-transition]).future {
2481+ -webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
2482+ transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); }
2483+
2484+.reveal .slides > section > section[data-transition=concave].past,
2485+.reveal .slides > section > section[data-transition~=concave-out].past,
2486+.reveal.concave .slides > section > section:not([data-transition]).past {
2487+ -webkit-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
2488+ transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0); }
2489+
2490+.reveal .slides > section > section[data-transition=concave].future,
2491+.reveal .slides > section > section[data-transition~=concave-in].future,
2492+.reveal.concave .slides > section > section:not([data-transition]).future {
2493+ -webkit-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
2494+ transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0); }
2495+
2496+/*********************************************
2497+ * ZOOM TRANSITION
2498+ *********************************************/
2499+.reveal .slides section[data-transition=zoom],
2500+.reveal.zoom .slides section:not([data-transition]) {
2501+ transition-timing-function: ease; }
2502+
2503+.reveal .slides > section[data-transition=zoom].past,
2504+.reveal .slides > section[data-transition~=zoom-out].past,
2505+.reveal.zoom .slides > section:not([data-transition]).past {
2506+ visibility: hidden;
2507+ -webkit-transform: scale(16);
2508+ transform: scale(16); }
2509+
2510+.reveal .slides > section[data-transition=zoom].future,
2511+.reveal .slides > section[data-transition~=zoom-in].future,
2512+.reveal.zoom .slides > section:not([data-transition]).future {
2513+ visibility: hidden;
2514+ -webkit-transform: scale(0.2);
2515+ transform: scale(0.2); }
2516+
2517+.reveal .slides > section > section[data-transition=zoom].past,
2518+.reveal .slides > section > section[data-transition~=zoom-out].past,
2519+.reveal.zoom .slides > section > section:not([data-transition]).past {
2520+ -webkit-transform: translate(0, -150%);
2521+ transform: translate(0, -150%); }
2522+
2523+.reveal .slides > section > section[data-transition=zoom].future,
2524+.reveal .slides > section > section[data-transition~=zoom-in].future,
2525+.reveal.zoom .slides > section > section:not([data-transition]).future {
2526+ -webkit-transform: translate(0, 150%);
2527+ transform: translate(0, 150%); }
2528+
2529+/*********************************************
2530+ * CUBE TRANSITION
2531+ *
2532+ * WARNING:
2533+ * this is deprecated and will be removed in a
2534+ * future version.
2535+ *********************************************/
2536+.reveal.cube .slides {
2537+ -webkit-perspective: 1300px;
2538+ perspective: 1300px; }
2539+
2540+.reveal.cube .slides section {
2541+ padding: 30px;
2542+ min-height: 700px;
2543+ -webkit-backface-visibility: hidden;
2544+ backface-visibility: hidden;
2545+ box-sizing: border-box;
2546+ -webkit-transform-style: preserve-3d;
2547+ transform-style: preserve-3d; }
2548+
2549+.reveal.center.cube .slides section {
2550+ min-height: 0; }
2551+
2552+.reveal.cube .slides section:not(.stack):before {
2553+ content: '';
2554+ position: absolute;
2555+ display: block;
2556+ width: 100%;
2557+ height: 100%;
2558+ left: 0;
2559+ top: 0;
2560+ background: rgba(0, 0, 0, 0.1);
2561+ border-radius: 4px;
2562+ -webkit-transform: translateZ(-20px);
2563+ transform: translateZ(-20px); }
2564+
2565+.reveal.cube .slides section:not(.stack):after {
2566+ content: '';
2567+ position: absolute;
2568+ display: block;
2569+ width: 90%;
2570+ height: 30px;
2571+ left: 5%;
2572+ bottom: 0;
2573+ background: none;
2574+ z-index: 1;
2575+ border-radius: 4px;
2576+ box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
2577+ -webkit-transform: translateZ(-90px) rotateX(65deg);
2578+ transform: translateZ(-90px) rotateX(65deg); }
2579+
2580+.reveal.cube .slides > section.stack {
2581+ padding: 0;
2582+ background: none; }
2583+
2584+.reveal.cube .slides > section.past {
2585+ -webkit-transform-origin: 100% 0%;
2586+ transform-origin: 100% 0%;
2587+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
2588+ transform: translate3d(-100%, 0, 0) rotateY(-90deg); }
2589+
2590+.reveal.cube .slides > section.future {
2591+ -webkit-transform-origin: 0% 0%;
2592+ transform-origin: 0% 0%;
2593+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg);
2594+ transform: translate3d(100%, 0, 0) rotateY(90deg); }
2595+
2596+.reveal.cube .slides > section > section.past {
2597+ -webkit-transform-origin: 0% 100%;
2598+ transform-origin: 0% 100%;
2599+ -webkit-transform: translate3d(0, -100%, 0) rotateX(90deg);
2600+ transform: translate3d(0, -100%, 0) rotateX(90deg); }
2601+
2602+.reveal.cube .slides > section > section.future {
2603+ -webkit-transform-origin: 0% 0%;
2604+ transform-origin: 0% 0%;
2605+ -webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg);
2606+ transform: translate3d(0, 100%, 0) rotateX(-90deg); }
2607+
2608+/*********************************************
2609+ * PAGE TRANSITION
2610+ *
2611+ * WARNING:
2612+ * this is deprecated and will be removed in a
2613+ * future version.
2614+ *********************************************/
2615+.reveal.page .slides {
2616+ -webkit-perspective-origin: 0% 50%;
2617+ perspective-origin: 0% 50%;
2618+ -webkit-perspective: 3000px;
2619+ perspective: 3000px; }
2620+
2621+.reveal.page .slides section {
2622+ padding: 30px;
2623+ min-height: 700px;
2624+ box-sizing: border-box;
2625+ -webkit-transform-style: preserve-3d;
2626+ transform-style: preserve-3d; }
2627+
2628+.reveal.page .slides section.past {
2629+ z-index: 12; }
2630+
2631+.reveal.page .slides section:not(.stack):before {
2632+ content: '';
2633+ position: absolute;
2634+ display: block;
2635+ width: 100%;
2636+ height: 100%;
2637+ left: 0;
2638+ top: 0;
2639+ background: rgba(0, 0, 0, 0.1);
2640+ -webkit-transform: translateZ(-20px);
2641+ transform: translateZ(-20px); }
2642+
2643+.reveal.page .slides section:not(.stack):after {
2644+ content: '';
2645+ position: absolute;
2646+ display: block;
2647+ width: 90%;
2648+ height: 30px;
2649+ left: 5%;
2650+ bottom: 0;
2651+ background: none;
2652+ z-index: 1;
2653+ border-radius: 4px;
2654+ box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
2655+ -webkit-transform: translateZ(-90px) rotateX(65deg); }
2656+
2657+.reveal.page .slides > section.stack {
2658+ padding: 0;
2659+ background: none; }
2660+
2661+.reveal.page .slides > section.past {
2662+ -webkit-transform-origin: 0% 0%;
2663+ transform-origin: 0% 0%;
2664+ -webkit-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
2665+ transform: translate3d(-40%, 0, 0) rotateY(-80deg); }
2666+
2667+.reveal.page .slides > section.future {
2668+ -webkit-transform-origin: 100% 0%;
2669+ transform-origin: 100% 0%;
2670+ -webkit-transform: translate3d(0, 0, 0);
2671+ transform: translate3d(0, 0, 0); }
2672+
2673+.reveal.page .slides > section > section.past {
2674+ -webkit-transform-origin: 0% 0%;
2675+ transform-origin: 0% 0%;
2676+ -webkit-transform: translate3d(0, -40%, 0) rotateX(80deg);
2677+ transform: translate3d(0, -40%, 0) rotateX(80deg); }
2678+
2679+.reveal.page .slides > section > section.future {
2680+ -webkit-transform-origin: 0% 100%;
2681+ transform-origin: 0% 100%;
2682+ -webkit-transform: translate3d(0, 0, 0);
2683+ transform: translate3d(0, 0, 0); }
2684+
2685+/*********************************************
2686+ * FADE TRANSITION
2687+ *********************************************/
2688+.reveal .slides section[data-transition=fade],
2689+.reveal.fade .slides section:not([data-transition]),
2690+.reveal.fade .slides > section > section:not([data-transition]) {
2691+ -webkit-transform: none;
2692+ transform: none;
2693+ transition: opacity 0.5s; }
2694+
2695+.reveal.fade.overview .slides section,
2696+.reveal.fade.overview .slides > section > section {
2697+ transition: none; }
2698+
2699+/*********************************************
2700+ * NO TRANSITION
2701+ *********************************************/
2702+.reveal .slides section[data-transition=none],
2703+.reveal.none .slides section:not([data-transition]) {
2704+ -webkit-transform: none;
2705+ transform: none;
2706+ transition: none; }
2707+
2708+/*********************************************
2709+ * PAUSED MODE
2710+ *********************************************/
2711+.reveal .pause-overlay {
2712+ position: absolute;
2713+ top: 0;
2714+ left: 0;
2715+ width: 100%;
2716+ height: 100%;
2717+ background: black;
2718+ visibility: hidden;
2719+ opacity: 0;
2720+ z-index: 100;
2721+ transition: all 1s ease; }
2722+
2723+.reveal .pause-overlay .resume-button {
2724+ position: absolute;
2725+ bottom: 20px;
2726+ right: 20px;
2727+ color: #ccc;
2728+ border-radius: 2px;
2729+ padding: 6px 14px;
2730+ border: 2px solid #ccc;
2731+ font-size: 16px;
2732+ background: transparent;
2733+ cursor: pointer; }
2734+ .reveal .pause-overlay .resume-button:hover {
2735+ color: #fff;
2736+ border-color: #fff; }
2737+
2738+.reveal.paused .pause-overlay {
2739+ visibility: visible;
2740+ opacity: 1; }
2741+
2742+/*********************************************
2743+ * FALLBACK
2744+ *********************************************/
2745+.no-transforms {
2746+ overflow-y: auto; }
2747+
2748+.no-transforms .reveal .slides {
2749+ position: relative;
2750+ width: 80%;
2751+ height: auto !important;
2752+ top: 0;
2753+ left: 50%;
2754+ margin: 0;
2755+ text-align: center; }
2756+
2757+.no-transforms .reveal .controls,
2758+.no-transforms .reveal .progress {
2759+ display: none !important; }
2760+
2761+.no-transforms .reveal .slides section {
2762+ display: block !important;
2763+ opacity: 1 !important;
2764+ position: relative !important;
2765+ height: auto;
2766+ min-height: 0;
2767+ top: 0;
2768+ left: -50%;
2769+ margin: 70px 0;
2770+ -webkit-transform: none;
2771+ transform: none; }
2772+
2773+.no-transforms .reveal .slides section section {
2774+ left: 0; }
2775+
2776+.reveal .no-transition,
2777+.reveal .no-transition * {
2778+ transition: none !important; }
2779+
2780+/*********************************************
2781+ * PER-SLIDE BACKGROUNDS
2782+ *********************************************/
2783+.reveal .backgrounds {
2784+ position: absolute;
2785+ width: 100%;
2786+ height: 100%;
2787+ top: 0;
2788+ left: 0;
2789+ -webkit-perspective: 600px;
2790+ perspective: 600px; }
2791+
2792+.reveal .slide-background {
2793+ display: none;
2794+ position: absolute;
2795+ width: 100%;
2796+ height: 100%;
2797+ opacity: 0;
2798+ visibility: hidden;
2799+ overflow: hidden;
2800+ background-color: transparent;
2801+ transition: all 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
2802+
2803+.reveal .slide-background-content {
2804+ position: absolute;
2805+ width: 100%;
2806+ height: 100%;
2807+ background-position: 50% 50%;
2808+ background-repeat: no-repeat;
2809+ background-size: cover; }
2810+
2811+.reveal .slide-background.stack {
2812+ display: block; }
2813+
2814+.reveal .slide-background.present {
2815+ opacity: 1;
2816+ visibility: visible;
2817+ z-index: 2; }
2818+
2819+.print-pdf .reveal .slide-background {
2820+ opacity: 1 !important;
2821+ visibility: visible !important; }
2822+
2823+/* Video backgrounds */
2824+.reveal .slide-background video {
2825+ position: absolute;
2826+ width: 100%;
2827+ height: 100%;
2828+ max-width: none;
2829+ max-height: none;
2830+ top: 0;
2831+ left: 0;
2832+ -o-object-fit: cover;
2833+ object-fit: cover; }
2834+
2835+.reveal .slide-background[data-background-size="contain"] video {
2836+ -o-object-fit: contain;
2837+ object-fit: contain; }
2838+
2839+/* Immediate transition style */
2840+.reveal[data-background-transition=none] > .backgrounds .slide-background,
2841+.reveal > .backgrounds .slide-background[data-background-transition=none] {
2842+ transition: none; }
2843+
2844+/* Slide */
2845+.reveal[data-background-transition=slide] > .backgrounds .slide-background,
2846+.reveal > .backgrounds .slide-background[data-background-transition=slide] {
2847+ opacity: 1;
2848+ -webkit-backface-visibility: hidden;
2849+ backface-visibility: hidden; }
2850+
2851+.reveal[data-background-transition=slide] > .backgrounds .slide-background.past,
2852+.reveal > .backgrounds .slide-background.past[data-background-transition=slide] {
2853+ -webkit-transform: translate(-100%, 0);
2854+ transform: translate(-100%, 0); }
2855+
2856+.reveal[data-background-transition=slide] > .backgrounds .slide-background.future,
2857+.reveal > .backgrounds .slide-background.future[data-background-transition=slide] {
2858+ -webkit-transform: translate(100%, 0);
2859+ transform: translate(100%, 0); }
2860+
2861+.reveal[data-background-transition=slide] > .backgrounds .slide-background > .slide-background.past,
2862+.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=slide] {
2863+ -webkit-transform: translate(0, -100%);
2864+ transform: translate(0, -100%); }
2865+
2866+.reveal[data-background-transition=slide] > .backgrounds .slide-background > .slide-background.future,
2867+.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=slide] {
2868+ -webkit-transform: translate(0, 100%);
2869+ transform: translate(0, 100%); }
2870+
2871+/* Convex */
2872+.reveal[data-background-transition=convex] > .backgrounds .slide-background.past,
2873+.reveal > .backgrounds .slide-background.past[data-background-transition=convex] {
2874+ opacity: 0;
2875+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
2876+ transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
2877+
2878+.reveal[data-background-transition=convex] > .backgrounds .slide-background.future,
2879+.reveal > .backgrounds .slide-background.future[data-background-transition=convex] {
2880+ opacity: 0;
2881+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
2882+ transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
2883+
2884+.reveal[data-background-transition=convex] > .backgrounds .slide-background > .slide-background.past,
2885+.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=convex] {
2886+ opacity: 0;
2887+ -webkit-transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0);
2888+ transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0); }
2889+
2890+.reveal[data-background-transition=convex] > .backgrounds .slide-background > .slide-background.future,
2891+.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=convex] {
2892+ opacity: 0;
2893+ -webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0);
2894+ transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0); }
2895+
2896+/* Concave */
2897+.reveal[data-background-transition=concave] > .backgrounds .slide-background.past,
2898+.reveal > .backgrounds .slide-background.past[data-background-transition=concave] {
2899+ opacity: 0;
2900+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
2901+ transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); }
2902+
2903+.reveal[data-background-transition=concave] > .backgrounds .slide-background.future,
2904+.reveal > .backgrounds .slide-background.future[data-background-transition=concave] {
2905+ opacity: 0;
2906+ -webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
2907+ transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); }
2908+
2909+.reveal[data-background-transition=concave] > .backgrounds .slide-background > .slide-background.past,
2910+.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=concave] {
2911+ opacity: 0;
2912+ -webkit-transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0);
2913+ transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0); }
2914+
2915+.reveal[data-background-transition=concave] > .backgrounds .slide-background > .slide-background.future,
2916+.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=concave] {
2917+ opacity: 0;
2918+ -webkit-transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0);
2919+ transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0); }
2920+
2921+/* Zoom */
2922+.reveal[data-background-transition=zoom] > .backgrounds .slide-background,
2923+.reveal > .backgrounds .slide-background[data-background-transition=zoom] {
2924+ transition-timing-function: ease; }
2925+
2926+.reveal[data-background-transition=zoom] > .backgrounds .slide-background.past,
2927+.reveal > .backgrounds .slide-background.past[data-background-transition=zoom] {
2928+ opacity: 0;
2929+ visibility: hidden;
2930+ -webkit-transform: scale(16);
2931+ transform: scale(16); }
2932+
2933+.reveal[data-background-transition=zoom] > .backgrounds .slide-background.future,
2934+.reveal > .backgrounds .slide-background.future[data-background-transition=zoom] {
2935+ opacity: 0;
2936+ visibility: hidden;
2937+ -webkit-transform: scale(0.2);
2938+ transform: scale(0.2); }
2939+
2940+.reveal[data-background-transition=zoom] > .backgrounds .slide-background > .slide-background.past,
2941+.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=zoom] {
2942+ opacity: 0;
2943+ visibility: hidden;
2944+ -webkit-transform: scale(16);
2945+ transform: scale(16); }
2946+
2947+.reveal[data-background-transition=zoom] > .backgrounds .slide-background > .slide-background.future,
2948+.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=zoom] {
2949+ opacity: 0;
2950+ visibility: hidden;
2951+ -webkit-transform: scale(0.2);
2952+ transform: scale(0.2); }
2953+
2954+/* Global transition speed settings */
2955+.reveal[data-transition-speed="fast"] > .backgrounds .slide-background {
2956+ transition-duration: 400ms; }
2957+
2958+.reveal[data-transition-speed="slow"] > .backgrounds .slide-background {
2959+ transition-duration: 1200ms; }
2960+
2961+/*********************************************
2962+ * OVERVIEW
2963+ *********************************************/
2964+.reveal.overview {
2965+ -webkit-perspective-origin: 50% 50%;
2966+ perspective-origin: 50% 50%;
2967+ -webkit-perspective: 700px;
2968+ perspective: 700px; }
2969+ .reveal.overview .slides {
2970+ -moz-transform-style: preserve-3d; }
2971+ .reveal.overview .slides section {
2972+ height: 100%;
2973+ top: 0 !important;
2974+ opacity: 1 !important;
2975+ overflow: hidden;
2976+ visibility: visible !important;
2977+ cursor: pointer;
2978+ box-sizing: border-box; }
2979+ .reveal.overview .slides section:hover,
2980+ .reveal.overview .slides section.present {
2981+ outline: 10px solid rgba(150, 150, 150, 0.4);
2982+ outline-offset: 10px; }
2983+ .reveal.overview .slides section .fragment {
2984+ opacity: 1;
2985+ transition: none; }
2986+ .reveal.overview .slides section:after,
2987+ .reveal.overview .slides section:before {
2988+ display: none !important; }
2989+ .reveal.overview .slides > section.stack {
2990+ padding: 0;
2991+ top: 0 !important;
2992+ background: none;
2993+ outline: none;
2994+ overflow: visible; }
2995+ .reveal.overview .backgrounds {
2996+ -webkit-perspective: inherit;
2997+ perspective: inherit;
2998+ -moz-transform-style: preserve-3d; }
2999+ .reveal.overview .backgrounds .slide-background {
3000+ opacity: 1;
3001+ visibility: visible;
3002+ outline: 10px solid rgba(150, 150, 150, 0.1);
3003+ outline-offset: 10px; }
3004+ .reveal.overview .backgrounds .slide-background.stack {
3005+ overflow: visible; }
3006+
3007+.reveal.overview .slides section,
3008+.reveal.overview-deactivating .slides section {
3009+ transition: none; }
3010+
3011+.reveal.overview .backgrounds .slide-background,
3012+.reveal.overview-deactivating .backgrounds .slide-background {
3013+ transition: none; }
3014+
3015+/*********************************************
3016+ * RTL SUPPORT
3017+ *********************************************/
3018+.reveal.rtl .slides,
3019+.reveal.rtl .slides h1,
3020+.reveal.rtl .slides h2,
3021+.reveal.rtl .slides h3,
3022+.reveal.rtl .slides h4,
3023+.reveal.rtl .slides h5,
3024+.reveal.rtl .slides h6 {
3025+ direction: rtl;
3026+ font-family: sans-serif; }
3027+
3028+.reveal.rtl pre,
3029+.reveal.rtl code {
3030+ direction: ltr; }
3031+
3032+.reveal.rtl ol,
3033+.reveal.rtl ul {
3034+ text-align: right; }
3035+
3036+.reveal.rtl .progress span {
3037+ float: right; }
3038+
3039+/*********************************************
3040+ * PARALLAX BACKGROUND
3041+ *********************************************/
3042+.reveal.has-parallax-background .backgrounds {
3043+ transition: all 0.8s ease; }
3044+
3045+/* Global transition speed settings */
3046+.reveal.has-parallax-background[data-transition-speed="fast"] .backgrounds {
3047+ transition-duration: 400ms; }
3048+
3049+.reveal.has-parallax-background[data-transition-speed="slow"] .backgrounds {
3050+ transition-duration: 1200ms; }
3051+
3052+/*********************************************
3053+ * LINK PREVIEW OVERLAY
3054+ *********************************************/
3055+.reveal .overlay {
3056+ position: absolute;
3057+ top: 0;
3058+ left: 0;
3059+ width: 100%;
3060+ height: 100%;
3061+ z-index: 1000;
3062+ background: rgba(0, 0, 0, 0.9);
3063+ opacity: 0;
3064+ visibility: hidden;
3065+ transition: all 0.3s ease; }
3066+
3067+.reveal .overlay.visible {
3068+ opacity: 1;
3069+ visibility: visible; }
3070+
3071+.reveal .overlay .spinner {
3072+ position: absolute;
3073+ display: block;
3074+ top: 50%;
3075+ left: 50%;
3076+ width: 32px;
3077+ height: 32px;
3078+ margin: -16px 0 0 -16px;
3079+ z-index: 10;
3080+ 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);
3081+ visibility: visible;
3082+ opacity: 0.6;
3083+ transition: all 0.3s ease; }
3084+
3085+.reveal .overlay header {
3086+ position: absolute;
3087+ left: 0;
3088+ top: 0;
3089+ width: 100%;
3090+ height: 40px;
3091+ z-index: 2;
3092+ border-bottom: 1px solid #222; }
3093+
3094+.reveal .overlay header a {
3095+ display: inline-block;
3096+ width: 40px;
3097+ height: 40px;
3098+ line-height: 36px;
3099+ padding: 0 10px;
3100+ float: right;
3101+ opacity: 0.6;
3102+ box-sizing: border-box; }
3103+
3104+.reveal .overlay header a:hover {
3105+ opacity: 1; }
3106+
3107+.reveal .overlay header a .icon {
3108+ display: inline-block;
3109+ width: 20px;
3110+ height: 20px;
3111+ background-position: 50% 50%;
3112+ background-size: 100%;
3113+ background-repeat: no-repeat; }
3114+
3115+.reveal .overlay header a.close .icon {
3116+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABkklEQVRYR8WX4VHDMAxG6wnoJrABZQPYBCaBTWAD2g1gE5gg6OOsXuxIlr40d81dfrSJ9V4c2VLK7spHuTJ/5wpM07QXuXc5X0opX2tEJcadjHuV80li/FgxTIEK/5QBCICBD6xEhSMGHgQPgBgLiYVAB1dpSqKDawxTohFw4JSEA3clzgIBPCURwE2JucBR7rhPJJv5OpJwDX+SfDjgx1wACQeJG1aChP9K/IMmdZ8DtESV1WyP3Bt4MwM6sj4NMxMYiqUWHQu4KYA/SYkIjOsm3BXYWMKFDwU2khjCQ4ELJUJ4SmClRArOCmSXGuKma0fYD5CbzHxFpCSGAhfAVSSUGDUk2BWZaff2g6GE15BsBQ9nwmpIGDiyHQddwNTMKkbZaf9fajXQca1EX44puJZUsnY0ObGmITE3GVLCbEhQUjGVt146j6oasWN+49Vph2w1pZ5EansNZqKBm1txbU57iRRcZ86RWMDdWtBJUHBHwoQPi1GV+JCbntmvok7iTX4/Up9mgyTc/FJYDTcndgH/AA5A/CHsyEkVAAAAAElFTkSuQmCC); }
3117+
3118+.reveal .overlay header a.external .icon {
3119+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAcElEQVRYR+2WSQoAIQwEzf8f7XiOMkUQxUPlGkM3hVmiQfQR9GYnH1SsAQlI4DiBqkCMoNb9y2e90IAEJPAcgdznU9+engMaeJ7Azh5Y1U67gAho4DqBqmB1buAf0MB1AlVBek83ZPkmJMGc1wAR+AAqod/B97TRpQAAAABJRU5ErkJggg==); }
3120+
3121+.reveal .overlay .viewport {
3122+ position: absolute;
3123+ display: -webkit-box;
3124+ display: -ms-flexbox;
3125+ display: flex;
3126+ top: 40px;
3127+ right: 0;
3128+ bottom: 0;
3129+ left: 0; }
3130+
3131+.reveal .overlay.overlay-preview .viewport iframe {
3132+ width: 100%;
3133+ height: 100%;
3134+ max-width: 100%;
3135+ max-height: 100%;
3136+ border: 0;
3137+ opacity: 0;
3138+ visibility: hidden;
3139+ transition: all 0.3s ease; }
3140+
3141+.reveal .overlay.overlay-preview.loaded .viewport iframe {
3142+ opacity: 1;
3143+ visibility: visible; }
3144+
3145+.reveal .overlay.overlay-preview.loaded .viewport-inner {
3146+ position: absolute;
3147+ z-index: -1;
3148+ left: 0;
3149+ top: 45%;
3150+ width: 100%;
3151+ text-align: center;
3152+ letter-spacing: normal; }
3153+
3154+.reveal .overlay.overlay-preview .x-frame-error {
3155+ opacity: 0;
3156+ transition: opacity 0.3s ease 0.3s; }
3157+
3158+.reveal .overlay.overlay-preview.loaded .x-frame-error {
3159+ opacity: 1; }
3160+
3161+.reveal .overlay.overlay-preview.loaded .spinner {
3162+ opacity: 0;
3163+ visibility: hidden;
3164+ -webkit-transform: scale(0.2);
3165+ transform: scale(0.2); }
3166+
3167+.reveal .overlay.overlay-help .viewport {
3168+ overflow: auto;
3169+ color: #fff; }
3170+
3171+.reveal .overlay.overlay-help .viewport .viewport-inner {
3172+ width: 600px;
3173+ margin: auto;
3174+ padding: 20px 20px 80px 20px;
3175+ text-align: center;
3176+ letter-spacing: normal; }
3177+
3178+.reveal .overlay.overlay-help .viewport .viewport-inner .title {
3179+ font-size: 20px; }
3180+
3181+.reveal .overlay.overlay-help .viewport .viewport-inner table {
3182+ border: 1px solid #fff;
3183+ border-collapse: collapse;
3184+ font-size: 16px; }
3185+
3186+.reveal .overlay.overlay-help .viewport .viewport-inner table th,
3187+.reveal .overlay.overlay-help .viewport .viewport-inner table td {
3188+ width: 200px;
3189+ padding: 14px;
3190+ border: 1px solid #fff;
3191+ vertical-align: middle; }
3192+
3193+.reveal .overlay.overlay-help .viewport .viewport-inner table th {
3194+ padding-top: 20px;
3195+ padding-bottom: 20px; }
3196+
3197+/*********************************************
3198+ * PLAYBACK COMPONENT
3199+ *********************************************/
3200+.reveal .playback {
3201+ position: absolute;
3202+ left: 15px;
3203+ bottom: 20px;
3204+ z-index: 30;
3205+ cursor: pointer;
3206+ transition: all 400ms ease;
3207+ -webkit-tap-highlight-color: transparent; }
3208+
3209+.reveal.overview .playback {
3210+ opacity: 0;
3211+ visibility: hidden; }
3212+
3213+/*********************************************
3214+ * ROLLING LINKS
3215+ *********************************************/
3216+.reveal .roll {
3217+ display: inline-block;
3218+ line-height: 1.2;
3219+ overflow: hidden;
3220+ vertical-align: top;
3221+ -webkit-perspective: 400px;
3222+ perspective: 400px;
3223+ -webkit-perspective-origin: 50% 50%;
3224+ perspective-origin: 50% 50%; }
3225+
3226+.reveal .roll:hover {
3227+ background: none;
3228+ text-shadow: none; }
3229+
3230+.reveal .roll span {
3231+ display: block;
3232+ position: relative;
3233+ padding: 0 2px;
3234+ pointer-events: none;
3235+ transition: all 400ms ease;
3236+ -webkit-transform-origin: 50% 0%;
3237+ transform-origin: 50% 0%;
3238+ -webkit-transform-style: preserve-3d;
3239+ transform-style: preserve-3d;
3240+ -webkit-backface-visibility: hidden;
3241+ backface-visibility: hidden; }
3242+
3243+.reveal .roll:hover span {
3244+ background: rgba(0, 0, 0, 0.5);
3245+ -webkit-transform: translate3d(0px, 0px, -45px) rotateX(90deg);
3246+ transform: translate3d(0px, 0px, -45px) rotateX(90deg); }
3247+
3248+.reveal .roll span:after {
3249+ content: attr(data-title);
3250+ display: block;
3251+ position: absolute;
3252+ left: 0;
3253+ top: 0;
3254+ padding: 0 2px;
3255+ -webkit-backface-visibility: hidden;
3256+ backface-visibility: hidden;
3257+ -webkit-transform-origin: 50% 0%;
3258+ transform-origin: 50% 0%;
3259+ -webkit-transform: translate3d(0px, 110%, 0px) rotateX(-90deg);
3260+ transform: translate3d(0px, 110%, 0px) rotateX(-90deg); }
3261+
3262+/*********************************************
3263+ * SPEAKER NOTES
3264+ *********************************************/
3265+.reveal aside.notes {
3266+ display: none; }
3267+
3268+.reveal .speaker-notes {
3269+ display: none;
3270+ position: absolute;
3271+ width: 25vw;
3272+ height: 100%;
3273+ top: 0;
3274+ left: 100%;
3275+ padding: 14px 18px 14px 18px;
3276+ z-index: 1;
3277+ font-size: 18px;
3278+ line-height: 1.4;
3279+ border: 1px solid rgba(0, 0, 0, 0.05);
3280+ color: #222;
3281+ background-color: #f5f5f5;
3282+ overflow: auto;
3283+ box-sizing: border-box;
3284+ text-align: left;
3285+ font-family: Helvetica, sans-serif;
3286+ -webkit-overflow-scrolling: touch; }
3287+ .reveal .speaker-notes .notes-placeholder {
3288+ color: #ccc;
3289+ font-style: italic; }
3290+ .reveal .speaker-notes:focus {
3291+ outline: none; }
3292+ .reveal .speaker-notes:before {
3293+ content: 'Speaker notes';
3294+ display: block;
3295+ margin-bottom: 10px;
3296+ opacity: 0.5; }
3297+
3298+.reveal.show-notes {
3299+ max-width: 75vw;
3300+ overflow: visible; }
3301+
3302+.reveal.show-notes .speaker-notes {
3303+ display: block; }
3304+
3305+@media screen and (min-width: 1600px) {
3306+ .reveal .speaker-notes {
3307+ font-size: 20px; } }
3308+
3309+@media screen and (max-width: 1024px) {
3310+ .reveal.show-notes {
3311+ border-left: 0;
3312+ max-width: none;
3313+ max-height: 70%;
3314+ overflow: visible; }
3315+ .reveal.show-notes .speaker-notes {
3316+ top: 100%;
3317+ left: 0;
3318+ width: 100%;
3319+ height: 42.8571428571%; } }
3320+
3321+@media screen and (max-width: 600px) {
3322+ .reveal.show-notes {
3323+ max-height: 60%; }
3324+ .reveal.show-notes .speaker-notes {
3325+ top: 100%;
3326+ height: 66.6666666667%; }
3327+ .reveal .speaker-notes {
3328+ font-size: 14px; } }
3329+
3330+/*********************************************
3331+ * ZOOM PLUGIN
3332+ *********************************************/
3333+.zoomed .reveal *,
3334+.zoomed .reveal *:before,
3335+.zoomed .reveal *:after {
3336+ -webkit-backface-visibility: visible !important;
3337+ backface-visibility: visible !important; }
3338+
3339+.zoomed .reveal .progress,
3340+.zoomed .reveal .controls {
3341+ opacity: 0; }
3342+
3343+.zoomed .reveal .roll span {
3344+ background: none; }
3345+
3346+.zoomed .reveal .roll span:after {
3347+ visibility: hidden; }
3348
3349=== added file 'openlp/core/display/html/reveal.js'
3350--- openlp/core/display/html/reveal.js 1970-01-01 00:00:00 +0000
3351+++ openlp/core/display/html/reveal.js 2019-02-13 20:54:40 +0000
3352@@ -0,0 +1,5586 @@
3353+/*!
3354+ * reveal.js
3355+ * http://revealjs.com
3356+ * MIT licensed
3357+ *
3358+ * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
3359+ */
3360+(function( root, factory ) {
3361+ if( typeof define === 'function' && define.amd ) {
3362+ // AMD. Register as an anonymous module.
3363+ define( function() {
3364+ root.Reveal = factory();
3365+ return root.Reveal;
3366+ } );
3367+ } else if( typeof exports === 'object' ) {
3368+ // Node. Does not work with strict CommonJS.
3369+ module.exports = factory();
3370+ } else {
3371+ // Browser globals.
3372+ root.Reveal = factory();
3373+ }
3374+}( this, function() {
3375+
3376+ 'use strict';
3377+
3378+ var Reveal;
3379+
3380+ // The reveal.js version
3381+ var VERSION = '3.7.0';
3382+
3383+ var SLIDES_SELECTOR = '.slides section',
3384+ HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
3385+ VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
3386+ HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
3387+ UA = navigator.userAgent,
3388+
3389+ // Configuration defaults, can be overridden at initialization time
3390+ config = {
3391+
3392+ // The "normal" size of the presentation, aspect ratio will be preserved
3393+ // when the presentation is scaled to fit different resolutions
3394+ width: 960,
3395+ height: 700,
3396+
3397+ // Factor of the display size that should remain empty around the content
3398+ margin: 0.04,
3399+
3400+ // Bounds for smallest/largest possible scale to apply to content
3401+ minScale: 0.2,
3402+ maxScale: 2.0,
3403+
3404+ // Display presentation control arrows
3405+ controls: true,
3406+
3407+ // Help the user learn the controls by providing hints, for example by
3408+ // bouncing the down arrow when they first encounter a vertical slide
3409+ controlsTutorial: true,
3410+
3411+ // Determines where controls appear, "edges" or "bottom-right"
3412+ controlsLayout: 'bottom-right',
3413+
3414+ // Visibility rule for backwards navigation arrows; "faded", "hidden"
3415+ // or "visible"
3416+ controlsBackArrows: 'faded',
3417+
3418+ // Display a presentation progress bar
3419+ progress: true,
3420+
3421+ // Display the page number of the current slide
3422+ slideNumber: false,
3423+
3424+ // Use 1 based indexing for # links to match slide number (default is zero
3425+ // based)
3426+ hashOneBasedIndex: false,
3427+
3428+ // Determine which displays to show the slide number on
3429+ showSlideNumber: 'all',
3430+
3431+ // Push each slide change to the browser history
3432+ history: false,
3433+
3434+ // Enable keyboard shortcuts for navigation
3435+ keyboard: true,
3436+
3437+ // Optional function that blocks keyboard events when retuning false
3438+ keyboardCondition: null,
3439+
3440+ // Enable the slide overview mode
3441+ overview: true,
3442+
3443+ // Disables the default reveal.js slide layout so that you can use
3444+ // custom CSS layout
3445+ disableLayout: false,
3446+
3447+ // Vertical centering of slides
3448+ center: true,
3449+
3450+ // Enables touch navigation on devices with touch input
3451+ touch: true,
3452+
3453+ // Loop the presentation
3454+ loop: false,
3455+
3456+ // Change the presentation direction to be RTL
3457+ rtl: false,
3458+
3459+ // Randomizes the order of slides each time the presentation loads
3460+ shuffle: false,
3461+
3462+ // Turns fragments on and off globally
3463+ fragments: true,
3464+
3465+ // Flags whether to include the current fragment in the URL,
3466+ // so that reloading brings you to the same fragment position
3467+ fragmentInURL: false,
3468+
3469+ // Flags if the presentation is running in an embedded mode,
3470+ // i.e. contained within a limited portion of the screen
3471+ embedded: false,
3472+
3473+ // Flags if we should show a help overlay when the question-mark
3474+ // key is pressed
3475+ help: true,
3476+
3477+ // Flags if it should be possible to pause the presentation (blackout)
3478+ pause: true,
3479+
3480+ // Flags if speaker notes should be visible to all viewers
3481+ showNotes: false,
3482+
3483+ // Global override for autolaying embedded media (video/audio/iframe)
3484+ // - null: Media will only autoplay if data-autoplay is present
3485+ // - true: All media will autoplay, regardless of individual setting
3486+ // - false: No media will autoplay, regardless of individual setting
3487+ autoPlayMedia: null,
3488+
3489+ // Controls automatic progression to the next slide
3490+ // - 0: Auto-sliding only happens if the data-autoslide HTML attribute
3491+ // is present on the current slide or fragment
3492+ // - 1+: All slides will progress automatically at the given interval
3493+ // - false: No auto-sliding, even if data-autoslide is present
3494+ autoSlide: 0,
3495+
3496+ // Stop auto-sliding after user input
3497+ autoSlideStoppable: true,
3498+
3499+ // Use this method for navigation when auto-sliding (defaults to navigateNext)
3500+ autoSlideMethod: null,
3501+
3502+ // Specify the average time in seconds that you think you will spend
3503+ // presenting each slide. This is used to show a pacing timer in the
3504+ // speaker view
3505+ defaultTiming: null,
3506+
3507+ // Enable slide navigation via mouse wheel
3508+ mouseWheel: false,
3509+
3510+ // Apply a 3D roll to links on hover
3511+ rollingLinks: false,
3512+
3513+ // Hides the address bar on mobile devices
3514+ hideAddressBar: true,
3515+
3516+ // Opens links in an iframe preview overlay
3517+ // Add `data-preview-link` and `data-preview-link="false"` to customise each link
3518+ // individually
3519+ previewLinks: false,
3520+
3521+ // Exposes the reveal.js API through window.postMessage
3522+ postMessage: true,
3523+
3524+ // Dispatches all reveal.js events to the parent window through postMessage
3525+ postMessageEvents: false,
3526+
3527+ // Focuses body when page changes visibility to ensure keyboard shortcuts work
3528+ focusBodyOnPageVisibilityChange: true,
3529+
3530+ // Transition style
3531+ transition: 'slide', // none/fade/slide/convex/concave/zoom
3532+
3533+ // Transition speed
3534+ transitionSpeed: 'default', // default/fast/slow
3535+
3536+ // Transition style for full page slide backgrounds
3537+ backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
3538+
3539+ // Parallax background image
3540+ parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
3541+
3542+ // Parallax background size
3543+ parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
3544+
3545+ // Parallax background repeat
3546+ parallaxBackgroundRepeat: '', // repeat/repeat-x/repeat-y/no-repeat/initial/inherit
3547+
3548+ // Parallax background position
3549+ parallaxBackgroundPosition: '', // CSS syntax, e.g. "top left"
3550+
3551+ // Amount of pixels to move the parallax background per slide step
3552+ parallaxBackgroundHorizontal: null,
3553+ parallaxBackgroundVertical: null,
3554+
3555+ // The maximum number of pages a single slide can expand onto when printing
3556+ // to PDF, unlimited by default
3557+ pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
3558+
3559+ // Prints each fragment on a separate slide
3560+ pdfSeparateFragments: true,
3561+
3562+ // Offset used to reduce the height of content within exported PDF pages.
3563+ // This exists to account for environment differences based on how you
3564+ // print to PDF. CLI printing options, like phantomjs and wkpdf, can end
3565+ // on precisely the total height of the document whereas in-browser
3566+ // printing has to end one pixel before.
3567+ pdfPageHeightOffset: -1,
3568+
3569+ // Number of slides away from the current that are visible
3570+ viewDistance: 3,
3571+
3572+ // The display mode that will be used to show slides
3573+ display: 'block',
3574+
3575+ // Script dependencies to load
3576+ dependencies: []
3577+
3578+ },
3579+
3580+ // Flags if Reveal.initialize() has been called
3581+ initialized = false,
3582+
3583+ // Flags if reveal.js is loaded (has dispatched the 'ready' event)
3584+ loaded = false,
3585+
3586+ // Flags if the overview mode is currently active
3587+ overview = false,
3588+
3589+ // Holds the dimensions of our overview slides, including margins
3590+ overviewSlideWidth = null,
3591+ overviewSlideHeight = null,
3592+
3593+ // The horizontal and vertical index of the currently active slide
3594+ indexh,
3595+ indexv,
3596+
3597+ // The previous and current slide HTML elements
3598+ previousSlide,
3599+ currentSlide,
3600+
3601+ previousBackground,
3602+
3603+ // Remember which directions that the user has navigated towards
3604+ hasNavigatedRight = false,
3605+ hasNavigatedDown = false,
3606+
3607+ // Slides may hold a data-state attribute which we pick up and apply
3608+ // as a class to the body. This list contains the combined state of
3609+ // all current slides.
3610+ state = [],
3611+
3612+ // The current scale of the presentation (see width/height config)
3613+ scale = 1,
3614+
3615+ // CSS transform that is currently applied to the slides container,
3616+ // split into two groups
3617+ slidesTransform = { layout: '', overview: '' },
3618+
3619+ // Cached references to DOM elements
3620+ dom = {},
3621+
3622+ // Features supported by the browser, see #checkCapabilities()
3623+ features = {},
3624+
3625+ // Client is a mobile device, see #checkCapabilities()
3626+ isMobileDevice,
3627+
3628+ // Client is a desktop Chrome, see #checkCapabilities()
3629+ isChrome,
3630+
3631+ // Throttles mouse wheel navigation
3632+ lastMouseWheelStep = 0,
3633+
3634+ // Delays updates to the URL due to a Chrome thumbnailer bug
3635+ writeURLTimeout = 0,
3636+
3637+ // Flags if the interaction event listeners are bound
3638+ eventsAreBound = false,
3639+
3640+ // The current auto-slide duration
3641+ autoSlide = 0,
3642+
3643+ // Auto slide properties
3644+ autoSlidePlayer,
3645+ autoSlideTimeout = 0,
3646+ autoSlideStartTime = -1,
3647+ autoSlidePaused = false,
3648+
3649+ // Holds information about the currently ongoing touch input
3650+ touch = {
3651+ startX: 0,
3652+ startY: 0,
3653+ startSpan: 0,
3654+ startCount: 0,
3655+ captured: false,
3656+ threshold: 40
3657+ },
3658+
3659+ // Holds information about the keyboard shortcuts
3660+ keyboardShortcuts = {
3661+ 'N , SPACE': 'Next slide',
3662+ 'P': 'Previous slide',
3663+ '&#8592; , H': 'Navigate left',
3664+ '&#8594; , L': 'Navigate right',
3665+ '&#8593; , K': 'Navigate up',
3666+ '&#8595; , J': 'Navigate down',
3667+ 'Home': 'First slide',
3668+ 'End': 'Last slide',
3669+ 'B , .': 'Pause',
3670+ 'F': 'Fullscreen',
3671+ 'ESC, O': 'Slide overview'
3672+ },
3673+
3674+ // Holds custom key code mappings
3675+ registeredKeyBindings = {};
3676+
3677+ /**
3678+ * Starts up the presentation if the client is capable.
3679+ */
3680+ function initialize( options ) {
3681+
3682+ // Make sure we only initialize once
3683+ if( initialized === true ) return;
3684+
3685+ initialized = true;
3686+
3687+ checkCapabilities();
3688+
3689+ if( !features.transforms2d && !features.transforms3d ) {
3690+ document.body.setAttribute( 'class', 'no-transforms' );
3691+
3692+ // Since JS won't be running any further, we load all lazy
3693+ // loading elements upfront
3694+ var images = toArray( document.getElementsByTagName( 'img' ) ),
3695+ iframes = toArray( document.getElementsByTagName( 'iframe' ) );
3696+
3697+ var lazyLoadable = images.concat( iframes );
3698+
3699+ for( var i = 0, len = lazyLoadable.length; i < len; i++ ) {
3700+ var element = lazyLoadable[i];
3701+ if( element.getAttribute( 'data-src' ) ) {
3702+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
3703+ element.removeAttribute( 'data-src' );
3704+ }
3705+ }
3706+
3707+ // If the browser doesn't support core features we won't be
3708+ // using JavaScript to control the presentation
3709+ return;
3710+ }
3711+
3712+ // Cache references to key DOM elements
3713+ dom.wrapper = document.querySelector( '.reveal' );
3714+ dom.slides = document.querySelector( '.reveal .slides' );
3715+
3716+ // Force a layout when the whole page, incl fonts, has loaded
3717+ window.addEventListener( 'load', layout, false );
3718+
3719+ var query = Reveal.getQueryHash();
3720+
3721+ // Do not accept new dependencies via query config to avoid
3722+ // the potential of malicious script injection
3723+ if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
3724+
3725+ // Copy options over to our config object
3726+ extend( config, options );
3727+ extend( config, query );
3728+
3729+ // Hide the address bar in mobile browsers
3730+ hideAddressBar();
3731+
3732+ // Loads the dependencies and continues to #start() once done
3733+ load();
3734+
3735+ }
3736+
3737+ /**
3738+ * Restarts up the presentation if the client is capable.
3739+ */
3740+ function reinitialize() {
3741+ initialized = false;
3742+ initialize(config);
3743+ }
3744+
3745+ /**
3746+ * Inspect the client to see what it's capable of, this
3747+ * should only happens once per runtime.
3748+ */
3749+ function checkCapabilities() {
3750+
3751+ isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( UA );
3752+ isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );
3753+
3754+ var testElement = document.createElement( 'div' );
3755+
3756+ features.transforms3d = 'WebkitPerspective' in testElement.style ||
3757+ 'MozPerspective' in testElement.style ||
3758+ 'msPerspective' in testElement.style ||
3759+ 'OPerspective' in testElement.style ||
3760+ 'perspective' in testElement.style;
3761+
3762+ features.transforms2d = 'WebkitTransform' in testElement.style ||
3763+ 'MozTransform' in testElement.style ||
3764+ 'msTransform' in testElement.style ||
3765+ 'OTransform' in testElement.style ||
3766+ 'transform' in testElement.style;
3767+
3768+ features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
3769+ features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
3770+
3771+ features.canvas = !!document.createElement( 'canvas' ).getContext;
3772+
3773+ // Transitions in the overview are disabled in desktop and
3774+ // Safari due to lag
3775+ features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test( UA );
3776+
3777+ // Flags if we should use zoom instead of transform to scale
3778+ // up slides. Zoom produces crisper results but has a lot of
3779+ // xbrowser quirks so we only use it in whitelsited browsers.
3780+ features.zoom = 'zoom' in testElement.style && !isMobileDevice &&
3781+ ( isChrome || /Version\/[\d\.]+.*Safari/.test( UA ) );
3782+
3783+ }
3784+
3785+ /**
3786+ * Loads the dependencies of reveal.js. Dependencies are
3787+ * defined via the configuration option 'dependencies'
3788+ * and will be loaded prior to starting/binding reveal.js.
3789+ * Some dependencies may have an 'async' flag, if so they
3790+ * will load after reveal.js has been started up.
3791+ */
3792+ function load() {
3793+
3794+ var scripts = [],
3795+ scriptsAsync = [],
3796+ scriptsToPreload = 0;
3797+
3798+ // Called once synchronous scripts finish loading
3799+ function proceed() {
3800+ if( scriptsAsync.length ) {
3801+ // Load asynchronous scripts
3802+ head.js.apply( null, scriptsAsync );
3803+ }
3804+
3805+ start();
3806+ }
3807+
3808+ function loadScript( s ) {
3809+ head.ready( s.src.match( /([\w\d_\-]*)\.?js(\?[\w\d.=&]*)?$|[^\\\/]*$/i )[0], function() {
3810+ // Extension may contain callback functions
3811+ if( typeof s.callback === 'function' ) {
3812+ s.callback.apply( this );
3813+ }
3814+
3815+ if( --scriptsToPreload === 0 ) {
3816+ proceed();
3817+ }
3818+ });
3819+ }
3820+
3821+ for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
3822+ var s = config.dependencies[i];
3823+
3824+ // Load if there's no condition or the condition is truthy
3825+ if( !s.condition || s.condition() ) {
3826+ if( s.async ) {
3827+ scriptsAsync.push( s.src );
3828+ }
3829+ else {
3830+ scripts.push( s.src );
3831+ }
3832+
3833+ loadScript( s );
3834+ }
3835+ }
3836+
3837+ if( scripts.length ) {
3838+ scriptsToPreload = scripts.length;
3839+
3840+ // Load synchronous scripts
3841+ head.js.apply( null, scripts );
3842+ }
3843+ else {
3844+ proceed();
3845+ }
3846+
3847+ }
3848+
3849+ /**
3850+ * Starts up reveal.js by binding input events and navigating
3851+ * to the current URL deeplink if there is one.
3852+ */
3853+ function start() {
3854+
3855+ loaded = true;
3856+
3857+ // Make sure we've got all the DOM elements we need
3858+ setupDOM();
3859+
3860+ // Listen to messages posted to this window
3861+ setupPostMessage();
3862+
3863+ // Prevent the slides from being scrolled out of view
3864+ setupScrollPrevention();
3865+
3866+ // Resets all vertical slides so that only the first is visible
3867+ resetVerticalSlides();
3868+
3869+ // Updates the presentation to match the current configuration values
3870+ configure();
3871+
3872+ // Read the initial hash
3873+ readURL();
3874+
3875+ // Update all backgrounds
3876+ updateBackground( true );
3877+
3878+ // Notify listeners that the presentation is ready but use a 1ms
3879+ // timeout to ensure it's not fired synchronously after #initialize()
3880+ setTimeout( function() {
3881+ // Enable transitions now that we're loaded
3882+ dom.slides.classList.remove( 'no-transition' );
3883+
3884+ dom.wrapper.classList.add( 'ready' );
3885+
3886+ dispatchEvent( 'ready', {
3887+ 'indexh': indexh,
3888+ 'indexv': indexv,
3889+ 'currentSlide': currentSlide
3890+ } );
3891+ }, 1 );
3892+
3893+ // Special setup and config is required when printing to PDF
3894+ if( isPrintingPDF() ) {
3895+ removeEventListeners();
3896+
3897+ // The document needs to have loaded for the PDF layout
3898+ // measurements to be accurate
3899+ if( document.readyState === 'complete' ) {
3900+ setupPDF();
3901+ }
3902+ else {
3903+ window.addEventListener( 'load', setupPDF );
3904+ }
3905+ }
3906+
3907+ }
3908+
3909+ /**
3910+ * Finds and stores references to DOM elements which are
3911+ * required by the presentation. If a required element is
3912+ * not found, it is created.
3913+ */
3914+ function setupDOM() {
3915+
3916+ // Prevent transitions while we're loading
3917+ dom.slides.classList.add( 'no-transition' );
3918+
3919+ if( isMobileDevice ) {
3920+ dom.wrapper.classList.add( 'no-hover' );
3921+ }
3922+ else {
3923+ dom.wrapper.classList.remove( 'no-hover' );
3924+ }
3925+
3926+ if( /iphone/gi.test( UA ) ) {
3927+ dom.wrapper.classList.add( 'ua-iphone' );
3928+ }
3929+ else {
3930+ dom.wrapper.classList.remove( 'ua-iphone' );
3931+ }
3932+
3933+ // Background element
3934+ dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );
3935+
3936+ // Progress bar
3937+ dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '<span></span>' );
3938+ dom.progressbar = dom.progress.querySelector( 'span' );
3939+
3940+ // Arrow controls
3941+ dom.controls = createSingletonNode( dom.wrapper, 'aside', 'controls',
3942+ '<button class="navigate-left" aria-label="previous slide"><div class="controls-arrow"></div></button>' +
3943+ '<button class="navigate-right" aria-label="next slide"><div class="controls-arrow"></div></button>' +
3944+ '<button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>' +
3945+ '<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>' );
3946+
3947+ // Slide number
3948+ dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
3949+
3950+ // Element containing notes that are visible to the audience
3951+ dom.speakerNotes = createSingletonNode( dom.wrapper, 'div', 'speaker-notes', null );
3952+ dom.speakerNotes.setAttribute( 'data-prevent-swipe', '' );
3953+ dom.speakerNotes.setAttribute( 'tabindex', '0' );
3954+
3955+ // Overlay graphic which is displayed during the paused mode
3956+ dom.pauseOverlay = createSingletonNode( dom.wrapper, 'div', 'pause-overlay', '<button class="resume-button">Resume presentation</button>' );
3957+ dom.resumeButton = dom.pauseOverlay.querySelector( '.resume-button' );
3958+
3959+ dom.wrapper.setAttribute( 'role', 'application' );
3960+
3961+ // There can be multiple instances of controls throughout the page
3962+ dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
3963+ dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
3964+ dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
3965+ dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
3966+ dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
3967+ dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
3968+
3969+ // The right and down arrows in the standard reveal.js controls
3970+ dom.controlsRightArrow = dom.controls.querySelector( '.navigate-right' );
3971+ dom.controlsDownArrow = dom.controls.querySelector( '.navigate-down' );
3972+
3973+ dom.statusDiv = createStatusDiv();
3974+ }
3975+
3976+ /**
3977+ * Creates a hidden div with role aria-live to announce the
3978+ * current slide content. Hide the div off-screen to make it
3979+ * available only to Assistive Technologies.
3980+ *
3981+ * @return {HTMLElement}
3982+ */
3983+ function createStatusDiv() {
3984+
3985+ var statusDiv = document.getElementById( 'aria-status-div' );
3986+ if( !statusDiv ) {
3987+ statusDiv = document.createElement( 'div' );
3988+ statusDiv.style.position = 'absolute';
3989+ statusDiv.style.height = '1px';
3990+ statusDiv.style.width = '1px';
3991+ statusDiv.style.overflow = 'hidden';
3992+ statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
3993+ statusDiv.setAttribute( 'id', 'aria-status-div' );
3994+ statusDiv.setAttribute( 'aria-live', 'polite' );
3995+ statusDiv.setAttribute( 'aria-atomic','true' );
3996+ dom.wrapper.appendChild( statusDiv );
3997+ }
3998+ return statusDiv;
3999+
4000+ }
4001+
4002+ /**
4003+ * Converts the given HTML element into a string of text
4004+ * that can be announced to a screen reader. Hidden
4005+ * elements are excluded.
4006+ */
4007+ function getStatusText( node ) {
4008+
4009+ var text = '';
4010+
4011+ // Text node
4012+ if( node.nodeType === 3 ) {
4013+ text += node.textContent;
4014+ }
4015+ // Element node
4016+ else if( node.nodeType === 1 ) {
4017+
4018+ var isAriaHidden = node.getAttribute( 'aria-hidden' );
4019+ var isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
4020+ if( isAriaHidden !== 'true' && !isDisplayHidden ) {
4021+
4022+ toArray( node.childNodes ).forEach( function( child ) {
4023+ text += getStatusText( child );
4024+ } );
4025+
4026+ }
4027+
4028+ }
4029+
4030+ return text;
4031+
4032+ }
4033+
4034+ /**
4035+ * Configures the presentation for printing to a static
4036+ * PDF.
4037+ */
4038+ function setupPDF() {
4039+
4040+ var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
4041+
4042+ // Dimensions of the PDF pages
4043+ var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
4044+ pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
4045+
4046+ // Dimensions of slides within the pages
4047+ var slideWidth = slideSize.width,
4048+ slideHeight = slideSize.height;
4049+
4050+ // Let the browser know what page size we want to print
4051+ injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' );
4052+
4053+ // Limit the size of certain elements to the dimensions of the slide
4054+ injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
4055+
4056+ document.body.classList.add( 'print-pdf' );
4057+ document.body.style.width = pageWidth + 'px';
4058+ document.body.style.height = pageHeight + 'px';
4059+
4060+ // Make sure stretch elements fit on slide
4061+ layoutSlideContents( slideWidth, slideHeight );
4062+
4063+ // Add each slide's index as attributes on itself, we need these
4064+ // indices to generate slide numbers below
4065+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
4066+ hslide.setAttribute( 'data-index-h', h );
4067+
4068+ if( hslide.classList.contains( 'stack' ) ) {
4069+ toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
4070+ vslide.setAttribute( 'data-index-h', h );
4071+ vslide.setAttribute( 'data-index-v', v );
4072+ } );
4073+ }
4074+ } );
4075+
4076+ // Slide and slide background layout
4077+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
4078+
4079+ // Vertical stacks are not centred since their section
4080+ // children will be
4081+ if( slide.classList.contains( 'stack' ) === false ) {
4082+ // Center the slide inside of the page, giving the slide some margin
4083+ var left = ( pageWidth - slideWidth ) / 2,
4084+ top = ( pageHeight - slideHeight ) / 2;
4085+
4086+ var contentHeight = slide.scrollHeight;
4087+ var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
4088+
4089+ // Adhere to configured pages per slide limit
4090+ numberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide );
4091+
4092+ // Center slides vertically
4093+ if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
4094+ top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
4095+ }
4096+
4097+ // Wrap the slide in a page element and hide its overflow
4098+ // so that no page ever flows onto another
4099+ var page = document.createElement( 'div' );
4100+ page.className = 'pdf-page';
4101+ page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px';
4102+ slide.parentNode.insertBefore( page, slide );
4103+ page.appendChild( slide );
4104+
4105+ // Position the slide inside of the page
4106+ slide.style.left = left + 'px';
4107+ slide.style.top = top + 'px';
4108+ slide.style.width = slideWidth + 'px';
4109+
4110+ if( slide.slideBackgroundElement ) {
4111+ page.insertBefore( slide.slideBackgroundElement, slide );
4112+ }
4113+
4114+ // Inject notes if `showNotes` is enabled
4115+ if( config.showNotes ) {
4116+
4117+ // Are there notes for this slide?
4118+ var notes = getSlideNotes( slide );
4119+ if( notes ) {
4120+
4121+ var notesSpacing = 8;
4122+ var notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline';
4123+ var notesElement = document.createElement( 'div' );
4124+ notesElement.classList.add( 'speaker-notes' );
4125+ notesElement.classList.add( 'speaker-notes-pdf' );
4126+ notesElement.setAttribute( 'data-layout', notesLayout );
4127+ notesElement.innerHTML = notes;
4128+
4129+ if( notesLayout === 'separate-page' ) {
4130+ page.parentNode.insertBefore( notesElement, page.nextSibling );
4131+ }
4132+ else {
4133+ notesElement.style.left = notesSpacing + 'px';
4134+ notesElement.style.bottom = notesSpacing + 'px';
4135+ notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px';
4136+ page.appendChild( notesElement );
4137+ }
4138+
4139+ }
4140+
4141+ }
4142+
4143+ // Inject slide numbers if `slideNumbers` are enabled
4144+ if( config.slideNumber && /all|print/i.test( config.showSlideNumber ) ) {
4145+ var slideNumberH = parseInt( slide.getAttribute( 'data-index-h' ), 10 ) + 1,
4146+ slideNumberV = parseInt( slide.getAttribute( 'data-index-v' ), 10 ) + 1;
4147+
4148+ var numberElement = document.createElement( 'div' );
4149+ numberElement.classList.add( 'slide-number' );
4150+ numberElement.classList.add( 'slide-number-pdf' );
4151+ numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV );
4152+ page.appendChild( numberElement );
4153+ }
4154+
4155+ // Copy page and show fragments one after another
4156+ if( config.pdfSeparateFragments ) {
4157+
4158+ // Each fragment 'group' is an array containing one or more
4159+ // fragments. Multiple fragments that appear at the same time
4160+ // are part of the same group.
4161+ var fragmentGroups = sortFragments( page.querySelectorAll( '.fragment' ), true );
4162+
4163+ var previousFragmentStep;
4164+ var previousPage;
4165+
4166+ fragmentGroups.forEach( function( fragments ) {
4167+
4168+ // Remove 'current-fragment' from the previous group
4169+ if( previousFragmentStep ) {
4170+ previousFragmentStep.forEach( function( fragment ) {
4171+ fragment.classList.remove( 'current-fragment' );
4172+ } );
4173+ }
4174+
4175+ // Show the fragments for the current index
4176+ fragments.forEach( function( fragment ) {
4177+ fragment.classList.add( 'visible', 'current-fragment' );
4178+ } );
4179+
4180+ // Create a separate page for the current fragment state
4181+ var clonedPage = page.cloneNode( true );
4182+ page.parentNode.insertBefore( clonedPage, ( previousPage || page ).nextSibling );
4183+
4184+ previousFragmentStep = fragments;
4185+ previousPage = clonedPage;
4186+
4187+ } );
4188+
4189+ // Reset the first/original page so that all fragments are hidden
4190+ fragmentGroups.forEach( function( fragments ) {
4191+ fragments.forEach( function( fragment ) {
4192+ fragment.classList.remove( 'visible', 'current-fragment' );
4193+ } );
4194+ } );
4195+
4196+ }
4197+ // Show all fragments
4198+ else {
4199+ toArray( page.querySelectorAll( '.fragment:not(.fade-out)' ) ).forEach( function( fragment ) {
4200+ fragment.classList.add( 'visible' );
4201+ } );
4202+ }
4203+
4204+ }
4205+
4206+ } );
4207+
4208+ // Notify subscribers that the PDF layout is good to go
4209+ dispatchEvent( 'pdf-ready' );
4210+
4211+ }
4212+
4213+ /**
4214+ * This is an unfortunate necessity. Some actions – such as
4215+ * an input field being focused in an iframe or using the
4216+ * keyboard to expand text selection beyond the bounds of
4217+ * a slide – can trigger our content to be pushed out of view.
4218+ * This scrolling can not be prevented by hiding overflow in
4219+ * CSS (we already do) so we have to resort to repeatedly
4220+ * checking if the slides have been offset :(
4221+ */
4222+ function setupScrollPrevention() {
4223+
4224+ setInterval( function() {
4225+ if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
4226+ dom.wrapper.scrollTop = 0;
4227+ dom.wrapper.scrollLeft = 0;
4228+ }
4229+ }, 1000 );
4230+
4231+ }
4232+
4233+ /**
4234+ * Creates an HTML element and returns a reference to it.
4235+ * If the element already exists the existing instance will
4236+ * be returned.
4237+ *
4238+ * @param {HTMLElement} container
4239+ * @param {string} tagname
4240+ * @param {string} classname
4241+ * @param {string} innerHTML
4242+ *
4243+ * @return {HTMLElement}
4244+ */
4245+ function createSingletonNode( container, tagname, classname, innerHTML ) {
4246+
4247+ // Find all nodes matching the description
4248+ var nodes = container.querySelectorAll( '.' + classname );
4249+
4250+ // Check all matches to find one which is a direct child of
4251+ // the specified container
4252+ for( var i = 0; i < nodes.length; i++ ) {
4253+ var testNode = nodes[i];
4254+ if( testNode.parentNode === container ) {
4255+ return testNode;
4256+ }
4257+ }
4258+
4259+ // If no node was found, create it now
4260+ var node = document.createElement( tagname );
4261+ node.className = classname;
4262+ if( typeof innerHTML === 'string' ) {
4263+ node.innerHTML = innerHTML;
4264+ }
4265+ container.appendChild( node );
4266+
4267+ return node;
4268+
4269+ }
4270+
4271+ /**
4272+ * Creates the slide background elements and appends them
4273+ * to the background container. One element is created per
4274+ * slide no matter if the given slide has visible background.
4275+ */
4276+ function createBackgrounds() {
4277+
4278+ var printMode = isPrintingPDF();
4279+
4280+ // Clear prior backgrounds
4281+ dom.background.innerHTML = '';
4282+ dom.background.classList.add( 'no-transition' );
4283+
4284+ // Iterate over all horizontal slides
4285+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
4286+
4287+ var backgroundStack = createBackground( slideh, dom.background );
4288+
4289+ // Iterate over all vertical slides
4290+ toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
4291+
4292+ createBackground( slidev, backgroundStack );
4293+
4294+ backgroundStack.classList.add( 'stack' );
4295+
4296+ } );
4297+
4298+ } );
4299+
4300+ // Add parallax background if specified
4301+ if( config.parallaxBackgroundImage ) {
4302+
4303+ dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
4304+ dom.background.style.backgroundSize = config.parallaxBackgroundSize;
4305+ dom.background.style.backgroundRepeat = config.parallaxBackgroundRepeat;
4306+ dom.background.style.backgroundPosition = config.parallaxBackgroundPosition;
4307+
4308+ // Make sure the below properties are set on the element - these properties are
4309+ // needed for proper transitions to be set on the element via CSS. To remove
4310+ // annoying background slide-in effect when the presentation starts, apply
4311+ // these properties after short time delay
4312+ setTimeout( function() {
4313+ dom.wrapper.classList.add( 'has-parallax-background' );
4314+ }, 1 );
4315+
4316+ }
4317+ else {
4318+
4319+ dom.background.style.backgroundImage = '';
4320+ dom.wrapper.classList.remove( 'has-parallax-background' );
4321+
4322+ }
4323+
4324+ }
4325+
4326+ /**
4327+ * Creates a background for the given slide.
4328+ *
4329+ * @param {HTMLElement} slide
4330+ * @param {HTMLElement} container The element that the background
4331+ * should be appended to
4332+ * @return {HTMLElement} New background div
4333+ */
4334+ function createBackground( slide, container ) {
4335+
4336+
4337+ // Main slide background element
4338+ var element = document.createElement( 'div' );
4339+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
4340+
4341+ // Inner background element that wraps images/videos/iframes
4342+ var contentElement = document.createElement( 'div' );
4343+ contentElement.className = 'slide-background-content';
4344+
4345+ element.appendChild( contentElement );
4346+ container.appendChild( element );
4347+
4348+ slide.slideBackgroundElement = element;
4349+ slide.slideBackgroundContentElement = contentElement;
4350+
4351+ // Syncs the background to reflect all current background settings
4352+ syncBackground( slide );
4353+
4354+ return element;
4355+
4356+ }
4357+
4358+ /**
4359+ * Renders all of the visual properties of a slide background
4360+ * based on the various background attributes.
4361+ *
4362+ * @param {HTMLElement} slide
4363+ */
4364+ function syncBackground( slide ) {
4365+
4366+ var element = slide.slideBackgroundElement,
4367+ contentElement = slide.slideBackgroundContentElement;
4368+
4369+ // Reset the prior background state in case this is not the
4370+ // initial sync
4371+ slide.classList.remove( 'has-dark-background' );
4372+ slide.classList.remove( 'has-light-background' );
4373+
4374+ element.removeAttribute( 'data-loaded' );
4375+ element.removeAttribute( 'data-background-hash' );
4376+ element.removeAttribute( 'data-background-size' );
4377+ element.removeAttribute( 'data-background-transition' );
4378+ element.style.backgroundColor = '';
4379+
4380+ contentElement.style.backgroundSize = '';
4381+ contentElement.style.backgroundRepeat = '';
4382+ contentElement.style.backgroundPosition = '';
4383+ contentElement.style.backgroundImage = '';
4384+ contentElement.style.opacity = '';
4385+ contentElement.innerHTML = '';
4386+
4387+ var data = {
4388+ background: slide.getAttribute( 'data-background' ),
4389+ backgroundSize: slide.getAttribute( 'data-background-size' ),
4390+ backgroundImage: slide.getAttribute( 'data-background-image' ),
4391+ backgroundVideo: slide.getAttribute( 'data-background-video' ),
4392+ backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
4393+ backgroundColor: slide.getAttribute( 'data-background-color' ),
4394+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
4395+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
4396+ backgroundTransition: slide.getAttribute( 'data-background-transition' ),
4397+ backgroundOpacity: slide.getAttribute( 'data-background-opacity' )
4398+ };
4399+
4400+ if( data.background ) {
4401+ // Auto-wrap image urls in url(...)
4402+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test( data.background ) ) {
4403+ slide.setAttribute( 'data-background-image', data.background );
4404+ }
4405+ else {
4406+ element.style.background = data.background;
4407+ }
4408+ }
4409+
4410+ // Create a hash for this combination of background settings.
4411+ // This is used to determine when two slide backgrounds are
4412+ // the same.
4413+ if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
4414+ element.setAttribute( 'data-background-hash', data.background +
4415+ data.backgroundSize +
4416+ data.backgroundImage +
4417+ data.backgroundVideo +
4418+ data.backgroundIframe +
4419+ data.backgroundColor +
4420+ data.backgroundRepeat +
4421+ data.backgroundPosition +
4422+ data.backgroundTransition +
4423+ data.backgroundOpacity );
4424+ }
4425+
4426+ // Additional and optional background properties
4427+ if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize );
4428+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
4429+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
4430+
4431+ // Background image options are set on the content wrapper
4432+ if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize;
4433+ if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat;
4434+ if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition;
4435+ if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity;
4436+
4437+ // If this slide has a background color, add a class that
4438+ // signals if it is light or dark. If the slide has no background
4439+ // color, no class will be set
4440+ var computedBackgroundStyle = window.getComputedStyle( element );
4441+ if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {
4442+ var rgb = colorToRgb( computedBackgroundStyle.backgroundColor );
4443+
4444+ // Ignore fully transparent backgrounds. Some browsers return
4445+ // rgba(0,0,0,0) when reading the computed background color of
4446+ // an element with no background
4447+ if( rgb && rgb.a !== 0 ) {
4448+ if( colorBrightness( computedBackgroundStyle.backgroundColor ) < 128 ) {
4449+ slide.classList.add( 'has-dark-background' );
4450+ }
4451+ else {
4452+ slide.classList.add( 'has-light-background' );
4453+ }
4454+ }
4455+ }
4456+
4457+ }
4458+
4459+ /**
4460+ * Registers a listener to postMessage events, this makes it
4461+ * possible to call all reveal.js API methods from another
4462+ * window. For example:
4463+ *
4464+ * revealWindow.postMessage( JSON.stringify({
4465+ * method: 'slide',
4466+ * args: [ 2 ]
4467+ * }), '*' );
4468+ */
4469+ function setupPostMessage() {
4470+
4471+ if( config.postMessage ) {
4472+ window.addEventListener( 'message', function ( event ) {
4473+ var data = event.data;
4474+
4475+ // Make sure we're dealing with JSON
4476+ if( typeof data === 'string' && data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
4477+ data = JSON.parse( data );
4478+
4479+ // Check if the requested method can be found
4480+ if( data.method && typeof Reveal[data.method] === 'function' ) {
4481+ Reveal[data.method].apply( Reveal, data.args );
4482+ }
4483+ }
4484+ }, false );
4485+ }
4486+
4487+ }
4488+
4489+ /**
4490+ * Applies the configuration settings from the config
4491+ * object. May be called multiple times.
4492+ *
4493+ * @param {object} options
4494+ */
4495+ function configure( options ) {
4496+
4497+ var oldTransition = config.transition;
4498+
4499+ // New config options may be passed when this method
4500+ // is invoked through the API after initialization
4501+ if( typeof options === 'object' ) extend( config, options );
4502+
4503+ // Abort if reveal.js hasn't finished loading, config
4504+ // changes will be applied automatically once loading
4505+ // finishes
4506+ if( loaded === false ) return;
4507+
4508+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
4509+
4510+ // Remove the previously configured transition class
4511+ dom.wrapper.classList.remove( oldTransition );
4512+
4513+ // Force linear transition based on browser capabilities
4514+ if( features.transforms3d === false ) config.transition = 'linear';
4515+
4516+ dom.wrapper.classList.add( config.transition );
4517+
4518+ dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
4519+ dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
4520+
4521+ dom.controls.style.display = config.controls ? 'block' : 'none';
4522+ dom.progress.style.display = config.progress ? 'block' : 'none';
4523+
4524+ dom.controls.setAttribute( 'data-controls-layout', config.controlsLayout );
4525+ dom.controls.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );
4526+
4527+ if( config.shuffle ) {
4528+ shuffle();
4529+ }
4530+
4531+ if( config.rtl ) {
4532+ dom.wrapper.classList.add( 'rtl' );
4533+ }
4534+ else {
4535+ dom.wrapper.classList.remove( 'rtl' );
4536+ }
4537+
4538+ if( config.center ) {
4539+ dom.wrapper.classList.add( 'center' );
4540+ }
4541+ else {
4542+ dom.wrapper.classList.remove( 'center' );
4543+ }
4544+
4545+ // Exit the paused mode if it was configured off
4546+ if( config.pause === false ) {
4547+ resume();
4548+ }
4549+
4550+ if( config.showNotes ) {
4551+ dom.speakerNotes.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' );
4552+ }
4553+
4554+ if( config.mouseWheel ) {
4555+ document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
4556+ document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
4557+ }
4558+ else {
4559+ document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
4560+ document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
4561+ }
4562+
4563+ // Rolling 3D links
4564+ if( config.rollingLinks ) {
4565+ enableRollingLinks();
4566+ }
4567+ else {
4568+ disableRollingLinks();
4569+ }
4570+
4571+ // Iframe link previews
4572+ if( config.previewLinks ) {
4573+ enablePreviewLinks();
4574+ disablePreviewLinks( '[data-preview-link=false]' );
4575+ }
4576+ else {
4577+ disablePreviewLinks();
4578+ enablePreviewLinks( '[data-preview-link]:not([data-preview-link=false])' );
4579+ }
4580+
4581+ // Remove existing auto-slide controls
4582+ if( autoSlidePlayer ) {
4583+ autoSlidePlayer.destroy();
4584+ autoSlidePlayer = null;
4585+ }
4586+
4587+ // Generate auto-slide controls if needed
4588+ if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
4589+ autoSlidePlayer = new Playback( dom.wrapper, function() {
4590+ return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
4591+ } );
4592+
4593+ autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
4594+ autoSlidePaused = false;
4595+ }
4596+
4597+ // When fragments are turned off they should be visible
4598+ if( config.fragments === false ) {
4599+ toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
4600+ element.classList.add( 'visible' );
4601+ element.classList.remove( 'current-fragment' );
4602+ } );
4603+ }
4604+
4605+ // Slide numbers
4606+ var slideNumberDisplay = 'none';
4607+ if( config.slideNumber && !isPrintingPDF() ) {
4608+ if( config.showSlideNumber === 'all' ) {
4609+ slideNumberDisplay = 'block';
4610+ }
4611+ else if( config.showSlideNumber === 'speaker' && isSpeakerNotes() ) {
4612+ slideNumberDisplay = 'block';
4613+ }
4614+ }
4615+
4616+ dom.slideNumber.style.display = slideNumberDisplay;
4617+
4618+ sync();
4619+
4620+ }
4621+
4622+ /**
4623+ * Binds all event listeners.
4624+ */
4625+ function addEventListeners() {
4626+
4627+ eventsAreBound = true;
4628+
4629+ window.addEventListener( 'hashchange', onWindowHashChange, false );
4630+ window.addEventListener( 'resize', onWindowResize, false );
4631+
4632+ if( config.touch ) {
4633+ if( 'onpointerdown' in window ) {
4634+ // Use W3C pointer events
4635+ dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
4636+ dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
4637+ dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
4638+ }
4639+ else if( window.navigator.msPointerEnabled ) {
4640+ // IE 10 uses prefixed version of pointer events
4641+ dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
4642+ dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
4643+ dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
4644+ }
4645+ else {
4646+ // Fall back to touch events
4647+ dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
4648+ dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
4649+ dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
4650+ }
4651+ }
4652+
4653+ if( config.keyboard ) {
4654+ document.addEventListener( 'keydown', onDocumentKeyDown, false );
4655+ document.addEventListener( 'keypress', onDocumentKeyPress, false );
4656+ }
4657+
4658+ if( config.progress && dom.progress ) {
4659+ dom.progress.addEventListener( 'click', onProgressClicked, false );
4660+ }
4661+
4662+ dom.resumeButton.addEventListener( 'click', resume, false );
4663+
4664+ if( config.focusBodyOnPageVisibilityChange ) {
4665+ var visibilityChange;
4666+
4667+ if( 'hidden' in document ) {
4668+ visibilityChange = 'visibilitychange';
4669+ }
4670+ else if( 'msHidden' in document ) {
4671+ visibilityChange = 'msvisibilitychange';
4672+ }
4673+ else if( 'webkitHidden' in document ) {
4674+ visibilityChange = 'webkitvisibilitychange';
4675+ }
4676+
4677+ if( visibilityChange ) {
4678+ document.addEventListener( visibilityChange, onPageVisibilityChange, false );
4679+ }
4680+ }
4681+
4682+ // Listen to both touch and click events, in case the device
4683+ // supports both
4684+ var pointerEvents = [ 'touchstart', 'click' ];
4685+
4686+ // Only support touch for Android, fixes double navigations in
4687+ // stock browser
4688+ if( UA.match( /android/gi ) ) {
4689+ pointerEvents = [ 'touchstart' ];
4690+ }
4691+
4692+ pointerEvents.forEach( function( eventName ) {
4693+ dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
4694+ dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
4695+ dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
4696+ dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
4697+ dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
4698+ dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
4699+ } );
4700+
4701+ }
4702+
4703+ /**
4704+ * Unbinds all event listeners.
4705+ */
4706+ function removeEventListeners() {
4707+
4708+ eventsAreBound = false;
4709+
4710+ document.removeEventListener( 'keydown', onDocumentKeyDown, false );
4711+ document.removeEventListener( 'keypress', onDocumentKeyPress, false );
4712+ window.removeEventListener( 'hashchange', onWindowHashChange, false );
4713+ window.removeEventListener( 'resize', onWindowResize, false );
4714+
4715+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
4716+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
4717+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
4718+
4719+ dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
4720+ dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
4721+ dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
4722+
4723+ dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
4724+ dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
4725+ dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
4726+
4727+ dom.resumeButton.removeEventListener( 'click', resume, false );
4728+
4729+ if ( config.progress && dom.progress ) {
4730+ dom.progress.removeEventListener( 'click', onProgressClicked, false );
4731+ }
4732+
4733+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
4734+ dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
4735+ dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
4736+ dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
4737+ dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
4738+ dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
4739+ dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
4740+ } );
4741+
4742+ }
4743+
4744+ /**
4745+ * Add a custom key binding with optional description to
4746+ * be added to the help screen.
4747+ */
4748+ function addKeyBinding( binding, callback ) {
4749+
4750+ if( typeof binding === 'object' && binding.keyCode ) {
4751+ registeredKeyBindings[binding.keyCode] = {
4752+ callback: callback,
4753+ key: binding.key,
4754+ description: binding.description
4755+ };
4756+ }
4757+ else {
4758+ registeredKeyBindings[binding] = {
4759+ callback: callback,
4760+ key: null,
4761+ description: null
4762+ };
4763+ }
4764+
4765+ }
4766+
4767+ /**
4768+ * Removes the specified custom key binding.
4769+ */
4770+ function removeKeyBinding( keyCode ) {
4771+
4772+ delete registeredKeyBindings[keyCode];
4773+
4774+ }
4775+
4776+ /**
4777+ * Extend object a with the properties of object b.
4778+ * If there's a conflict, object b takes precedence.
4779+ *
4780+ * @param {object} a
4781+ * @param {object} b
4782+ */
4783+ function extend( a, b ) {
4784+
4785+ for( var i in b ) {
4786+ a[ i ] = b[ i ];
4787+ }
4788+
4789+ return a;
4790+
4791+ }
4792+
4793+ /**
4794+ * Converts the target object to an array.
4795+ *
4796+ * @param {object} o
4797+ * @return {object[]}
4798+ */
4799+ function toArray( o ) {
4800+
4801+ return Array.prototype.slice.call( o );
4802+
4803+ }
4804+
4805+ /**
4806+ * Utility for deserializing a value.
4807+ *
4808+ * @param {*} value
4809+ * @return {*}
4810+ */
4811+ function deserialize( value ) {
4812+
4813+ if( typeof value === 'string' ) {
4814+ if( value === 'null' ) return null;
4815+ else if( value === 'true' ) return true;
4816+ else if( value === 'false' ) return false;
4817+ else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );
4818+ }
4819+
4820+ return value;
4821+
4822+ }
4823+
4824+ /**
4825+ * Measures the distance in pixels between point a
4826+ * and point b.
4827+ *
4828+ * @param {object} a point with x/y properties
4829+ * @param {object} b point with x/y properties
4830+ *
4831+ * @return {number}
4832+ */
4833+ function distanceBetween( a, b ) {
4834+
4835+ var dx = a.x - b.x,
4836+ dy = a.y - b.y;
4837+
4838+ return Math.sqrt( dx*dx + dy*dy );
4839+
4840+ }
4841+
4842+ /**
4843+ * Applies a CSS transform to the target element.
4844+ *
4845+ * @param {HTMLElement} element
4846+ * @param {string} transform
4847+ */
4848+ function transformElement( element, transform ) {
4849+
4850+ element.style.WebkitTransform = transform;
4851+ element.style.MozTransform = transform;
4852+ element.style.msTransform = transform;
4853+ element.style.transform = transform;
4854+
4855+ }
4856+
4857+ /**
4858+ * Applies CSS transforms to the slides container. The container
4859+ * is transformed from two separate sources: layout and the overview
4860+ * mode.
4861+ *
4862+ * @param {object} transforms
4863+ */
4864+ function transformSlides( transforms ) {
4865+
4866+ // Pick up new transforms from arguments
4867+ if( typeof transforms.layout === 'string' ) slidesTransform.layout = transforms.layout;
4868+ if( typeof transforms.overview === 'string' ) slidesTransform.overview = transforms.overview;
4869+
4870+ // Apply the transforms to the slides container
4871+ if( slidesTransform.layout ) {
4872+ transformElement( dom.slides, slidesTransform.layout + ' ' + slidesTransform.overview );
4873+ }
4874+ else {
4875+ transformElement( dom.slides, slidesTransform.overview );
4876+ }
4877+
4878+ }
4879+
4880+ /**
4881+ * Injects the given CSS styles into the DOM.
4882+ *
4883+ * @param {string} value
4884+ */
4885+ function injectStyleSheet( value ) {
4886+
4887+ var tag = document.createElement( 'style' );
4888+ tag.type = 'text/css';
4889+ if( tag.styleSheet ) {
4890+ tag.styleSheet.cssText = value;
4891+ }
4892+ else {
4893+ tag.appendChild( document.createTextNode( value ) );
4894+ }
4895+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
4896+
4897+ }
4898+
4899+ /**
4900+ * Find the closest parent that matches the given
4901+ * selector.
4902+ *
4903+ * @param {HTMLElement} target The child element
4904+ * @param {String} selector The CSS selector to match
4905+ * the parents against
4906+ *
4907+ * @return {HTMLElement} The matched parent or null
4908+ * if no matching parent was found
4909+ */
4910+ function closestParent( target, selector ) {
4911+
4912+ var parent = target.parentNode;
4913+
4914+ while( parent ) {
4915+
4916+ // There's some overhead doing this each time, we don't
4917+ // want to rewrite the element prototype but should still
4918+ // be enough to feature detect once at startup...
4919+ var matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;
4920+
4921+ // If we find a match, we're all set
4922+ if( matchesMethod && matchesMethod.call( parent, selector ) ) {
4923+ return parent;
4924+ }
4925+
4926+ // Keep searching
4927+ parent = parent.parentNode;
4928+
4929+ }
4930+
4931+ return null;
4932+
4933+ }
4934+
4935+ /**
4936+ * Converts various color input formats to an {r:0,g:0,b:0} object.
4937+ *
4938+ * @param {string} color The string representation of a color
4939+ * @example
4940+ * colorToRgb('#000');
4941+ * @example
4942+ * colorToRgb('#000000');
4943+ * @example
4944+ * colorToRgb('rgb(0,0,0)');
4945+ * @example
4946+ * colorToRgb('rgba(0,0,0)');
4947+ *
4948+ * @return {{r: number, g: number, b: number, [a]: number}|null}
4949+ */
4950+ function colorToRgb( color ) {
4951+
4952+ var hex3 = color.match( /^#([0-9a-f]{3})$/i );
4953+ if( hex3 && hex3[1] ) {
4954+ hex3 = hex3[1];
4955+ return {
4956+ r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
4957+ g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
4958+ b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
4959+ };
4960+ }
4961+
4962+ var hex6 = color.match( /^#([0-9a-f]{6})$/i );
4963+ if( hex6 && hex6[1] ) {
4964+ hex6 = hex6[1];
4965+ return {
4966+ r: parseInt( hex6.substr( 0, 2 ), 16 ),
4967+ g: parseInt( hex6.substr( 2, 2 ), 16 ),
4968+ b: parseInt( hex6.substr( 4, 2 ), 16 )
4969+ };
4970+ }
4971+
4972+ var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
4973+ if( rgb ) {
4974+ return {
4975+ r: parseInt( rgb[1], 10 ),
4976+ g: parseInt( rgb[2], 10 ),
4977+ b: parseInt( rgb[3], 10 )
4978+ };
4979+ }
4980+
4981+ var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
4982+ if( rgba ) {
4983+ return {
4984+ r: parseInt( rgba[1], 10 ),
4985+ g: parseInt( rgba[2], 10 ),
4986+ b: parseInt( rgba[3], 10 ),
4987+ a: parseFloat( rgba[4] )
4988+ };
4989+ }
4990+
4991+ return null;
4992+
4993+ }
4994+
4995+ /**
4996+ * Calculates brightness on a scale of 0-255.
4997+ *
4998+ * @param {string} color See colorToRgb for supported formats.
4999+ * @see {@link colorToRgb}
5000+ */
The diff has been truncated for viewing.