├── .gitignore ├── LICENSE ├── README.md ├── _quarto.yml ├── custom.scss ├── data ├── country_rank.rds ├── goalscorers.csv ├── preprocessing.R ├── results.csv ├── shootouts.csv ├── soccer_matches.rds └── soccer_scorers.rds ├── docs ├── images │ └── logo.png ├── index.html ├── search.json ├── shiny101-modular-app-blueprint.html └── site_libs │ ├── clipboard │ └── clipboard.min.js │ ├── quarto-html │ ├── light-border.css │ ├── popper.min.js │ ├── quarto-html.min.css │ ├── quarto-syntax-highlighting.css │ ├── tabby.min.js │ ├── tippy.css │ └── tippy.umd.min.js │ └── revealjs │ ├── dist │ ├── reset.css │ ├── reveal.css │ ├── reveal.esm.js │ ├── reveal.esm.js.map │ ├── reveal.js │ ├── reveal.js.map │ └── theme │ │ ├── fonts │ │ ├── league-gothic │ │ │ ├── LICENSE │ │ │ ├── league-gothic.css │ │ │ ├── league-gothic.eot │ │ │ ├── league-gothic.ttf │ │ │ └── league-gothic.woff │ │ └── source-sans-pro │ │ │ ├── LICENSE │ │ │ ├── source-sans-pro-italic.eot │ │ │ ├── source-sans-pro-italic.ttf │ │ │ ├── source-sans-pro-italic.woff │ │ │ ├── source-sans-pro-regular.eot │ │ │ ├── source-sans-pro-regular.ttf │ │ │ ├── source-sans-pro-regular.woff │ │ │ ├── source-sans-pro-semibold.eot │ │ │ ├── source-sans-pro-semibold.ttf │ │ │ ├── source-sans-pro-semibold.woff │ │ │ ├── source-sans-pro-semibolditalic.eot │ │ │ ├── source-sans-pro-semibolditalic.ttf │ │ │ ├── source-sans-pro-semibolditalic.woff │ │ │ └── source-sans-pro.css │ │ └── quarto.css │ └── plugin │ ├── highlight │ ├── highlight.esm.js │ ├── highlight.js │ ├── monokai.css │ ├── plugin.js │ └── zenburn.css │ ├── markdown │ ├── markdown.esm.js │ ├── markdown.js │ └── plugin.js │ ├── math │ ├── katex.js │ ├── math.esm.js │ ├── math.js │ ├── mathjax2.js │ ├── mathjax3.js │ └── plugin.js │ ├── notes │ ├── notes.esm.js │ ├── notes.js │ ├── plugin.js │ └── speaker-view.html │ ├── pdf-export │ ├── pdfexport.js │ └── plugin.yml │ ├── quarto-line-highlight │ ├── line-highlight.css │ ├── line-highlight.js │ └── plugin.yml │ ├── quarto-support │ ├── footer.css │ ├── plugin.yml │ └── support.js │ ├── reveal-menu │ ├── menu.css │ ├── menu.js │ ├── plugin.yml │ ├── quarto-menu.css │ └── quarto-menu.js │ ├── search │ ├── plugin.js │ ├── search.esm.js │ └── search.js │ └── zoom │ ├── plugin.js │ ├── zoom.esm.js │ └── zoom.js ├── images ├── ProductioniZingShiny.png ├── athlyticz.png ├── background.png └── logo.png ├── shescores └── app.R ├── shiny101-modular-app-blueprint.qmd ├── shinyconf2024-shiny101.Rproj └── templates ├── 00_base.R ├── 01_start.R ├── 02_bslib.R ├── 03_modules.R ├── 04_nested_modules.R └── 05_sharing_data.R /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | .RDataTmp 8 | 9 | # User-specific files 10 | .Ruserdata 11 | 12 | # Example code in package build process 13 | *-Ex.R 14 | 15 | # Output files from R CMD build 16 | /*.tar.gz 17 | 18 | # Output files from R CMD check 19 | /*.Rcheck/ 20 | 21 | # RStudio files 22 | .Rproj.user/ 23 | 24 | # produced vignettes 25 | vignettes/*.html 26 | vignettes/*.pdf 27 | 28 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 29 | .httr-oauth 30 | 31 | # knitr and R markdown default cache directories 32 | *_cache/ 33 | /cache/ 34 | 35 | # Temporary files created by R markdown 36 | *.utf8.md 37 | *.knit.md 38 | 39 | # R Environment Variables 40 | .Renviron 41 | 42 | # translation temp files 43 | po/*~ 44 | 45 | # RStudio Connect folder 46 | rsconnect/ 47 | 48 | /.quarto/ 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Veerle van Leemput 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShinyConf2024 workshop: Modular App Blue print 2 | 3 | This repository contains the code for the ShinyConf2024 workshop on building modular Shiny apps. The workshop will be held on the 17th of April, 2024 📅. 4 | 5 | ## About the data 6 | 7 | In this workshop, we will be using data related to Women's International Football results ⚽️. The data is available in the `data` folder and it contains some pre-processing. The data is sourced from [Kaggle](https://www.kaggle.com/datasets/martj42/womens-international-football-results?resource=download). 8 | 9 | ## Shiny courses 10 | 11 | Want to keep learning? We are launching two online courses that are all about Shiny ✨: 12 | 13 | - The [ProductioniZing Shiny Course](https://athlyticz.com/shiny-ii), during which you will learn everything about Shiny: from building your first app to testing and deployment 🚀. 14 | - The [CustomiZing WidgetZ Course](https://athlyticz.com/shiny-iii), which is all about creating outstanding user interfaces with Shiny. During this course you will build your own Shiny package and some very cool widgets. 15 | 16 | [](https://athlyticz.com/shiny-ii) 17 | 18 | 19 | ## Other resources 20 | 21 | Some of my favourite resources to learn more about Shiny: 22 | 23 | - [Outstanding User Interfaces with Shiny](https://unleash-shiny.rinterface.com) by David Granjon 24 | - [Mastering Shiny](https://mastering-shiny.org) 25 | - [Engineering Production-Grade Shiny Applications](https://engineering-shiny.org) 26 | - [Official Shiny Documentation](https://shiny.posit.co/r/getstarted/shiny-basics/lesson1/index.html) 27 | -------------------------------------------------------------------------------- /_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | title: "Shiny 101: The Modular App Blueprint" 3 | type: website 4 | output-dir: docs 5 | -------------------------------------------------------------------------------- /custom.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:defaults --*/ 2 | $theme-darkblue: #085088; 3 | $theme-blue: #3c8fbc; 4 | $theme-white: #F2F2F2; 5 | $theme-pink: #C567A3; 6 | $theme-gray: #9BAEBC; 7 | $theme-oldpink: #d1bcc8; 8 | $theme-darkgrey: #20313a; 9 | 10 | $body-bg: $theme-white; 11 | $link-color: $theme-blue; 12 | $body-color: $theme-darkgrey; 13 | $presentation-heading-color: $theme-darkgrey; 14 | 15 | @import url('https://fonts.googleapis.com/css?family=Lato:400,700'); 16 | 17 | $presentation-heading-font: 'Lato', serif; 18 | $font-family-sans-serif: 'Lato', sans-serif; 19 | 20 | .reveal .slide code { 21 | color: $theme-darkblue; 22 | } 23 | 24 | .reveal h2 { 25 | padding-bottom: 40px; 26 | } 27 | 28 | .reveal .slide ul { 29 | list-style-type: square; 30 | } 31 | .reveal .slide ul li::marker { 32 | color: $theme-blue; 33 | } 34 | 35 | .reveal .footer { 36 | height: 10%; 37 | max-height: 50px; 38 | min-height: 20px; 39 | bottom: 0px !important; 40 | background-color: $theme-oldpink; 41 | z-index: 1 !important; 42 | } 43 | 44 | .reveal .footer a { 45 | color: $theme-white !important; 46 | } 47 | 48 | .reveal .footer p { 49 | margin-top: 0.5em; 50 | } 51 | -------------------------------------------------------------------------------- /data/country_rank.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/data/country_rank.rds -------------------------------------------------------------------------------- /data/preprocessing.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(countrycode) 3 | 4 | # Read the csv files 5 | results <- read.csv("data/results.csv") 6 | goalscorers <- read.csv("data/goalscorers.csv") 7 | 8 | # Merge the two dataframes on date, home_team, away_team 9 | # Remove any games with no known outcome 10 | data_scorers <- results |> 11 | left_join(goalscorers, 12 | by = c("date", "home_team", "away_team")) |> 13 | filter(!is.na(home_score) | !is.na(away_score)) 14 | 15 | # function to get the unicode for the country flag 16 | get_flag <- function(country_codes) { 17 | sapply(country_codes, function(country_code) { 18 | # question mark emoji 19 | if (is.null(country_code) || is.na(country_code)) { 20 | return(intToUtf8(10067)) 21 | } else { 22 | intToUtf8(127397 + strtoi(charToRaw(toupper(country_code)), 16L)) 23 | } 24 | }) |> 25 | as.vector() 26 | } 27 | 28 | data_scorers <- 29 | data_scorers |> 30 | left_join(select(countrycode::codelist, c(country.name.en, iso2c)), 31 | by = c("home_team" = "country.name.en")) |> 32 | mutate(country_flag_home = get_flag(iso2c)) |> 33 | select(-iso2c) |> 34 | left_join(select(countrycode::codelist, c(country.name.en, iso2c)), 35 | by = c("away_team" = "country.name.en")) |> 36 | mutate(country_flag_away = get_flag(iso2c)) |> 37 | select(-iso2c) |> 38 | group_by(scorer, team) |> 39 | summarise(goals = sum(ifelse(team == home_team, home_score, away_score)), 40 | penalties = sum(penalty), 41 | country_flag = first(ifelse(team == home_team, country_flag_home, country_flag_away))) |> 42 | arrange(desc(goals)) 43 | 44 | 45 | data_matches <- results |> 46 | filter(!is.na(home_score) | !is.na(away_score)) |> 47 | left_join(select(countrycode::codelist, c(country.name.en, iso2c)), 48 | by = c("home_team" = "country.name.en")) |> 49 | mutate(country_flag_home = get_flag(iso2c)) |> 50 | select(-iso2c) |> 51 | left_join(select(countrycode::codelist, c(country.name.en, iso2c)), 52 | by = c("away_team" = "country.name.en")) |> 53 | mutate(country_flag_away = get_flag(iso2c)) |> 54 | select(-iso2c) 55 | 56 | # construct country rank 57 | # Get the number of matches played by each country 58 | # Get the number of goals scored by each country 59 | # Both when the country when was in the home_team and away_team 60 | country_rank <- data_matches |> 61 | group_by(home_team) |> 62 | summarise( 63 | country = first(home_team), 64 | matches = n(), 65 | goals = sum(home_score), 66 | country_flag = first(country_flag_home) 67 | ) |> 68 | bind_rows( 69 | data_matches |> 70 | group_by(away_team) |> 71 | summarise( 72 | country = first(away_team), 73 | matches = n(), 74 | goals = sum(away_score), 75 | country_flag = first(country_flag_away) 76 | ) 77 | ) |> 78 | group_by(country) |> 79 | summarise( 80 | matches = sum(matches), 81 | goals = sum(goals), 82 | country_flag = first(country_flag) 83 | ) |> 84 | arrange(desc(goals)) 85 | 86 | saveRDS(data_scorers, "data/soccer_scorers.rds") 87 | saveRDS(data_matches, "data/soccer_matches.rds") 88 | saveRDS(country_rank, "data/country_rank.rds") 89 | -------------------------------------------------------------------------------- /data/shootouts.csv: -------------------------------------------------------------------------------- 1 | date,home_team,away_team,winner 2 | 1995-06-13,Sweden,China PR,China PR 3 | 1998-12-15,North Korea,Taiwan,North Korea 4 | 1999-07-10,Brazil,Norway,Brazil 5 | 1999-07-10,United States,China PR,United States 6 | 1999-08-02,Canada,Mexico,Mexico 7 | 1999-08-04,Canada,Costa Rica,Costa Rica 8 | 2001-09-12,Vietnam,Myanmar,Vietnam 9 | 2006-12-13,Japan,North Korea,North Korea 10 | 2009-03-12,France,New Zealand,France 11 | 2009-12-16,Thailand,Vietnam,Vietnam 12 | 2011-06-15,Colombia,Denmark,Colombia 13 | 2011-07-09,England,France,France 14 | 2011-07-10,Brazil,United States,United States 15 | 2011-07-17,Japan,United States,Japan 16 | 2011-10-27,Brazil,Canada,Canada 17 | 2012-03-06,New Zealand,Netherlands,Netherlands 18 | 2012-03-06,South Korea,Finland,South Korea 19 | 2013-03-11,Slovenia,Bosnia and Herzegovina,Slovenia 20 | 2013-03-11,Russia,Czech Republic,Russia 21 | 2013-03-13,South Africa,Northern Ireland,South Africa 22 | 2013-12-18,Myanmar,Thailand,Thailand 23 | 2014-03-12,Scotland,South Korea,South Korea 24 | 2015-03-11,Northern Ireland,Bosnia and Herzegovina,Bosnia and Herzegovina 25 | 2015-03-11,Croatia,Wales,Wales 26 | 2015-03-11,South Korea,Belgium,South Korea 27 | 2015-06-26,Germany,France,Germany 28 | 2017-03-08,Belgium,Austria,Belgium 29 | 2017-03-08,Scotland,Wales,Scotland 30 | 2018-03-06,Poland,Ukraine,Ukraine 31 | 2018-03-07,Wales,Austria,Austria 32 | 2018-08-24,Taiwan,Vietnam,Taiwan 33 | 2019-03-04,Croatia,Montenegro,Montenegro 34 | 2019-03-05,India,Kazakhstan,Kazakhstan 35 | 2019-03-06,Austria,Belgium,Belgium 36 | 2019-03-06,North Korea,Italy,North Korea 37 | 2019-06-22,Norway,Australia,Norway 38 | 2019-08-09,Argentina,Colombia,Colombia 39 | 2019-09-01,Brazil,Chile,Chile 40 | 2019-11-10,China PR,Brazil,China PR 41 | 2021-04-13,Switzerland,Czech Republic,Switzerland 42 | 2021-06-10,Estonia,Faroe Islands,Faroe Islands 43 | 2021-07-30,Canada,Brazil,Canada 44 | 2021-07-30,Netherlands,United States,United States 45 | 2021-08-06,Sweden,Canada,Canada 46 | 2021-09-25,Jordan,Iran,Iran 47 | 2021-10-26,Ethiopia,Uganda,Uganda 48 | 2022-01-30,Taiwan,Philippines,Philippines 49 | 2022-02-03,China PR,Japan,China PR 50 | 2022-02-16,Hungary,Russia,Russia 51 | 2022-02-19,Wales,Belgium,Belgium 52 | 2022-02-22,Scotland,Hungary,Scotland 53 | 2022-02-22,Belgium,Russia,Belgium 54 | 2022-02-22,Mali,Senegal,Senegal 55 | 2022-02-23,Sweden,Italy,Sweden 56 | 2022-07-13,Zambia,Senegal,Zambia 57 | 2022-07-17,Senegal,Tunisia,Senegal 58 | 2022-07-18,Morocco,Nigeria,Morocco 59 | 2022-07-23,Papua New Guinea,Tonga,Papua New Guinea 60 | 2022-07-24,Chile,Venezuela,Chile 61 | 2022-07-30,Samoa,Solomon Islands,Solomon Islands 62 | 2023-02-19,Taiwan,Paraguay,Paraguay 63 | 2023-04-06,England,Brazil,England 64 | 2023-08-06,Sweden,United States,Sweden 65 | 2023-08-07,England,Nigeria,England 66 | 2023-08-12,Australia,France,Australia -------------------------------------------------------------------------------- /data/soccer_matches.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/data/soccer_matches.rds -------------------------------------------------------------------------------- /data/soccer_scorers.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/data/soccer_scorers.rds -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/images/logo.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Redirect to shiny101-modular-app-blueprint.html 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/site_libs/clipboard/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.11 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light-border]>.tippy-arrow:after,.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=light-border]>.tippy-arrow:after{border-color:transparent;border-style:solid}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:after{border-top-color:rgba(0,8,16,.2);border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:rgba(0,8,16,.2);border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:after{border-left-color:rgba(0,8,16,.2);border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:rgba(0,8,16,.2)}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow{fill:#fff}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px} -------------------------------------------------------------------------------- /docs/site_libs/quarto-html/quarto-html.min.css: -------------------------------------------------------------------------------- 1 | /*# sourceMappingURL=0a6b880beb84f9b6f36107a76f82c5b1.css.map */ 2 | -------------------------------------------------------------------------------- /docs/site_libs/quarto-html/quarto-syntax-highlighting.css: -------------------------------------------------------------------------------- 1 | /* quarto syntax highlight colors */ 2 | :root { 3 | --quarto-hl-al-color: #bf616a; 4 | --quarto-hl-an-color: #d08770; 5 | --quarto-hl-at-color: #8fbcbb; 6 | --quarto-hl-bn-color: #b48ead; 7 | --quarto-hl-bu-color: #88c0d0; 8 | --quarto-hl-ch-color: #ebcb8b; 9 | --quarto-hl-co-color: #616e88; 10 | --quarto-hl-cv-color: #e5e9f0; 11 | --quarto-hl-cn-color: #eceff4; 12 | --quarto-hl-cf-color: #81a1c1; 13 | --quarto-hl-dt-color: #81a1c1; 14 | --quarto-hl-dv-color: #b48ead; 15 | --quarto-hl-do-color: #5e81ac; 16 | --quarto-hl-er-color: #bf616a; 17 | --quarto-hl-ex-color: #8fbcbb; 18 | --quarto-hl-fl-color: #b48ead; 19 | --quarto-hl-fu-color: #88c0d0; 20 | --quarto-hl-im-color: #a3be8c; 21 | --quarto-hl-in-color: #ebcb8b; 22 | --quarto-hl-kw-color: #81a1c1; 23 | --quarto-hl-op-color: #81a1c1; 24 | --quarto-hl-pp-color: #5e81ac; 25 | --quarto-hl-re-color: #88c0d0; 26 | --quarto-hl-sc-color: #ebcb8b; 27 | --quarto-hl-ss-color: #d08770; 28 | --quarto-hl-st-color: #a3be8c; 29 | --quarto-hl-va-color: #5e81ac; 30 | --quarto-hl-vs-color: #a3be8c; 31 | --quarto-hl-wa-color: #bf616a; 32 | } 33 | 34 | /* other quarto variables */ 35 | :root { 36 | --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 37 | } 38 | 39 | code span.al { 40 | background-color: #3b4252; 41 | font-weight: bold; 42 | color: #bf616a; 43 | } 44 | 45 | code span.an { 46 | color: #d08770; 47 | } 48 | 49 | code span.at { 50 | color: #8fbcbb; 51 | } 52 | 53 | code span.bn { 54 | color: #b48ead; 55 | } 56 | 57 | code span.bu { 58 | font-style: italic; 59 | color: #88c0d0; 60 | } 61 | 62 | code span.ch { 63 | color: #ebcb8b; 64 | } 65 | 66 | code span.co { 67 | color: #616e88; 68 | } 69 | 70 | code span.cv { 71 | color: #e5e9f0; 72 | } 73 | 74 | code span.cn { 75 | font-weight: bold; 76 | color: #eceff4; 77 | } 78 | 79 | code span.cf { 80 | font-weight: bold; 81 | color: #81a1c1; 82 | } 83 | 84 | code span.dt { 85 | color: #81a1c1; 86 | } 87 | 88 | code span.dv { 89 | color: #b48ead; 90 | } 91 | 92 | code span.do { 93 | color: #5e81ac; 94 | } 95 | 96 | code span.er { 97 | color: #bf616a; 98 | text-decoration: underline; 99 | } 100 | 101 | code span.ex { 102 | font-weight: bold; 103 | color: #8fbcbb; 104 | } 105 | 106 | code span.fl { 107 | color: #b48ead; 108 | } 109 | 110 | code span.fu { 111 | color: #88c0d0; 112 | } 113 | 114 | code span.im { 115 | color: #a3be8c; 116 | } 117 | 118 | code span.in { 119 | color: #ebcb8b; 120 | } 121 | 122 | code span.kw { 123 | font-weight: bold; 124 | color: #81a1c1; 125 | } 126 | 127 | pre > code.sourceCode > span { 128 | color: #d8dee9; 129 | } 130 | 131 | code span { 132 | color: #d8dee9; 133 | } 134 | 135 | code.sourceCode > span { 136 | color: #d8dee9; 137 | } 138 | 139 | div.sourceCode, 140 | div.sourceCode pre.sourceCode { 141 | color: #d8dee9; 142 | } 143 | 144 | code span.op { 145 | color: #81a1c1; 146 | } 147 | 148 | code span.pp { 149 | color: #5e81ac; 150 | } 151 | 152 | code span.re { 153 | background-color: #3b4252; 154 | color: #88c0d0; 155 | } 156 | 157 | code span.sc { 158 | color: #ebcb8b; 159 | } 160 | 161 | code span.ss { 162 | color: #d08770; 163 | } 164 | 165 | code span.st { 166 | color: #a3be8c; 167 | } 168 | 169 | code span.va { 170 | color: #5e81ac; 171 | } 172 | 173 | code span.vs { 174 | color: #a3be8c; 175 | } 176 | 177 | code span.wa { 178 | color: #bf616a; 179 | } 180 | 181 | .prevent-inlining { 182 | content: " !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden") 84 | ); 85 | }; 86 | 87 | /** 88 | * Remove roles and attributes from a tab and its content 89 | * @param {Node} tab The tab 90 | * @param {Node} content The tab content 91 | * @param {Object} settings User settings and options 92 | */ 93 | var destroyTab = function (tab, content, settings) { 94 | // Remove the generated ID 95 | if (tab.id.slice(0, settings.idPrefix.length) === settings.idPrefix) { 96 | tab.id = ""; 97 | } 98 | 99 | // remove event listener 100 | tab.removeEventListener("focus", focusHandler, true); 101 | 102 | // Remove roles 103 | tab.removeAttribute("role"); 104 | tab.removeAttribute("aria-controls"); 105 | tab.removeAttribute("aria-selected"); 106 | tab.removeAttribute("tabindex"); 107 | tab.closest("li").removeAttribute("role"); 108 | content.removeAttribute("role"); 109 | content.removeAttribute("aria-labelledby"); 110 | content.removeAttribute("hidden"); 111 | }; 112 | 113 | /** 114 | * Add the required roles and attributes to a tab and its content 115 | * @param {Node} tab The tab 116 | * @param {Node} content The tab content 117 | * @param {Object} settings User settings and options 118 | */ 119 | var setupTab = function (tab, content, settings) { 120 | // Give tab an ID if it doesn't already have one 121 | if (!tab.id) { 122 | tab.id = settings.idPrefix + content.id; 123 | } 124 | 125 | // Add roles 126 | tab.setAttribute("role", "tab"); 127 | tab.setAttribute("aria-controls", content.id); 128 | tab.closest("li").setAttribute("role", "presentation"); 129 | content.setAttribute("role", "tabpanel"); 130 | content.setAttribute("aria-labelledby", tab.id); 131 | 132 | // Add selected state 133 | if (tab.matches(settings.default)) { 134 | tab.setAttribute("aria-selected", "true"); 135 | } else { 136 | tab.setAttribute("aria-selected", "false"); 137 | content.setAttribute("hidden", "hidden"); 138 | } 139 | 140 | // add focus event listender 141 | tab.addEventListener("focus", focusHandler); 142 | }; 143 | 144 | /** 145 | * Hide a tab and its content 146 | * @param {Node} newTab The new tab that's replacing it 147 | */ 148 | var hide = function (newTab) { 149 | // Variables 150 | var tabGroup = newTab.closest('[role="tablist"]'); 151 | if (!tabGroup) return {}; 152 | var tab = tabGroup.querySelector('[role="tab"][aria-selected="true"]'); 153 | if (!tab) return {}; 154 | var content = document.querySelector(tab.hash); 155 | 156 | // Hide the tab 157 | tab.setAttribute("aria-selected", "false"); 158 | 159 | // Hide the content 160 | if (!content) return { previousTab: tab }; 161 | content.setAttribute("hidden", "hidden"); 162 | 163 | // Return the hidden tab and content 164 | return { 165 | previousTab: tab, 166 | previousContent: content, 167 | }; 168 | }; 169 | 170 | /** 171 | * Show a tab and its content 172 | * @param {Node} tab The tab 173 | * @param {Node} content The tab content 174 | */ 175 | var show = function (tab, content) { 176 | tab.setAttribute("aria-selected", "true"); 177 | content.removeAttribute("hidden"); 178 | tab.focus(); 179 | }; 180 | 181 | /** 182 | * Toggle a new tab 183 | * @param {Node} tab The tab to show 184 | */ 185 | var toggle = function (tab) { 186 | // Make sure there's a tab to toggle and it's not already active 187 | if (!tab || tab.getAttribute("aria-selected") == "true") return; 188 | 189 | // Variables 190 | var content = document.querySelector(tab.hash); 191 | if (!content) return; 192 | 193 | // Hide active tab and content 194 | var details = hide(tab); 195 | 196 | // Show new tab and content 197 | show(tab, content); 198 | 199 | // Add event details 200 | details.tab = tab; 201 | details.content = content; 202 | 203 | // Emit a custom event 204 | emitEvent(tab, details); 205 | }; 206 | 207 | /** 208 | * Get all of the tabs in a tablist 209 | * @param {Node} tab A tab from the list 210 | * @return {Object} The tabs and the index of the currently active one 211 | */ 212 | var getTabsMap = function (tab) { 213 | var tabGroup = tab.closest('[role="tablist"]'); 214 | var tabs = tabGroup ? tabGroup.querySelectorAll('[role="tab"]') : null; 215 | if (!tabs) return; 216 | return { 217 | tabs: tabs, 218 | index: Array.prototype.indexOf.call(tabs, tab), 219 | }; 220 | }; 221 | 222 | /** 223 | * Switch the active tab based on keyboard activity 224 | * @param {Node} tab The currently active tab 225 | * @param {Key} key The key that was pressed 226 | */ 227 | var switchTabs = function (tab, key) { 228 | // Get a map of tabs 229 | var map = getTabsMap(tab); 230 | if (!map) return; 231 | var length = map.tabs.length - 1; 232 | var index; 233 | 234 | // Go to previous tab 235 | if (["ArrowUp", "ArrowLeft", "Up", "Left"].indexOf(key) > -1) { 236 | index = map.index < 1 ? length : map.index - 1; 237 | } 238 | 239 | // Go to next tab 240 | else if (["ArrowDown", "ArrowRight", "Down", "Right"].indexOf(key) > -1) { 241 | index = map.index === length ? 0 : map.index + 1; 242 | } 243 | 244 | // Go to home 245 | else if (key === "Home") { 246 | index = 0; 247 | } 248 | 249 | // Go to end 250 | else if (key === "End") { 251 | index = length; 252 | } 253 | 254 | // Toggle the tab 255 | toggle(map.tabs[index]); 256 | }; 257 | 258 | /** 259 | * Create the Constructor object 260 | */ 261 | var Constructor = function (selector, options) { 262 | // 263 | // Variables 264 | // 265 | 266 | var publicAPIs = {}; 267 | var settings, tabWrapper; 268 | 269 | // 270 | // Methods 271 | // 272 | 273 | publicAPIs.destroy = function () { 274 | // Get all tabs 275 | var tabs = tabWrapper.querySelectorAll("a"); 276 | 277 | // Add roles to tabs 278 | Array.prototype.forEach.call(tabs, function (tab) { 279 | // Get the tab content 280 | var content = document.querySelector(tab.hash); 281 | if (!content) return; 282 | 283 | // Setup the tab 284 | destroyTab(tab, content, settings); 285 | }); 286 | 287 | // Remove role from wrapper 288 | tabWrapper.removeAttribute("role"); 289 | 290 | // Remove event listeners 291 | document.documentElement.removeEventListener( 292 | "click", 293 | clickHandler, 294 | true 295 | ); 296 | tabWrapper.removeEventListener("keydown", keyHandler, true); 297 | 298 | // Reset variables 299 | settings = null; 300 | tabWrapper = null; 301 | }; 302 | 303 | /** 304 | * Setup the DOM with the proper attributes 305 | */ 306 | publicAPIs.setup = function () { 307 | // Variables 308 | tabWrapper = document.querySelector(selector); 309 | if (!tabWrapper) return; 310 | var tabs = tabWrapper.querySelectorAll("a"); 311 | 312 | // Add role to wrapper 313 | tabWrapper.setAttribute("role", "tablist"); 314 | 315 | // Add roles to tabs. provide dynanmic tab indexes if we are within reveal 316 | var contentTabindexes = 317 | window.document.body.classList.contains("reveal-viewport"); 318 | var nextTabindex = 1; 319 | Array.prototype.forEach.call(tabs, function (tab) { 320 | if (contentTabindexes) { 321 | tab.setAttribute("tabindex", "" + nextTabindex++); 322 | } else { 323 | tab.setAttribute("tabindex", "0"); 324 | } 325 | 326 | // Get the tab content 327 | var content = document.querySelector(tab.hash); 328 | if (!content) return; 329 | 330 | // set tab indexes for content 331 | if (contentTabindexes) { 332 | getKeyboardFocusableElements(content).forEach(function (el) { 333 | el.setAttribute("tabindex", "" + nextTabindex++); 334 | }); 335 | } 336 | 337 | // Setup the tab 338 | setupTab(tab, content, settings); 339 | }); 340 | }; 341 | 342 | /** 343 | * Toggle a tab based on an ID 344 | * @param {String|Node} id The tab to toggle 345 | */ 346 | publicAPIs.toggle = function (id) { 347 | // Get the tab 348 | var tab = id; 349 | if (typeof id === "string") { 350 | tab = document.querySelector( 351 | selector + ' [role="tab"][href*="' + id + '"]' 352 | ); 353 | } 354 | 355 | // Toggle the tab 356 | toggle(tab); 357 | }; 358 | 359 | /** 360 | * Handle click events 361 | */ 362 | var clickHandler = function (event) { 363 | // Only run on toggles 364 | var tab = event.target.closest(selector + ' [role="tab"]'); 365 | if (!tab) return; 366 | 367 | // Prevent link behavior 368 | event.preventDefault(); 369 | 370 | // Toggle the tab 371 | toggle(tab); 372 | }; 373 | 374 | /** 375 | * Handle keydown events 376 | */ 377 | var keyHandler = function (event) { 378 | // Only run if a tab is in focus 379 | var tab = document.activeElement; 380 | if (!tab.matches(selector + ' [role="tab"]')) return; 381 | 382 | // Only run for specific keys 383 | if (["Home", "End"].indexOf(event.key) < 0) return; 384 | 385 | // Switch tabs 386 | switchTabs(tab, event.key); 387 | }; 388 | 389 | /** 390 | * Initialize the instance 391 | */ 392 | var init = function () { 393 | // Merge user options with defaults 394 | settings = extend(defaults, options || {}); 395 | 396 | // Setup the DOM 397 | publicAPIs.setup(); 398 | 399 | // Add event listeners 400 | document.documentElement.addEventListener("click", clickHandler, true); 401 | tabWrapper.addEventListener("keydown", keyHandler, true); 402 | }; 403 | 404 | // 405 | // Initialize and return the Public APIs 406 | // 407 | 408 | init(); 409 | return publicAPIs; 410 | }; 411 | 412 | // 413 | // Return the Constructor 414 | // 415 | 416 | return Constructor; 417 | } 418 | ); 419 | -------------------------------------------------------------------------------- /docs/site_libs/quarto-html/tippy.css: -------------------------------------------------------------------------------- 1 | .tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/league-gothic/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License (OFL) 2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('./league-gothic.eot'); 4 | src: url('./league-gothic.eot?#iefix') format('embedded-opentype'), 5 | url('./league-gothic.woff') format('woff'), 6 | url('./league-gothic.ttf') format('truetype'); 7 | 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.eot -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.ttf -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.woff -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License 2 | 3 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 7 | 8 | —————————————————————————————- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | —————————————————————————————- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 14 | 15 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 16 | 17 | DEFINITIONS 18 | “Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 19 | 20 | “Reserved Font Name” refers to any names specified as such after the copyright statement(s). 21 | 22 | “Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). 23 | 24 | “Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 25 | 26 | “Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 27 | 28 | PERMISSION & CONDITIONS 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 30 | 31 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 32 | 33 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 34 | 35 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 36 | 37 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 38 | 39 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 40 | 41 | TERMINATION 42 | This license becomes null and void if any of the above conditions are not met. 43 | 44 | DISCLAIMER 45 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff -------------------------------------------------------------------------------- /docs/site_libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Sans Pro'; 3 | src: url('./source-sans-pro-regular.eot'); 4 | src: url('./source-sans-pro-regular.eot?#iefix') format('embedded-opentype'), 5 | url('./source-sans-pro-regular.woff') format('woff'), 6 | url('./source-sans-pro-regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Source Sans Pro'; 13 | src: url('./source-sans-pro-italic.eot'); 14 | src: url('./source-sans-pro-italic.eot?#iefix') format('embedded-opentype'), 15 | url('./source-sans-pro-italic.woff') format('woff'), 16 | url('./source-sans-pro-italic.ttf') format('truetype'); 17 | font-weight: normal; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Source Sans Pro'; 23 | src: url('./source-sans-pro-semibold.eot'); 24 | src: url('./source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'), 25 | url('./source-sans-pro-semibold.woff') format('woff'), 26 | url('./source-sans-pro-semibold.ttf') format('truetype'); 27 | font-weight: 600; 28 | font-style: normal; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Source Sans Pro'; 33 | src: url('./source-sans-pro-semibolditalic.eot'); 34 | src: url('./source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'), 35 | url('./source-sans-pro-semibolditalic.woff') format('woff'), 36 | url('./source-sans-pro-semibolditalic.ttf') format('truetype'); 37 | font-weight: 600; 38 | font-style: italic; 39 | } 40 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/highlight/monokai.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; 10 | color: #ddd; 11 | } 12 | 13 | .hljs-tag, 14 | .hljs-keyword, 15 | .hljs-selector-tag, 16 | .hljs-literal, 17 | .hljs-strong, 18 | .hljs-name { 19 | color: #f92672; 20 | } 21 | 22 | .hljs-code { 23 | color: #66d9ef; 24 | } 25 | 26 | .hljs-class .hljs-title { 27 | color: white; 28 | } 29 | 30 | .hljs-attribute, 31 | .hljs-symbol, 32 | .hljs-regexp, 33 | .hljs-link { 34 | color: #bf79db; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-bullet, 39 | .hljs-subst, 40 | .hljs-title, 41 | .hljs-section, 42 | .hljs-emphasis, 43 | .hljs-type, 44 | .hljs-built_in, 45 | .hljs-builtin-name, 46 | .hljs-selector-attr, 47 | .hljs-selector-pseudo, 48 | .hljs-addition, 49 | .hljs-variable, 50 | .hljs-template-tag, 51 | .hljs-template-variable { 52 | color: #a6e22e; 53 | } 54 | 55 | .hljs-comment, 56 | .hljs-quote, 57 | .hljs-deletion, 58 | .hljs-meta { 59 | color: #75715e; 60 | } 61 | 62 | .hljs-keyword, 63 | .hljs-selector-tag, 64 | .hljs-literal, 65 | .hljs-doctag, 66 | .hljs-title, 67 | .hljs-section, 68 | .hljs-type, 69 | .hljs-selector-id { 70 | font-weight: bold; 71 | } 72 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/highlight/plugin.js: -------------------------------------------------------------------------------- 1 | import hljs from 'highlight.js'; 2 | 3 | /* highlightjs-line-numbers.js 2.8.0 | (C) 2018 Yauheni Pakala | MIT License | github.com/wcoder/highlightjs-line-numbers.js */ 4 | !function(r,o){"use strict";var e,i="hljs-ln",l="hljs-ln-line",h="hljs-ln-code",s="hljs-ln-numbers",c="hljs-ln-n",m="data-line-number",a=/\r\n|\r|\n/g;function u(e){for(var n=e.toString(),t=e.anchorNode;"TD"!==t.nodeName;)t=t.parentNode;for(var r=e.focusNode;"TD"!==r.nodeName;)r=r.parentNode;var o=parseInt(t.dataset.lineNumber),a=parseInt(r.dataset.lineNumber);if(o==a)return n;var i,l=t.textContent,s=r.textContent;for(a
{6}',[l,s,c,m,h,o+n.startFrom,0{1}',[i,r])}return e}(e.innerHTML,o)}function v(e){var n=e.className;if(/hljs-/.test(n)){for(var t=g(e.innerHTML),r=0,o="";r{1}\n',[n,0 { 38 | 39 | block.parentNode.classList.add('code-wrapper'); 40 | 41 | // Code can optionally be wrapped in script template to avoid 42 | // HTML being parsed by the browser (i.e. when you need to 43 | // include <, > or & in your code). 44 | let substitute = block.querySelector( 'script[type="text/template"]' ); 45 | if( substitute ) { 46 | // textContent handles the HTML entity escapes for us 47 | block.textContent = substitute.innerHTML; 48 | } 49 | 50 | // Trim whitespace if the "data-trim" attribute is present 51 | if( block.hasAttribute( 'data-trim' ) && typeof block.innerHTML.trim === 'function' ) { 52 | block.innerHTML = betterTrim( block ); 53 | } 54 | 55 | // Escape HTML tags unless the "data-noescape" attrbute is present 56 | if( config.escapeHTML && !block.hasAttribute( 'data-noescape' )) { 57 | block.innerHTML = block.innerHTML.replace( //g, '>' ); 58 | } 59 | 60 | // Re-highlight when focus is lost (for contenteditable code) 61 | block.addEventListener( 'focusout', function( event ) { 62 | hljs.highlightElement( event.currentTarget ); 63 | }, false ); 64 | 65 | } ); 66 | 67 | // Triggers a callback function before we trigger highlighting 68 | if( typeof config.beforeHighlight === 'function' ) { 69 | config.beforeHighlight( hljs ); 70 | } 71 | 72 | // Run initial highlighting for all code 73 | if( config.highlightOnLoad ) { 74 | Array.from( reveal.getRevealElement().querySelectorAll( 'pre code' ) ).forEach( block => { 75 | Plugin.highlightBlock( block ); 76 | } ); 77 | } 78 | 79 | // If we're printing to PDF, scroll the code highlights of 80 | // all blocks in the deck into view at once 81 | reveal.on( 'pdf-ready', function() { 82 | [].slice.call( reveal.getRevealElement().querySelectorAll( 'pre code[data-line-numbers].current-fragment' ) ).forEach( function( block ) { 83 | Plugin.scrollHighlightedLineIntoView( block, {}, true ); 84 | } ); 85 | } ); 86 | 87 | }, 88 | 89 | /** 90 | * Highlights a code block. If the node has the 91 | * 'data-line-numbers' attribute we also generate slide 92 | * numbers. 93 | * 94 | * If the block contains multiple line highlight steps, 95 | * we clone the block and create a fragment for each step. 96 | */ 97 | highlightBlock: function( block ) { 98 | 99 | hljs.highlightElement( block ); 100 | 101 | // Don't generate line numbers for empty code blocks 102 | if( block.innerHTML.trim().length === 0 ) return; 103 | 104 | if( block.hasAttribute( 'data-line-numbers' ) ) { 105 | hljs.lineNumbersBlock( block, { singleLine: true } ); 106 | 107 | var scrollState = { currentBlock: block }; 108 | 109 | // If there is more than one highlight step, generate 110 | // fragments 111 | var highlightSteps = Plugin.deserializeHighlightSteps( block.getAttribute( 'data-line-numbers' ) ); 112 | if( highlightSteps.length > 1 ) { 113 | 114 | // If the original code block has a fragment-index, 115 | // each clone should follow in an incremental sequence 116 | var fragmentIndex = parseInt( block.getAttribute( 'data-fragment-index' ), 10 ); 117 | 118 | if( typeof fragmentIndex !== 'number' || isNaN( fragmentIndex ) ) { 119 | fragmentIndex = null; 120 | } 121 | 122 | // Generate fragments for all steps except the original block 123 | highlightSteps.slice(1).forEach( function( highlight ) { 124 | 125 | var fragmentBlock = block.cloneNode( true ); 126 | fragmentBlock.setAttribute( 'data-line-numbers', Plugin.serializeHighlightSteps( [ highlight ] ) ); 127 | fragmentBlock.classList.add( 'fragment' ); 128 | block.parentNode.appendChild( fragmentBlock ); 129 | Plugin.highlightLines( fragmentBlock ); 130 | 131 | if( typeof fragmentIndex === 'number' ) { 132 | fragmentBlock.setAttribute( 'data-fragment-index', fragmentIndex ); 133 | fragmentIndex += 1; 134 | } 135 | else { 136 | fragmentBlock.removeAttribute( 'data-fragment-index' ); 137 | } 138 | 139 | // Scroll highlights into view as we step through them 140 | fragmentBlock.addEventListener( 'visible', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock, scrollState ) ); 141 | fragmentBlock.addEventListener( 'hidden', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock.previousSibling, scrollState ) ); 142 | 143 | } ); 144 | 145 | block.removeAttribute( 'data-fragment-index' ); 146 | block.setAttribute( 'data-line-numbers', Plugin.serializeHighlightSteps( [ highlightSteps[0] ] ) ); 147 | 148 | } 149 | 150 | // Scroll the first highlight into view when the slide 151 | // becomes visible. Note supported in IE11 since it lacks 152 | // support for Element.closest. 153 | var slide = typeof block.closest === 'function' ? block.closest( 'section:not(.stack)' ) : null; 154 | if( slide ) { 155 | var scrollFirstHighlightIntoView = function() { 156 | Plugin.scrollHighlightedLineIntoView( block, scrollState, true ); 157 | slide.removeEventListener( 'visible', scrollFirstHighlightIntoView ); 158 | } 159 | slide.addEventListener( 'visible', scrollFirstHighlightIntoView ); 160 | } 161 | 162 | Plugin.highlightLines( block ); 163 | 164 | } 165 | 166 | }, 167 | 168 | /** 169 | * Animates scrolling to the first highlighted line 170 | * in the given code block. 171 | */ 172 | scrollHighlightedLineIntoView: function( block, scrollState, skipAnimation ) { 173 | 174 | cancelAnimationFrame( scrollState.animationFrameID ); 175 | 176 | // Match the scroll position of the currently visible 177 | // code block 178 | if( scrollState.currentBlock ) { 179 | block.scrollTop = scrollState.currentBlock.scrollTop; 180 | } 181 | 182 | // Remember the current code block so that we can match 183 | // its scroll position when showing/hiding fragments 184 | scrollState.currentBlock = block; 185 | 186 | var highlightBounds = this.getHighlightedLineBounds( block ) 187 | var viewportHeight = block.offsetHeight; 188 | 189 | // Subtract padding from the viewport height 190 | var blockStyles = getComputedStyle( block ); 191 | viewportHeight -= parseInt( blockStyles.paddingTop ) + parseInt( blockStyles.paddingBottom ); 192 | 193 | // Scroll position which centers all highlights 194 | var startTop = block.scrollTop; 195 | var targetTop = highlightBounds.top + ( Math.min( highlightBounds.bottom - highlightBounds.top, viewportHeight ) - viewportHeight ) / 2; 196 | 197 | // Account for offsets in position applied to the 198 | // that holds our lines of code 199 | var lineTable = block.querySelector( '.hljs-ln' ); 200 | if( lineTable ) targetTop += lineTable.offsetTop - parseInt( blockStyles.paddingTop ); 201 | 202 | // Make sure the scroll target is within bounds 203 | targetTop = Math.max( Math.min( targetTop, block.scrollHeight - viewportHeight ), 0 ); 204 | 205 | if( skipAnimation === true || startTop === targetTop ) { 206 | block.scrollTop = targetTop; 207 | } 208 | else { 209 | 210 | // Don't attempt to scroll if there is no overflow 211 | if( block.scrollHeight <= viewportHeight ) return; 212 | 213 | var time = 0; 214 | var animate = function() { 215 | time = Math.min( time + 0.02, 1 ); 216 | 217 | // Update our eased scroll position 218 | block.scrollTop = startTop + ( targetTop - startTop ) * Plugin.easeInOutQuart( time ); 219 | 220 | // Keep animating unless we've reached the end 221 | if( time < 1 ) { 222 | scrollState.animationFrameID = requestAnimationFrame( animate ); 223 | } 224 | }; 225 | 226 | animate(); 227 | 228 | } 229 | 230 | }, 231 | 232 | /** 233 | * The easing function used when scrolling. 234 | */ 235 | easeInOutQuart: function( t ) { 236 | 237 | // easeInOutQuart 238 | return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t; 239 | 240 | }, 241 | 242 | getHighlightedLineBounds: function( block ) { 243 | 244 | var highlightedLines = block.querySelectorAll( '.highlight-line' ); 245 | if( highlightedLines.length === 0 ) { 246 | return { top: 0, bottom: 0 }; 247 | } 248 | else { 249 | var firstHighlight = highlightedLines[0]; 250 | var lastHighlight = highlightedLines[ highlightedLines.length -1 ]; 251 | 252 | return { 253 | top: firstHighlight.offsetTop, 254 | bottom: lastHighlight.offsetTop + lastHighlight.offsetHeight 255 | } 256 | } 257 | 258 | }, 259 | 260 | /** 261 | * Visually emphasize specific lines within a code block. 262 | * This only works on blocks with line numbering turned on. 263 | * 264 | * @param {HTMLElement} block a block 265 | * @param {String} [linesToHighlight] The lines that should be 266 | * highlighted in this format: 267 | * "1" = highlights line 1 268 | * "2,5" = highlights lines 2 & 5 269 | * "2,5-7" = highlights lines 2, 5, 6 & 7 270 | */ 271 | highlightLines: function( block, linesToHighlight ) { 272 | 273 | var highlightSteps = Plugin.deserializeHighlightSteps( linesToHighlight || block.getAttribute( 'data-line-numbers' ) ); 274 | 275 | if( highlightSteps.length ) { 276 | 277 | highlightSteps[0].forEach( function( highlight ) { 278 | 279 | var elementsToHighlight = []; 280 | 281 | // Highlight a range 282 | if( typeof highlight.end === 'number' ) { 283 | elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child(n+'+highlight.start+'):nth-child(-n+'+highlight.end+')' ) ); 284 | } 285 | // Highlight a single line 286 | else if( typeof highlight.start === 'number' ) { 287 | elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child('+highlight.start+')' ) ); 288 | } 289 | 290 | if( elementsToHighlight.length ) { 291 | elementsToHighlight.forEach( function( lineElement ) { 292 | lineElement.classList.add( 'highlight-line' ); 293 | } ); 294 | 295 | block.classList.add( 'has-highlights' ); 296 | } 297 | 298 | } ); 299 | 300 | } 301 | 302 | }, 303 | 304 | /** 305 | * Parses and formats a user-defined string of line 306 | * numbers to highlight. 307 | * 308 | * @example 309 | * Plugin.deserializeHighlightSteps( '1,2|3,5-10' ) 310 | * // [ 311 | * // [ { start: 1 }, { start: 2 } ], 312 | * // [ { start: 3 }, { start: 5, end: 10 } ] 313 | * // ] 314 | */ 315 | deserializeHighlightSteps: function( highlightSteps ) { 316 | 317 | // Remove whitespace 318 | highlightSteps = highlightSteps.replace( /\s/g, '' ); 319 | 320 | // Divide up our line number groups 321 | highlightSteps = highlightSteps.split( Plugin.HIGHLIGHT_STEP_DELIMITER ); 322 | 323 | return highlightSteps.map( function( highlights ) { 324 | 325 | return highlights.split( Plugin.HIGHLIGHT_LINE_DELIMITER ).map( function( highlight ) { 326 | 327 | // Parse valid line numbers 328 | if( /^[\d-]+$/.test( highlight ) ) { 329 | 330 | highlight = highlight.split( Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER ); 331 | 332 | var lineStart = parseInt( highlight[0], 10 ), 333 | lineEnd = parseInt( highlight[1], 10 ); 334 | 335 | if( isNaN( lineEnd ) ) { 336 | return { 337 | start: lineStart 338 | }; 339 | } 340 | else { 341 | return { 342 | start: lineStart, 343 | end: lineEnd 344 | }; 345 | } 346 | 347 | } 348 | // If no line numbers are provided, no code will be highlighted 349 | else { 350 | 351 | return {}; 352 | 353 | } 354 | 355 | } ); 356 | 357 | } ); 358 | 359 | }, 360 | 361 | /** 362 | * Serializes parsed line number data into a string so 363 | * that we can store it in the DOM. 364 | */ 365 | serializeHighlightSteps: function( highlightSteps ) { 366 | 367 | return highlightSteps.map( function( highlights ) { 368 | 369 | return highlights.map( function( highlight ) { 370 | 371 | // Line range 372 | if( typeof highlight.end === 'number' ) { 373 | return highlight.start + Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER + highlight.end; 374 | } 375 | // Single line 376 | else if( typeof highlight.start === 'number' ) { 377 | return highlight.start; 378 | } 379 | // All lines 380 | else { 381 | return ''; 382 | } 383 | 384 | } ).join( Plugin.HIGHLIGHT_LINE_DELIMITER ); 385 | 386 | } ).join( Plugin.HIGHLIGHT_STEP_DELIMITER ); 387 | 388 | } 389 | 390 | } 391 | 392 | // Function to perform a better "data-trim" on code snippets 393 | // Will slice an indentation amount on each line of the snippet (amount based on the line having the lowest indentation length) 394 | function betterTrim(snippetEl) { 395 | // Helper functions 396 | function trimLeft(val) { 397 | // Adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill 398 | return val.replace(/^[\s\uFEFF\xA0]+/g, ''); 399 | } 400 | function trimLineBreaks(input) { 401 | var lines = input.split('\n'); 402 | 403 | // Trim line-breaks from the beginning 404 | for (var i = 0; i < lines.length; i++) { 405 | if (lines[i].trim() === '') { 406 | lines.splice(i--, 1); 407 | } else break; 408 | } 409 | 410 | // Trim line-breaks from the end 411 | for (var i = lines.length-1; i >= 0; i--) { 412 | if (lines[i].trim() === '') { 413 | lines.splice(i, 1); 414 | } else break; 415 | } 416 | 417 | return lines.join('\n'); 418 | } 419 | 420 | // Main function for betterTrim() 421 | return (function(snippetEl) { 422 | var content = trimLineBreaks(snippetEl.innerHTML); 423 | var lines = content.split('\n'); 424 | // Calculate the minimum amount to remove on each line start of the snippet (can be 0) 425 | var pad = lines.reduce(function(acc, line) { 426 | if (line.length > 0 && trimLeft(line).length > 0 && acc > line.length - trimLeft(line).length) { 427 | return line.length - trimLeft(line).length; 428 | } 429 | return acc; 430 | }, Number.POSITIVE_INFINITY); 431 | // Slice each line with this amount 432 | return lines.map(function(line, index) { 433 | return line.slice(pad); 434 | }) 435 | .join('\n'); 436 | })(snippetEl); 437 | } 438 | 439 | export default () => Plugin; 440 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/highlight/zenburn.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov 4 | based on dark.css by Ivan Sagalaev 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #3f3f3f; 13 | color: #dcdcdc; 14 | } 15 | 16 | .hljs-keyword, 17 | .hljs-selector-tag, 18 | .hljs-tag { 19 | color: #e3ceab; 20 | } 21 | 22 | .hljs-template-tag { 23 | color: #dcdcdc; 24 | } 25 | 26 | .hljs-number { 27 | color: #8cd0d3; 28 | } 29 | 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-attribute { 33 | color: #efdcbc; 34 | } 35 | 36 | .hljs-literal { 37 | color: #efefaf; 38 | } 39 | 40 | .hljs-subst { 41 | color: #8f8f8f; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-name, 46 | .hljs-selector-id, 47 | .hljs-selector-class, 48 | .hljs-section, 49 | .hljs-type { 50 | color: #efef8f; 51 | } 52 | 53 | .hljs-symbol, 54 | .hljs-bullet, 55 | .hljs-link { 56 | color: #dca3a3; 57 | } 58 | 59 | .hljs-deletion, 60 | .hljs-string, 61 | .hljs-built_in, 62 | .hljs-builtin-name { 63 | color: #cc9393; 64 | } 65 | 66 | .hljs-addition, 67 | .hljs-comment, 68 | .hljs-quote, 69 | .hljs-meta { 70 | color: #7f9f7f; 71 | } 72 | 73 | 74 | .hljs-emphasis { 75 | font-style: italic; 76 | } 77 | 78 | .hljs-strong { 79 | font-weight: bold; 80 | } 81 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/markdown/plugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * The reveal.js markdown plugin. Handles parsing of 3 | * markdown inside of presentations as well as loading 4 | * of external markdown documents. 5 | */ 6 | 7 | import { marked } from 'marked'; 8 | 9 | const DEFAULT_SLIDE_SEPARATOR = '\r?\n---\r?\n', 10 | DEFAULT_NOTES_SEPARATOR = 'notes?:', 11 | DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$', 12 | DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$'; 13 | 14 | const SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__'; 15 | 16 | const CODE_LINE_NUMBER_REGEX = /\[([\s\d,|-]*)\]/; 17 | 18 | const HTML_ESCAPE_MAP = { 19 | '&': '&', 20 | '<': '<', 21 | '>': '>', 22 | '"': '"', 23 | "'": ''' 24 | }; 25 | 26 | const Plugin = () => { 27 | 28 | // The reveal.js instance this plugin is attached to 29 | let deck; 30 | 31 | /** 32 | * Retrieves the markdown contents of a slide section 33 | * element. Normalizes leading tabs/whitespace. 34 | */ 35 | function getMarkdownFromSlide( section ) { 36 | 37 | // look for a ' ); 45 | 46 | var leadingWs = text.match( /^\n?(\s*)/ )[1].length, 47 | leadingTabs = text.match( /^\n?(\t*)/ )[1].length; 48 | 49 | if( leadingTabs > 0 ) { 50 | text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' ); 51 | } 52 | else if( leadingWs > 1 ) { 53 | text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' ); 54 | } 55 | 56 | return text; 57 | 58 | } 59 | 60 | /** 61 | * Given a markdown slide section element, this will 62 | * return all arguments that aren't related to markdown 63 | * parsing. Used to forward any other user-defined arguments 64 | * to the output markdown slide. 65 | */ 66 | function getForwardedAttributes( section ) { 67 | 68 | var attributes = section.attributes; 69 | var result = []; 70 | 71 | for( var i = 0, len = attributes.length; i < len; i++ ) { 72 | var name = attributes[i].name, 73 | value = attributes[i].value; 74 | 75 | // disregard attributes that are used for markdown loading/parsing 76 | if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue; 77 | 78 | if( value ) { 79 | result.push( name + '="' + value + '"' ); 80 | } 81 | else { 82 | result.push( name ); 83 | } 84 | } 85 | 86 | return result.join( ' ' ); 87 | 88 | } 89 | 90 | /** 91 | * Inspects the given options and fills out default 92 | * values for what's not defined. 93 | */ 94 | function getSlidifyOptions( options ) { 95 | 96 | options = options || {}; 97 | options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR; 98 | options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR; 99 | options.attributes = options.attributes || ''; 100 | 101 | return options; 102 | 103 | } 104 | 105 | /** 106 | * Helper function for constructing a markdown slide. 107 | */ 108 | function createMarkdownSlide( content, options ) { 109 | 110 | options = getSlidifyOptions( options ); 111 | 112 | var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) ); 113 | 114 | if( notesMatch.length === 2 ) { 115 | content = notesMatch[0] + ''; 116 | } 117 | 118 | // prevent script end tags in the content from interfering 119 | // with parsing 120 | content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER ); 121 | 122 | return ''; 123 | 124 | } 125 | 126 | /** 127 | * Parses a data string into multiple slides based 128 | * on the passed in separator arguments. 129 | */ 130 | function slidify( markdown, options ) { 131 | 132 | options = getSlidifyOptions( options ); 133 | 134 | var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ), 135 | horizontalSeparatorRegex = new RegExp( options.separator ); 136 | 137 | var matches, 138 | lastIndex = 0, 139 | isHorizontal, 140 | wasHorizontal = true, 141 | content, 142 | sectionStack = []; 143 | 144 | // iterate until all blocks between separators are stacked up 145 | while( matches = separatorRegex.exec( markdown ) ) { 146 | var notes = null; 147 | 148 | // determine direction (horizontal by default) 149 | isHorizontal = horizontalSeparatorRegex.test( matches[0] ); 150 | 151 | if( !isHorizontal && wasHorizontal ) { 152 | // create vertical stack 153 | sectionStack.push( [] ); 154 | } 155 | 156 | // pluck slide content from markdown input 157 | content = markdown.substring( lastIndex, matches.index ); 158 | 159 | if( isHorizontal && wasHorizontal ) { 160 | // add to horizontal stack 161 | sectionStack.push( content ); 162 | } 163 | else { 164 | // add to vertical stack 165 | sectionStack[sectionStack.length-1].push( content ); 166 | } 167 | 168 | lastIndex = separatorRegex.lastIndex; 169 | wasHorizontal = isHorizontal; 170 | } 171 | 172 | // add the remaining slide 173 | ( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) ); 174 | 175 | var markdownSections = ''; 176 | 177 | // flatten the hierarchical stack, and insert
tags 178 | for( var i = 0, len = sectionStack.length; i < len; i++ ) { 179 | // vertical 180 | if( sectionStack[i] instanceof Array ) { 181 | markdownSections += '
'; 182 | 183 | sectionStack[i].forEach( function( child ) { 184 | markdownSections += '
' + createMarkdownSlide( child, options ) + '
'; 185 | } ); 186 | 187 | markdownSections += '
'; 188 | } 189 | else { 190 | markdownSections += '
' + createMarkdownSlide( sectionStack[i], options ) + '
'; 191 | } 192 | } 193 | 194 | return markdownSections; 195 | 196 | } 197 | 198 | /** 199 | * Parses any current data-markdown slides, splits 200 | * multi-slide markdown into separate sections and 201 | * handles loading of external markdown. 202 | */ 203 | function processSlides( scope ) { 204 | 205 | return new Promise( function( resolve ) { 206 | 207 | var externalPromises = []; 208 | 209 | [].slice.call( scope.querySelectorAll( 'section[data-markdown]:not([data-markdown-parsed])') ).forEach( function( section, i ) { 210 | 211 | if( section.getAttribute( 'data-markdown' ).length ) { 212 | 213 | externalPromises.push( loadExternalMarkdown( section ).then( 214 | 215 | // Finished loading external file 216 | function( xhr, url ) { 217 | section.outerHTML = slidify( xhr.responseText, { 218 | separator: section.getAttribute( 'data-separator' ), 219 | verticalSeparator: section.getAttribute( 'data-separator-vertical' ), 220 | notesSeparator: section.getAttribute( 'data-separator-notes' ), 221 | attributes: getForwardedAttributes( section ) 222 | }); 223 | }, 224 | 225 | // Failed to load markdown 226 | function( xhr, url ) { 227 | section.outerHTML = '
' + 228 | 'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' + 229 | 'Check your browser\'s JavaScript console for more details.' + 230 | '

Remember that you need to serve the presentation HTML from a HTTP server.

' + 231 | '
'; 232 | } 233 | 234 | ) ); 235 | 236 | } 237 | else { 238 | 239 | section.outerHTML = slidify( getMarkdownFromSlide( section ), { 240 | separator: section.getAttribute( 'data-separator' ), 241 | verticalSeparator: section.getAttribute( 'data-separator-vertical' ), 242 | notesSeparator: section.getAttribute( 'data-separator-notes' ), 243 | attributes: getForwardedAttributes( section ) 244 | }); 245 | 246 | } 247 | 248 | }); 249 | 250 | Promise.all( externalPromises ).then( resolve ); 251 | 252 | } ); 253 | 254 | } 255 | 256 | function loadExternalMarkdown( section ) { 257 | 258 | return new Promise( function( resolve, reject ) { 259 | 260 | var xhr = new XMLHttpRequest(), 261 | url = section.getAttribute( 'data-markdown' ); 262 | 263 | var datacharset = section.getAttribute( 'data-charset' ); 264 | 265 | // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes 266 | if( datacharset != null && datacharset != '' ) { 267 | xhr.overrideMimeType( 'text/html; charset=' + datacharset ); 268 | } 269 | 270 | xhr.onreadystatechange = function( section, xhr ) { 271 | if( xhr.readyState === 4 ) { 272 | // file protocol yields status code 0 (useful for local debug, mobile applications etc.) 273 | if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) { 274 | 275 | resolve( xhr, url ); 276 | 277 | } 278 | else { 279 | 280 | reject( xhr, url ); 281 | 282 | } 283 | } 284 | }.bind( this, section, xhr ); 285 | 286 | xhr.open( 'GET', url, true ); 287 | 288 | try { 289 | xhr.send(); 290 | } 291 | catch ( e ) { 292 | console.warn( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e ); 293 | resolve( xhr, url ); 294 | } 295 | 296 | } ); 297 | 298 | } 299 | 300 | /** 301 | * Check if a node value has the attributes pattern. 302 | * If yes, extract it and add that value as one or several attributes 303 | * to the target element. 304 | * 305 | * You need Cache Killer on Chrome to see the effect on any FOM transformation 306 | * directly on refresh (F5) 307 | * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277 308 | */ 309 | function addAttributeInElement( node, elementTarget, separator ) { 310 | 311 | var mardownClassesInElementsRegex = new RegExp( separator, 'mg' ); 312 | var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"]+?)\"|(data-[^\"= ]+?)(?=[\" ])", 'mg' ); 313 | var nodeValue = node.nodeValue; 314 | var matches, 315 | matchesClass; 316 | if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) { 317 | 318 | var classes = matches[1]; 319 | nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex ); 320 | node.nodeValue = nodeValue; 321 | while( matchesClass = mardownClassRegex.exec( classes ) ) { 322 | if( matchesClass[2] ) { 323 | elementTarget.setAttribute( matchesClass[1], matchesClass[2] ); 324 | } else { 325 | elementTarget.setAttribute( matchesClass[3], "" ); 326 | } 327 | } 328 | return true; 329 | } 330 | return false; 331 | } 332 | 333 | /** 334 | * Add attributes to the parent element of a text node, 335 | * or the element of an attribute node. 336 | */ 337 | function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) { 338 | 339 | if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) { 340 | var previousParentElement = element; 341 | for( var i = 0; i < element.childNodes.length; i++ ) { 342 | var childElement = element.childNodes[i]; 343 | if ( i > 0 ) { 344 | var j = i - 1; 345 | while ( j >= 0 ) { 346 | var aPreviousChildElement = element.childNodes[j]; 347 | if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) { 348 | previousParentElement = aPreviousChildElement; 349 | break; 350 | } 351 | j = j - 1; 352 | } 353 | } 354 | var parentSection = section; 355 | if( childElement.nodeName == "section" ) { 356 | parentSection = childElement ; 357 | previousParentElement = childElement ; 358 | } 359 | if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) { 360 | addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes ); 361 | } 362 | } 363 | } 364 | 365 | if ( element.nodeType == Node.COMMENT_NODE ) { 366 | if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) { 367 | addAttributeInElement( element, section, separatorSectionAttributes ); 368 | } 369 | } 370 | } 371 | 372 | /** 373 | * Converts any current data-markdown slides in the 374 | * DOM to HTML. 375 | */ 376 | function convertSlides() { 377 | 378 | var sections = deck.getRevealElement().querySelectorAll( '[data-markdown]:not([data-markdown-parsed])'); 379 | 380 | [].slice.call( sections ).forEach( function( section ) { 381 | 382 | section.setAttribute( 'data-markdown-parsed', true ) 383 | 384 | var notes = section.querySelector( 'aside.notes' ); 385 | var markdown = getMarkdownFromSlide( section ); 386 | 387 | section.innerHTML = marked( markdown ); 388 | addAttributes( section, section, null, section.getAttribute( 'data-element-attributes' ) || 389 | section.parentNode.getAttribute( 'data-element-attributes' ) || 390 | DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR, 391 | section.getAttribute( 'data-attributes' ) || 392 | section.parentNode.getAttribute( 'data-attributes' ) || 393 | DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR); 394 | 395 | // If there were notes, we need to re-add them after 396 | // having overwritten the section's HTML 397 | if( notes ) { 398 | section.appendChild( notes ); 399 | } 400 | 401 | } ); 402 | 403 | return Promise.resolve(); 404 | 405 | } 406 | 407 | function escapeForHTML( input ) { 408 | 409 | return input.replace( /([&<>'"])/g, char => HTML_ESCAPE_MAP[char] ); 410 | 411 | } 412 | 413 | return { 414 | id: 'markdown', 415 | 416 | /** 417 | * Starts processing and converting Markdown within the 418 | * current reveal.js deck. 419 | */ 420 | init: function( reveal ) { 421 | 422 | deck = reveal; 423 | 424 | let { renderer, animateLists, ...markedOptions } = deck.getConfig().markdown || {}; 425 | 426 | if( !renderer ) { 427 | renderer = new marked.Renderer(); 428 | 429 | renderer.code = ( code, language ) => { 430 | 431 | // Off by default 432 | let lineNumbers = ''; 433 | 434 | // Users can opt in to show line numbers and highlight 435 | // specific lines. 436 | // ```javascript [] show line numbers 437 | // ```javascript [1,4-8] highlights lines 1 and 4-8 438 | if( CODE_LINE_NUMBER_REGEX.test( language ) ) { 439 | lineNumbers = language.match( CODE_LINE_NUMBER_REGEX )[1].trim(); 440 | lineNumbers = `data-line-numbers="${lineNumbers}"`; 441 | language = language.replace( CODE_LINE_NUMBER_REGEX, '' ).trim(); 442 | } 443 | 444 | // Escape before this gets injected into the DOM to 445 | // avoid having the HTML parser alter our code before 446 | // highlight.js is able to read it 447 | code = escapeForHTML( code ); 448 | 449 | return `
${code}
`; 450 | }; 451 | } 452 | 453 | if( animateLists === true ) { 454 | renderer.listitem = text => `
  • ${text}
  • `; 455 | } 456 | 457 | marked.setOptions( { 458 | renderer, 459 | ...markedOptions 460 | } ); 461 | 462 | return processSlides( deck.getRevealElement() ).then( convertSlides ); 463 | 464 | }, 465 | 466 | // TODO: Do these belong in the API? 467 | processSlides: processSlides, 468 | convertSlides: convertSlides, 469 | slidify: slidify, 470 | marked: marked 471 | } 472 | 473 | }; 474 | 475 | export default Plugin; 476 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/math/katex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A plugin which enables rendering of math equations inside 3 | * of reveal.js slides. Essentially a thin wrapper for KaTeX. 4 | * 5 | * @author Hakim El Hattab 6 | * @author Gerhard Burger 7 | */ 8 | export const KaTeX = () => { 9 | let deck; 10 | 11 | let defaultOptions = { 12 | version: 'latest', 13 | delimiters: [ 14 | {left: '$$', right: '$$', display: true}, // Note: $$ has to come before $ 15 | {left: '$', right: '$', display: false}, 16 | {left: '\\(', right: '\\)', display: false}, 17 | {left: '\\[', right: '\\]', display: true} 18 | ], 19 | ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre'] 20 | } 21 | 22 | const loadCss = src => { 23 | let link = document.createElement('link'); 24 | link.rel = 'stylesheet'; 25 | link.href = src; 26 | document.head.appendChild(link); 27 | }; 28 | 29 | /** 30 | * Loads a JavaScript file and returns a Promise for when it is loaded 31 | * Credits: https://aaronsmith.online/easily-load-an-external-script-using-javascript/ 32 | */ 33 | const loadScript = src => { 34 | return new Promise((resolve, reject) => { 35 | const script = document.createElement('script') 36 | script.type = 'text/javascript' 37 | script.onload = resolve 38 | script.onerror = reject 39 | script.src = src 40 | document.head.append(script) 41 | }) 42 | }; 43 | 44 | async function loadScripts(urls) { 45 | for(const url of urls) { 46 | await loadScript(url); 47 | } 48 | } 49 | 50 | return { 51 | id: 'katex', 52 | 53 | init: function (reveal) { 54 | 55 | deck = reveal; 56 | 57 | let revealOptions = deck.getConfig().katex || {}; 58 | 59 | let options = {...defaultOptions, ...revealOptions}; 60 | const {local, version, extensions, ...katexOptions} = options; 61 | 62 | let baseUrl = options.local || 'https://cdn.jsdelivr.net/npm/katex'; 63 | let versionString = options.local ? '' : '@' + options.version; 64 | 65 | let cssUrl = baseUrl + versionString + '/dist/katex.min.css'; 66 | let katexUrl = baseUrl + versionString + '/dist/katex.min.js'; 67 | let mhchemUrl = baseUrl + versionString + '/dist/contrib/mhchem.min.js' 68 | let karUrl = baseUrl + versionString + '/dist/contrib/auto-render.min.js'; 69 | 70 | let katexScripts = [katexUrl]; 71 | if(options.extensions && options.extensions.includes("mhchem")) { 72 | katexScripts.push(mhchemUrl); 73 | } 74 | katexScripts.push(karUrl); 75 | 76 | const renderMath = () => { 77 | renderMathInElement(reveal.getSlidesElement(), katexOptions); 78 | deck.layout(); 79 | } 80 | 81 | loadCss(cssUrl); 82 | 83 | // For some reason dynamically loading with defer attribute doesn't result in the expected behavior, the below code does 84 | loadScripts(katexScripts).then(() => { 85 | if( deck.isReady() ) { 86 | renderMath(); 87 | } 88 | else { 89 | deck.on( 'ready', renderMath.bind( this ) ); 90 | } 91 | }); 92 | 93 | } 94 | } 95 | 96 | }; 97 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/math/mathjax2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A plugin which enables rendering of math equations inside 3 | * of reveal.js slides. Essentially a thin wrapper for MathJax. 4 | * 5 | * @author Hakim El Hattab 6 | */ 7 | export const MathJax2 = () => { 8 | 9 | // The reveal.js instance this plugin is attached to 10 | let deck; 11 | 12 | let defaultOptions = { 13 | messageStyle: 'none', 14 | tex2jax: { 15 | inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ], 16 | skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ] 17 | }, 18 | skipStartupTypeset: true 19 | }; 20 | 21 | function loadScript( url, callback ) { 22 | 23 | let head = document.querySelector( 'head' ); 24 | let script = document.createElement( 'script' ); 25 | script.type = 'text/javascript'; 26 | script.src = url; 27 | 28 | // Wrapper for callback to make sure it only fires once 29 | let finish = () => { 30 | if( typeof callback === 'function' ) { 31 | callback.call(); 32 | callback = null; 33 | } 34 | } 35 | 36 | script.onload = finish; 37 | 38 | // IE 39 | script.onreadystatechange = () => { 40 | if ( this.readyState === 'loaded' ) { 41 | finish(); 42 | } 43 | } 44 | 45 | // Normal browsers 46 | head.appendChild( script ); 47 | 48 | } 49 | 50 | return { 51 | id: 'mathjax2', 52 | 53 | init: function( reveal ) { 54 | 55 | deck = reveal; 56 | 57 | let revealOptions = deck.getConfig().mathjax2 || deck.getConfig().math || {}; 58 | 59 | let options = { ...defaultOptions, ...revealOptions }; 60 | let mathjax = options.mathjax || 'https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js'; 61 | let config = options.config || 'TeX-AMS_HTML-full'; 62 | let url = mathjax + '?config=' + config; 63 | 64 | options.tex2jax = { ...defaultOptions.tex2jax, ...revealOptions.tex2jax }; 65 | 66 | options.mathjax = options.config = null; 67 | 68 | loadScript( url, function() { 69 | 70 | MathJax.Hub.Config( options ); 71 | 72 | // Typeset followed by an immediate reveal.js layout since 73 | // the typesetting process could affect slide height 74 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, deck.getRevealElement() ] ); 75 | MathJax.Hub.Queue( deck.layout ); 76 | 77 | // Reprocess equations in slides when they turn visible 78 | deck.on( 'slidechanged', function( event ) { 79 | 80 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] ); 81 | 82 | } ); 83 | 84 | } ); 85 | 86 | } 87 | } 88 | 89 | }; 90 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/math/mathjax3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A plugin which enables rendering of math equations inside 3 | * of reveal.js slides. Essentially a thin wrapper for MathJax 3 4 | * 5 | * @author Hakim El Hattab 6 | * @author Gerhard Burger 7 | */ 8 | export const MathJax3 = () => { 9 | 10 | // The reveal.js instance this plugin is attached to 11 | let deck; 12 | 13 | let defaultOptions = { 14 | tex: { 15 | inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ] 16 | }, 17 | options: { 18 | skipHtmlTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ] 19 | }, 20 | startup: { 21 | ready: () => { 22 | MathJax.startup.defaultReady(); 23 | MathJax.startup.promise.then(() => { 24 | Reveal.layout(); 25 | }); 26 | } 27 | } 28 | }; 29 | 30 | function loadScript( url, callback ) { 31 | 32 | let script = document.createElement( 'script' ); 33 | script.type = "text/javascript" 34 | script.id = "MathJax-script" 35 | script.src = url; 36 | script.async = true 37 | 38 | // Wrapper for callback to make sure it only fires once 39 | script.onload = () => { 40 | if (typeof callback === 'function') { 41 | callback.call(); 42 | callback = null; 43 | } 44 | }; 45 | 46 | document.head.appendChild( script ); 47 | 48 | } 49 | 50 | return { 51 | id: 'mathjax3', 52 | init: function(reveal) { 53 | 54 | deck = reveal; 55 | 56 | let revealOptions = deck.getConfig().mathjax3 || {}; 57 | let options = {...defaultOptions, ...revealOptions}; 58 | options.tex = {...defaultOptions.tex, ...revealOptions.tex} 59 | options.options = {...defaultOptions.options, ...revealOptions.options} 60 | options.startup = {...defaultOptions.startup, ...revealOptions.startup} 61 | 62 | let url = options.mathjax || 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'; 63 | options.mathjax = null; 64 | 65 | window.MathJax = options; 66 | 67 | loadScript( url, function() { 68 | // Reprocess equations in slides when they turn visible 69 | Reveal.addEventListener( 'slidechanged', function( event ) { 70 | MathJax.typeset(); 71 | } ); 72 | } ); 73 | 74 | } 75 | } 76 | 77 | }; 78 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/math/plugin.js: -------------------------------------------------------------------------------- 1 | import {KaTeX} from "./katex"; 2 | import {MathJax2} from "./mathjax2"; 3 | import {MathJax3} from "./mathjax3"; 4 | 5 | const defaultTypesetter = MathJax2; 6 | 7 | /*! 8 | * This plugin is a wrapper for the MathJax2, 9 | * MathJax3 and KaTeX typesetter plugins. 10 | */ 11 | export default Plugin = Object.assign( defaultTypesetter(), { 12 | KaTeX, 13 | MathJax2, 14 | MathJax3 15 | } ); -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/notes/plugin.js: -------------------------------------------------------------------------------- 1 | import speakerViewHTML from './speaker-view.html'; 2 | 3 | import { marked } from 'marked'; 4 | 5 | /** 6 | * Handles opening of and synchronization with the reveal.js 7 | * notes window. 8 | * 9 | * Handshake process: 10 | * 1. This window posts 'connect' to notes window 11 | * - Includes URL of presentation to show 12 | * 2. Notes window responds with 'connected' when it is available 13 | * 3. This window proceeds to send the current presentation state 14 | * to the notes window 15 | */ 16 | const Plugin = () => { 17 | 18 | let connectInterval; 19 | let speakerWindow = null; 20 | let deck; 21 | 22 | /** 23 | * Opens a new speaker view window. 24 | */ 25 | function openSpeakerWindow() { 26 | 27 | // If a window is already open, focus it 28 | if( speakerWindow && !speakerWindow.closed ) { 29 | speakerWindow.focus(); 30 | } 31 | else { 32 | speakerWindow = window.open( 'about:blank', 'reveal.js - Notes', 'width=1100,height=700' ); 33 | speakerWindow.marked = marked; 34 | speakerWindow.document.write( speakerViewHTML ); 35 | 36 | if( !speakerWindow ) { 37 | alert( 'Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.' ); 38 | return; 39 | } 40 | 41 | connect(); 42 | } 43 | 44 | } 45 | 46 | /** 47 | * Reconnect with an existing speaker view window. 48 | */ 49 | function reconnectSpeakerWindow( reconnectWindow ) { 50 | 51 | if( speakerWindow && !speakerWindow.closed ) { 52 | speakerWindow.focus(); 53 | } 54 | else { 55 | speakerWindow = reconnectWindow; 56 | window.addEventListener( 'message', onPostMessage ); 57 | onConnected(); 58 | } 59 | 60 | } 61 | 62 | /** 63 | * Connect to the notes window through a postmessage handshake. 64 | * Using postmessage enables us to work in situations where the 65 | * origins differ, such as a presentation being opened from the 66 | * file system. 67 | */ 68 | function connect() { 69 | 70 | const presentationURL = deck.getConfig().url; 71 | 72 | const url = typeof presentationURL === 'string' ? presentationURL : 73 | window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search; 74 | 75 | // Keep trying to connect until we get a 'connected' message back 76 | connectInterval = setInterval( function() { 77 | speakerWindow.postMessage( JSON.stringify( { 78 | namespace: 'reveal-notes', 79 | type: 'connect', 80 | state: deck.getState(), 81 | url 82 | } ), '*' ); 83 | }, 500 ); 84 | 85 | window.addEventListener( 'message', onPostMessage ); 86 | 87 | } 88 | 89 | /** 90 | * Calls the specified Reveal.js method with the provided argument 91 | * and then pushes the result to the notes frame. 92 | */ 93 | function callRevealApi( methodName, methodArguments, callId ) { 94 | 95 | let result = deck[methodName].apply( deck, methodArguments ); 96 | speakerWindow.postMessage( JSON.stringify( { 97 | namespace: 'reveal-notes', 98 | type: 'return', 99 | result, 100 | callId 101 | } ), '*' ); 102 | 103 | } 104 | 105 | /** 106 | * Posts the current slide data to the notes window. 107 | */ 108 | function post( event ) { 109 | 110 | let slideElement = deck.getCurrentSlide(), 111 | notesElement = slideElement.querySelector( 'aside.notes' ), 112 | fragmentElement = slideElement.querySelector( '.current-fragment' ); 113 | 114 | let messageData = { 115 | namespace: 'reveal-notes', 116 | type: 'state', 117 | notes: '', 118 | markdown: false, 119 | whitespace: 'normal', 120 | state: deck.getState() 121 | }; 122 | 123 | // Look for notes defined in a slide attribute 124 | if( slideElement.hasAttribute( 'data-notes' ) ) { 125 | messageData.notes = slideElement.getAttribute( 'data-notes' ); 126 | messageData.whitespace = 'pre-wrap'; 127 | } 128 | 129 | // Look for notes defined in a fragment 130 | if( fragmentElement ) { 131 | let fragmentNotes = fragmentElement.querySelector( 'aside.notes' ); 132 | if( fragmentNotes ) { 133 | notesElement = fragmentNotes; 134 | } 135 | else if( fragmentElement.hasAttribute( 'data-notes' ) ) { 136 | messageData.notes = fragmentElement.getAttribute( 'data-notes' ); 137 | messageData.whitespace = 'pre-wrap'; 138 | 139 | // In case there are slide notes 140 | notesElement = null; 141 | } 142 | } 143 | 144 | // Look for notes defined in an aside element 145 | if( notesElement ) { 146 | messageData.notes = notesElement.innerHTML; 147 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string'; 148 | } 149 | 150 | speakerWindow.postMessage( JSON.stringify( messageData ), '*' ); 151 | 152 | } 153 | 154 | function onPostMessage( event ) { 155 | 156 | let data = JSON.parse( event.data ); 157 | if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) { 158 | clearInterval( connectInterval ); 159 | onConnected(); 160 | } 161 | else if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) { 162 | callRevealApi( data.methodName, data.arguments, data.callId ); 163 | } 164 | 165 | } 166 | 167 | /** 168 | * Called once we have established a connection to the notes 169 | * window. 170 | */ 171 | function onConnected() { 172 | 173 | // Monitor events that trigger a change in state 174 | deck.on( 'slidechanged', post ); 175 | deck.on( 'fragmentshown', post ); 176 | deck.on( 'fragmenthidden', post ); 177 | deck.on( 'overviewhidden', post ); 178 | deck.on( 'overviewshown', post ); 179 | deck.on( 'paused', post ); 180 | deck.on( 'resumed', post ); 181 | 182 | // Post the initial state 183 | post(); 184 | 185 | } 186 | 187 | return { 188 | id: 'notes', 189 | 190 | init: function( reveal ) { 191 | 192 | deck = reveal; 193 | 194 | if( !/receiver/i.test( window.location.search ) ) { 195 | 196 | // If the there's a 'notes' query set, open directly 197 | if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) { 198 | openSpeakerWindow(); 199 | } 200 | else { 201 | // Keep listening for speaker view hearbeats. If we receive a 202 | // heartbeat from an orphaned window, reconnect it. This ensures 203 | // that we remain connected to the notes even if the presentation 204 | // is reloaded. 205 | window.addEventListener( 'message', event => { 206 | 207 | if( !speakerWindow && typeof event.data === 'string' ) { 208 | let data; 209 | 210 | try { 211 | data = JSON.parse( event.data ); 212 | } 213 | catch( error ) {} 214 | 215 | if( data && data.namespace === 'reveal-notes' && data.type === 'heartbeat' ) { 216 | reconnectSpeakerWindow( event.source ); 217 | } 218 | } 219 | }); 220 | } 221 | 222 | // Open the notes when the 's' key is hit 223 | deck.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() { 224 | openSpeakerWindow(); 225 | } ); 226 | 227 | } 228 | 229 | }, 230 | 231 | open: openSpeakerWindow 232 | }; 233 | 234 | }; 235 | 236 | export default Plugin; 237 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/pdf-export/pdfexport.js: -------------------------------------------------------------------------------- 1 | var PdfExport = ( function( _Reveal ){ 2 | 3 | var Reveal = _Reveal; 4 | var setStylesheet = null; 5 | var installAltKeyBindings = null; 6 | 7 | function getRevealJsPath(){ 8 | var regex = /\b[^/]+\/reveal.css$/i; 9 | var script = Array.from( document.querySelectorAll( 'link' ) ).find( function( e ){ 10 | return e.attributes.href && e.attributes.href.value.search( regex ) >= 0; 11 | }); 12 | if( !script ){ 13 | console.error( 'reveal.css could not be found in included elements. Did you rename this file?' ); 14 | return ''; 15 | } 16 | return script.attributes.href.value.replace( regex, '' ); 17 | } 18 | 19 | function setStylesheet3( pdfExport ){ 20 | var link = document.querySelector( '#print' ); 21 | if( !link ){ 22 | link = document.createElement( 'link' ); 23 | link.rel = 'stylesheet'; 24 | link.id = 'print'; 25 | document.querySelector( 'head' ).appendChild( link ); 26 | } 27 | var style = 'paper'; 28 | if( pdfExport ){ 29 | style = 'pdf'; 30 | } 31 | link.href = getRevealJsPath() + 'css/print/' + style + '.css'; 32 | } 33 | 34 | function setStylesheet4( pdfExport ){ 35 | } 36 | 37 | function installAltKeyBindings3(){ 38 | } 39 | 40 | function installAltKeyBindings4(){ 41 | if( isPrintingPDF() ){ 42 | var config = Reveal.getConfig(); 43 | var shortcut = config.pdfExportShortcut || 'E'; 44 | window.addEventListener( 'keydown', function( e ){ 45 | if( e.target.nodeName.toUpperCase() == 'BODY' 46 | && ( e.key.toUpperCase() == shortcut.toUpperCase() || e.keyCode == shortcut.toUpperCase().charCodeAt( 0 ) ) ){ 47 | e.preventDefault(); 48 | togglePdfExport(); 49 | return false; 50 | } 51 | }, true ); 52 | } 53 | } 54 | 55 | function isPrintingPDF(){ 56 | return ( /print-pdf/gi ).test( window.location.search ); 57 | } 58 | 59 | function togglePdfExport(){ 60 | var url_doc = new URL( document.URL ); 61 | var query_doc = new URLSearchParams( url_doc.searchParams ); 62 | if( isPrintingPDF() ){ 63 | query_doc.delete( 'print-pdf' ); 64 | }else{ 65 | query_doc.set( 'print-pdf', '' ); 66 | } 67 | url_doc.search = ( query_doc.toString() ? '?' + query_doc.toString() : '' ); 68 | window.location.href = url_doc.toString(); 69 | } 70 | 71 | function installKeyBindings(){ 72 | var config = Reveal.getConfig(); 73 | var shortcut = config.pdfExportShortcut || 'E'; 74 | Reveal.addKeyBinding({ 75 | keyCode: shortcut.toUpperCase().charCodeAt( 0 ), 76 | key: shortcut.toUpperCase(), 77 | description: 'PDF export mode' 78 | }, togglePdfExport ); 79 | installAltKeyBindings(); 80 | } 81 | 82 | function install(){ 83 | installKeyBindings(); 84 | setStylesheet( isPrintingPDF() ); 85 | } 86 | 87 | var Plugin = { 88 | } 89 | 90 | if( Reveal && Reveal.VERSION && Reveal.VERSION.length && Reveal.VERSION[ 0 ] == '3' ){ 91 | // reveal 3.x 92 | setStylesheet = setStylesheet3; 93 | installAltKeyBindings = installAltKeyBindings3; 94 | install(); 95 | }else{ 96 | // must be reveal 4.x 97 | setStylesheet = setStylesheet4; 98 | installAltKeyBindings = installAltKeyBindings4; 99 | Plugin.id = 'pdf-export'; 100 | Plugin.init = function( _Reveal ){ 101 | Reveal = _Reveal; 102 | install(); 103 | }; 104 | Plugin.togglePdfExport = function () { 105 | togglePdfExport(); 106 | }; 107 | } 108 | 109 | return Plugin; 110 | 111 | })( Reveal ); 112 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/pdf-export/plugin.yml: -------------------------------------------------------------------------------- 1 | name: PdfExport 2 | script: pdfexport.js 3 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/quarto-line-highlight/line-highlight.css: -------------------------------------------------------------------------------- 1 | .reveal 2 | div.sourceCode 3 | pre 4 | code.has-line-highlights 5 | > span:not(.highlight-line) { 6 | opacity: 0.4; 7 | } 8 | 9 | .reveal pre.numberSource { 10 | padding-left: 0; 11 | } 12 | 13 | .reveal pre.numberSource code > span { 14 | left: -2.1em; 15 | } 16 | 17 | pre.numberSource code > span > a:first-child::before { 18 | left: -0.7em; 19 | } 20 | 21 | .reveal pre > code:not(:first-child).fragment { 22 | position: absolute; 23 | top: 0; 24 | left: 0; 25 | width: 100%; 26 | box-sizing: border-box; 27 | } 28 | 29 | .reveal div.sourceCode pre code { 30 | min-height: 100%; 31 | } 32 | -------------------------------------------------------------------------------- /docs/site_libs/revealjs/plugin/quarto-line-highlight/line-highlight.js: -------------------------------------------------------------------------------- 1 | window.QuartoLineHighlight = function () { 2 | function isPrintView() { 3 | return /print-pdf/gi.test(window.location.search); 4 | } 5 | 6 | const delimiters = { 7 | step: "|", 8 | line: ",", 9 | lineRange: "-", 10 | }; 11 | 12 | const regex = new RegExp( 13 | "^[\\d" + Object.values(delimiters).join("") + "]+$" 14 | ); 15 | 16 | function handleLinesSelector(deck, attr) { 17 | // if we are in printview with pdfSeparateFragments: false 18 | // then we'll also want to supress 19 | if (regex.test(attr)) { 20 | if (isPrintView() && deck.getConfig().pdfSeparateFragments !== true) { 21 | return false; 22 | } else { 23 | return true; 24 | } 25 | } else { 26 | return false; 27 | } 28 | } 29 | 30 | const kCodeLineNumbersAttr = "data-code-line-numbers"; 31 | const kFragmentIndex = "data-fragment-index"; 32 | 33 | function initQuartoLineHighlight(deck) { 34 | const divSourceCode = deck 35 | .getRevealElement() 36 | .querySelectorAll("div.sourceCode"); 37 | // Process each div created by Pandoc highlighting - numbered line are already included. 38 | divSourceCode.forEach((el) => { 39 | if (el.hasAttribute(kCodeLineNumbersAttr)) { 40 | const codeLineAttr = el.getAttribute(kCodeLineNumbersAttr); 41 | el.removeAttribute("data-code-line-numbers"); 42 | if (handleLinesSelector(deck, codeLineAttr)) { 43 | // Only process if attr is a string to select lines to highlights 44 | // e.g "1|3,6|8-11" 45 | const codeBlock = el.querySelectorAll("pre code"); 46 | codeBlock.forEach((code) => { 47 | // move attributes on code block 48 | code.setAttribute(kCodeLineNumbersAttr, codeLineAttr); 49 | 50 | const scrollState = { currentBlock: code }; 51 | 52 | // Check if there are steps and duplicate code block accordingly 53 | const highlightSteps = splitLineNumbers(codeLineAttr); 54 | if (highlightSteps.length > 1) { 55 | // If the original code block has a fragment-index, 56 | // each clone should follow in an incremental sequence 57 | let fragmentIndex = parseInt( 58 | code.getAttribute(kFragmentIndex), 59 | 10 60 | ); 61 | fragmentIndex = 62 | typeof fragmentIndex !== "number" || isNaN(fragmentIndex) 63 | ? null 64 | : fragmentIndex; 65 | 66 | let stepN = 1; 67 | highlightSteps.slice(1).forEach( 68 | // Generate fragments for all steps except the original block 69 | (step) => { 70 | var fragmentBlock = code.cloneNode(true); 71 | fragmentBlock.setAttribute( 72 | "data-code-line-numbers", 73 | joinLineNumbers([step]) 74 | ); 75 | fragmentBlock.classList.add("fragment"); 76 | 77 | // Pandoc sets id on spans we need to keep unique 78 | fragmentBlock 79 | .querySelectorAll(":scope > span") 80 | .forEach((span) => { 81 | if (span.hasAttribute("id")) { 82 | span.setAttribute( 83 | "id", 84 | span.getAttribute("id").concat("-" + stepN) 85 | ); 86 | } 87 | }); 88 | stepN = ++stepN; 89 | 90 | // Add duplicated element after existing one 91 | code.parentNode.appendChild(fragmentBlock); 92 | 93 | // Each new element is highlighted based on the new attributes value 94 | highlightCodeBlock(fragmentBlock); 95 | 96 | if (typeof fragmentIndex === "number") { 97 | fragmentBlock.setAttribute(kFragmentIndex, fragmentIndex); 98 | fragmentIndex += 1; 99 | } else { 100 | fragmentBlock.removeAttribute(kFragmentIndex); 101 | } 102 | 103 | // Scroll highlights into view as we step through them 104 | fragmentBlock.addEventListener( 105 | "visible", 106 | scrollHighlightedLineIntoView.bind( 107 | this, 108 | fragmentBlock, 109 | scrollState 110 | ) 111 | ); 112 | fragmentBlock.addEventListener( 113 | "hidden", 114 | scrollHighlightedLineIntoView.bind( 115 | this, 116 | fragmentBlock.previousSibling, 117 | scrollState 118 | ) 119 | ); 120 | } 121 | ); 122 | code.removeAttribute(kFragmentIndex); 123 | code.setAttribute( 124 | kCodeLineNumbersAttr, 125 | joinLineNumbers([highlightSteps[0]]) 126 | ); 127 | } 128 | 129 | // Scroll the first highlight into view when the slide becomes visible. 130 | const slide = 131 | typeof code.closest === "function" 132 | ? code.closest("section:not(.stack)") 133 | : null; 134 | if (slide) { 135 | const scrollFirstHighlightIntoView = function () { 136 | scrollHighlightedLineIntoView(code, scrollState, true); 137 | slide.removeEventListener( 138 | "visible", 139 | scrollFirstHighlightIntoView 140 | ); 141 | }; 142 | slide.addEventListener("visible", scrollFirstHighlightIntoView); 143 | } 144 | 145 | highlightCodeBlock(code); 146 | }); 147 | } 148 | } 149 | }); 150 | } 151 | 152 | function highlightCodeBlock(codeBlock) { 153 | const highlightSteps = splitLineNumbers( 154 | codeBlock.getAttribute(kCodeLineNumbersAttr) 155 | ); 156 | 157 | if (highlightSteps.length) { 158 | // If we have at least one step, we generate fragments 159 | highlightSteps[0].forEach((highlight) => { 160 | // Add expected class on
     for reveal CSS
    161 |         codeBlock.parentNode.classList.add("code-wrapper");
    162 | 
    163 |         // Select lines to highlight
    164 |         spanToHighlight = [];
    165 |         if (typeof highlight.last === "number") {
    166 |           spanToHighlight = [].slice.call(
    167 |             codeBlock.querySelectorAll(
    168 |               ":scope > span:nth-child(n+" +
    169 |                 highlight.first +
    170 |                 "):nth-child(-n+" +
    171 |                 highlight.last +
    172 |                 ")"
    173 |             )
    174 |           );
    175 |         } else if (typeof highlight.first === "number") {
    176 |           spanToHighlight = [].slice.call(
    177 |             codeBlock.querySelectorAll(
    178 |               ":scope > span:nth-child(" + highlight.first + ")"
    179 |             )
    180 |           );
    181 |         }
    182 |         if (spanToHighlight.length) {
    183 |           // Add a class on  and  to select line to highlight
    184 |           spanToHighlight.forEach((span) =>
    185 |             span.classList.add("highlight-line")
    186 |           );
    187 |           codeBlock.classList.add("has-line-highlights");
    188 |         }
    189 |       });
    190 |     }
    191 |   }
    192 | 
    193 |   /**
    194 |    * Animates scrolling to the first highlighted line
    195 |    * in the given code block.
    196 |    */
    197 |   function scrollHighlightedLineIntoView(block, scrollState, skipAnimation) {
    198 |     window.cancelAnimationFrame(scrollState.animationFrameID);
    199 | 
    200 |     // Match the scroll position of the currently visible
    201 |     // code block
    202 |     if (scrollState.currentBlock) {
    203 |       block.scrollTop = scrollState.currentBlock.scrollTop;
    204 |     }
    205 | 
    206 |     // Remember the current code block so that we can match
    207 |     // its scroll position when showing/hiding fragments
    208 |     scrollState.currentBlock = block;
    209 | 
    210 |     const highlightBounds = getHighlightedLineBounds(block);
    211 |     let viewportHeight = block.offsetHeight;
    212 | 
    213 |     // Subtract padding from the viewport height
    214 |     const blockStyles = window.getComputedStyle(block);
    215 |     viewportHeight -=
    216 |       parseInt(blockStyles.paddingTop) + parseInt(blockStyles.paddingBottom);
    217 | 
    218 |     // Scroll position which centers all highlights
    219 |     const startTop = block.scrollTop;
    220 |     let targetTop =
    221 |       highlightBounds.top +
    222 |       (Math.min(highlightBounds.bottom - highlightBounds.top, viewportHeight) -
    223 |         viewportHeight) /
    224 |         2;
    225 | 
    226 |     // Make sure the scroll target is within bounds
    227 |     targetTop = Math.max(
    228 |       Math.min(targetTop, block.scrollHeight - viewportHeight),
    229 |       0
    230 |     );
    231 | 
    232 |     if (skipAnimation === true || startTop === targetTop) {
    233 |       block.scrollTop = targetTop;
    234 |     } else {
    235 |       // Don't attempt to scroll if there is no overflow
    236 |       if (block.scrollHeight <= viewportHeight) return;
    237 | 
    238 |       let time = 0;
    239 | 
    240 |       const animate = function () {
    241 |         time = Math.min(time + 0.02, 1);
    242 | 
    243 |         // Update our eased scroll position
    244 |         block.scrollTop =
    245 |           startTop + (targetTop - startTop) * easeInOutQuart(time);
    246 | 
    247 |         // Keep animating unless we've reached the end
    248 |         if (time < 1) {
    249 |           scrollState.animationFrameID = requestAnimationFrame(animate);
    250 |         }
    251 |       };
    252 | 
    253 |       animate();
    254 |     }
    255 |   }
    256 | 
    257 |   function getHighlightedLineBounds(block) {
    258 |     const highlightedLines = block.querySelectorAll(".highlight-line");
    259 |     if (highlightedLines.length === 0) {
    260 |       return { top: 0, bottom: 0 };
    261 |     } else {
    262 |       const firstHighlight = highlightedLines[0];
    263 |       const lastHighlight = highlightedLines[highlightedLines.length - 1];
    264 | 
    265 |       return {
    266 |         top: firstHighlight.offsetTop,
    267 |         bottom: lastHighlight.offsetTop + lastHighlight.offsetHeight,
    268 |       };
    269 |     }
    270 |   }
    271 | 
    272 |   /**
    273 |    * The easing function used when scrolling.
    274 |    */
    275 |   function easeInOutQuart(t) {
    276 |     // easeInOutQuart
    277 |     return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
    278 |   }
    279 | 
    280 |   function splitLineNumbers(lineNumbersAttr) {
    281 |     // remove space
    282 |     lineNumbersAttr = lineNumbersAttr.replace("/s/g", "");
    283 |     // seperate steps (for fragment)
    284 |     lineNumbersAttr = lineNumbersAttr.split(delimiters.step);
    285 | 
    286 |     // for each step, calculate first and last line, if any
    287 |     return lineNumbersAttr.map((highlights) => {
    288 |       // detect lines
    289 |       const lines = highlights.split(delimiters.line);
    290 |       return lines.map((range) => {
    291 |         if (/^[\d-]+$/.test(range)) {
    292 |           range = range.split(delimiters.lineRange);
    293 |           const firstLine = parseInt(range[0], 10);
    294 |           const lastLine = range[1] ? parseInt(range[1], 10) : undefined;
    295 |           return {
    296 |             first: firstLine,
    297 |             last: lastLine,
    298 |           };
    299 |         } else {
    300 |           return {};
    301 |         }
    302 |       });
    303 |     });
    304 |   }
    305 | 
    306 |   function joinLineNumbers(splittedLineNumbers) {
    307 |     return splittedLineNumbers
    308 |       .map(function (highlights) {
    309 |         return highlights
    310 |           .map(function (highlight) {
    311 |             // Line range
    312 |             if (typeof highlight.last === "number") {
    313 |               return highlight.first + delimiters.lineRange + highlight.last;
    314 |             }
    315 |             // Single line
    316 |             else if (typeof highlight.first === "number") {
    317 |               return highlight.first;
    318 |             }
    319 |             // All lines
    320 |             else {
    321 |               return "";
    322 |             }
    323 |           })
    324 |           .join(delimiters.line);
    325 |       })
    326 |       .join(delimiters.step);
    327 |   }
    328 | 
    329 |   return {
    330 |     id: "quarto-line-highlight",
    331 |     init: function (deck) {
    332 |       initQuartoLineHighlight(deck);
    333 | 
    334 |       // If we're printing to PDF, scroll the code highlights of
    335 |       // all blocks in the deck into view at once
    336 |       deck.on("pdf-ready", function () {
    337 |         [].slice
    338 |           .call(
    339 |             deck
    340 |               .getRevealElement()
    341 |               .querySelectorAll(
    342 |                 "pre code[data-code-line-numbers].current-fragment"
    343 |               )
    344 |           )
    345 |           .forEach(function (block) {
    346 |             scrollHighlightedLineIntoView(block, {}, true);
    347 |           });
    348 |       });
    349 |     },
    350 |   };
    351 | };
    352 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/quarto-line-highlight/plugin.yml:
    --------------------------------------------------------------------------------
    1 | # adapted from https://github.com/hakimel/reveal.js/tree/master/plugin/highlight
    2 | name: QuartoLineHighlight
    3 | script: line-highlight.js
    4 | stylesheet: line-highlight.css
    5 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/quarto-support/footer.css:
    --------------------------------------------------------------------------------
      1 | .reveal .slide-logo {
      2 |   display: block;
      3 |   position: fixed;
      4 |   bottom: 0;
      5 |   right: 12px;
      6 |   max-height: 2.2rem;
      7 |   height: 100%;
      8 |   width: auto;
      9 |   z-index: 2;
     10 | }
     11 | 
     12 | .reveal .footer {
     13 |   display: block;
     14 |   position: fixed;
     15 |   bottom: 18px;
     16 |   width: 100%;
     17 |   margin: 0 auto;
     18 |   text-align: center;
     19 |   font-size: 18px;
     20 |   z-index: 2;
     21 | }
     22 | 
     23 | .reveal .footer > * {
     24 |   margin-top: 0;
     25 |   margin-bottom: 0;
     26 | }
     27 | 
     28 | .reveal .slide .footer {
     29 |   display: none;
     30 | }
     31 | 
     32 | .reveal .slide-number {
     33 |   bottom: 10px;
     34 |   right: 10px;
     35 |   font-size: 16px;
     36 |   background-color: transparent;
     37 | }
     38 | 
     39 | .reveal.has-logo .slide-number {
     40 |   bottom: initial;
     41 |   top: 8px;
     42 |   right: 8px;
     43 | }
     44 | 
     45 | .reveal .slide-number .slide-number-delimiter {
     46 |   margin: 0;
     47 | }
     48 | 
     49 | .reveal .slide-menu-button {
     50 |   left: 8px;
     51 |   bottom: 8px;
     52 | }
     53 | 
     54 | .reveal .slide-chalkboard-buttons {
     55 |   position: fixed;
     56 |   left: 12px;
     57 |   bottom: 8px;
     58 |   z-index: 30;
     59 |   font-size: 24px;
     60 | }
     61 | 
     62 | .reveal .slide-chalkboard-buttons.slide-menu-offset {
     63 |   left: 54px;
     64 | }
     65 | 
     66 | .reveal .slide-chalkboard-buttons > span {
     67 |   margin-right: 14px;
     68 |   cursor: pointer;
     69 | }
     70 | 
     71 | @media screen and (max-width: 800px) {
     72 |   .reveal .slide-logo {
     73 |     max-height: 1.1rem;
     74 |     bottom: -2px;
     75 |     right: 10px;
     76 |   }
     77 |   .reveal .footer {
     78 |     font-size: 14px;
     79 |     bottom: 12px;
     80 |   }
     81 |   .reveal .slide-number {
     82 |     font-size: 12px;
     83 |     bottom: 7px;
     84 |   }
     85 |   .reveal .slide-menu-button .fas::before {
     86 |     height: 1.3rem;
     87 |     width: 1.3rem;
     88 |     vertical-align: -0.125em;
     89 |     background-size: 1.3rem 1.3rem;
     90 |   }
     91 | 
     92 |   .reveal .slide-chalkboard-buttons .fas::before {
     93 |     height: 0.95rem;
     94 |     width: 0.95rem;
     95 |     background-size: 0.95rem 0.95rem;
     96 |     vertical-align: -0em;
     97 |   }
     98 | 
     99 |   .reveal .slide-chalkboard-buttons.slide-menu-offset {
    100 |     left: 36px;
    101 |   }
    102 |   .reveal .slide-chalkboard-buttons > span {
    103 |     margin-right: 9px;
    104 |   }
    105 | }
    106 | 
    107 | html.print-pdf .reveal .slide-menu-button,
    108 | html.print-pdf .reveal .slide-chalkboard-buttons {
    109 |   display: none;
    110 | }
    111 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/quarto-support/plugin.yml:
    --------------------------------------------------------------------------------
    1 | name: QuartoSupport
    2 | script: support.js
    3 | stylesheet: footer.css
    4 | config:
    5 |   smaller: false
    6 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/quarto-support/support.js:
    --------------------------------------------------------------------------------
      1 | // catch all plugin for various quarto features
      2 | window.QuartoSupport = function () {
      3 |   function isPrintView() {
      4 |     return /print-pdf/gi.test(window.location.search);
      5 |   }
      6 | 
      7 |   // implement controlsAudo
      8 |   function controlsAuto(deck) {
      9 |     const config = deck.getConfig();
     10 |     if (config.controlsAuto === true) {
     11 |       const iframe = window.location !== window.parent.location;
     12 |       const localhost =
     13 |         window.location.hostname === "localhost" ||
     14 |         window.location.hostname === "127.0.0.1";
     15 |       deck.configure({
     16 |         controls:
     17 |           (iframe && !localhost) ||
     18 |           (deck.hasVerticalSlides() && config.navigationMode !== "linear"),
     19 |       });
     20 |     }
     21 |   }
     22 | 
     23 |   // helper to provide event handlers for all links in a container
     24 |   function handleLinkClickEvents(deck, container) {
     25 |     Array.from(container.querySelectorAll("a")).forEach((el) => {
     26 |       const url = el.getAttribute("href");
     27 |       if (/^(http|www)/gi.test(url)) {
     28 |         el.addEventListener(
     29 |           "click",
     30 |           (ev) => {
     31 |             const fullscreen = !!window.document.fullscreen;
     32 |             const dataPreviewLink = el.getAttribute("data-preview-link");
     33 | 
     34 |             // if there is a local specifcation then use that
     35 |             if (dataPreviewLink) {
     36 |               if (
     37 |                 dataPreviewLink === "true" ||
     38 |                 (dataPreviewLink === "auto" && fullscreen)
     39 |               ) {
     40 |                 ev.preventDefault();
     41 |                 deck.showPreview(url);
     42 |                 return false;
     43 |               }
     44 |             } else {
     45 |               const previewLinks = !!deck.getConfig().previewLinks;
     46 |               const previewLinksAuto =
     47 |                 deck.getConfig().previewLinksAuto === true;
     48 |               if (previewLinks == true || (previewLinksAuto && fullscreen)) {
     49 |                 ev.preventDefault();
     50 |                 deck.showPreview(url);
     51 |                 return false;
     52 |               }
     53 |             }
     54 | 
     55 |             // if the deck is in an iframe we want to open it externally
     56 |             // (don't do this when in vscode though as it has its own
     57 |             // handler for opening links externally that will be play)
     58 |             const iframe = window.location !== window.parent.location;
     59 |             if (
     60 |               iframe &&
     61 |               !window.location.search.includes("quartoPreviewReqId=")
     62 |             ) {
     63 |               ev.preventDefault();
     64 |               ev.stopImmediatePropagation();
     65 |               window.open(url, "_blank");
     66 |               return false;
     67 |             }
     68 | 
     69 |             // if the user has set data-preview-link to "auto" we need to handle the event
     70 |             // (because reveal will interpret "auto" as true)
     71 |             if (dataPreviewLink === "auto") {
     72 |               ev.preventDefault();
     73 |               ev.stopImmediatePropagation();
     74 |               const target =
     75 |                 el.getAttribute("target") ||
     76 |                 (ev.ctrlKey || ev.metaKey ? "_blank" : "");
     77 |               if (target) {
     78 |                 window.open(url, target);
     79 |               } else {
     80 |                 window.location.href = url;
     81 |               }
     82 |               return false;
     83 |             }
     84 |           },
     85 |           false
     86 |         );
     87 |       }
     88 |     });
     89 |   }
     90 | 
     91 |   // implement previewLinksAuto
     92 |   function previewLinksAuto(deck) {
     93 |     handleLinkClickEvents(deck, deck.getRevealElement());
     94 |   }
     95 | 
     96 |   // apply styles
     97 |   function applyGlobalStyles(deck) {
     98 |     if (deck.getConfig()["smaller"] === true) {
     99 |       const revealParent = deck.getRevealElement();
    100 |       revealParent.classList.add("smaller");
    101 |     }
    102 |   }
    103 | 
    104 |   // add logo image
    105 |   function addLogoImage(deck) {
    106 |     const revealParent = deck.getRevealElement();
    107 |     const logoImg = document.querySelector(".slide-logo");
    108 |     if (logoImg) {
    109 |       revealParent.appendChild(logoImg);
    110 |       revealParent.classList.add("has-logo");
    111 |     }
    112 |   }
    113 | 
    114 |   // add footer text
    115 |   function addFooter(deck) {
    116 |     const revealParent = deck.getRevealElement();
    117 |     const defaultFooterDiv = document.querySelector(".footer-default");
    118 |     if (defaultFooterDiv) {
    119 |       revealParent.appendChild(defaultFooterDiv);
    120 |       handleLinkClickEvents(deck, defaultFooterDiv);
    121 |       if (!isPrintView()) {
    122 |         deck.on("slidechanged", function (ev) {
    123 |           const prevSlideFooter = document.querySelector(
    124 |             ".reveal > .footer:not(.footer-default)"
    125 |           );
    126 |           if (prevSlideFooter) {
    127 |             prevSlideFooter.remove();
    128 |           }
    129 |           const currentSlideFooter = ev.currentSlide.querySelector(".footer");
    130 |           if (currentSlideFooter) {
    131 |             defaultFooterDiv.style.display = "none";
    132 |             const slideFooter = currentSlideFooter.cloneNode(true);
    133 |             handleLinkClickEvents(deck, slideFooter);
    134 |             deck.getRevealElement().appendChild(slideFooter);
    135 |           } else {
    136 |             defaultFooterDiv.style.display = "block";
    137 |           }
    138 |         });
    139 |       }
    140 |     }
    141 |   }
    142 | 
    143 |   // add chalkboard buttons
    144 |   function addChalkboardButtons(deck) {
    145 |     const chalkboard = deck.getPlugin("RevealChalkboard");
    146 |     if (chalkboard && !isPrintView()) {
    147 |       const revealParent = deck.getRevealElement();
    148 |       const chalkboardDiv = document.createElement("div");
    149 |       chalkboardDiv.classList.add("slide-chalkboard-buttons");
    150 |       if (document.querySelector(".slide-menu-button")) {
    151 |         chalkboardDiv.classList.add("slide-menu-offset");
    152 |       }
    153 |       // add buttons
    154 |       const buttons = [
    155 |         {
    156 |           icon: "easel2",
    157 |           title: "Toggle Chalkboard (b)",
    158 |           onclick: chalkboard.toggleChalkboard,
    159 |         },
    160 |         {
    161 |           icon: "brush",
    162 |           title: "Toggle Notes Canvas (c)",
    163 |           onclick: chalkboard.toggleNotesCanvas,
    164 |         },
    165 |       ];
    166 |       buttons.forEach(function (button) {
    167 |         const span = document.createElement("span");
    168 |         span.title = button.title;
    169 |         const icon = document.createElement("i");
    170 |         icon.classList.add("fas");
    171 |         icon.classList.add("fa-" + button.icon);
    172 |         span.appendChild(icon);
    173 |         span.onclick = function (event) {
    174 |           event.preventDefault();
    175 |           button.onclick();
    176 |         };
    177 |         chalkboardDiv.appendChild(span);
    178 |       });
    179 |       revealParent.appendChild(chalkboardDiv);
    180 |       const config = deck.getConfig();
    181 |       if (!config.chalkboard.buttons) {
    182 |         chalkboardDiv.classList.add("hidden");
    183 |       }
    184 | 
    185 |       // show and hide chalkboard buttons on slidechange
    186 |       deck.on("slidechanged", function (ev) {
    187 |         const config = deck.getConfig();
    188 |         let buttons = !!config.chalkboard.buttons;
    189 |         const slideButtons = ev.currentSlide.getAttribute(
    190 |           "data-chalkboard-buttons"
    191 |         );
    192 |         if (slideButtons) {
    193 |           if (slideButtons === "true" || slideButtons === "1") {
    194 |             buttons = true;
    195 |           } else if (slideButtons === "false" || slideButtons === "0") {
    196 |             buttons = false;
    197 |           }
    198 |         }
    199 |         if (buttons) {
    200 |           chalkboardDiv.classList.remove("hidden");
    201 |         } else {
    202 |           chalkboardDiv.classList.add("hidden");
    203 |         }
    204 |       });
    205 |     }
    206 |   }
    207 | 
    208 |   function handleTabbyClicks() {
    209 |     const tabs = document.querySelectorAll(".panel-tabset-tabby > li > a");
    210 |     for (let i = 0; i < tabs.length; i++) {
    211 |       const tab = tabs[i];
    212 |       tab.onclick = function (ev) {
    213 |         ev.preventDefault();
    214 |         ev.stopPropagation();
    215 |         return false;
    216 |       };
    217 |     }
    218 |   }
    219 | 
    220 |   function fixupForPrint(deck) {
    221 |     if (isPrintView()) {
    222 |       const slides = deck.getSlides();
    223 |       slides.forEach(function (slide) {
    224 |         slide.removeAttribute("data-auto-animate");
    225 |       });
    226 |       window.document.querySelectorAll(".hljs").forEach(function (el) {
    227 |         el.classList.remove("hljs");
    228 |       });
    229 |       window.document.querySelectorAll(".hljs-ln-code").forEach(function (el) {
    230 |         el.classList.remove("hljs-ln-code");
    231 |       });
    232 |     }
    233 |   }
    234 | 
    235 |   function handleSlideChanges(deck) {
    236 |     // dispatch for htmlwidgets
    237 |     const fireSlideEnter = () => {
    238 |       const event = window.document.createEvent("Event");
    239 |       event.initEvent("slideenter", true, true);
    240 |       window.document.dispatchEvent(event);
    241 |     };
    242 | 
    243 |     const fireSlideChanged = (previousSlide, currentSlide) => {
    244 |       fireSlideEnter();
    245 | 
    246 |       // dispatch for shiny
    247 |       if (window.jQuery) {
    248 |         if (previousSlide) {
    249 |           window.jQuery(previousSlide).trigger("hidden");
    250 |         }
    251 |         if (currentSlide) {
    252 |           window.jQuery(currentSlide).trigger("shown");
    253 |         }
    254 |       }
    255 |     };
    256 | 
    257 |     // fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
    258 |     document.addEventListener("tabby", fireSlideEnter, false);
    259 | 
    260 |     deck.on("slidechanged", function (event) {
    261 |       fireSlideChanged(event.previousSlide, event.currentSlide);
    262 |     });
    263 |   }
    264 | 
    265 |   function workaroundMermaidDistance(deck) {
    266 |     if (window.document.querySelector("pre.mermaid-js")) {
    267 |       const slideCount = deck.getTotalSlides();
    268 |       deck.configure({
    269 |         mobileViewDistance: slideCount,
    270 |         viewDistance: slideCount,
    271 |       });
    272 |     }
    273 |   }
    274 | 
    275 |   return {
    276 |     id: "quarto-support",
    277 |     init: function (deck) {
    278 |       controlsAuto(deck);
    279 |       previewLinksAuto(deck);
    280 |       fixupForPrint(deck);
    281 |       applyGlobalStyles(deck);
    282 |       addLogoImage(deck);
    283 |       addFooter(deck);
    284 |       addChalkboardButtons(deck);
    285 |       handleTabbyClicks();
    286 |       handleSlideChanges(deck);
    287 |       workaroundMermaidDistance(deck);
    288 |     },
    289 |   };
    290 | };
    291 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/reveal-menu/menu.css:
    --------------------------------------------------------------------------------
      1 | .slide-menu-wrapper {
      2 |   font-family: 'Source Sans Pro', Helvetica, sans-serif;
      3 | }
      4 | 
      5 | .slide-menu-wrapper .slide-menu {
      6 |   background-color: #333;
      7 |   z-index: 200;
      8 |   position: fixed;
      9 |   top: 0;
     10 |   width: 300px;
     11 |   height: 100%;
     12 |   /*overflow-y: scroll;*/
     13 |   transition: transform 0.3s;
     14 |   font-size: 16px;
     15 |   font-weight: normal;
     16 | }
     17 | 
     18 | .slide-menu-wrapper .slide-menu.slide-menu--wide {
     19 |   width: 500px;
     20 | }
     21 | 
     22 | .slide-menu-wrapper .slide-menu.slide-menu--third {
     23 |   width: 33%;
     24 | }
     25 | 
     26 | .slide-menu-wrapper .slide-menu.slide-menu--half {
     27 |   width: 50%;
     28 | }
     29 | 
     30 | .slide-menu-wrapper .slide-menu.slide-menu--full {
     31 |   width: 95%;
     32 | }
     33 | 
     34 | /*
     35 |  * Slides menu
     36 |  */
     37 | 
     38 | .slide-menu-wrapper .slide-menu-items {
     39 |   margin: 0;
     40 |   padding: 0;
     41 |   width: 100%;
     42 |   border-bottom: solid 1px #555;
     43 | }
     44 | 
     45 | .slide-menu-wrapper .slide-menu-item,
     46 | .slide-menu-wrapper .slide-menu-item-vertical {
     47 |   display: block;
     48 |   text-align: left;
     49 |   padding: 10px 18px;
     50 |   color: #aaa;
     51 |   cursor: pointer;
     52 | }
     53 | 
     54 | .slide-menu-wrapper .slide-menu-item-vertical {
     55 |   padding-left: 30px;
     56 | }
     57 | 
     58 | .slide-menu-wrapper .slide-menu--wide .slide-menu-item-vertical,
     59 | .slide-menu-wrapper .slide-menu--third .slide-menu-item-vertical,
     60 | .slide-menu-wrapper .slide-menu--half .slide-menu-item-vertical,
     61 | .slide-menu-wrapper .slide-menu--full .slide-menu-item-vertical,
     62 | .slide-menu-wrapper .slide-menu--custom .slide-menu-item-vertical {
     63 |   padding-left: 50px;
     64 | }
     65 | 
     66 | .slide-menu-wrapper .slide-menu-item {
     67 |   border-top: solid 1px #555;
     68 | }
     69 | 
     70 | .slide-menu-wrapper .active-menu-panel li.selected {
     71 |   background-color: #222;
     72 |   color: white;
     73 | }
     74 | 
     75 | .slide-menu-wrapper .active-menu-panel li.active {
     76 |   color: #eee;
     77 | }
     78 | 
     79 | .slide-menu-wrapper .slide-menu-item.no-title .slide-menu-item-title,
     80 | .slide-menu-wrapper .slide-menu-item-vertical.no-title .slide-menu-item-title {
     81 |   font-style: italic;
     82 | }
     83 | 
     84 | .slide-menu-wrapper .slide-menu-item-number {
     85 |   color: #999;
     86 |   padding-right: 6px;
     87 | }
     88 | 
     89 | .slide-menu-wrapper .slide-menu-item i.far,
     90 | .slide-menu-wrapper .slide-menu-item i.fas,
     91 | .slide-menu-wrapper .slide-menu-item-vertical i.far,
     92 | .slide-menu-wrapper .slide-menu-item-vertical i.fas,
     93 | .slide-menu-wrapper .slide-menu-item svg.svg-inline--fa,
     94 | .slide-menu-wrapper .slide-menu-item-vertical svg.svg-inline--fa {
     95 |   padding-right: 12px;
     96 |   display: none;
     97 | }
     98 | 
     99 | .slide-menu-wrapper .slide-menu-item.past i.fas.past,
    100 | .slide-menu-wrapper .slide-menu-item-vertical.past i.fas.past,
    101 | .slide-menu-wrapper .slide-menu-item.active i.fas.active,
    102 | .slide-menu-wrapper .slide-menu-item-vertical.active i.fas.active,
    103 | .slide-menu-wrapper .slide-menu-item.future i.far.future,
    104 | .slide-menu-wrapper .slide-menu-item-vertical.future i.far.future,
    105 | .slide-menu-wrapper .slide-menu-item.past svg.svg-inline--fa.past,
    106 | .slide-menu-wrapper .slide-menu-item-vertical.past svg.svg-inline--fa.past,
    107 | .slide-menu-wrapper .slide-menu-item.active svg.svg-inline--fa.active,
    108 | .slide-menu-wrapper .slide-menu-item-vertical.active svg.svg-inline--fa.active,
    109 | .slide-menu-wrapper .slide-menu-item.future svg.svg-inline--fa.future,
    110 | .slide-menu-wrapper .slide-menu-item-vertical.future svg.svg-inline--fa.future {
    111 |   display: inline-block;
    112 | }
    113 | 
    114 | .slide-menu-wrapper .slide-menu-item.past i.fas.past,
    115 | .slide-menu-wrapper .slide-menu-item-vertical.past i.fas.past,
    116 | .slide-menu-wrapper .slide-menu-item.future i.far.future,
    117 | .slide-menu-wrapper .slide-menu-item-vertical.future i.far.future,
    118 | .slide-menu-wrapper .slide-menu-item.past svg.svg-inline--fa.past,
    119 | .slide-menu-wrapper .slide-menu-item-vertical.past svg.svg-inline--fa.past,
    120 | .slide-menu-wrapper .slide-menu-item.future svg.svg-inline--fa.future,
    121 | .slide-menu-wrapper .slide-menu-item-vertical.future svg.svg-inline--fa.future {
    122 |   opacity: 0.4;
    123 | }
    124 | 
    125 | .slide-menu-wrapper .slide-menu-item.active i.fas.active,
    126 | .slide-menu-wrapper .slide-menu-item-vertical.active i.fas.active,
    127 | .slide-menu-wrapper .slide-menu-item.active svg.svg-inline--fa.active,
    128 | .slide-menu-wrapper .slide-menu-item-vertical.active svg.svg-inline--fa.active {
    129 |   opacity: 0.8;
    130 | }
    131 | 
    132 | .slide-menu-wrapper .slide-menu--left {
    133 |   left: 0;
    134 |   -webkit-transform: translateX(-100%);
    135 |   -ms-transform: translateX(-100%);
    136 |   transform: translateX(-100%);
    137 | }
    138 | 
    139 | .slide-menu-wrapper .slide-menu--left.active {
    140 |   -webkit-transform: translateX(0);
    141 |   -ms-transform: translateX(0);
    142 |   transform: translateX(0);
    143 | }
    144 | 
    145 | .slide-menu-wrapper .slide-menu--right {
    146 |   right: 0;
    147 |   -webkit-transform: translateX(100%);
    148 |   -ms-transform: translateX(100%);
    149 |   transform: translateX(100%);
    150 | }
    151 | 
    152 | .slide-menu-wrapper .slide-menu--right.active {
    153 |   -webkit-transform: translateX(0);
    154 |   -ms-transform: translateX(0);
    155 |   transform: translateX(0);
    156 | }
    157 | 
    158 | .slide-menu-wrapper {
    159 |   transition: transform 0.3s;
    160 | }
    161 | 
    162 | /*
    163 |  * Toolbar
    164 |  */
    165 | .slide-menu-wrapper .slide-menu-toolbar {
    166 |   height: 60px;
    167 |   width: 100%;
    168 |   font-size: 12px;
    169 |   display: table;
    170 |   table-layout: fixed; /* ensures equal width */
    171 |   margin: 0;
    172 |   padding: 0;
    173 |   border-bottom: solid 2px #666;
    174 | }
    175 | 
    176 | .slide-menu-wrapper .slide-menu-toolbar > li {
    177 |   display: table-cell;
    178 |   line-height: 150%;
    179 |   text-align: center;
    180 |   vertical-align: middle;
    181 |   cursor: pointer;
    182 |   color: #aaa;
    183 |   border-radius: 3px;
    184 | }
    185 | 
    186 | .slide-menu-wrapper .slide-menu-toolbar > li.toolbar-panel-button i,
    187 | .slide-menu-wrapper
    188 |   .slide-menu-toolbar
    189 |   > li.toolbar-panel-button
    190 |   svg.svg-inline--fa {
    191 |   font-size: 1.7em;
    192 | }
    193 | 
    194 | .slide-menu-wrapper .slide-menu-toolbar > li.active-toolbar-button {
    195 |   color: white;
    196 |   text-shadow: 0 1px black;
    197 |   text-decoration: underline;
    198 | }
    199 | 
    200 | .slide-menu-toolbar > li.toolbar-panel-button:hover {
    201 |   color: white;
    202 | }
    203 | 
    204 | .slide-menu-toolbar
    205 |   > li.toolbar-panel-button:hover
    206 |   span.slide-menu-toolbar-label,
    207 | .slide-menu-wrapper
    208 |   .slide-menu-toolbar
    209 |   > li.active-toolbar-button
    210 |   span.slide-menu-toolbar-label {
    211 |   visibility: visible;
    212 | }
    213 | 
    214 | /*
    215 |  * Panels
    216 |  */
    217 | .slide-menu-wrapper .slide-menu-panel {
    218 |   position: absolute;
    219 |   width: 100%;
    220 |   visibility: hidden;
    221 |   height: calc(100% - 60px);
    222 |   overflow-x: hidden;
    223 |   overflow-y: auto;
    224 |   color: #aaa;
    225 | }
    226 | 
    227 | .slide-menu-wrapper .slide-menu-panel.active-menu-panel {
    228 |   visibility: visible;
    229 | }
    230 | 
    231 | .slide-menu-wrapper .slide-menu-panel h1,
    232 | .slide-menu-wrapper .slide-menu-panel h2,
    233 | .slide-menu-wrapper .slide-menu-panel h3,
    234 | .slide-menu-wrapper .slide-menu-panel h4,
    235 | .slide-menu-wrapper .slide-menu-panel h5,
    236 | .slide-menu-wrapper .slide-menu-panel h6 {
    237 |   margin: 20px 0 10px 0;
    238 |   color: #fff;
    239 |   line-height: 1.2;
    240 |   letter-spacing: normal;
    241 |   text-shadow: none;
    242 | }
    243 | 
    244 | .slide-menu-wrapper .slide-menu-panel h1 {
    245 |   font-size: 1.6em;
    246 | }
    247 | .slide-menu-wrapper .slide-menu-panel h2 {
    248 |   font-size: 1.4em;
    249 | }
    250 | .slide-menu-wrapper .slide-menu-panel h3 {
    251 |   font-size: 1.3em;
    252 | }
    253 | .slide-menu-wrapper .slide-menu-panel h4 {
    254 |   font-size: 1.1em;
    255 | }
    256 | .slide-menu-wrapper .slide-menu-panel h5 {
    257 |   font-size: 1em;
    258 | }
    259 | .slide-menu-wrapper .slide-menu-panel h6 {
    260 |   font-size: 0.9em;
    261 | }
    262 | 
    263 | .slide-menu-wrapper .slide-menu-panel p {
    264 |   margin: 10px 0 5px 0;
    265 | }
    266 | 
    267 | .slide-menu-wrapper .slide-menu-panel a {
    268 |   color: #ccc;
    269 |   text-decoration: underline;
    270 | }
    271 | 
    272 | .slide-menu-wrapper .slide-menu-panel a:hover {
    273 |   color: white;
    274 | }
    275 | 
    276 | .slide-menu-wrapper .slide-menu-item a {
    277 |   text-decoration: none;
    278 | }
    279 | 
    280 | .slide-menu-wrapper .slide-menu-custom-panel {
    281 |   width: calc(100% - 20px);
    282 |   padding-left: 10px;
    283 |   padding-right: 10px;
    284 | }
    285 | 
    286 | .slide-menu-wrapper .slide-menu-custom-panel .slide-menu-items {
    287 |   width: calc(100% + 20px);
    288 |   margin-left: -10px;
    289 |   margin-right: 10px;
    290 | }
    291 | 
    292 | /*
    293 |  * Theme and Transitions buttons
    294 |  */
    295 | 
    296 | .slide-menu-wrapper div[data-panel='Themes'] li,
    297 | .slide-menu-wrapper div[data-panel='Transitions'] li {
    298 |   display: block;
    299 |   text-align: left;
    300 |   cursor: pointer;
    301 |   color: #848484;
    302 | }
    303 | 
    304 | /*
    305 |  * Menu controls
    306 |  */
    307 | .reveal .slide-menu-button {
    308 |   position: fixed;
    309 |   left: 30px;
    310 |   bottom: 30px;
    311 |   z-index: 30;
    312 |   font-size: 24px;
    313 | }
    314 | 
    315 | /*
    316 |  * Menu overlay
    317 |  */
    318 | 
    319 | .slide-menu-wrapper .slide-menu-overlay {
    320 |   position: fixed;
    321 |   z-index: 199;
    322 |   top: 0;
    323 |   left: 0;
    324 |   overflow: hidden;
    325 |   width: 0;
    326 |   height: 0;
    327 |   background-color: #000;
    328 |   opacity: 0;
    329 |   transition: opacity 0.3s, width 0s 0.3s, height 0s 0.3s;
    330 | }
    331 | 
    332 | .slide-menu-wrapper .slide-menu-overlay.active {
    333 |   width: 100%;
    334 |   height: 100%;
    335 |   opacity: 0.7;
    336 |   transition: opacity 0.3s;
    337 | }
    338 | 
    339 | /*
    340 |  * Hide menu for pdf printing
    341 |  */
    342 | body.print-pdf .slide-menu-wrapper .slide-menu,
    343 | body.print-pdf .reveal .slide-menu-button,
    344 | body.print-pdf .slide-menu-wrapper .slide-menu-overlay {
    345 |   display: none;
    346 | }
    347 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/reveal-menu/plugin.yml:
    --------------------------------------------------------------------------------
     1 | name: RevealMenu
     2 | script: [menu.js, quarto-menu.js]
     3 | stylesheet: [menu.css, quarto-menu.css]
     4 | config:
     5 |   menu:
     6 |     side: "left"
     7 |     useTextContentForMissingTitles: true
     8 |     markers: false
     9 |     loadIcons: false
    10 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/reveal-menu/quarto-menu.css:
    --------------------------------------------------------------------------------
     1 | .slide-menu-wrapper .slide-tool-item {
     2 |   display: block;
     3 |   text-align: left;
     4 |   padding: 10px 18px;
     5 |   color: #aaa;
     6 |   cursor: pointer;
     7 |   border-top: solid 1px #555;
     8 | }
     9 | 
    10 | .slide-menu-wrapper .slide-tool-item a {
    11 |   text-decoration: none;
    12 | }
    13 | 
    14 | .slide-menu-wrapper .slide-tool-item kbd {
    15 |   font-family: monospace;
    16 |   margin-right: 10px;
    17 |   padding: 3px 8px;
    18 |   color: inherit;
    19 |   border: 1px solid;
    20 |   border-radius: 5px;
    21 |   border-color: #555;
    22 | }
    23 | 
    24 | .slide-menu-wrapper .slide-menu-toolbar > li.active-toolbar-button {
    25 |   text-decoration: none;
    26 | }
    27 | 
    28 | .reveal .slide-menu-button {
    29 |   left: 8px;
    30 |   bottom: 8px;
    31 | }
    32 | 
    33 | .reveal .slide-menu-button .fas::before,
    34 | .reveal .slide-chalkboard-buttons .fas::before,
    35 | .slide-menu-wrapper .slide-menu-toolbar .fas::before {
    36 |   display: inline-block;
    37 |   height: 2.2rem;
    38 |   width: 2.2rem;
    39 |   content: "";
    40 |   vertical-align: -0.125em;
    41 |   background-repeat: no-repeat;
    42 |   background-size: 2.2rem 2.2rem;
    43 | }
    44 | 
    45 | .reveal .slide-chalkboard-buttons .fas::before {
    46 |   height: 1.45rem;
    47 |   width: 1.45rem;
    48 |   background-size: 1.45rem 1.45rem;
    49 |   vertical-align: 0.1em;
    50 | }
    51 | 
    52 | .slide-menu-wrapper .slide-menu-toolbar .fas::before {
    53 |   height: 1.8rem;
    54 |   width: 1.8rem;
    55 |   background-size: 1.8rem 1.8rem;
    56 | }
    57 | 
    58 | .slide-menu-wrapper .slide-menu-toolbar .fa-images::before {
    59 |   background-image: url('data:image/svg+xml,');
    60 | }
    61 | 
    62 | .slide-menu-wrapper .slide-menu-toolbar .fa-gear::before {
    63 |   background-image: url('data:image/svg+xml,');
    64 | }
    65 | 
    66 | .slide-menu-wrapper .slide-menu-toolbar .fa-times::before {
    67 |   background-image: url('data:image/svg+xml,');
    68 | }
    69 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/reveal-menu/quarto-menu.js:
    --------------------------------------------------------------------------------
     1 | window.revealMenuToolHandler = function (handler) {
     2 |   return function (event) {
     3 |     event.preventDefault();
     4 |     handler();
     5 |     Reveal.getPlugin("menu").closeMenu();
     6 |   };
     7 | };
     8 | 
     9 | window.RevealMenuToolHandlers = {
    10 |   fullscreen: revealMenuToolHandler(function () {
    11 |     const element = document.documentElement;
    12 |     const requestMethod =
    13 |       element.requestFullscreen ||
    14 |       element.webkitRequestFullscreen ||
    15 |       element.webkitRequestFullScreen ||
    16 |       element.mozRequestFullScreen ||
    17 |       element.msRequestFullscreen;
    18 |     if (requestMethod) {
    19 |       requestMethod.apply(element);
    20 |     }
    21 |   }),
    22 |   speakerMode: revealMenuToolHandler(function () {
    23 |     Reveal.getPlugin("notes").open();
    24 |   }),
    25 |   keyboardHelp: revealMenuToolHandler(function () {
    26 |     Reveal.toggleHelp(true);
    27 |   }),
    28 |   overview: revealMenuToolHandler(function () {
    29 |     Reveal.toggleOverview(true);
    30 |   }),
    31 |   toggleChalkboard: revealMenuToolHandler(function () {
    32 |     RevealChalkboard.toggleChalkboard();
    33 |   }),
    34 |   toggleNotesCanvas: revealMenuToolHandler(function () {
    35 |     RevealChalkboard.toggleNotesCanvas();
    36 |   }),
    37 |   downloadDrawings: revealMenuToolHandler(function () {
    38 |     RevealChalkboard.download();
    39 |   }),
    40 |   togglePdfExport: revealMenuToolHandler(function () {
    41 |     PdfExport.togglePdfExport();
    42 |   }),
    43 | };
    44 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/search/plugin.js:
    --------------------------------------------------------------------------------
      1 | /*!
      2 |  * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
      3 |  * by navigatating to that slide and highlighting it.
      4 |  *
      5 |  * @author Jon Snyder , February 2013
      6 |  */
      7 | 
      8 | const Plugin = () => {
      9 | 
     10 | 	// The reveal.js instance this plugin is attached to
     11 | 	let deck;
     12 | 
     13 | 	let searchElement;
     14 | 	let searchButton;
     15 | 	let searchInput;
     16 | 
     17 | 	let matchedSlides;
     18 | 	let currentMatchedIndex;
     19 | 	let searchboxDirty;
     20 | 	let hilitor;
     21 | 
     22 | 	function render() {
     23 | 
     24 | 		searchElement = document.createElement( 'div' );
     25 | 		searchElement.classList.add( 'searchbox' );
     26 | 		searchElement.style.position = 'absolute';
     27 | 		searchElement.style.top = '10px';
     28 | 		searchElement.style.right = '10px';
     29 | 		searchElement.style.zIndex = 10;
     30 | 
     31 | 		//embedded base64 search icon Designed by Sketchdock - http://www.sketchdock.com/:
     32 | 		searchElement.innerHTML = `
     33 | 		`;
     34 | 
     35 | 		searchInput = searchElement.querySelector( '.searchinput' );
     36 | 		searchInput.style.width = '240px';
     37 | 		searchInput.style.fontSize = '14px';
     38 | 		searchInput.style.padding = '4px 6px';
     39 | 		searchInput.style.color = '#000';
     40 | 		searchInput.style.background = '#fff';
     41 | 		searchInput.style.borderRadius = '2px';
     42 | 		searchInput.style.border = '0';
     43 | 		searchInput.style.outline = '0';
     44 | 		searchInput.style.boxShadow = '0 2px 18px rgba(0, 0, 0, 0.2)';
     45 | 		searchInput.style['-webkit-appearance']  = 'none';
     46 | 
     47 | 		deck.getRevealElement().appendChild( searchElement );
     48 | 
     49 | 		// searchButton.addEventListener( 'click', function(event) {
     50 | 		// 	doSearch();
     51 | 		// }, false );
     52 | 
     53 | 		searchInput.addEventListener( 'keyup', function( event ) {
     54 | 			switch (event.keyCode) {
     55 | 				case 13:
     56 | 					event.preventDefault();
     57 | 					doSearch();
     58 | 					searchboxDirty = false;
     59 | 					break;
     60 | 				default:
     61 | 					searchboxDirty = true;
     62 | 			}
     63 | 		}, false );
     64 | 
     65 | 		closeSearch();
     66 | 
     67 | 	}
     68 | 
     69 | 	function openSearch() {
     70 | 		if( !searchElement ) render();
     71 | 
     72 | 		searchElement.style.display = 'inline';
     73 | 		searchInput.focus();
     74 | 		searchInput.select();
     75 | 	}
     76 | 
     77 | 	function closeSearch() {
     78 | 		if( !searchElement ) render();
     79 | 
     80 | 		searchElement.style.display = 'none';
     81 | 		if(hilitor) hilitor.remove();
     82 | 	}
     83 | 
     84 | 	function toggleSearch() {
     85 | 		if( !searchElement ) render();
     86 | 
     87 | 		if (searchElement.style.display !== 'inline') {
     88 | 			openSearch();
     89 | 		}
     90 | 		else {
     91 | 			closeSearch();
     92 | 		}
     93 | 	}
     94 | 
     95 | 	function doSearch() {
     96 | 		//if there's been a change in the search term, perform a new search:
     97 | 		if (searchboxDirty) {
     98 | 			var searchstring = searchInput.value;
     99 | 
    100 | 			if (searchstring === '') {
    101 | 				if(hilitor) hilitor.remove();
    102 | 				matchedSlides = null;
    103 | 			}
    104 | 			else {
    105 | 				//find the keyword amongst the slides
    106 | 				hilitor = new Hilitor("slidecontent");
    107 | 				matchedSlides = hilitor.apply(searchstring);
    108 | 				currentMatchedIndex = 0;
    109 | 			}
    110 | 		}
    111 | 
    112 | 		if (matchedSlides) {
    113 | 			//navigate to the next slide that has the keyword, wrapping to the first if necessary
    114 | 			if (matchedSlides.length && (matchedSlides.length <= currentMatchedIndex)) {
    115 | 				currentMatchedIndex = 0;
    116 | 			}
    117 | 			if (matchedSlides.length > currentMatchedIndex) {
    118 | 				deck.slide(matchedSlides[currentMatchedIndex].h, matchedSlides[currentMatchedIndex].v);
    119 | 				currentMatchedIndex++;
    120 | 			}
    121 | 		}
    122 | 	}
    123 | 
    124 | 	// Original JavaScript code by Chirp Internet: www.chirp.com.au
    125 | 	// Please acknowledge use of this code by including this header.
    126 | 	// 2/2013 jon: modified regex to display any match, not restricted to word boundaries.
    127 | 	function Hilitor(id, tag) {
    128 | 
    129 | 		var targetNode = document.getElementById(id) || document.body;
    130 | 		var hiliteTag = tag || "EM";
    131 | 		var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM)$");
    132 | 		var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"];
    133 | 		var wordColor = [];
    134 | 		var colorIdx = 0;
    135 | 		var matchRegex = "";
    136 | 		var matchingSlides = [];
    137 | 
    138 | 		this.setRegex = function(input)
    139 | 		{
    140 | 			input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|");
    141 | 			matchRegex = new RegExp("(" + input + ")","i");
    142 | 		}
    143 | 
    144 | 		this.getRegex = function()
    145 | 		{
    146 | 			return matchRegex.toString().replace(/^\/\\b\(|\)\\b\/i$/g, "").replace(/\|/g, " ");
    147 | 		}
    148 | 
    149 | 		// recursively apply word highlighting
    150 | 		this.hiliteWords = function(node)
    151 | 		{
    152 | 			if(node == undefined || !node) return;
    153 | 			if(!matchRegex) return;
    154 | 			if(skipTags.test(node.nodeName)) return;
    155 | 
    156 | 			if(node.hasChildNodes()) {
    157 | 				for(var i=0; i < node.childNodes.length; i++)
    158 | 					this.hiliteWords(node.childNodes[i]);
    159 | 			}
    160 | 			if(node.nodeType == 3) { // NODE_TEXT
    161 | 				var nv, regs;
    162 | 				if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) {
    163 | 					//find the slide's section element and save it in our list of matching slides
    164 | 					var secnode = node;
    165 | 					while (secnode != null && secnode.nodeName != 'SECTION') {
    166 | 						secnode = secnode.parentNode;
    167 | 					}
    168 | 
    169 | 					var slideIndex = deck.getIndices(secnode);
    170 | 					var slidelen = matchingSlides.length;
    171 | 					var alreadyAdded = false;
    172 | 					for (var i=0; i < slidelen; i++) {
    173 | 						if ( (matchingSlides[i].h === slideIndex.h) && (matchingSlides[i].v === slideIndex.v) ) {
    174 | 							alreadyAdded = true;
    175 | 						}
    176 | 					}
    177 | 					if (! alreadyAdded) {
    178 | 						matchingSlides.push(slideIndex);
    179 | 					}
    180 | 
    181 | 					if(!wordColor[regs[0].toLowerCase()]) {
    182 | 						wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length];
    183 | 					}
    184 | 
    185 | 					var match = document.createElement(hiliteTag);
    186 | 					match.appendChild(document.createTextNode(regs[0]));
    187 | 					match.style.backgroundColor = wordColor[regs[0].toLowerCase()];
    188 | 					match.style.fontStyle = "inherit";
    189 | 					match.style.color = "#000";
    190 | 
    191 | 					var after = node.splitText(regs.index);
    192 | 					after.nodeValue = after.nodeValue.substring(regs[0].length);
    193 | 					node.parentNode.insertBefore(match, after);
    194 | 				}
    195 | 			}
    196 | 		};
    197 | 
    198 | 		// remove highlighting
    199 | 		this.remove = function()
    200 | 		{
    201 | 			var arr = document.getElementsByTagName(hiliteTag);
    202 | 			var el;
    203 | 			while(arr.length && (el = arr[0])) {
    204 | 				el.parentNode.replaceChild(el.firstChild, el);
    205 | 			}
    206 | 		};
    207 | 
    208 | 		// start highlighting at target node
    209 | 		this.apply = function(input)
    210 | 		{
    211 | 			if(input == undefined || !input) return;
    212 | 			this.remove();
    213 | 			this.setRegex(input);
    214 | 			this.hiliteWords(targetNode);
    215 | 			return matchingSlides;
    216 | 		};
    217 | 
    218 | 	}
    219 | 
    220 | 	return {
    221 | 
    222 | 		id: 'search',
    223 | 
    224 | 		init: reveal => {
    225 | 
    226 | 			deck = reveal;
    227 | 			deck.registerKeyboardShortcut( 'CTRL + Shift + F', 'Search' );
    228 | 
    229 | 			document.addEventListener( 'keydown', function( event ) {
    230 | 				if( event.key == "F" && (event.ctrlKey || event.metaKey) ) { //Control+Shift+f
    231 | 					event.preventDefault();
    232 | 					toggleSearch();
    233 | 				}
    234 | 			}, false );
    235 | 
    236 | 		},
    237 | 
    238 | 		open: openSearch
    239 | 
    240 | 	}
    241 | };
    242 | 
    243 | export default Plugin;
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/search/search.esm.js:
    --------------------------------------------------------------------------------
    1 | var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},t=function(e){try{return!!e()}catch(e){return!0}},n=!t((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),r=function(e){return e&&e.Math==Math&&e},o=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof e&&e)||function(){return this}()||Function("return this")(),i=t,c=/#|\.prototype\./,a=function(e,t){var n=l[u(e)];return n==s||n!=f&&("function"==typeof t?i(t):!!t)},u=a.normalize=function(e){return String(e).replace(c,".").toLowerCase()},l=a.data={},f=a.NATIVE="N",s=a.POLYFILL="P",p=a,g=function(e){return"object"==typeof e?null!==e:"function"==typeof e},d=g,h=function(e){if(!d(e))throw TypeError(String(e)+" is not an object");return e},y=g,v=h,x=function(e){if(!y(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e},b=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(n,[]),t=n instanceof Array}catch(e){}return function(n,r){return v(n),x(r),t?e.call(n,r):n.__proto__=r,n}}():void 0),E=g,m=b,S={},w=g,O=o.document,R=w(O)&&w(O.createElement),T=function(e){return R?O.createElement(e):{}},_=!n&&!t((function(){return 7!=Object.defineProperty(T("div"),"a",{get:function(){return 7}}).a})),j=g,P=function(e,t){if(!j(e))return e;var n,r;if(t&&"function"==typeof(n=e.toString)&&!j(r=n.call(e)))return r;if("function"==typeof(n=e.valueOf)&&!j(r=n.call(e)))return r;if(!t&&"function"==typeof(n=e.toString)&&!j(r=n.call(e)))return r;throw TypeError("Can't convert object to primitive value")},I=n,C=_,N=h,A=P,k=Object.defineProperty;S.f=I?k:function(e,t,n){if(N(e),t=A(t,!0),N(n),C)try{return k(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e};var $={},L=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e},M=L,U=function(e){return Object(M(e))},D=U,F={}.hasOwnProperty,z=function(e,t){return F.call(D(e),t)},K={}.toString,B=function(e){return K.call(e).slice(8,-1)},W=B,G="".split,V=t((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==W(e)?G.call(e,""):Object(e)}:Object,Y=L,q=function(e){return V(Y(e))},X=Math.ceil,H=Math.floor,J=function(e){return isNaN(e=+e)?0:(e>0?H:X)(e)},Q=J,Z=Math.min,ee=function(e){return e>0?Z(Q(e),9007199254740991):0},te=J,ne=Math.max,re=Math.min,oe=q,ie=ee,ce=function(e,t){var n=te(e);return n<0?ne(n+t,0):re(n,t)},ae=function(e){return function(t,n,r){var o,i=oe(t),c=ie(i.length),a=ce(r,c);if(e&&n!=n){for(;c>a;)if((o=i[a++])!=o)return!0}else for(;c>a;a++)if((e||a in i)&&i[a]===n)return e||a||0;return!e&&-1}},ue={includes:ae(!0),indexOf:ae(!1)},le={},fe=z,se=q,pe=ue.indexOf,ge=le,de=function(e,t){var n,r=se(e),o=0,i=[];for(n in r)!fe(ge,n)&&fe(r,n)&&i.push(n);for(;t.length>o;)fe(r,n=t[o++])&&(~pe(i,n)||i.push(n));return i},he=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"].concat("length","prototype");$.f=Object.getOwnPropertyNames||function(e){return de(e,he)};var ye={exports:{}},ve=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}},xe=S,be=ve,Ee=n?function(e,t,n){return xe.f(e,t,be(1,n))}:function(e,t,n){return e[t]=n,e},me=o,Se=Ee,we=function(e,t){try{Se(me,e,t)}catch(n){me[e]=t}return t},Oe=we,Re=o["__core-js_shared__"]||Oe("__core-js_shared__",{}),Te=Re;(ye.exports=function(e,t){return Te[e]||(Te[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.12.1",mode:"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"});var _e,je,Pe=0,Ie=Math.random(),Ce=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++Pe+Ie).toString(36)},Ne=o,Ae=o,ke=function(e){return"function"==typeof e?e:void 0},$e=function(e,t){return arguments.length<2?ke(Ne[e])||ke(Ae[e]):Ne[e]&&Ne[e][t]||Ae[e]&&Ae[e][t]},Le=$e("navigator","userAgent")||"",Me=o.process,Ue=Me&&Me.versions,De=Ue&&Ue.v8;De?je=(_e=De.split("."))[0]<4?1:_e[0]+_e[1]:Le&&(!(_e=Le.match(/Edge\/(\d+)/))||_e[1]>=74)&&(_e=Le.match(/Chrome\/(\d+)/))&&(je=_e[1]);var Fe=je&&+je,ze=t,Ke=!!Object.getOwnPropertySymbols&&!ze((function(){return!String(Symbol())||!Symbol.sham&&Fe&&Fe<41})),Be=Ke&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,We=o,Ge=ye.exports,Ve=z,Ye=Ce,qe=Ke,Xe=Be,He=Ge("wks"),Je=We.Symbol,Qe=Xe?Je:Je&&Je.withoutSetter||Ye,Ze=function(e){return Ve(He,e)&&(qe||"string"==typeof He[e])||(qe&&Ve(Je,e)?He[e]=Je[e]:He[e]=Qe("Symbol."+e)),He[e]},et=g,tt=B,nt=Ze("match"),rt=h,ot=function(){var e=rt(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.dotAll&&(t+="s"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t},it={},ct=t;function at(e,t){return RegExp(e,t)}it.UNSUPPORTED_Y=ct((function(){var e=at("a","y");return e.lastIndex=2,null!=e.exec("abcd")})),it.BROKEN_CARET=ct((function(){var e=at("^r","gy");return e.lastIndex=2,null!=e.exec("str")}));var ut={exports:{}},lt=Re,ft=Function.toString;"function"!=typeof lt.inspectSource&&(lt.inspectSource=function(e){return ft.call(e)});var st,pt,gt,dt=lt.inspectSource,ht=dt,yt=o.WeakMap,vt="function"==typeof yt&&/native code/.test(ht(yt)),xt=ye.exports,bt=Ce,Et=xt("keys"),mt=vt,St=g,wt=Ee,Ot=z,Rt=Re,Tt=function(e){return Et[e]||(Et[e]=bt(e))},_t=le,jt=o.WeakMap;if(mt||Rt.state){var Pt=Rt.state||(Rt.state=new jt),It=Pt.get,Ct=Pt.has,Nt=Pt.set;st=function(e,t){if(Ct.call(Pt,e))throw new TypeError("Object already initialized");return t.facade=e,Nt.call(Pt,e,t),t},pt=function(e){return It.call(Pt,e)||{}},gt=function(e){return Ct.call(Pt,e)}}else{var At=Tt("state");_t[At]=!0,st=function(e,t){if(Ot(e,At))throw new TypeError("Object already initialized");return t.facade=e,wt(e,At,t),t},pt=function(e){return Ot(e,At)?e[At]:{}},gt=function(e){return Ot(e,At)}}var kt={set:st,get:pt,has:gt,enforce:function(e){return gt(e)?pt(e):st(e,{})},getterFor:function(e){return function(t){var n;if(!St(t)||(n=pt(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}},$t=o,Lt=Ee,Mt=z,Ut=we,Dt=dt,Ft=kt.get,zt=kt.enforce,Kt=String(String).split("String");(ut.exports=function(e,t,n,r){var o,i=!!r&&!!r.unsafe,c=!!r&&!!r.enumerable,a=!!r&&!!r.noTargetGet;"function"==typeof n&&("string"!=typeof t||Mt(n,"name")||Lt(n,"name",t),(o=zt(n)).source||(o.source=Kt.join("string"==typeof t?t:""))),e!==$t?(i?!a&&e[t]&&(c=!0):delete e[t],c?e[t]=n:Lt(e,t,n)):c?e[t]=n:Ut(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&Ft(this).source||Dt(this)}));var Bt=$e,Wt=S,Gt=n,Vt=Ze("species"),Yt=n,qt=o,Xt=p,Ht=function(e,t,n){var r,o;return m&&"function"==typeof(r=t.constructor)&&r!==n&&E(o=r.prototype)&&o!==n.prototype&&m(e,o),e},Jt=S.f,Qt=$.f,Zt=function(e){var t;return et(e)&&(void 0!==(t=e[nt])?!!t:"RegExp"==tt(e))},en=ot,tn=it,nn=ut.exports,rn=t,on=kt.enforce,cn=function(e){var t=Bt(e),n=Wt.f;Gt&&t&&!t[Vt]&&n(t,Vt,{configurable:!0,get:function(){return this}})},an=Ze("match"),un=qt.RegExp,ln=un.prototype,fn=/a/g,sn=/a/g,pn=new un(fn)!==fn,gn=tn.UNSUPPORTED_Y;if(Yt&&Xt("RegExp",!pn||gn||rn((function(){return sn[an]=!1,un(fn)!=fn||un(sn)==sn||"/a/i"!=un(fn,"i")})))){for(var dn=function(e,t){var n,r=this instanceof dn,o=Zt(e),i=void 0===t;if(!r&&o&&e.constructor===dn&&i)return e;pn?o&&!i&&(e=e.source):e instanceof dn&&(i&&(t=en.call(e)),e=e.source),gn&&(n=!!t&&t.indexOf("y")>-1)&&(t=t.replace(/y/g,""));var c=Ht(pn?new un(e,t):un(e,t),r?this:ln,dn);gn&&n&&(on(c).sticky=!0);return c},hn=function(e){e in dn||Jt(dn,e,{configurable:!0,get:function(){return un[e]},set:function(t){un[e]=t}})},yn=Qt(un),vn=0;yn.length>vn;)hn(yn[vn++]);ln.constructor=dn,dn.prototype=ln,nn(qt,"RegExp",dn)}cn("RegExp");var xn={},bn={},En={}.propertyIsEnumerable,mn=Object.getOwnPropertyDescriptor,Sn=mn&&!En.call({1:2},1);bn.f=Sn?function(e){var t=mn(this,e);return!!t&&t.enumerable}:En;var wn=n,On=bn,Rn=ve,Tn=q,_n=P,jn=z,Pn=_,In=Object.getOwnPropertyDescriptor;xn.f=wn?In:function(e,t){if(e=Tn(e),t=_n(t,!0),Pn)try{return In(e,t)}catch(e){}if(jn(e,t))return Rn(!On.f.call(e,t),e[t])};var Cn={};Cn.f=Object.getOwnPropertySymbols;var Nn=$,An=Cn,kn=h,$n=$e("Reflect","ownKeys")||function(e){var t=Nn.f(kn(e)),n=An.f;return n?t.concat(n(e)):t},Ln=z,Mn=$n,Un=xn,Dn=S,Fn=o,zn=xn.f,Kn=Ee,Bn=ut.exports,Wn=we,Gn=function(e,t){for(var n=Mn(t),r=Dn.f,o=Un.f,i=0;i0&&(!i.multiline||i.multiline&&"\n"!==e[i.lastIndex-1])&&(u="(?: "+u+")",f=" "+f,l++),n=new RegExp("^(?:"+u+")",a)),tr&&(n=new RegExp("^"+u+"$(?!\\s)",a)),Zn&&(t=i.lastIndex),r=Hn.call(c?n:i,f),c?r?(r.input=r.input.slice(l),r[0]=r[0].slice(l),r.index=i.lastIndex,i.lastIndex+=r[0].length):i.lastIndex=0:Zn&&r&&(i.lastIndex=i.global?r.index+r[0].length:t),tr&&r&&r.length>1&&Jn.call(r[0],n,(function(){for(o=1;o")})),br="$0"==="a".replace(/./,"$0"),Er=dr("replace"),mr=!!/./[Er]&&""===/./[Er]("a","$0"),Sr=!gr((function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var n="ab".split(e);return 2!==n.length||"a"!==n[0]||"b"!==n[1]})),wr=J,Or=L,Rr=function(e){return function(t,n){var r,o,i=String(Or(t)),c=wr(n),a=i.length;return c<0||c>=a?e?"":void 0:(r=i.charCodeAt(c))<55296||r>56319||c+1===a||(o=i.charCodeAt(c+1))<56320||o>57343?e?i.charAt(c):r:e?i.slice(c,c+2):o-56320+(r-55296<<10)+65536}},Tr={codeAt:Rr(!1),charAt:Rr(!0)}.charAt,_r=U,jr=Math.floor,Pr="".replace,Ir=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,Cr=/\$([$&'`]|\d{1,2})/g,Nr=B,Ar=nr,kr=function(e,t,n,r){var o=dr(e),i=!gr((function(){var t={};return t[o]=function(){return 7},7!=""[e](t)})),c=i&&!gr((function(){var t=!1,n=/a/;return"split"===e&&((n={}).constructor={},n.constructor[yr]=function(){return n},n.flags="",n[o]=/./[o]),n.exec=function(){return t=!0,null},n[o](""),!t}));if(!i||!c||"replace"===e&&(!xr||!br||mr)||"split"===e&&!Sr){var a=/./[o],u=n(o,""[e],(function(e,t,n,r,o){var c=t.exec;return c===pr||c===vr.exec?i&&!o?{done:!0,value:a.call(t,n,r)}:{done:!0,value:e.call(n,t,r)}:{done:!1}}),{REPLACE_KEEPS_$0:br,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:mr}),l=u[0],f=u[1];sr(String.prototype,e,l),sr(vr,o,2==t?function(e,t){return f.call(e,this,t)}:function(e){return f.call(e,this)})}r&&hr(vr[o],"sham",!0)},$r=h,Lr=ee,Mr=J,Ur=L,Dr=function(e,t,n){return t+(n?Tr(e,t).length:1)},Fr=function(e,t,n,r,o,i){var c=n+e.length,a=r.length,u=Cr;return void 0!==o&&(o=_r(o),u=Ir),Pr.call(i,u,(function(i,u){var l;switch(u.charAt(0)){case"$":return"$";case"&":return e;case"`":return t.slice(0,n);case"'":return t.slice(c);case"<":l=o[u.slice(1,-1)];break;default:var f=+u;if(0===f)return i;if(f>a){var s=jr(f/10);return 0===s?i:s<=a?void 0===r[s-1]?u.charAt(1):r[s-1]+u.charAt(1):i}l=r[f-1]}return void 0===l?"":l}))},zr=function(e,t){var n=e.exec;if("function"==typeof n){var r=n.call(e,t);if("object"!=typeof r)throw TypeError("RegExp exec method returned something other than an Object or null");return r}if("RegExp"!==Nr(e))throw TypeError("RegExp#exec called on incompatible receiver");return Ar.call(e,t)},Kr=Math.max,Br=Math.min;kr("replace",2,(function(e,t,n,r){var o=r.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,i=r.REPLACE_KEEPS_$0,c=o?"$":"$0";return[function(n,r){var o=Ur(this),i=null==n?void 0:n[e];return void 0!==i?i.call(n,o,r):t.call(String(o),n,r)},function(e,r){if(!o&&i||"string"==typeof r&&-1===r.indexOf(c)){var a=n(t,e,this,r);if(a.done)return a.value}var u=$r(e),l=String(this),f="function"==typeof r;f||(r=String(r));var s=u.global;if(s){var p=u.unicode;u.lastIndex=0}for(var g=[];;){var d=zr(u,l);if(null===d)break;if(g.push(d),!s)break;""===String(d[0])&&(u.lastIndex=Dr(l,Lr(u.lastIndex),p))}for(var h,y="",v=0,x=0;x=v&&(y+=l.slice(v,E)+R,v=E+b.length)}return y+l.slice(v)}]}));var Wr={};Wr[Ze("toStringTag")]="z";var Gr="[object z]"===String(Wr),Vr=Gr,Yr=B,qr=Ze("toStringTag"),Xr="Arguments"==Yr(function(){return arguments}()),Hr=Vr?Yr:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),qr))?n:Xr?Yr(t):"Object"==(r=Yr(t))&&"function"==typeof t.callee?"Arguments":r},Jr=Gr?{}.toString:function(){return"[object "+Hr(this)+"]"},Qr=Gr,Zr=ut.exports,eo=Jr;Qr||Zr(Object.prototype,"toString",eo,{unsafe:!0})
    2 | /*!
    3 |  * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
    4 |  * by navigatating to that slide and highlighting it.
    5 |  *
    6 |  * @author Jon Snyder , February 2013
    7 |  */;export default function(){var e,t,n,r,o,i,c;function a(){(t=document.createElement("div")).classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='\n\t\t',(n=t.querySelector(".searchinput")).style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){switch(t.keyCode){case 13:t.preventDefault(),function(){if(i){var t=n.value;""===t?(c&&c.remove(),r=null):(c=new f("slidecontent"),r=c.apply(t),o=0)}r&&(r.length&&r.length<=o&&(o=0),r.length>o&&(e.slide(r[o].h,r[o].v),o++))}(),i=!1;break;default:i=!0}}),!1),l()}function u(){t||a(),t.style.display="inline",n.focus(),n.select()}function l(){t||a(),t.style.display="none",c&&c.remove()}function f(t,n){var r=document.getElementById(t)||document.body,o=n||"EM",i=new RegExp("^(?:"+o+"|SCRIPT|FORM)$"),c=["#ff6","#a0ffff","#9f9","#f99","#f6f"],a=[],u=0,l="",f=[];this.setRegex=function(e){e=e.replace(/^[^\w]+|[^\w]+$/g,"").replace(/[^\w'-]+/g,"|"),l=new RegExp("("+e+")","i")},this.getRegex=function(){return l.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&l&&!i.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n0?H:X)(e)},Q=J,Z=Math.min,ee=function(e){return e>0?Z(Q(e),9007199254740991):0},te=J,ne=Math.max,re=Math.min,oe=q,ie=ee,ce=function(e,t){var n=te(e);return n<0?ne(n+t,0):re(n,t)},ae=function(e){return function(t,n,r){var o,i=oe(t),c=ie(i.length),a=ce(r,c);if(e&&n!=n){for(;c>a;)if((o=i[a++])!=o)return!0}else for(;c>a;a++)if((e||a in i)&&i[a]===n)return e||a||0;return!e&&-1}},ue={includes:ae(!0),indexOf:ae(!1)},le={},fe=z,se=q,pe=ue.indexOf,de=le,ge=function(e,t){var n,r=se(e),o=0,i=[];for(n in r)!fe(de,n)&&fe(r,n)&&i.push(n);for(;t.length>o;)fe(r,n=t[o++])&&(~pe(i,n)||i.push(n));return i},he=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"].concat("length","prototype");$.f=Object.getOwnPropertyNames||function(e){return ge(e,he)};var ye={exports:{}},ve=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}},xe=S,be=ve,me=n?function(e,t,n){return xe.f(e,t,be(1,n))}:function(e,t,n){return e[t]=n,e},Ee=o,Se=me,we=function(e,t){try{Se(Ee,e,t)}catch(n){Ee[e]=t}return t},Oe=we,Re="__core-js_shared__",Te=o[Re]||Oe(Re,{}),_e=Te;(ye.exports=function(e,t){return _e[e]||(_e[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.12.1",mode:"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"});var je,Pe,Ie=0,Ce=Math.random(),Ne=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++Ie+Ce).toString(36)},Ae=o,ke=o,$e=function(e){return"function"==typeof e?e:void 0},Le=function(e,t){return arguments.length<2?$e(Ae[e])||$e(ke[e]):Ae[e]&&Ae[e][t]||ke[e]&&ke[e][t]},Me=Le("navigator","userAgent")||"",Ue=o.process,De=Ue&&Ue.versions,Fe=De&&De.v8;Fe?Pe=(je=Fe.split("."))[0]<4?1:je[0]+je[1]:Me&&(!(je=Me.match(/Edge\/(\d+)/))||je[1]>=74)&&(je=Me.match(/Chrome\/(\d+)/))&&(Pe=je[1]);var ze=Pe&&+Pe,Ke=t,Be=!!Object.getOwnPropertySymbols&&!Ke((function(){return!String(Symbol())||!Symbol.sham&&ze&&ze<41})),We=Be&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,Ge=o,Ve=ye.exports,Ye=z,qe=Ne,Xe=Be,He=We,Je=Ve("wks"),Qe=Ge.Symbol,Ze=He?Qe:Qe&&Qe.withoutSetter||qe,et=function(e){return Ye(Je,e)&&(Xe||"string"==typeof Je[e])||(Xe&&Ye(Qe,e)?Je[e]=Qe[e]:Je[e]=Ze("Symbol."+e)),Je[e]},tt=d,nt=B,rt=et("match"),ot=h,it=function(){var e=ot(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.dotAll&&(t+="s"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t},ct={},at=t;function ut(e,t){return RegExp(e,t)}ct.UNSUPPORTED_Y=at((function(){var e=ut("a","y");return e.lastIndex=2,null!=e.exec("abcd")})),ct.BROKEN_CARET=at((function(){var e=ut("^r","gy");return e.lastIndex=2,null!=e.exec("str")}));var lt={exports:{}},ft=Te,st=Function.toString;"function"!=typeof ft.inspectSource&&(ft.inspectSource=function(e){return st.call(e)});var pt,dt,gt,ht=ft.inspectSource,yt=ht,vt=o.WeakMap,xt="function"==typeof vt&&/native code/.test(yt(vt)),bt=ye.exports,mt=Ne,Et=bt("keys"),St=xt,wt=d,Ot=me,Rt=z,Tt=Te,_t=function(e){return Et[e]||(Et[e]=mt(e))},jt=le,Pt="Object already initialized",It=o.WeakMap;if(St||Tt.state){var Ct=Tt.state||(Tt.state=new It),Nt=Ct.get,At=Ct.has,kt=Ct.set;pt=function(e,t){if(At.call(Ct,e))throw new TypeError(Pt);return t.facade=e,kt.call(Ct,e,t),t},dt=function(e){return Nt.call(Ct,e)||{}},gt=function(e){return At.call(Ct,e)}}else{var $t=_t("state");jt[$t]=!0,pt=function(e,t){if(Rt(e,$t))throw new TypeError(Pt);return t.facade=e,Ot(e,$t,t),t},dt=function(e){return Rt(e,$t)?e[$t]:{}},gt=function(e){return Rt(e,$t)}}var Lt={set:pt,get:dt,has:gt,enforce:function(e){return gt(e)?dt(e):pt(e,{})},getterFor:function(e){return function(t){var n;if(!wt(t)||(n=dt(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}},Mt=o,Ut=me,Dt=z,Ft=we,zt=ht,Kt=Lt.get,Bt=Lt.enforce,Wt=String(String).split("String");(lt.exports=function(e,t,n,r){var o,i=!!r&&!!r.unsafe,c=!!r&&!!r.enumerable,a=!!r&&!!r.noTargetGet;"function"==typeof n&&("string"!=typeof t||Dt(n,"name")||Ut(n,"name",t),(o=Bt(n)).source||(o.source=Wt.join("string"==typeof t?t:""))),e!==Mt?(i?!a&&e[t]&&(c=!0):delete e[t],c?e[t]=n:Ut(e,t,n)):c?e[t]=n:Ft(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&Kt(this).source||zt(this)}));var Gt=Le,Vt=S,Yt=n,qt=et("species"),Xt=n,Ht=o,Jt=p,Qt=function(e,t,n){var r,o;return E&&"function"==typeof(r=t.constructor)&&r!==n&&m(o=r.prototype)&&o!==n.prototype&&E(e,o),e},Zt=S.f,en=$.f,tn=function(e){var t;return tt(e)&&(void 0!==(t=e[rt])?!!t:"RegExp"==nt(e))},nn=it,rn=ct,on=lt.exports,cn=t,an=Lt.enforce,un=function(e){var t=Gt(e),n=Vt.f;Yt&&t&&!t[qt]&&n(t,qt,{configurable:!0,get:function(){return this}})},ln=et("match"),fn=Ht.RegExp,sn=fn.prototype,pn=/a/g,dn=/a/g,gn=new fn(pn)!==pn,hn=rn.UNSUPPORTED_Y;if(Xt&&Jt("RegExp",!gn||hn||cn((function(){return dn[ln]=!1,fn(pn)!=pn||fn(dn)==dn||"/a/i"!=fn(pn,"i")})))){for(var yn=function(e,t){var n,r=this instanceof yn,o=tn(e),i=void 0===t;if(!r&&o&&e.constructor===yn&&i)return e;gn?o&&!i&&(e=e.source):e instanceof yn&&(i&&(t=nn.call(e)),e=e.source),hn&&(n=!!t&&t.indexOf("y")>-1)&&(t=t.replace(/y/g,""));var c=Qt(gn?new fn(e,t):fn(e,t),r?this:sn,yn);hn&&n&&(an(c).sticky=!0);return c},vn=function(e){e in yn||Zt(yn,e,{configurable:!0,get:function(){return fn[e]},set:function(t){fn[e]=t}})},xn=en(fn),bn=0;xn.length>bn;)vn(xn[bn++]);sn.constructor=yn,yn.prototype=sn,on(Ht,"RegExp",yn)}un("RegExp");var mn={},En={},Sn={}.propertyIsEnumerable,wn=Object.getOwnPropertyDescriptor,On=wn&&!Sn.call({1:2},1);En.f=On?function(e){var t=wn(this,e);return!!t&&t.enumerable}:Sn;var Rn=n,Tn=En,_n=ve,jn=q,Pn=P,In=z,Cn=_,Nn=Object.getOwnPropertyDescriptor;mn.f=Rn?Nn:function(e,t){if(e=jn(e),t=Pn(t,!0),Cn)try{return Nn(e,t)}catch(e){}if(In(e,t))return _n(!Tn.f.call(e,t),e[t])};var An={};An.f=Object.getOwnPropertySymbols;var kn=$,$n=An,Ln=h,Mn=Le("Reflect","ownKeys")||function(e){var t=kn.f(Ln(e)),n=$n.f;return n?t.concat(n(e)):t},Un=z,Dn=Mn,Fn=mn,zn=S,Kn=o,Bn=mn.f,Wn=me,Gn=lt.exports,Vn=we,Yn=function(e,t){for(var n=Dn(t),r=zn.f,o=Fn.f,i=0;i0&&(!i.multiline||i.multiline&&"\n"!==e[i.lastIndex-1])&&(u="(?: "+u+")",f=" "+f,l++),n=new RegExp("^(?:"+u+")",a)),rr&&(n=new RegExp("^"+u+"$(?!\\s)",a)),tr&&(t=i.lastIndex),r=Qn.call(c?n:i,f),c?r?(r.input=r.input.slice(l),r[0]=r[0].slice(l),r.index=i.lastIndex,i.lastIndex+=r[0].length):i.lastIndex=0:tr&&r&&(i.lastIndex=i.global?r.index+r[0].length:t),rr&&r&&r.length>1&&Zn.call(r[0],n,(function(){for(o=1;o")})),Sr="$0"==="a".replace(/./,"$0"),wr=vr("replace"),Or=!!/./[wr]&&""===/./[wr]("a","$0"),Rr=!yr((function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var n="ab".split(e);return 2!==n.length||"a"!==n[0]||"b"!==n[1]})),Tr=J,_r=L,jr=function(e){return function(t,n){var r,o,i=String(_r(t)),c=Tr(n),a=i.length;return c<0||c>=a?e?"":void 0:(r=i.charCodeAt(c))<55296||r>56319||c+1===a||(o=i.charCodeAt(c+1))<56320||o>57343?e?i.charAt(c):r:e?i.slice(c,c+2):o-56320+(r-55296<<10)+65536}},Pr={codeAt:jr(!1),charAt:jr(!0)}.charAt,Ir=U,Cr=Math.floor,Nr="".replace,Ar=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,kr=/\$([$&'`]|\d{1,2})/g,$r=B,Lr=or,Mr=function(e,t,n,r){var o=vr(e),i=!yr((function(){var t={};return t[o]=function(){return 7},7!=""[e](t)})),c=i&&!yr((function(){var t=!1,n=/a/;return"split"===e&&((n={}).constructor={},n.constructor[br]=function(){return n},n.flags="",n[o]=/./[o]),n.exec=function(){return t=!0,null},n[o](""),!t}));if(!i||!c||"replace"===e&&(!Er||!Sr||Or)||"split"===e&&!Rr){var a=/./[o],u=n(o,""[e],(function(e,t,n,r,o){var c=t.exec;return c===hr||c===mr.exec?i&&!o?{done:!0,value:a.call(t,n,r)}:{done:!0,value:e.call(n,t,r)}:{done:!1}}),{REPLACE_KEEPS_$0:Sr,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:Or}),l=u[0],f=u[1];gr(String.prototype,e,l),gr(mr,o,2==t?function(e,t){return f.call(e,this,t)}:function(e){return f.call(e,this)})}r&&xr(mr[o],"sham",!0)},Ur=h,Dr=ee,Fr=J,zr=L,Kr=function(e,t,n){return t+(n?Pr(e,t).length:1)},Br=function(e,t,n,r,o,i){var c=n+e.length,a=r.length,u=kr;return void 0!==o&&(o=Ir(o),u=Ar),Nr.call(i,u,(function(i,u){var l;switch(u.charAt(0)){case"$":return"$";case"&":return e;case"`":return t.slice(0,n);case"'":return t.slice(c);case"<":l=o[u.slice(1,-1)];break;default:var f=+u;if(0===f)return i;if(f>a){var s=Cr(f/10);return 0===s?i:s<=a?void 0===r[s-1]?u.charAt(1):r[s-1]+u.charAt(1):i}l=r[f-1]}return void 0===l?"":l}))},Wr=function(e,t){var n=e.exec;if("function"==typeof n){var r=n.call(e,t);if("object"!=typeof r)throw TypeError("RegExp exec method returned something other than an Object or null");return r}if("RegExp"!==$r(e))throw TypeError("RegExp#exec called on incompatible receiver");return Lr.call(e,t)},Gr=Math.max,Vr=Math.min;Mr("replace",2,(function(e,t,n,r){var o=r.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,i=r.REPLACE_KEEPS_$0,c=o?"$":"$0";return[function(n,r){var o=zr(this),i=null==n?void 0:n[e];return void 0!==i?i.call(n,o,r):t.call(String(o),n,r)},function(e,r){if(!o&&i||"string"==typeof r&&-1===r.indexOf(c)){var a=n(t,e,this,r);if(a.done)return a.value}var u=Ur(e),l=String(this),f="function"==typeof r;f||(r=String(r));var s=u.global;if(s){var p=u.unicode;u.lastIndex=0}for(var d=[];;){var g=Wr(u,l);if(null===g)break;if(d.push(g),!s)break;""===String(g[0])&&(u.lastIndex=Kr(l,Dr(u.lastIndex),p))}for(var h,y="",v=0,x=0;x=v&&(y+=l.slice(v,m)+R,v=m+b.length)}return y+l.slice(v)}]}));var Yr={};Yr[et("toStringTag")]="z";var qr="[object z]"===String(Yr),Xr=qr,Hr=B,Jr=et("toStringTag"),Qr="Arguments"==Hr(function(){return arguments}()),Zr=Xr?Hr:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),Jr))?n:Qr?Hr(t):"Object"==(r=Hr(t))&&"function"==typeof t.callee?"Arguments":r},eo=qr?{}.toString:function(){return"[object "+Zr(this)+"]"},to=qr,no=lt.exports,ro=eo;to||no(Object.prototype,"toString",ro,{unsafe:!0})
    2 | /*!
    3 | 	 * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
    4 | 	 * by navigatating to that slide and highlighting it.
    5 | 	 *
    6 | 	 * @author Jon Snyder , February 2013
    7 | 	 */;return function(){var e,t,n,r,o,i,c;function a(){(t=document.createElement("div")).classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='\n\t\t',(n=t.querySelector(".searchinput")).style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){switch(t.keyCode){case 13:t.preventDefault(),function(){if(i){var t=n.value;""===t?(c&&c.remove(),r=null):(c=new f("slidecontent"),r=c.apply(t),o=0)}r&&(r.length&&r.length<=o&&(o=0),r.length>o&&(e.slide(r[o].h,r[o].v),o++))}(),i=!1;break;default:i=!0}}),!1),l()}function u(){t||a(),t.style.display="inline",n.focus(),n.select()}function l(){t||a(),t.style.display="none",c&&c.remove()}function f(t,n){var r=document.getElementById(t)||document.body,o=n||"EM",i=new RegExp("^(?:"+o+"|SCRIPT|FORM)$"),c=["#ff6","#a0ffff","#9f9","#f99","#f6f"],a=[],u=0,l="",f=[];this.setRegex=function(e){e=e.replace(/^[^\w]+|[^\w]+$/g,"").replace(/[^\w'-]+/g,"|"),l=new RegExp("("+e+")","i")},this.getRegex=function(){return l.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&l&&!i.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n {
     31 | 
     32 | 		zoom.reset();
     33 | 
     34 | 	}
     35 | 
     36 | };
     37 | 
     38 | export default () => Plugin;
     39 | 
     40 | /*!
     41 |  * zoom.js 0.3 (modified for use with reveal.js)
     42 |  * http://lab.hakim.se/zoom-js
     43 |  * MIT licensed
     44 |  *
     45 |  * Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se
     46 |  */
     47 | var zoom = (function(){
     48 | 
     49 | 	// The current zoom level (scale)
     50 | 	var level = 1;
     51 | 
     52 | 	// The current mouse position, used for panning
     53 | 	var mouseX = 0,
     54 | 		mouseY = 0;
     55 | 
     56 | 	// Timeout before pan is activated
     57 | 	var panEngageTimeout = -1,
     58 | 		panUpdateInterval = -1;
     59 | 
     60 | 	// Check for transform support so that we can fallback otherwise
     61 | 	var supportsTransforms = 	'transform' in document.body.style;
     62 | 
     63 | 	if( supportsTransforms ) {
     64 | 		// The easing that will be applied when we zoom in/out
     65 | 		document.body.style.transition = 'transform 0.8s ease';
     66 | 	}
     67 | 
     68 | 	// Zoom out if the user hits escape
     69 | 	document.addEventListener( 'keyup', function( event ) {
     70 | 		if( level !== 1 && event.keyCode === 27 ) {
     71 | 			zoom.out();
     72 | 		}
     73 | 	} );
     74 | 
     75 | 	// Monitor mouse movement for panning
     76 | 	document.addEventListener( 'mousemove', function( event ) {
     77 | 		if( level !== 1 ) {
     78 | 			mouseX = event.clientX;
     79 | 			mouseY = event.clientY;
     80 | 		}
     81 | 	} );
     82 | 
     83 | 	/**
     84 | 	 * Applies the CSS required to zoom in, prefers the use of CSS3
     85 | 	 * transforms but falls back on zoom for IE.
     86 | 	 *
     87 | 	 * @param {Object} rect
     88 | 	 * @param {Number} scale
     89 | 	 */
     90 | 	function magnify( rect, scale ) {
     91 | 
     92 | 		var scrollOffset = getScrollOffset();
     93 | 
     94 | 		// Ensure a width/height is set
     95 | 		rect.width = rect.width || 1;
     96 | 		rect.height = rect.height || 1;
     97 | 
     98 | 		// Center the rect within the zoomed viewport
     99 | 		rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2;
    100 | 		rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2;
    101 | 
    102 | 		if( supportsTransforms ) {
    103 | 			// Reset
    104 | 			if( scale === 1 ) {
    105 | 				document.body.style.transform = '';
    106 | 			}
    107 | 			// Scale
    108 | 			else {
    109 | 				var origin = scrollOffset.x +'px '+ scrollOffset.y +'px',
    110 | 					transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')';
    111 | 
    112 | 				document.body.style.transformOrigin = origin;
    113 | 				document.body.style.transform = transform;
    114 | 			}
    115 | 		}
    116 | 		else {
    117 | 			// Reset
    118 | 			if( scale === 1 ) {
    119 | 				document.body.style.position = '';
    120 | 				document.body.style.left = '';
    121 | 				document.body.style.top = '';
    122 | 				document.body.style.width = '';
    123 | 				document.body.style.height = '';
    124 | 				document.body.style.zoom = '';
    125 | 			}
    126 | 			// Scale
    127 | 			else {
    128 | 				document.body.style.position = 'relative';
    129 | 				document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px';
    130 | 				document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px';
    131 | 				document.body.style.width = ( scale * 100 ) + '%';
    132 | 				document.body.style.height = ( scale * 100 ) + '%';
    133 | 				document.body.style.zoom = scale;
    134 | 			}
    135 | 		}
    136 | 
    137 | 		level = scale;
    138 | 
    139 | 		if( document.documentElement.classList ) {
    140 | 			if( level !== 1 ) {
    141 | 				document.documentElement.classList.add( 'zoomed' );
    142 | 			}
    143 | 			else {
    144 | 				document.documentElement.classList.remove( 'zoomed' );
    145 | 			}
    146 | 		}
    147 | 	}
    148 | 
    149 | 	/**
    150 | 	 * Pan the document when the mosue cursor approaches the edges
    151 | 	 * of the window.
    152 | 	 */
    153 | 	function pan() {
    154 | 		var range = 0.12,
    155 | 			rangeX = window.innerWidth * range,
    156 | 			rangeY = window.innerHeight * range,
    157 | 			scrollOffset = getScrollOffset();
    158 | 
    159 | 		// Up
    160 | 		if( mouseY < rangeY ) {
    161 | 			window.scroll( scrollOffset.x, scrollOffset.y - ( 1 - ( mouseY / rangeY ) ) * ( 14 / level ) );
    162 | 		}
    163 | 		// Down
    164 | 		else if( mouseY > window.innerHeight - rangeY ) {
    165 | 			window.scroll( scrollOffset.x, scrollOffset.y + ( 1 - ( window.innerHeight - mouseY ) / rangeY ) * ( 14 / level ) );
    166 | 		}
    167 | 
    168 | 		// Left
    169 | 		if( mouseX < rangeX ) {
    170 | 			window.scroll( scrollOffset.x - ( 1 - ( mouseX / rangeX ) ) * ( 14 / level ), scrollOffset.y );
    171 | 		}
    172 | 		// Right
    173 | 		else if( mouseX > window.innerWidth - rangeX ) {
    174 | 			window.scroll( scrollOffset.x + ( 1 - ( window.innerWidth - mouseX ) / rangeX ) * ( 14 / level ), scrollOffset.y );
    175 | 		}
    176 | 	}
    177 | 
    178 | 	function getScrollOffset() {
    179 | 		return {
    180 | 			x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset,
    181 | 			y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset
    182 | 		}
    183 | 	}
    184 | 
    185 | 	return {
    186 | 		/**
    187 | 		 * Zooms in on either a rectangle or HTML element.
    188 | 		 *
    189 | 		 * @param {Object} options
    190 | 		 *   - element: HTML element to zoom in on
    191 | 		 *   OR
    192 | 		 *   - x/y: coordinates in non-transformed space to zoom in on
    193 | 		 *   - width/height: the portion of the screen to zoom in on
    194 | 		 *   - scale: can be used instead of width/height to explicitly set scale
    195 | 		 */
    196 | 		to: function( options ) {
    197 | 
    198 | 			// Due to an implementation limitation we can't zoom in
    199 | 			// to another element without zooming out first
    200 | 			if( level !== 1 ) {
    201 | 				zoom.out();
    202 | 			}
    203 | 			else {
    204 | 				options.x = options.x || 0;
    205 | 				options.y = options.y || 0;
    206 | 
    207 | 				// If an element is set, that takes precedence
    208 | 				if( !!options.element ) {
    209 | 					// Space around the zoomed in element to leave on screen
    210 | 					var padding = 20;
    211 | 					var bounds = options.element.getBoundingClientRect();
    212 | 
    213 | 					options.x = bounds.left - padding;
    214 | 					options.y = bounds.top - padding;
    215 | 					options.width = bounds.width + ( padding * 2 );
    216 | 					options.height = bounds.height + ( padding * 2 );
    217 | 				}
    218 | 
    219 | 				// If width/height values are set, calculate scale from those values
    220 | 				if( options.width !== undefined && options.height !== undefined ) {
    221 | 					options.scale = Math.max( Math.min( window.innerWidth / options.width, window.innerHeight / options.height ), 1 );
    222 | 				}
    223 | 
    224 | 				if( options.scale > 1 ) {
    225 | 					options.x *= options.scale;
    226 | 					options.y *= options.scale;
    227 | 
    228 | 					magnify( options, options.scale );
    229 | 
    230 | 					if( options.pan !== false ) {
    231 | 
    232 | 						// Wait with engaging panning as it may conflict with the
    233 | 						// zoom transition
    234 | 						panEngageTimeout = setTimeout( function() {
    235 | 							panUpdateInterval = setInterval( pan, 1000 / 60 );
    236 | 						}, 800 );
    237 | 
    238 | 					}
    239 | 				}
    240 | 			}
    241 | 		},
    242 | 
    243 | 		/**
    244 | 		 * Resets the document zoom state to its default.
    245 | 		 */
    246 | 		out: function() {
    247 | 			clearTimeout( panEngageTimeout );
    248 | 			clearInterval( panUpdateInterval );
    249 | 
    250 | 			magnify( { x: 0, y: 0 }, 1 );
    251 | 
    252 | 			level = 1;
    253 | 		},
    254 | 
    255 | 		// Alias
    256 | 		magnify: function( options ) { this.to( options ) },
    257 | 		reset: function() { this.out() },
    258 | 
    259 | 		zoomLevel: function() {
    260 | 			return level;
    261 | 		}
    262 | 	}
    263 | 
    264 | })();
    265 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/zoom/zoom.esm.js:
    --------------------------------------------------------------------------------
    1 | /*!
    2 |  * reveal.js Zoom plugin
    3 |  */
    4 | var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(n){var o=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:o)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;n[i]&&!e.isOverview()&&(n.preventDefault(),t.to({x:n.clientX,y:n.clientY,scale:d,pan:!1}))}))},destroy:function(){t.reset()}},t=function(){var e=1,n=0,o=0,i=-1,d=-1,l="transform"in document.body.style;function s(t,n){var o=r();if(t.width=t.width||1,t.height=t.height||1,t.x-=(window.innerWidth-t.width*n)/2,t.y-=(window.innerHeight-t.height*n)/2,l)if(1===n)document.body.style.transform="";else{var i=o.x+"px "+o.y+"px",d="translate("+-t.x+"px,"+-t.y+"px) scale("+n+")";document.body.style.transformOrigin=i,document.body.style.transform=d}else 1===n?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(o.x+t.x)/n+"px",document.body.style.top=-(o.y+t.y)/n+"px",document.body.style.width=100*n+"%",document.body.style.height=100*n+"%",document.body.style.zoom=n);e=n,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function c(){var t=.12*window.innerWidth,i=.12*window.innerHeight,d=r();owindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-o)/i)*(14/e)),nwindow.innerWidth-t&&window.scroll(d.x+(1-(window.innerWidth-n)/t)*(14/e),d.y)}function r(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return l&&(document.body.style.transition="transform 0.8s ease"),document.addEventListener("keyup",(function(n){1!==e&&27===n.keyCode&&t.out()})),document.addEventListener("mousemove",(function(t){1!==e&&(n=t.clientX,o=t.clientY)})),{to:function(n){if(1!==e)t.out();else{if(n.x=n.x||0,n.y=n.y||0,n.element){var o=n.element.getBoundingClientRect();n.x=o.left-20,n.y=o.top-20,n.width=o.width+40,n.height=o.height+40}void 0!==n.width&&void 0!==n.height&&(n.scale=Math.max(Math.min(window.innerWidth/n.width,window.innerHeight/n.height),1)),n.scale>1&&(n.x*=n.scale,n.y*=n.scale,s(n,n.scale),!1!==n.pan&&(i=setTimeout((function(){d=setInterval(c,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),s({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();export default function(){return e}
    5 | 
    
    
    --------------------------------------------------------------------------------
    /docs/site_libs/revealjs/plugin/zoom/zoom.js:
    --------------------------------------------------------------------------------
    1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).RevealZoom=t()}(this,(function(){"use strict";
    2 | /*!
    3 | 	 * reveal.js Zoom plugin
    4 | 	 */var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(o){var n=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:n)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;o[i]&&!e.isOverview()&&(o.preventDefault(),t.to({x:o.clientX,y:o.clientY,scale:d,pan:!1}))}))},destroy:function(){t.reset()}},t=function(){var e=1,o=0,n=0,i=-1,d=-1,l="transform"in document.body.style;function s(t,o){var n=r();if(t.width=t.width||1,t.height=t.height||1,t.x-=(window.innerWidth-t.width*o)/2,t.y-=(window.innerHeight-t.height*o)/2,l)if(1===o)document.body.style.transform="";else{var i=n.x+"px "+n.y+"px",d="translate("+-t.x+"px,"+-t.y+"px) scale("+o+")";document.body.style.transformOrigin=i,document.body.style.transform=d}else 1===o?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(n.x+t.x)/o+"px",document.body.style.top=-(n.y+t.y)/o+"px",document.body.style.width=100*o+"%",document.body.style.height=100*o+"%",document.body.style.zoom=o);e=o,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function c(){var t=.12*window.innerWidth,i=.12*window.innerHeight,d=r();nwindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-n)/i)*(14/e)),owindow.innerWidth-t&&window.scroll(d.x+(1-(window.innerWidth-o)/t)*(14/e),d.y)}function r(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return l&&(document.body.style.transition="transform 0.8s ease"),document.addEventListener("keyup",(function(o){1!==e&&27===o.keyCode&&t.out()})),document.addEventListener("mousemove",(function(t){1!==e&&(o=t.clientX,n=t.clientY)})),{to:function(o){if(1!==e)t.out();else{if(o.x=o.x||0,o.y=o.y||0,o.element){var n=o.element.getBoundingClientRect();o.x=n.left-20,o.y=n.top-20,o.width=n.width+40,o.height=n.height+40}void 0!==o.width&&void 0!==o.height&&(o.scale=Math.max(Math.min(window.innerWidth/o.width,window.innerHeight/o.height),1)),o.scale>1&&(o.x*=o.scale,o.y*=o.scale,s(o,o.scale),!1!==o.pan&&(i=setTimeout((function(){d=setInterval(c,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),s({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();return function(){return e}}));
    5 | 
    
    
    --------------------------------------------------------------------------------
    /images/ProductioniZingShiny.png:
    --------------------------------------------------------------------------------
    https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/images/ProductioniZingShiny.png
    
    
    --------------------------------------------------------------------------------
    /images/athlyticz.png:
    --------------------------------------------------------------------------------
    https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/images/athlyticz.png
    
    
    --------------------------------------------------------------------------------
    /images/background.png:
    --------------------------------------------------------------------------------
    https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/images/background.png
    
    
    --------------------------------------------------------------------------------
    /images/logo.png:
    --------------------------------------------------------------------------------
    https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/images/logo.png
    
    
    --------------------------------------------------------------------------------
    /shescores/app.R:
    --------------------------------------------------------------------------------
      1 | # global ------------------------------------------------
      2 | library(shiny)
      3 | library(bslib)
      4 | library(shinyWidgets)
      5 | library(lubridate)
      6 | library(dplyr)
      7 | library(echarts4r)
      8 | library(DT)
      9 | 
     10 | custom_theme <- bs_theme(
     11 |   version = 5,
     12 |   # or any other theme that you want
     13 |   bootswatch = "quartz",
     14 |   base_font = font_google("PT Sans")
     15 | )
     16 | 
     17 | # set font for echarts
     18 | e_common(
     19 |   font_family = "PT Sans",
     20 |   theme = NULL
     21 | )
     22 | 
     23 | # read in data
     24 | # note that for demo purposes this is in a top-level folder
     25 | # normally, you would have this inside the folder that contains app.R
     26 | soccer_scorers <- readRDS("../data/soccer_scorers.rds")
     27 | soccer_matches <- readRDS("../data/soccer_matches.rds")
     28 | soccer_rank <- readRDS("../data/country_rank.rds")
     29 | 
     30 | # options for in pickerInput
     31 | available_countries <- sort(unique(soccer_matches$home_team))
     32 | 
     33 | available_countries <- setNames(available_countries,
     34 |                                 sort(unique(paste(soccer_matches$home_team, soccer_matches$country_flag_home))))
     35 | 
     36 | # country page module ------------------------------------
     37 | countryPageUI <- function(id, page_name) {
     38 |   ns <- NS(id)
     39 |   tagList(
     40 |     h2(page_name),
     41 |     prettyCheckbox(
     42 |       inputId = ns("favourite"),
     43 |       label = "This is my favourite",
     44 |       value = FALSE,
     45 |       status = "warning",
     46 |       icon = icon("star"),
     47 |       plain = TRUE,
     48 |       outline = TRUE
     49 |     ),
     50 |     card(
     51 |       min_height = "700px",
     52 |       card_header("General Info"),
     53 |       card_body(
     54 |         textOutput(ns("general_info")),
     55 |         DTOutput(ns("matches_table"))
     56 |       )
     57 |     )
     58 |   )
     59 | }
     60 | 
     61 | countryPageServer <- function(id, chosen_country, r) {
     62 |   moduleServer(id, function(input, output, session) {
     63 | 
     64 |     this_soccer_matches <- reactive({
     65 |       soccer_matches |>
     66 |         filter(home_team == chosen_country | away_team == chosen_country) |>
     67 |         arrange(desc(date))
     68 |     })
     69 | 
     70 |     output$general_info <- renderText({
     71 |       sprintf("%s has played %s matches in total. The first match was on %s and the last match was on %s.",
     72 |               chosen_country,
     73 |               nrow(this_soccer_matches()),
     74 |               min(this_soccer_matches()$date),
     75 |               max(this_soccer_matches()$date)
     76 |       )
     77 |     })
     78 | 
     79 |     output$matches_table <- renderDT({
     80 |       this_soccer_matches() |>
     81 |         select(date, home_team, away_team, home_score, away_score, tournament) |>
     82 |         datatable()
     83 |     })
     84 | 
     85 |     observe({
     86 | 
     87 |       req(!is.null(input$favourite))
     88 | 
     89 |       current_favourites <- isolate(r$favourites)
     90 | 
     91 |       if (input$favourite) {
     92 |         r$favourites <- c(current_favourites, chosen_country)
     93 |       } else {
     94 |         r$favourites <- current_favourites[current_favourites != chosen_country]
     95 |       }
     96 |     })
     97 | 
     98 |   })
     99 | }
    100 | 
    101 | # main app -----------------------------------------------
    102 | ui <- page_navbar(
    103 |   theme = custom_theme,
    104 |   title = "She Scores ⚽️: Women's International Soccer Matches",
    105 |   id = "navbar_id",
    106 |   nav_panel(
    107 |     title = "Overview",
    108 |     fluidRow(
    109 |       column(4,
    110 |              value_box(
    111 |                title = "Top scoring country",
    112 |                value = paste(
    113 |                  head(soccer_rank$country, 1),
    114 |                  head(soccer_rank$country_flag, 1)
    115 |                )
    116 |              )),
    117 |       column(4,
    118 |              value_box(
    119 |                title = "Top scorer",
    120 |                value = paste(
    121 |                  head(soccer_scorers$scorer, 1),
    122 |                  head(soccer_scorers$country_flag, 1)
    123 |                )
    124 |              )),
    125 |       column(4,
    126 |              value_box(
    127 |                title = "Total countries",
    128 |                value = length(unique(soccer_matches$home_team))
    129 |              ))
    130 |     ),
    131 |     card(
    132 |       min_height = "600px",
    133 |       echarts4rOutput("overview")
    134 |     )
    135 |   ),
    136 |   nav_menu(title = "Countries",
    137 |            nav_panel(
    138 |              title = "Set-up",
    139 |              pickerInput(
    140 |                inputId = "countries",
    141 |                label = "Select a country",
    142 |                multiple = TRUE,
    143 |                choices = available_countries,
    144 |                selected = NULL,
    145 |                options = pickerOptions(
    146 |                  actionsBox = TRUE,
    147 |                  liveSearch = TRUE,
    148 |                  liveSearchPlaceholder = "Search for a country",
    149 |                  selectedTextFormat = "count > 1",
    150 |                  countSelectedText = "{0} countries selected"
    151 |                )
    152 |              ),
    153 |              actionButton(
    154 |                inputId = "pages",
    155 |                label = "Generate pages",
    156 |                icon = icon("plus-circle"),
    157 |                width = "250px"
    158 |              ),
    159 |              textOutput("favourites")
    160 |            )
    161 |   )
    162 | )
    163 | 
    164 | server <- function(input, output, session) {
    165 | 
    166 |   r <- reactiveValues(active_pages = NULL,
    167 |                       favourites = NULL)
    168 | 
    169 |   output$overview <- renderEcharts4r({
    170 |     # get the number of matches over time
    171 |     soccer_matches |>
    172 |       mutate(date = as.Date(date)) |>
    173 |       group_by(date = lubridate::floor_date(date, "year")) |>
    174 |       summarise(matches = n()) |>
    175 |       e_charts(date) |>
    176 |       e_line(matches,
    177 |              lineStyle = list(
    178 |                width = 3,
    179 |                color = "#0f0437"
    180 |              )) |>
    181 |       e_title("Soccer matches over time",
    182 |               left = "40%", # somehow textAlign does not work
    183 |               textStyle = list(
    184 |                 color = "white",
    185 |                 fontSize = 26
    186 |               )) |>
    187 |       e_tooltip() |>
    188 |       e_legend(show = FALSE) |>
    189 |       e_x_axis(
    190 |         axisLabel = list(
    191 |           color = "white"
    192 |         )
    193 |       ) |>
    194 |       e_y_axis(
    195 |         axisLabel = list(
    196 |           color = "white"
    197 |         )
    198 |       )
    199 |   })
    200 | 
    201 |   observe({
    202 | 
    203 |     chosen_countries <- input$countries
    204 | 
    205 |     lapply(chosen_countries, function(this_country) {
    206 | 
    207 |       if (this_country %in% r$active_pages) {
    208 |         # the country already has a page, and is chosen by the user, so don't do anything
    209 |         return()
    210 |       }
    211 | 
    212 |       nav_insert(id = "navbar_id",
    213 |                  target = "Set-up",
    214 |                  nav = nav_panel(
    215 |                    title = this_country,
    216 |                    countryPageUI(id = this_country, page_name = this_country)
    217 |                  ),
    218 |                  position = "after")
    219 | 
    220 |       countryPageServer(id = this_country,
    221 |                         chosen_country = this_country,
    222 |                         r = r)
    223 | 
    224 |     })
    225 | 
    226 |     if (setdiff(r$active_pages, chosen_countries) |> length() > 0) {
    227 | 
    228 |       remove_countries <- setdiff(r$active_pages, chosen_countries)
    229 | 
    230 |       for (country in remove_countries) {
    231 |         nav_remove(id = "navbar_id",
    232 |                    target = country)
    233 | 
    234 |       }
    235 | 
    236 |     }
    237 | 
    238 |     r$active_pages <- chosen_countries
    239 | 
    240 |   }) |> bindEvent(input$pages)
    241 | 
    242 |   output$favourites <- renderText({
    243 |     if (is.null(r$favourites)) {
    244 |       "You have not selected any favourites yet."
    245 |     } else {
    246 |       sprintf("Your favourite countries are: %s",
    247 |               paste(r$favourites, collapse = ", "))
    248 |     }
    249 |   })
    250 | 
    251 | }
    252 | 
    253 | shinyApp(ui, server)
    254 | 
    
    
    --------------------------------------------------------------------------------
    /shinyconf2024-shiny101.Rproj:
    --------------------------------------------------------------------------------
     1 | Version: 1.0
     2 | 
     3 | RestoreWorkspace: Default
     4 | SaveWorkspace: Default
     5 | AlwaysSaveHistory: Default
     6 | 
     7 | EnableCodeIndexing: Yes
     8 | UseSpacesForTab: Yes
     9 | NumSpacesForTab: 2
    10 | Encoding: UTF-8
    11 | 
    12 | RnwWeave: Sweave
    13 | LaTeX: pdfLaTeX
    14 | 
    15 | AutoAppendNewline: Yes
    16 | StripTrailingWhitespace: Yes
    17 | 
    
    
    --------------------------------------------------------------------------------
    /templates/00_base.R:
    --------------------------------------------------------------------------------
     1 | library(shiny)
     2 | 
     3 | ui <- fluidPage(
     4 |   numericInput(inputId = "number",
     5 |                label = "Enter a number",
     6 |                value = 0),
     7 | 
     8 |   textOutput(outputId = "text")
     9 | )
    10 | 
    11 | server <- function(input, output, session) {
    12 |   output$text <- renderText({
    13 |     input$number^2
    14 |   })
    15 | }
    16 | 
    17 | shinyApp(ui, server)
    18 | 
    
    
    --------------------------------------------------------------------------------
    /templates/01_start.R:
    --------------------------------------------------------------------------------
     1 | library(shiny)
     2 | 
     3 | ui <- fluidPage(
     4 |   numericInput(inputId = "number",
     5 |                label = "Enter a number",
     6 |                value = 0),
     7 | 
     8 |   actionButton(inputId = "button",
     9 |                label = "Calculate"),
    10 | 
    11 |   textOutput(outputId = "text")
    12 | )
    13 | 
    14 | server <- function(input, output, session) {
    15 |   output$text <- renderText({
    16 |     input$number^2
    17 |   }) |> bindEvent(input$button)
    18 | }
    19 | 
    20 | shinyApp(ui, server)
    21 | 
    
    
    --------------------------------------------------------------------------------
    /templates/02_bslib.R:
    --------------------------------------------------------------------------------
     1 | library(shiny)
     2 | library(bslib)
     3 | 
     4 | custom_theme <- bs_theme(
     5 |   version = 5,
     6 |   # for themes see: https://bootswatch.com
     7 |   preset = "quartz",
     8 |   base_font = font_google("PT Sans"),
     9 |   bg = NULL,
    10 |   fg = NULL,
    11 |   primary = NULL,
    12 |   secondary = NULL,
    13 |   success = NULL,
    14 |   info = NULL,
    15 |   warning = NULL,
    16 |   danger = NULL,
    17 |   code_font = NULL,
    18 |   heading_font = NULL,
    19 |   font_scale = NULL
    20 | )
    21 | 
    22 | ui <- page_navbar(
    23 |   theme = custom_theme,
    24 |   title = "Modular App Blueprint",
    25 |   nav_panel(
    26 |     title = "Numbers",
    27 |     numericInput(inputId = "number",
    28 |                  label = "Enter a number",
    29 |                  value = 0),
    30 |     actionButton(inputId = "button",
    31 |                  label = "Calculate"),
    32 |     textOutput(outputId = "text")
    33 |   )
    34 | )
    35 | 
    36 | server <- function(input, output, session) {
    37 |   output$text <- renderText({
    38 |     input$number^2
    39 |   }) |> bindEvent(input$button)
    40 | }
    41 | 
    42 | shinyApp(ui, server)
    43 | 
    
    
    --------------------------------------------------------------------------------
    /templates/03_modules.R:
    --------------------------------------------------------------------------------
     1 | library(shiny)
     2 | library(bslib)
     3 | 
     4 | custom_theme <- bs_theme(
     5 |   version = 5,
     6 |   # for themes see: https://bootswatch.com
     7 |   preset = "quartz",
     8 |   base_font = font_google("PT Sans"),
     9 |   bg = NULL,
    10 |   fg = NULL,
    11 |   primary = NULL,
    12 |   secondary = NULL,
    13 |   success = NULL,
    14 |   info = NULL,
    15 |   warning = NULL,
    16 |   danger = NULL,
    17 |   code_font = NULL,
    18 |   heading_font = NULL,
    19 |   font_scale = NULL
    20 | )
    21 | 
    22 | numberModUI <- function(id) {
    23 |   ns <- NS(id)
    24 |   tagList(
    25 |     numericInput(inputId = ns("number"),
    26 |                  label = "Enter a number",
    27 |                  value = 0),
    28 |     actionButton(inputId = ns("button"),
    29 |                  label = "Calculate"),
    30 |     textOutput(outputId = ns("text"))
    31 |   )
    32 | }
    33 | 
    34 | numberModServer <- function(id) {
    35 |   moduleServer(id, function(input, output, session) {
    36 |     output$text <- renderText({
    37 |       input$number^2
    38 |     }) |> bindEvent(input$button)
    39 |   })
    40 | }
    41 | 
    42 | ui <- page_navbar(
    43 |   theme = custom_theme,
    44 |   title = "Modular App Blueprint",
    45 |   nav_panel(
    46 |     title = "Numbers",
    47 |     numberModUI("numbers")
    48 |   )
    49 | )
    50 | 
    51 | server <- function(input, output, session) {
    52 |   numberModServer("numbers")
    53 | }
    54 | 
    55 | shinyApp(ui, server)
    56 | 
    
    
    --------------------------------------------------------------------------------
    /templates/04_nested_modules.R:
    --------------------------------------------------------------------------------
     1 | library(shiny)
     2 | library(bslib)
     3 | library(DT)
     4 | 
     5 | custom_theme <- bs_theme(
     6 |   version = 5,
     7 |   # for themes see: https://bootswatch.com
     8 |   preset = "quartz",
     9 |   base_font = font_google("PT Sans"),
    10 |   bg = NULL,
    11 |   fg = NULL,
    12 |   primary = NULL,
    13 |   secondary = NULL,
    14 |   success = NULL,
    15 |   info = NULL,
    16 |   warning = NULL,
    17 |   danger = NULL,
    18 |   code_font = NULL,
    19 |   heading_font = NULL,
    20 |   font_scale = NULL
    21 | )
    22 | 
    23 | # module 1 -------------------------------------------------------
    24 | numberModUI <- function(id) {
    25 |   ns <- NS(id)
    26 |   tagList(
    27 |     numericInput(inputId = ns("number"),
    28 |                  label = "Enter a number",
    29 |                  value = 0),
    30 |     actionButton(inputId = ns("button"),
    31 |                  label = "Calculate"),
    32 |     textOutput(outputId = ns("text")),
    33 |     numberAnalysisModUI(ns("analysis"))
    34 |   )
    35 | }
    36 | 
    37 | numberModServer <- function(id) {
    38 |   moduleServer(id, function(input, output, session) {
    39 | 
    40 |     output$text <- renderText({
    41 |       input$number^2
    42 |     }) |> bindEvent(input$button)
    43 | 
    44 |     numberAnalysisServer("analysis")
    45 | 
    46 |   })
    47 | }
    48 | 
    49 | # module 2 -------------------------------------------------------
    50 | numberAnalysisModUI <- function(id) {
    51 |   ns <- NS(id)
    52 |   dataTableOutput(outputId = ns("table"))
    53 | }
    54 | 
    55 | numberAnalysisServer <- function(id) {
    56 |   moduleServer(id, function(input, output, session) {
    57 |     # display table of squares and highlight the number
    58 |     output$table <- renderDataTable({
    59 |       squares <- 1:10
    60 |       squares <- data.frame(number = squares, square = squares^2)
    61 |       datatable(squares, rownames = FALSE)
    62 |     })
    63 |   })
    64 | }
    65 | 
    66 | # app ------------------------------------------------------------
    67 | ui <- page_navbar(
    68 |   theme = custom_theme,
    69 |   title = "Modular App Blueprint",
    70 |   nav_panel(
    71 |     title = "Numbers",
    72 |     numberModUI("numbers")
    73 |   )
    74 | )
    75 | 
    76 | server <- function(input, output, session) {
    77 |   numberModServer("numbers")
    78 | }
    79 | 
    80 | shinyApp(ui, server)
    81 | 
    
    
    --------------------------------------------------------------------------------
    /templates/05_sharing_data.R:
    --------------------------------------------------------------------------------
     1 | library(shiny)
     2 | library(bslib)
     3 | library(DT)
     4 | 
     5 | custom_theme <- bs_theme(
     6 |   version = 5,
     7 |   # for themes see: https://bootswatch.com
     8 |   preset = "quartz",
     9 |   base_font = font_google("PT Sans"),
    10 |   bg = NULL,
    11 |   fg = NULL,
    12 |   primary = NULL,
    13 |   secondary = NULL,
    14 |   success = NULL,
    15 |   info = NULL,
    16 |   warning = NULL,
    17 |   danger = NULL,
    18 |   code_font = NULL,
    19 |   heading_font = NULL,
    20 |   font_scale = NULL
    21 | )
    22 | 
    23 | # module 1 -------------------------------------------------------
    24 | numberModUI <- function(id) {
    25 |   ns <- NS(id)
    26 |   tagList(
    27 |     numericInput(inputId = ns("number"),
    28 |                  label = "Enter a number",
    29 |                  value = 0),
    30 |     actionButton(inputId = ns("button"),
    31 |                  label = "Calculate"),
    32 |     textOutput(outputId = ns("text")),
    33 |     numberAnalysisModUI(ns("analysis"))
    34 |   )
    35 | }
    36 | 
    37 | numberModServer <- function(id, r) {
    38 |   moduleServer(id, function(input, output, session) {
    39 | 
    40 |     output$text <- renderText({
    41 |       input$number^2
    42 |     }) |> bindEvent(input$button)
    43 | 
    44 |     observe({
    45 |       r$number <- input$number
    46 |       r$button <- input$button
    47 |     })
    48 | 
    49 |     numberAnalysisServer("analysis", r = r)
    50 | 
    51 |   })
    52 | }
    53 | 
    54 | # module 2 -------------------------------------------------------
    55 | numberAnalysisModUI <- function(id) {
    56 |   ns <- NS(id)
    57 |   dataTableOutput(outputId = ns("table"))
    58 | }
    59 | 
    60 | numberAnalysisServer <- function(id, r) {
    61 |   moduleServer(id, function(input, output, session) {
    62 |     # display table of squares and highlight the number
    63 |     output$table <- renderDataTable({
    64 |       req(r$number > 0)
    65 |       squares <- 1:(r$number + 5)
    66 |       squares <- data.frame(number = squares, square = squares^2)
    67 |       datatable(squares, rownames = FALSE, selection = "none") |>
    68 |         formatStyle(columns = "number",
    69 |                     target = "row",
    70 |                     border = styleEqual(r$number, "3px"))
    71 |     }) |> bindEvent(r$button)
    72 |   })
    73 | }
    74 | 
    75 | # app ------------------------------------------------------------
    76 | ui <- page_navbar(
    77 |   theme = custom_theme,
    78 |   title = "Modular App Blueprint",
    79 |   nav_panel(
    80 |     title = "Numbers",
    81 |     numberModUI("numbers")
    82 |   )
    83 | )
    84 | 
    85 | server <- function(input, output, session) {
    86 | 
    87 |   r <- reactiveValues(number = NULL,
    88 |                       button = NULL)
    89 | 
    90 |   numberModServer("numbers", r = r)
    91 | }
    92 | 
    93 | shinyApp(ui, server)
    94 | 
    
    
    --------------------------------------------------------------------------------