├── binder
├── runtime.txt
└── install.R
├── netlify.toml
├── data
├── test.csv
└── natsal_3_teaching_open.sav
├── static
├── favi.png
├── icon.png
├── profile.jpg
├── social.jpg
├── icon_check.svg
├── icon_slides.svg
└── logo.svg
├── src
├── context.js
├── styles
│ ├── chapter.module.sass
│ ├── link.module.sass
│ ├── index.module.sass
│ ├── hint.module.sass
│ ├── choice.module.sass
│ ├── typography.module.sass
│ ├── code.module.sass
│ ├── layout.module.sass
│ ├── button.module.sass
│ ├── exercise.module.sass
│ ├── slides.module.sass
│ ├── index.sass
│ └── reveal.css
├── components
│ ├── typography.js
│ ├── hint.js
│ ├── button.js
│ ├── link.js
│ ├── choice.js
│ ├── exercise.js
│ ├── seo.js
│ ├── slides.js
│ ├── layout.js
│ ├── code.js
│ └── juniper.js
├── markdown.js
├── pages
│ └── index.js
└── templates
│ └── chapter.js
├── exercises
├── exc_02_02.R
├── exc_01_04.R
├── solution_01_04.R
├── exc_01_05.R
├── exc_02_03.R
├── exc_02_04.R
├── test_01_04.R
├── exc_01_03.R
├── test_01_03.R
├── solution_01_03.R
├── exc_03_01.R
└── solution_03_01.R
├── gatsby-browser.js
├── .prettierrc
├── slides
└── chapter1_01_introduction.md
├── LICENSE
├── .gitignore
├── meta.json
├── theme.sass
├── chapters
├── chapter1.md
├── chapter2.md
└── chapter3.md
├── package.json
├── gatsby-node.js
├── gatsby-config.js
└── README.md
/binder/runtime.txt:
--------------------------------------------------------------------------------
1 | r-2019-04-10
2 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | environment = { NODE_VERSION = "10" }
3 |
--------------------------------------------------------------------------------
/data/test.csv:
--------------------------------------------------------------------------------
1 | Col1,Col2,Col3
2 | 1,2,3
3 | 4,5,6
4 | 7,8,9
5 | a,b,c
6 |
--------------------------------------------------------------------------------
/static/favi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/static/favi.png
--------------------------------------------------------------------------------
/static/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/static/icon.png
--------------------------------------------------------------------------------
/src/context.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export const ChapterContext = React.createContext()
4 |
--------------------------------------------------------------------------------
/static/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/static/profile.jpg
--------------------------------------------------------------------------------
/static/social.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/static/social.jpg
--------------------------------------------------------------------------------
/binder/install.R:
--------------------------------------------------------------------------------
1 | install.packages ("janitor")
2 | install.packages("tidyverse")
3 | install.packages ("haven")
--------------------------------------------------------------------------------
/exercises/exc_02_02.R:
--------------------------------------------------------------------------------
1 | library (haven)
2 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav")
3 |
4 | colnames(ntsl3)
--------------------------------------------------------------------------------
/data/natsal_3_teaching_open.sav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/data/natsal_3_teaching_open.sav
--------------------------------------------------------------------------------
/exercises/exc_01_04.R:
--------------------------------------------------------------------------------
1 | # Create a variable storing the value of 2 + 2
2 | total <- ____
3 |
4 | # Print the variable
5 | print(____)
6 |
--------------------------------------------------------------------------------
/exercises/solution_01_04.R:
--------------------------------------------------------------------------------
1 | # Create a variable storing the value of 2 + 2
2 | total <- 2 + 2
3 |
4 | # Print the variable
5 | print(total)
--------------------------------------------------------------------------------
/exercises/exc_01_05.R:
--------------------------------------------------------------------------------
1 | # test read in the dataset from /data/test.csv
2 | df <- read.csv2("data/test.csv",
3 | header= FALSE)
4 |
5 | df
6 |
--------------------------------------------------------------------------------
/exercises/exc_02_03.R:
--------------------------------------------------------------------------------
1 | library (haven)
2 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav")
3 |
4 | hist(ntsl3$dage1ch)
5 | summary(ntsl3$dage1ch, na.rm = T)
--------------------------------------------------------------------------------
/src/styles/chapter.module.sass:
--------------------------------------------------------------------------------
1 | .pagination
2 | max-width: 100%
3 | width: var(--width-container)
4 | margin: 4rem auto 0
5 | display: flex
6 | justify-content: space-between
7 |
--------------------------------------------------------------------------------
/static/icon_check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | import python from 'codemirror/mode/r/r' // eslint-disable-line no-unused-vars
2 |
3 | // This doesn't have to be here – but if we do import Juniper here, it's already
4 | // preloaded and cached when we dynamically import it in code.js.
5 | import Juniper from './src/components/juniper' // eslint-disable-line no-unused-vars
6 |
--------------------------------------------------------------------------------
/src/styles/link.module.sass:
--------------------------------------------------------------------------------
1 | .root
2 | cursor: pointer
3 | border-bottom: 1px solid var(--color-theme)
4 |
5 | &:hover
6 | border-bottom-color: var(--color-front)
7 |
8 | .secondary
9 | border-bottom-color: var(--color-subtle-dark)
10 |
11 | &:hover
12 | border-bottom-color: var(--color-front)
13 |
14 | .hidden
15 | border-bottom: 0
16 |
--------------------------------------------------------------------------------
/exercises/exc_02_04.R:
--------------------------------------------------------------------------------
1 | library (haven)
2 | library(ggplot2)
3 | library(dplyr)
4 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav")
5 |
6 | #convert sex into factor
7 | ntsl3$sex <- as_factor(ntsl3$rsex)
8 |
9 | # Create summary data
10 | lr_gen <- ntsl3 %>%
11 | group_by(sex) %>%
12 | summarize(n = mean(dage1ch, na.rm = T))
13 |
14 | # Plot it
15 | ggplot(lr_gen, aes(x = sex, y = n)) +
16 | geom_bar(aes(fill = sex), stat = "identity") +
17 | theme_minimal()
--------------------------------------------------------------------------------
/exercises/test_01_04.R:
--------------------------------------------------------------------------------
1 | test <- function() {
2 | # Here we can either check objects created in the solution code, or the
3 | # string value of the solution, available as .solution. See the testTemplate
4 | # in the meta.json for details.
5 | if (total != 4) {
6 | stop("Don't doesn't quite look right")
7 | }
8 | if (!grepl("print(total)", .solution, fixed = TRUE)) {
9 | stop("Are you printing the correct variable?")
10 | }
11 |
12 | # This function is defined in the testTemplate
13 | success("Well done!")
14 | }
15 |
--------------------------------------------------------------------------------
/exercises/exc_01_03.R:
--------------------------------------------------------------------------------
1 | library(ggplot2)
2 |
3 | mtcars$gear <- factor(mtcars$gear,levels=c(3,4,5),
4 | labels=c("3gears","4gears","5gears"))
5 | mtcars$am <- factor(mtcars$am,levels=c(0,1),
6 | labels=c("Automatic","Manual"))
7 | mtcars$cyl <- factor(mtcars$cyl,levels=c(4,6,8),
8 | labels=c("4cyl","6cyl","8cyl"))
9 |
10 | # Print the gear variable of mtcars
11 | print(____)
12 |
13 | # Assign the length of mtcars to some_var
14 | some_var <- ____
15 |
16 | # Uncomment this to see the plot
17 | # print(qplot(mpg, data=mtcars, geom="density", fill=gear, alpha=I(.5)))
18 |
--------------------------------------------------------------------------------
/exercises/test_01_03.R:
--------------------------------------------------------------------------------
1 | test <- function() {
2 | # Here we can either check objects created in the solution code, or the
3 | # string value of the solution, available as .solution. See the testTemplate
4 | # in the meta.json for details.
5 | if (some_var != length(mtcars)) {
6 | stop("Are you getting the correct length?")
7 | }
8 | if (!grepl("print(mtcars$gear)", .solution, fixed = TRUE)) {
9 | stop("Are you printing the correct variable?")
10 | }
11 |
12 | # This function is defined in the testTemplate
13 | success("Well done!")
14 | }
15 |
--------------------------------------------------------------------------------
/exercises/solution_01_03.R:
--------------------------------------------------------------------------------
1 | library(ggplot2)
2 |
3 | mtcars$gear <- factor(mtcars$gear,levels=c(3,4,5),
4 | labels=c("3gears","4gears","5gears"))
5 | mtcars$am <- factor(mtcars$am,levels=c(0,1),
6 | labels=c("Automatic","Manual"))
7 | mtcars$cyl <- factor(mtcars$cyl,levels=c(4,6,8),
8 | labels=c("4cyl","6cyl","8cyl"))
9 |
10 | # Print the gear variable of mtcars
11 | print(mtcars$gear)
12 |
13 | # Assign the length of mtcars to some_var
14 | some_var <- length(mtcars)
15 |
16 | # Uncomment this to see the plot
17 | # print(qplot(mpg, data=mtcars, geom="density", fill=gear, alpha=I(.5)))
18 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "es5",
5 | "tabWidth": 4,
6 | "printWidth": 100,
7 | "overrides": [
8 | {
9 | "files": "*.sass",
10 | "options": {
11 | "printWidth": 999
12 | }
13 | },
14 | {
15 | "files": "*.md",
16 | "options": {
17 | "tabWidth": 2,
18 | "printWidth": 80,
19 | "proseWrap": "always",
20 | "htmlWhitespaceSensitivity": "strict"
21 | }
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/typography.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import classes from '../styles/typography.module.sass'
4 |
5 | export const H3 = ({ children }) =>
{children}
6 | export const Hr = () =>
7 | export const InlineCode = ({ children }) => {children}
8 |
9 | export const Ol = ({ children }) => {children}
10 | export const Ul = ({ children }) =>
11 | export const Li = ({ children }) => {children}
12 |
--------------------------------------------------------------------------------
/src/styles/index.module.sass:
--------------------------------------------------------------------------------
1 | .logo
2 | width: 300px
3 | height: auto
4 | max-width: 100%
5 | margin: 0 auto 6rem
6 | display: block
7 | color: var(--color-theme)
8 |
9 | .chapter
10 | width: var(--width-container)
11 | max-width: 100%
12 | margin: 0 auto
13 | padding: 2rem
14 | border: 1px solid var(--color-subtle-medium)
15 | border-radius: var(--border-radius)
16 | margin-bottom: 2rem
17 |
18 | .chapter-title
19 | font-family: var(--font-display)
20 | font-size: var(--font-size-lg)
21 | margin-bottom: 1.5rem
22 |
23 | .chapter-desc
24 | font-size: var(--font-size-sm)
25 | color: var(--color-subtle-dark)
26 | margin-bottom: 0
27 |
--------------------------------------------------------------------------------
/static/icon_slides.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/styles/hint.module.sass:
--------------------------------------------------------------------------------
1 | .root
2 | width: 100%
3 |
4 | .actions
5 | padding-left: 2rem
6 |
7 | .label
8 | cursor: pointer
9 | background: var(--color-subtle-light)
10 | border-width: 0 1px 1px 1px
11 | border-style: solid
12 | border-color: var(--color-subtle-medium)
13 | padding: 0.5rem 1rem 0.5rem
14 | border-bottom-left-radius: var(--border-radius)
15 | border-bottom-right-radius: var(--border-radius)
16 | font-family: var(--font-primary)
17 | font-size: var(--font-size-sm)
18 | font-weight: bold
19 | margin-right: 1rem
20 |
21 | .content
22 | background: var(--color-subtle-light)
23 | padding: 1rem 2rem
24 | font-size: var(--font-size-sm)
25 | border-bottom: 1px solid var(--color-subtle-medium)
26 |
27 | p, ol, ul
28 | margin-bottom: 0
29 |
--------------------------------------------------------------------------------
/src/markdown.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import rehypeReact from 'rehype-react'
3 |
4 | import Exercise from './components/exercise'
5 | import CodeBlock from './components/code'
6 | import { Link } from './components/link'
7 | import Slides from './components/slides'
8 | import Choice, { Option } from './components/choice'
9 | import { H3, Hr, Ol, Ul, Li, InlineCode } from './components/typography'
10 |
11 | export const renderAst = new rehypeReact({
12 | createElement: React.createElement,
13 | components: {
14 | exercise: Exercise,
15 | slides: Slides,
16 | codeblock: CodeBlock,
17 | choice: Choice,
18 | opt: Option,
19 | a: Link,
20 | hr: Hr,
21 | h3: H3,
22 | ol: Ol,
23 | ul: Ul,
24 | li: Li,
25 | code: InlineCode,
26 | },
27 | }).Compiler
28 |
--------------------------------------------------------------------------------
/exercises/exc_03_01.R:
--------------------------------------------------------------------------------
1 | # Imports libaries and supress warnings
2 | suppressMessages(library(dplyr, warn.conflict = FALSE, quietly = TRUE))
3 | library(haven)
4 | library(dplyr)
5 |
6 |
7 | # Read in our dataset
8 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav")
9 |
10 | # Convert columns
11 | ntsl3$religimp <- as_factor(ntsl3$religimp)
12 | ntsl3$snnolov <- as_factor(ntsl3$snnolov)
13 | ntsl3$snnolov <-recode_factor(ntsl3$snnolov, "Don't know" = NA_character_)
14 | ntsl3$snpres <- as_factor(ntsl3$snpres)
15 | ntsl3$snpres <-recode_factor(ntsl3$snpres, "Don't know" = NA_character_)
16 | ntsl3$snmedia<- as_factor(ntsl3$snmedia)
17 | ntsl3$snmedia <-recode_factor(ntsl3$snmedia, "Don't know" = NA_character_)
18 | ntsl3$snearly<- as_factor(ntsl3$snearly)
19 | ntsl3$snearly <-recode_factor(ntsl3$snearly, "Don't know" = NA_character_)
20 | ntsl3$rsex<- as_factor(ntsl3$rsex)
21 | ntsl3$agrp<- as_factor(ntsl3$agrp)
22 |
23 | # Print out all column names
24 | names(ntsl3)
--------------------------------------------------------------------------------
/exercises/solution_03_01.R:
--------------------------------------------------------------------------------
1 | # Imports libaries and supress warnings
2 | suppressMessages(library(dplyr, warn.conflict = FALSE, quietly = TRUE))
3 | library(haven)
4 | library(dplyr)
5 |
6 |
7 | # Read in our dataset
8 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav")
9 |
10 | # Convert columns
11 | ntsl3$religimp <- as_factor(ntsl3$religimp)
12 | ntsl3$snnolov <- as_factor(ntsl3$snnolov)
13 | ntsl3$snnolov <-recode_factor(ntsl3$snnolov, "Don't know" = NA_character_)
14 | ntsl3$snpres <- as_factor(ntsl3$snpres)
15 | ntsl3$snpres <-recode_factor(ntsl3$snpres, "Don't know" = NA_character_)
16 | ntsl3$snmedia<- as_factor(ntsl3$snmedia)
17 | ntsl3$snmedia <-recode_factor(ntsl3$snmedia, "Don't know" = NA_character_)
18 | ntsl3$snearly<- as_factor(ntsl3$snearly)
19 | ntsl3$snearly <-recode_factor(ntsl3$snearly, "Don't know" = NA_character_)
20 | ntsl3$rsex<- as_factor(ntsl3$rsex)
21 | ntsl3$agrp<- as_factor(ntsl3$agrp)
22 |
23 | # Print out the number of variables
24 | print('The number of variables is:')
25 | length(names(ntsl3))
--------------------------------------------------------------------------------
/src/styles/choice.module.sass:
--------------------------------------------------------------------------------
1 | .option
2 | margin-bottom: 0.5rem
3 | margin-left: 1rem
4 |
5 | .input
6 | display: none
7 |
8 | .label
9 | cursor: pointer
10 | display: flex
11 | align-items: flex-start
12 |
13 | &:before
14 | content: ""
15 | flex: 0 0 20px
16 | width: 20px
17 | height: 20px
18 | display: inline-block
19 | border: 2px solid var(--color-theme)
20 | border-radius: 50%
21 | margin-right: 1rem
22 | margin-top: 0.5rem
23 |
24 | input[type="radio"]:checked + &:before
25 | border-width: 7px
26 |
27 | .answer
28 | padding: 2rem
29 | border-radius: var(--border-radius)
30 | background: var(--color-incorrect-light)
31 | margin-bottom: 2rem
32 |
33 | p
34 | margin-bottom: 0
35 | display: inline
36 |
37 | .correct
38 | background: var(--color-correct-light)
39 |
40 | .answer-label
41 | color: var(--color-incorrect-dark)
42 |
43 | .answer-label-correct
44 | color: var(--color-correct-dark)
45 |
--------------------------------------------------------------------------------
/src/styles/typography.module.sass:
--------------------------------------------------------------------------------
1 | .h3
2 | font-size: var(--font-size-md)
3 | font-weight: bold
4 | margin-bottom: 2rem
5 |
6 | .hr
7 | border: 0
8 | height: 0
9 | margin-top: 7rem
10 | margin-bottom: 7rem
11 |
12 | .ul,
13 | .ol
14 | margin-bottom: 3rem
15 |
16 | .ol
17 | list-style: none
18 | counter-reset: ul
19 |
20 | .li
21 | margin-left: 0.5rem
22 |
23 | &:before
24 | margin-right: 1rem
25 | font-weight: bold
26 | counter-increment: ul
27 | content: counter(ul) ". "
28 |
29 | .li
30 | margin-left: 2rem
31 |
32 | .code
33 | background: var(--color-background-code)
34 | padding: 2px 5px
35 | border-radius: var(--border-radius)
36 |
37 | pre &
38 | background: var(--color-subtle-light)
39 |
40 | \:global
41 | pre code
42 | background: var(--color-subtle-light)
43 |
44 | p code, li code
45 | background: var(--color-background-code)
46 | padding: 2px 5px
47 | border-radius: var(--border-radius)
48 |
--------------------------------------------------------------------------------
/slides/chapter1_01_introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | type: slides
3 | ---
4 |
5 | # Introduction
6 |
7 | Notes: Text at the end of a slide prefixed like this will be displayed as
8 | speaker notes on the side. Slides can be separated with a divider: ---.
9 |
10 | ---
11 |
12 | # This is a slide
13 |
14 | ```r
15 | # Print something
16 | print("Hello world", quote = FALSE)
17 | ```
18 |
19 | ```out
20 | Hello world
21 | ```
22 |
23 | - Slides can have code, bullet points, tables and pretty much all other Markdown
24 | elements.
25 | - This is another bullet point.
26 |
27 |
28 |
29 | Notes: Some more notes go here
30 |
31 | ---
32 |
33 | # Let's practice!
34 |
35 | Notes: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam tristique
36 | libero at est congue, sed vestibulum tortor laoreet. Aenean egestas massa non
37 | commodo consequat. Curabitur faucibus, sapien vitae euismod imperdiet, arcu erat
38 | semper urna, in accumsan sapien dui ac mi. Pellentesque felis lorem, semper nec
39 | velit nec, consectetur placerat enim.
40 |
--------------------------------------------------------------------------------
/src/components/hint.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react'
2 |
3 | import classes from '../styles/hint.module.sass'
4 |
5 | export const Hint = ({ expanded = false, actions = [], children }) => {
6 | const [isExpanded, setIsExpanded] = useState(expanded)
7 | const handleExpand = useCallback(() => setIsExpanded(!isExpanded), [isExpanded])
8 | return (
9 |
10 | {isExpanded && children && {children}
}
11 |
12 | {children && (
13 |
14 | {isExpanded ? 'Hide hints' : 'Show hints'}
15 |
16 | )}
17 | {actions.map(({ text, onClick }, i) => (
18 |
19 | {text}
20 |
21 | ))}
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (C) 2019 Ines Montani
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (http://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # Typescript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # dotenv environment variables file
57 | .env
58 |
59 | # gatsby files
60 | .cache/
61 | public
62 |
63 | # Mac files
64 | .DS_Store
65 |
66 | # Yarn
67 | yarn-error.log
68 | .pnp/
69 | .pnp.js
70 | # Yarn Integrity file
71 | .yarn-integrity
72 |
73 | yarn.lock
--------------------------------------------------------------------------------
/src/styles/code.module.sass:
--------------------------------------------------------------------------------
1 | .root
2 | margin-left: -2rem
3 | width: calc(100% + 4rem)
4 | margin-bottom: 5rem
5 |
6 | .cell
7 | background: var(--syntax-background)
8 | border-top: 1px solid var(--color-subtle-medium)
9 |
10 | & > div:first-child
11 | padding: 1.5rem 2rem 1rem
12 | position: relative
13 |
14 | &:before
15 | content: "Input"
16 | display: inline-block
17 | border-radius: var(--border-radius)
18 | color: var(--syntax-background)
19 | background: var(--syntax-text)
20 | padding: 0 0.5rem
21 | position: absolute
22 | top: 1rem
23 | right: 1.5rem
24 | font-size: var(--font-size-xs)
25 | font-weight: bold
26 | text-transform: uppercase
27 | z-index: 5
28 |
29 | .output
30 | padding: 2rem 9rem 2rem 3rem
31 | background: var(--syntax-text)
32 | color: var(--syntax-background)
33 | position: relative
34 |
35 | &:before
36 | content: "Output"
37 | display: inline-block
38 | border-radius: var(--border-radius)
39 | background: var(--syntax-background)
40 | color: var(--syntax-text)
41 | padding: 0 0.5rem
42 | position: absolute
43 | top: 1rem
44 | right: 1.5rem
45 | font-size: var(--font-size-xs)
46 | font-weight: bold
47 | text-transform: uppercase
48 |
--------------------------------------------------------------------------------
/src/components/button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import classNames from 'classnames'
3 |
4 | import IconCheck from '../../static/icon_check.svg'
5 | import classes from '../styles/button.module.sass'
6 |
7 | export const Button = ({ Component = 'button', children, onClick, variant, small, className }) => {
8 | const buttonClassNames = classNames(classes.root, className, {
9 | [classes.primary]: variant === 'primary',
10 | [classes.secondary]: variant === 'secondary',
11 | [classes.small]: !!small,
12 | })
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const CompleteButton = ({ completed, toggleComplete, small = true }) => {
21 | const buttonClassNames = classNames({
22 | [classes.completeInactive]: !completed,
23 | [classes.completeActive]: completed,
24 | })
25 | return (
26 |
27 | {!completed ? (
28 | 'Mark as completed'
29 | ) : (
30 | <>
31 | {' '}
32 | Completed {' '}
33 | Remove from completed
34 | >
35 | )}
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "courseId": "course-starter-r",
3 | "title": "My cool online course",
4 | "slogan": "A free online course",
5 | "description": "This course covers exploring the open teaching NATSAL dataset. This course was built in collaboration with the QStep programme at the University of Manchester.",
6 | "bio": "The UK Data Service are pioneers in data curation and managing long-term research access to high quality data, our expertise continues to transform social science teaching and learning.",
7 | "siteUrl": "https://course-starter-r.netlify.com",
8 | "twitter": "spacy_io",
9 | "fonts": "IBM+Plex+Mono:500|IBM+Plex+Sans:700|Lato:400,400i,700,700i",
10 | "testTemplate": "success <- function(text) {\n cat(paste(\"\\033[32m\", text, \"\\033[0m\", sep = \"\"))\n}\n\n.solution <- \"${solutionEscaped}\"\n\n${solution}\n\n${test}\ntryCatch({\n test()\n}, error = function(e) {\n cat(paste(\"\\033[31m\", e[1], \"\\033[0m\", sep = \"\"))\n})",
11 | "juniper": {
12 | "repo": "UKDataServiceOpen/r-course-starter",
13 | "branch": "master",
14 | "lang": "r",
15 | "kernelType": "ir",
16 | "debug": false
17 | },
18 | "showProfileImage": true,
19 | "footerLinks": [
20 | { "text": "Website", "url": "https://ukdataservice.ac.uk/" },
21 | { "text": "Source", "url": "https://github.com/ines/course-starter-r" },
22 | { "text": "Built with ♥", "url": "https://github.com/ines/course-starter-r" }
23 | ],
24 | "theme": "#de7878"
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/link.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Link as GatsbyLink } from 'gatsby'
4 | import classNames from 'classnames'
5 |
6 | import classes from '../styles/link.module.sass'
7 |
8 | export const Link = ({ children, to, href, onClick, variant, hidden, className, ...other }) => {
9 | const dest = to || href
10 | const external = /(http(s?)):\/\//gi.test(dest)
11 | const linkClassNames = classNames(classes.root, className, {
12 | [classes.hidden]: hidden,
13 | [classes.secondary]: variant === 'secondary',
14 | })
15 |
16 | if (!external) {
17 | if ((dest && /^#/.test(dest)) || onClick) {
18 | return (
19 |
20 | {children}
21 |
22 | )
23 | }
24 | return (
25 |
26 | {children}
27 |
28 | )
29 | }
30 | return (
31 |
38 | {children}
39 |
40 | )
41 | }
42 |
43 | Link.propTypes = {
44 | children: PropTypes.node.isRequired,
45 | to: PropTypes.string,
46 | href: PropTypes.string,
47 | onClick: PropTypes.func,
48 | variant: PropTypes.oneOf(['secondary', null]),
49 | hidden: PropTypes.bool,
50 | className: PropTypes.string,
51 | }
52 |
--------------------------------------------------------------------------------
/src/styles/layout.module.sass:
--------------------------------------------------------------------------------
1 | .root
2 | width: 100%
3 |
4 | .content
5 | margin-bottom: 4rem
6 | padding: 6rem 1rem 1rem
7 |
8 | .logo
9 | color: var(--color-theme)
10 | position: absolute
11 | top: 2rem
12 | left: 2rem
13 |
14 | @media(min-width: 1200px)
15 | .logo
16 | position: fixed
17 |
18 | .header
19 | width: var(--width-container)
20 | max-width: 100%
21 | margin: 5rem auto
22 |
23 | .title
24 | font-size: var(--font-size-xl)
25 | font-family: var(--font-display)
26 | line-height: 1.3
27 | margin-bottom: 2rem
28 |
29 | .footer
30 | background: var(--color-subtle-light)
31 | width: 100%
32 | padding: 3rem
33 | margin-top: 5.5rem
34 |
35 | .footer-content
36 | max-width: 100%
37 | width: var(--width-container)
38 | margin: 0 auto
39 | font-size: var(--font-size-sm)
40 | justify-content: space-between
41 | flex-flow: row wrap
42 |
43 | @media(min-width: 900px)
44 | .footer-content
45 | display: flex
46 |
47 | .footer-section
48 | flex: 0 0 48%
49 | padding: 0 2rem
50 | margin-bottom: 1rem
51 |
52 | .footer-links
53 | flex: 0 0 100%
54 | padding-top: 2rem
55 | font-size: var(--font-size-xs)
56 | text-align: center
57 | color: var(--color-subtle-dark)
58 |
59 | .footer-link
60 | display: inline
61 |
62 | &:not(:last-child)
63 | margin-right: 1rem
64 |
65 | &:after
66 | content: "\b7"
67 | margin-left: 1rem
68 |
69 | .profile
70 | width: var(--size-profile-pic)
71 | height: var(--size-profile-pic)
72 | border-radius: 50%
73 | float: right
74 | margin: 0 0 1rem 1rem
75 | shape-outside: circle()
76 |
--------------------------------------------------------------------------------
/src/styles/button.module.sass:
--------------------------------------------------------------------------------
1 | .root
2 | border: 0
3 | background: var(--color-button-secondary)
4 | color: var(--color-button-secondary-contrast)
5 | padding: 0.75rem 1rem
6 | font-family: var(--font-primary)
7 | font-size: var(--font-size-sm)
8 | font-weight: bold
9 | border-radius: var(--border-radius)
10 | margin-top: 3rem
11 | margin-bottom: 2.25rem
12 | margin-left: 2.5rem
13 | transition: opacity 0.2s ease
14 | box-shadow: 0 0 3px var(--color-subtle-medium)
15 |
16 | &:hover
17 | opacity: 0.9
18 | box-shadow: none
19 |
20 | .primary
21 | background: var(--color-button-primary)
22 | color: var(--color-button-primary-contrast)
23 |
24 | .secondary
25 | background: var(--color-theme)
26 | color: var(--color-theme-contrast)
27 |
28 | .small
29 | padding: 0.75rem 1rem
30 | margin: 0
31 | font-size: var(--font-size-sm)
32 |
33 | &:not(:last-child)
34 | margin-right: 1rem
35 |
36 | .complete-icon
37 | position: relative
38 | top: 0.15rem
39 | margin-right: 0.5rem
40 |
41 | .complete-inactive
42 | &:hover
43 | opacity: 1
44 | background: var(--color-button-primary)
45 | color: var(--color-button-primary-contrast)
46 |
47 | .complete-active
48 | background: var(--color-button-primary)
49 | color: var(--color-button-primary-contrast)
50 |
51 | &:hover
52 | opacity: 1
53 | background: var(--color-button-danger)
54 | color: var(--color-button-danger-contrast)
55 |
56 | .complete-label
57 | .root:hover &
58 | display: none
59 |
60 | .complete-label-hover
61 | display: none
62 |
63 | .root:hover &
64 | display: inline
65 |
--------------------------------------------------------------------------------
/src/styles/exercise.module.sass:
--------------------------------------------------------------------------------
1 | .root
2 | width: var(--width-container)
3 | max-width: 100%
4 | padding: 1rem 2rem
5 | border: 1px solid var(--color-subtle-medium)
6 | border-radius: var(--border-radius)
7 | margin: 0 auto 2rem
8 | position: relative
9 | background: var(--color-back)
10 |
11 | .wide
12 | width: 95%
13 |
14 | .expanded
15 | box-shadow: 0 0 10px var(--color-subtle-medium)
16 |
17 | .completed
18 | color: var(--color-subtle-dark)
19 | background: var(--color-subtle-light)
20 |
21 | .title
22 | cursor: pointer
23 | user-select: none
24 | font-size: var(--font-size-lg)
25 | font-family: var(--font-display)
26 |
27 | .id
28 | background: var(--color-theme)
29 | color: var(--color-theme-contrast)
30 | font-size: 0.8em
31 | width: 1.75em
32 | height: 1.75em
33 | display: inline-flex
34 | border-radius: 50%
35 | justify-content: center
36 | align-items: center
37 | margin-right: 1rem
38 |
39 | .id-completed
40 | background: var(--color-button-primary)
41 | color: var(--color-button-primary-contrast)
42 |
43 | .title-expanded
44 | margin-bottom: 3rem
45 |
46 | .icon
47 | float: right
48 | vertical-align: middle
49 | margin-right: 0.5rem
50 |
51 | .content
52 | visibility: hidden
53 | height: 0
54 | opacity: 0
55 | z-index: 0
56 | overflow: hidden
57 |
58 | .footer
59 | width: 100%
60 | text-align: right
61 | margin-top: 1rem
62 | margin-bottom: 1rem
63 |
64 | .button
65 | cursor: pointer
66 | border: 0
67 | background: var(--color-theme)
68 | color: var(--color-theme-contrast)
69 | padding: 0.75rem 1rem
70 | font-family: var(--font-primary)
71 | font-size: var(--font-size-sm)
72 | font-weight: bold
73 | border-radius: var(--border-radius)
74 |
--------------------------------------------------------------------------------
/theme.sass:
--------------------------------------------------------------------------------
1 | \:root
2 | --font-primary: Lato, Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'
3 | --font-display: 'IBM Plex Sans', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'
4 | --font-code: 'IBM Plex Mono', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace
5 |
6 | // Font Sizes
7 | --font-size-xs: 1.15rem
8 | --font-size-sm: 1.3rem
9 | --font-size-md: 1.5rem
10 | --font-size-lg: 1.8rem
11 | --font-size-xl: 2.4rem
12 |
13 | // Line heights
14 | --line-height: 1.6
15 |
16 | // Colors
17 | --color-back: #fff
18 | --color-front: #444
19 | --color-theme: #702082
20 | --color-theme-contrast: #fff
21 | --color-subtle-dark: #777
22 | --color-subtle-medium: #eee
23 | --color-subtle-light: #f9f9f9
24 | --color-background-code: #7a818117
25 |
26 | --color-button-primary: #29c15b
27 | --color-button-primary-contrast: #fff
28 | --color-button-secondary: #cdcdd4
29 | --color-button-secondary-contrast: #686873
30 | --color-button-danger: #d21313
31 | --color-button-danger-contrast: #fff
32 |
33 | --color-correct-light: #e3fde8
34 | --color-correct-dark: #007737
35 | --color-incorrect-light: #fde7e7
36 | --color-incorrect-dark: #d21313
37 |
38 | // Syntax Highlighting
39 | // based on https://github.com/sdras/night-owl-vscode-theme
40 | --syntax-background: #f7f7f7
41 | --syntax-text: #403f53
42 | --syntax-selected-background: #7a81812b
43 | --syntax-comment: #989fb1
44 | --syntax-tag: #994cc3
45 | --syntax-number: #aa0982
46 | --syntax-selector: #994cc3
47 | --syntax-operator: #994cc3
48 | --syntax-function: #4876d6
49 | --syntax-keyword: #994cc3
50 | --syntax-regex: #5ca7e4
51 |
52 | --ansi-green: #12dc55
53 | --ansi-red: #f76464
54 | --ansi-cyan: #00d8ff
55 |
56 | // Sizes
57 | --width-container: 800px
58 | --size-profile-pic: 125px
59 | --border-radius: 5px
60 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { graphql } from 'gatsby'
3 |
4 | import Layout from '../components/layout'
5 | import { Link } from '../components/link'
6 | import Logo from '../../static/logo.svg'
7 |
8 | import classes from '../styles/index.module.sass'
9 |
10 | export default ({ data }) => {
11 | const siteMetadata = data.site.siteMetadata
12 | const chapters = data.allMarkdownRemark.edges.map(({ node }) => ({
13 | slug: node.fields.slug,
14 | title: node.frontmatter.title,
15 | description: node.frontmatter.description,
16 | }))
17 | return (
18 |
19 |
20 | {chapters.map(({ slug, title, description }) => (
21 |
22 |
23 |
24 | {title}
25 |
26 |
27 |
28 |
29 | {description}
30 |
31 |
32 |
33 | ))}
34 |
35 | )
36 | }
37 |
38 | export const pageQuery = graphql`
39 | {
40 | site {
41 | siteMetadata {
42 | title
43 | }
44 | }
45 | allMarkdownRemark(
46 | sort: { fields: [frontmatter___title], order: ASC }
47 | filter: { frontmatter: { type: { eq: "chapter" } } }
48 | ) {
49 | edges {
50 | node {
51 | fields {
52 | slug
53 | }
54 | frontmatter {
55 | title
56 | description
57 | }
58 | }
59 | }
60 | }
61 | }
62 | `
63 |
--------------------------------------------------------------------------------
/chapters/chapter1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Chapter 1: Getting started'
3 | description:
4 | 'This chapter will teach you about many cool things and introduce you to the
5 | most important concepts of the course.'
6 | prev: null
7 | next: /chapter2
8 | type: chapter
9 | id: 1
10 | ---
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Let's ask some questions about the slides. Whats the correct answer?
22 |
23 |
24 |
25 |
26 | This is not the correct answer.
27 |
28 |
29 |
30 |
31 |
32 | Good job!
33 |
34 |
35 |
36 |
37 |
38 | This is not correct either.
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | This is a code exercise. The content can be formatted in simple Markdown – so
48 | you can have **bold text**, `code` or [links](https://spacy.io) or lists, like
49 | the one for the instructions below.
50 |
51 | - These are instructions and they can have bullet points.
52 | - The code block below will look for the files `exc_01_03`, `solution_01_03` and
53 | `test_01_03` in `/exercises`.
54 |
55 |
56 |
57 | This is a hint.
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | This is a hint.
68 |
69 |
70 |
71 |
72 |
73 |
74 | No, think about what 2 + 2 might evaluate to?
75 |
76 |
77 |
78 |
79 |
80 | Good job! 2 + 2 is 4!
81 |
82 |
83 |
84 |
85 |
86 | A little too high, guess again!
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | you can import data using the read_csv function.
96 |
97 |
98 |
99 | This is a hint.
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "course-starter-r",
3 | "private": true,
4 | "description": "Starter package to build interactive R courses",
5 | "version": "0.0.1",
6 | "author": "Ines Montani ",
7 | "dependencies": {
8 | "@illinois/react-use-local-storage": "^1.1.0",
9 | "@jupyterlab/outputarea": "^0.19.1",
10 | "@jupyterlab/rendermime": "^0.19.1",
11 | "@phosphor/widgets": "^1.6.0",
12 | "autoprefixer": "^9.4.7",
13 | "classnames": "^2.2.6",
14 | "codemirror": "^5.43.0",
15 | "gatsby": "^2.1.4",
16 | "gatsby-image": "^2.0.29",
17 | "gatsby-plugin-manifest": "^2.0.17",
18 | "gatsby-plugin-offline": "^2.0.23",
19 | "gatsby-plugin-react-helmet": "^3.0.6",
20 | "gatsby-plugin-react-svg": "^2.1.1",
21 | "gatsby-plugin-sass": "^2.0.10",
22 | "gatsby-plugin-sharp": "^2.0.29",
23 | "gatsby-plugin-sitemap": "^2.0.5",
24 | "gatsby-remark-copy-linked-files": "^2.0.9",
25 | "gatsby-remark-images": "^3.0.4",
26 | "gatsby-remark-prismjs": "^3.2.4",
27 | "gatsby-remark-smartypants": "^2.0.8",
28 | "gatsby-remark-unwrap-images": "^1.0.1",
29 | "gatsby-source-filesystem": "^2.0.20",
30 | "gatsby-transformer-remark": "^2.2.5",
31 | "gatsby-transformer-sharp": "^2.1.17",
32 | "juniper-js": "^0.1.0",
33 | "node-sass": "^4.11.0",
34 | "prismjs": "^1.15.0",
35 | "react": "^16.8.2",
36 | "react-dom": "^16.8.2",
37 | "react-helmet": "^5.2.0",
38 | "rehype-react": "^3.1.0",
39 | "remark-react": "^5.0.1",
40 | "reveal.js": "^3.8.0"
41 | },
42 | "scripts": {
43 | "build": "gatsby build",
44 | "dev": "gatsby develop",
45 | "lint": "eslint **",
46 | "clear": "rm -rf .cache",
47 | "test": "echo \"Write tests! -> https://gatsby.app/unit-testing\""
48 | },
49 | "devDependencies": {
50 | "browser-monads": "^1.0.0",
51 | "prettier": "^1.16.4"
52 | },
53 | "repository": {
54 | "type": "git",
55 | "url": "https://github.com/ines/course-starter-python"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/templates/chapter.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { graphql, navigate } from 'gatsby'
3 | import useLocalStorage from '@illinois/react-use-local-storage'
4 |
5 | import { renderAst } from '../markdown'
6 | import { ChapterContext } from '../context'
7 | import Layout from '../components/layout'
8 | import { Button } from '../components/button'
9 |
10 | import classes from '../styles/chapter.module.sass'
11 |
12 | const Template = ({ data }) => {
13 | const { markdownRemark, site } = data
14 | const { courseId } = site.siteMetadata
15 | const { frontmatter, htmlAst } = markdownRemark
16 | const { title, description, prev, next, id } = frontmatter
17 | const [activeExc, setActiveExc] = useState(null)
18 | const [completed, setCompleted] = useLocalStorage(`${courseId}-completed-${id}`, [])
19 | const html = renderAst(htmlAst)
20 | const buttons = [
21 | { slug: prev, text: '« Previous Chapter' },
22 | { slug: next, text: 'Next Chapter »' },
23 | ]
24 |
25 | return (
26 |
27 |
28 | {html}
29 |
30 |
31 | {buttons.map(({ slug, text }) => (
32 |
33 | {slug && (
34 | navigate(slug)}>
35 | {text}
36 |
37 | )}
38 |
39 | ))}
40 |
41 |
42 |
43 | )
44 | }
45 |
46 | export default Template
47 |
48 | export const pageQuery = graphql`
49 | query($slug: String!) {
50 | site {
51 | siteMetadata {
52 | courseId
53 | }
54 | }
55 | markdownRemark(fields: { slug: { eq: $slug } }) {
56 | htmlAst
57 | frontmatter {
58 | id
59 | title
60 | description
61 | next
62 | prev
63 | }
64 | }
65 | }
66 | `
67 |
--------------------------------------------------------------------------------
/src/components/choice.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react'
2 | import classNames from 'classnames'
3 |
4 | import { Button } from './button'
5 | import classes from '../styles/choice.module.sass'
6 |
7 | const Choice = ({ id = '0', children = [] }) => {
8 | const [selected, setSelected] = useState(null)
9 | const [answer, setAnswer] = useState(null)
10 | const handleAnswer = useCallback(() => setAnswer(selected), [selected])
11 | const options = children.filter(child => child !== '\n')
12 | return (
13 | <>
14 | {options.map(({ key, props }, i) => (
15 |
16 | setSelected(i)}
24 | />
25 | ${props.text}` }}
29 | />
30 |
31 | ))}
32 |
33 | Submit
34 |
35 | {options.map(({ key, props }, i) => {
36 | const isCorrect = !!props.correct
37 | return answer === i ? (
38 |
42 |
47 | {isCorrect ? "That's correct! " : 'Incorrect. '}
48 |
49 | {props.children}
50 |
51 | ) : null
52 | })}
53 | >
54 | )
55 | }
56 |
57 | export const Option = ({ children }) => {
58 | return children
59 | }
60 |
61 | export default Choice
62 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const { createFilePath } = require('gatsby-source-filesystem')
3 |
4 | const chapterTemplate = path.resolve('src/templates/chapter.js')
5 |
6 | function replacePath(pagePath) {
7 | return pagePath === `/` ? pagePath : pagePath.replace(/\/$/, ``)
8 | }
9 |
10 | async function onCreateNode({
11 | node,
12 | actions,
13 | getNode,
14 | loadNodeContent,
15 | createNodeId,
16 | createContentDigest,
17 | }) {
18 | const { createNodeField, createNode, createParentChildLink } = actions
19 | if (node.internal.type === 'MarkdownRemark') {
20 | const slug = createFilePath({ node, getNode, basePath: 'chapters', trailingSlash: false })
21 | createNodeField({ name: 'slug', node, value: slug })
22 | } else if (node.extension === 'r') {
23 | // Load the contents of the Python file and make it available via GraphQL
24 | // https://www.gatsbyjs.org/docs/creating-a-transformer-plugin/
25 | const content = await loadNodeContent(node)
26 | const contentDigest = createContentDigest(content)
27 | const id = createNodeId(`${node.id}-code`)
28 | const internal = { type: 'Code', contentDigest }
29 | const codeNode = {
30 | id,
31 | parent: node.id,
32 | children: [],
33 | code: content,
34 | name: node.name,
35 | internal,
36 | }
37 | createNode(codeNode)
38 | createParentChildLink({ parent: node, child: codeNode })
39 | }
40 | }
41 |
42 | exports.onCreateNode = onCreateNode
43 |
44 | exports.createPages = ({ actions, graphql }) => {
45 | const { createPage } = actions
46 | return graphql(`
47 | {
48 | allMarkdownRemark {
49 | edges {
50 | node {
51 | frontmatter {
52 | title
53 | type
54 | }
55 | fields {
56 | slug
57 | }
58 | }
59 | }
60 | }
61 | }
62 | `).then(result => {
63 | if (result.errors) {
64 | return Promise.reject(result.errors)
65 | }
66 | const posts = result.data.allMarkdownRemark.edges.filter(
67 | ({ node }) => node.frontmatter.type == 'chapter'
68 | )
69 | posts.forEach(({ node }) => {
70 | createPage({
71 | path: replacePath(node.fields.slug),
72 | component: chapterTemplate,
73 | context: { slug: node.fields.slug },
74 | })
75 | })
76 | })
77 | }
78 |
--------------------------------------------------------------------------------
/src/components/exercise.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useCallback, useContext, useEffect } from 'react'
2 | import classNames from 'classnames'
3 |
4 | import { Button, CompleteButton } from './button'
5 | import { ChapterContext } from '../context'
6 | import IconSlides from '../../static/icon_slides.svg'
7 | import classes from '../styles/exercise.module.sass'
8 |
9 | const Exercise = ({ id, title, type, children }) => {
10 | const excRef = useRef()
11 | const excId = parseInt(id)
12 | const { activeExc, setActiveExc, completed, setCompleted } = useContext(ChapterContext)
13 | const isExpanded = activeExc === excId
14 | const isCompleted = completed.includes(excId)
15 | useEffect(() => {
16 | if (isExpanded && excRef.current) {
17 | excRef.current.scrollIntoView()
18 | }
19 | }, [isExpanded])
20 | const handleExpand = useCallback(() => setActiveExc(isExpanded ? null : excId), [
21 | isExpanded,
22 | excId,
23 | ])
24 | const handleNext = useCallback(() => setActiveExc(excId + 1))
25 | const handleSetCompleted = useCallback(() => {
26 | const newCompleted = isCompleted
27 | ? completed.filter(v => v !== excId)
28 | : [...completed, excId]
29 | setCompleted(newCompleted)
30 | }, [isCompleted, completed, excId])
31 | const rootClassNames = classNames(classes.root, {
32 | [classes.expanded]: isExpanded,
33 | [classes.wide]: isExpanded && type === 'slides',
34 | [classes.completed]: !isExpanded && isCompleted,
35 | })
36 | const titleClassNames = classNames(classes.title, {
37 | [classes.titleExpanded]: isExpanded,
38 | })
39 | return (
40 |
41 |
42 |
43 |
46 | {excId}
47 |
48 | {title}
49 |
50 | {type === 'slides' && }
51 |
52 | {isExpanded && (
53 |
54 | {children}
55 |
56 |
60 |
61 | Next
62 |
63 |
64 |
65 | )}
66 |
67 | )
68 | }
69 |
70 | export default Exercise
71 |
--------------------------------------------------------------------------------
/chapters/chapter2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Chapter 2: What age do people have children?'
3 | description:
4 | 'Explore the National Survey of Sexual Attitudes and Lifestyles to find out what age people tend to have children'
5 | prev: /chapter1
6 | next: null
7 | type: chapter
8 | id: 2
9 | ---
10 |
11 |
12 | The age of having children has been increasing since the middle of the 1970s (ONS).
13 |
14 | What is the average age of having children? How does the age of first child vary in the population? What factors are associated with having your first child older or younger?
15 |
16 | Let's find out.
17 |
18 |
19 |
20 |
21 |
22 | ## Data
23 | We look at these questions using data from the National Survey of Sexual Attitudes and Lifestyles (NATSAL). More information about natsal and why it's cool.
24 |
25 |
26 | ## Coding in R
27 | To analyse the data we will use some thing called R. R is a computer language often used to explore data but you can use it for other things to, such as building websites or games. This may go in an introduction somewhere else. TBC.
28 |
29 | You don't need to know R to use this resource, you just need to be able to run the code by pressing a button. However, at the end you have the option to adjust the code to create your own analysis.
30 |
31 | Let's give it a go. This code opens the data and prints a list of variables.
32 |
33 |
34 | This is a hint.
35 |
36 |
37 |
38 |
39 |
40 |
41 | The distribution of age at birth of first child
42 | The data set contains a variable named dageCh1 which records age at birth of first child.
43 |
44 | *What's a variable? A variable is....*
45 |
46 | We can visualise the distribution of values for this variable using a histogram. We can also get descriptive statistics such as mean and standard deviation. Together, we can use this information to understand when people tend to have their first child.
47 |
48 |
49 | Hint space
50 |
51 |
52 | Questions about the results
53 |
54 |
55 |
56 |
57 | We can also look at differences between men and women. Usually fathers are older than mothers, so we should expect to find the mean is higher for women and men. Run the code to find out.
58 |
59 | We can calculate means and we can plot them using a bar chart.
60 |
61 |
62 | Hint space
63 |
64 |
65 | Question - On average, are men or women older at the age of their first child?
66 |
67 |
68 |
69 | This is not the correct answer.
70 |
71 |
72 | Good job!
73 |
74 |
75 |
76 | could extend this bit to include confidence intervals or a t-test.
77 |
--------------------------------------------------------------------------------
/src/components/seo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Helmet from 'react-helmet'
3 | import { StaticQuery, graphql } from 'gatsby'
4 |
5 | const SEO = ({ title, description }) => (
6 | {
9 | const lang = 'en'
10 | const siteMetadata = data.site.siteMetadata
11 | const pageTitle = title
12 | ? `${title} · ${siteMetadata.title}`
13 | : `${siteMetadata.title} · ${siteMetadata.slogan}`
14 | const pageDesc = description || siteMetadata.description
15 | const image = `${siteMetadata.siteUrl}/social.jpg`
16 | const meta = [
17 | {
18 | name: 'description',
19 | content: pageDesc,
20 | },
21 | {
22 | property: 'og:title',
23 | content: pageTitle,
24 | },
25 | {
26 | property: 'og:description',
27 | content: pageDesc,
28 | },
29 | {
30 | property: 'og:type',
31 | content: `website`,
32 | },
33 | {
34 | property: 'og:site_name',
35 | content: siteMetadata.title,
36 | },
37 | {
38 | property: 'og:image',
39 | content: image,
40 | },
41 | {
42 | name: 'twitter:card',
43 | content: 'summary_large_image',
44 | },
45 | {
46 | name: 'twitter:image',
47 | content: image,
48 | },
49 | {
50 | name: 'twitter:creator',
51 | content: `@${siteMetadata.twitter}`,
52 | },
53 | {
54 | name: 'twitter:site',
55 | content: `@${siteMetadata.twitter}`,
56 | },
57 | {
58 | name: 'twitter:title',
59 | content: pageTitle,
60 | },
61 | {
62 | name: 'twitter:description',
63 | content: pageDesc,
64 | },
65 | ]
66 |
67 | return (
68 |
69 | {siteMetadata.fonts && (
70 |
74 | )}
75 |
76 | )
77 | }}
78 | />
79 | )
80 |
81 | export default SEO
82 |
83 | const query = graphql`
84 | query DefaultSEOQuery {
85 | site {
86 | siteMetadata {
87 | title
88 | description
89 | slogan
90 | siteUrl
91 | twitter
92 | fonts
93 | }
94 | }
95 | }
96 | `
97 |
--------------------------------------------------------------------------------
/src/styles/slides.module.sass:
--------------------------------------------------------------------------------
1 | .root
2 | position: relative
3 | width: 100%
4 | height: 600px
5 | overflow: hidden
6 |
7 | .slides
8 | text-align: left !important
9 |
10 | \:global
11 | .reveal
12 | h1
13 | border-bottom: 1px solid var(--color-subtle-medium)
14 |
15 | h1, h2, h3, pre, ul, ol
16 | margin-bottom: 3rem
17 |
18 | li
19 | margin-left: 2rem
20 |
21 | img
22 | margin-bottom: 2rem
23 |
24 | img[align]
25 | padding: 2rem
26 |
27 | td
28 | border-bottom: 1px solid var(--color-subtle-medium)
29 |
30 | th
31 | border-bottom: 2px solid var(--color-theme)
32 |
33 | a
34 | color: var(--color-theme)
35 | text-decoration: underline
36 |
37 | section
38 | height: 100%
39 | padding: 1rem
40 |
41 | section:first-child h1,
42 | section:last-child h1
43 | font-size: 4rem
44 | border: 0
45 | text-align: center
46 | width: 100%
47 | height: 100%
48 | display: flex
49 | align-items: center
50 | justify-content: center
51 |
52 | pre
53 | margin-bottom: 1.5rem
54 |
55 | pre code
56 | width: 100%
57 | display: block
58 | padding: 0.5rem 1rem !important
59 | border-radius: var(--border-radius)
60 | background: var(--syntax-background)
61 | color: var(--syntax-text)
62 | position: relative
63 |
64 | pre code.language-out
65 | background: var(--syntax-text)
66 | color: var(--syntax-background)
67 |
68 | &:before
69 | content: "Output"
70 | position: absolute
71 | top: 0.5rem
72 | right: 1rem
73 | font-family: var(--font-primary)
74 | text-transform: uppercase
75 | font-weight: bold
76 | font-size: var(--font-size-xs)
77 |
78 | .reveal.show-notes
79 | max-width: 70%
80 |
81 | .reveal .speaker-notes
82 | background: var(--color-back)
83 | border: 2px solid var(--color-subtle-medium)
84 | color: var(--color-subtle-dark)
85 | font-size: var(--font-size-xs)
86 | font-family: var(--font-primary)
87 | border-radius: var(--border-radius)
88 | width: 40%
89 | margin-left: 2.75%
90 | height: 100%
91 | overflow: auto
92 |
93 | p
94 | margin-bottom: 1.5rem
95 |
96 | &:before
97 | content: "Script"
98 | font-weight: bold
99 | color: inherit
100 | opacity: 1
101 |
102 | @media(max-width: 1024px)
103 | .reveal.show-notes
104 | max-width: 100%
105 |
106 | .reveal.show-notes .speaker-notes
107 | width: 100%
108 | margin-left: 0
109 | top: 125%
110 | overflow: auto
111 |
112 | @media(max-width: 600px)
113 | .reveal.show-notes .speaker-notes
114 | height: auto
115 | top: 100%
116 |
--------------------------------------------------------------------------------
/gatsby-config.js:
--------------------------------------------------------------------------------
1 | const meta = require('./meta.json')
2 | const autoprefixer = require('autoprefixer')
3 |
4 | module.exports = {
5 | siteMetadata: meta,
6 | plugins: [
7 | {
8 | resolve: `gatsby-plugin-sass`,
9 | options: {
10 | indentedSyntax: true,
11 | postCssPlugins: [autoprefixer()],
12 | cssLoaderOptions: {
13 | localIdentName:
14 | process.env.NODE_ENV == 'development'
15 | ? '[name]-[local]-[hash:8]'
16 | : '[hash:8]',
17 | },
18 | },
19 | },
20 | `gatsby-plugin-react-helmet`,
21 | {
22 | resolve: `gatsby-source-filesystem`,
23 | options: {
24 | name: `chapters`,
25 | path: `${__dirname}/chapters`,
26 | },
27 | },
28 | {
29 | resolve: `gatsby-source-filesystem`,
30 | options: {
31 | name: `slides`,
32 | path: `${__dirname}/slides`,
33 | },
34 | },
35 | {
36 | resolve: `gatsby-source-filesystem`,
37 | options: {
38 | name: `exercises`,
39 | path: `${__dirname}/exercises`,
40 | },
41 | },
42 | {
43 | resolve: 'gatsby-plugin-react-svg',
44 | options: {
45 | rule: {
46 | include: /static/,
47 | },
48 | },
49 | },
50 | {
51 | resolve: `gatsby-transformer-remark`,
52 | options: {
53 | plugins: [
54 | `gatsby-remark-copy-linked-files`,
55 | {
56 | resolve: `gatsby-remark-prismjs`,
57 | options: {
58 | noInlineHighlight: true,
59 | },
60 | },
61 | {
62 | resolve: `gatsby-remark-smartypants`,
63 | options: {
64 | dashes: 'oldschool',
65 | },
66 | },
67 | {
68 | resolve: `gatsby-remark-images`,
69 | options: {
70 | maxWidth: 790,
71 | linkImagesToOriginal: true,
72 | sizeByPixelDensity: false,
73 | showCaptions: true,
74 | quality: 80,
75 | withWebp: { quality: 80 },
76 | },
77 | },
78 | `gatsby-remark-unwrap-images`,
79 | ],
80 | },
81 | },
82 | `gatsby-transformer-sharp`,
83 | `gatsby-plugin-sharp`,
84 | `gatsby-plugin-sitemap`,
85 | {
86 | resolve: `gatsby-plugin-manifest`,
87 | options: {
88 | name: meta.title,
89 | short_name: meta.title,
90 | start_url: `/`,
91 | background_color: meta.theme,
92 | theme_color: meta.theme,
93 | display: `minimal-ui`,
94 | icon: `static/favi.png`,
95 | },
96 | },
97 | `gatsby-plugin-offline`,
98 | ],
99 | }
100 |
--------------------------------------------------------------------------------
/static/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
10 |
14 |
19 |
21 |
25 |
30 |
33 |
38 |
42 |
44 |
49 |
52 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/components/slides.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StaticQuery, graphql } from 'gatsby'
3 | import Marked from 'reveal.js/plugin/markdown/marked.js'
4 | import classNames from 'classnames'
5 |
6 | import '../styles/reveal.css'
7 | import classes from '../styles/slides.module.sass'
8 |
9 | function getFiles({ allMarkdownRemark }) {
10 | return Object.assign(
11 | {},
12 | ...allMarkdownRemark.edges.map(({ node }) => ({
13 | [node.fields.slug.replace('/', '')]: node.rawMarkdownBody,
14 | }))
15 | )
16 | }
17 |
18 | function getSlideContent(data, source) {
19 | const files = getFiles(data)
20 | const file = files[source] || ''
21 | return file.split('\n---\n').map(f => f.trim())
22 | }
23 |
24 | class Slides extends React.Component {
25 | componentDidMount() {
26 | import('reveal.js').then(({ default: Reveal }) => {
27 | window.Reveal = Reveal
28 | window.marked = Marked
29 | import('reveal.js/plugin/markdown/markdown.js').then(({ RevealMarkdown }) => {
30 | RevealMarkdown.init()
31 | Reveal.initialize({
32 | center: false,
33 | progress: false,
34 | showNotes: true,
35 | controls: true,
36 | width: '100%',
37 | height: 600,
38 | minScale: 0.75,
39 | maxScale: 1,
40 | })
41 | })
42 | })
43 | }
44 |
45 | componentWillUnmount() {
46 | // Work around default reveal.js behaviour that doesn't allow
47 | // re-initialization and clashes with React
48 | delete window.Reveal
49 | delete window.marked
50 | delete require.cache[require.resolve('reveal.js')]
51 | delete require.cache[require.resolve('reveal.js/plugin/markdown/markdown.js')]
52 | }
53 |
54 | render() {
55 | const { source } = this.props
56 | const revealClassNames = classNames('reveal', 'show-notes', classes.reveal)
57 | const slideClassNames = classNames('slides', classes.slides)
58 |
59 | return (
60 |
61 |
62 |
{
80 | const content = getSlideContent(data, source)
81 | return (
82 |
83 | {content.map((markdown, i) => (
84 |
91 | ))}
92 |
93 | )
94 | }}
95 | />
96 |
97 |
98 | )
99 | }
100 | }
101 |
102 | export default Slides
103 |
--------------------------------------------------------------------------------
/chapters/chapter3.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Chapter 3: Sex and Religion?'
3 | description:
4 | 'During the second half of the twentieth century, sexuality has become a more common medium of intimacy outside of marriage. In this resource, we will explore real data to examine how attitudies towards sex relate to religion.'
5 | prev: /chapter2
6 | next: null
7 | type: chapter
8 | id: 3
9 | ---
10 |
11 |
12 |
13 | During the second half of the twentieth century, sexuality has become a more common medium of intimacy outside of marriage. In this resource, we will explore real data to examine how attitudies towards sex relate to religion.
14 |
15 | 
16 |
17 | With sexuality institutionalized within marriage, sex outside of marriage was broadly condemned by society. However, with the secularization of society and sexual and contraceptive revolutions, the British public has mostly separated sexual intercourse and procreation. Views on sex, same-sex relationships, and traditional marriage have become increasingly liberal. Along with the advents of dating apps such as Tindr and the normalization of hook-up culture, attitudes towards casual sex are changing.
18 |
19 | Religion, if important to an individual, can be a dominant influence on sexual behavior. For example, Christianity condemns promiscuity (casual sex) and teaches that sex should exclusively take place within marriage, as the purpose is to procreate. Many religious beliefs maintain that sex should serve gods will by deepening love between a married couple and for procreation. However, religious identity has fallen in recent decades (in 2018, 52% of Brits did not identify with a religion comaprd to 30 percent in 1983, according to the [British Social Attitudes survey](https://www.bsa.natcen.ac.uk/latest-report/british-social-attitudes-36/religion.aspx)).
20 |
21 | Although most religions remain constant on their stances on sexual behaviors, the dramatic shifts in religiosity and public attitudes towards sex and casual sex leave scope for asking how much influence religious beliefs still have on an individual’s attitudes towards sex without love.
22 |
23 | **Let's explore the data to find out...**
24 |
25 |
26 |
27 |
28 |
29 | ## National Survey of Sexual Attitudes and Lifestyles
30 | We will use data from the third, National Survey of Sexual Attitudes and Lifestyles (Natsal-3),
31 |
32 | The British National Surveys of Sexual Attitudes and Lifestyles (Natsal) are some of the largest and most detailed studies of sexual attitudes and behavior in the world.
33 |
34 | We are going to use a chunk of the data with information about attitudes towards sexual relationships, including sexual relationships without love, opinions on one night stands, whether people are under too much pressure to have sex.
35 |
36 | ## Data exploring
37 |
38 | We can examine responses to the attitude questions and how they vary by characteristics such as religion and gender.
39 |
40 | We will use some statistical tools including tables, bar charts, histograms and summary statistics such as mean and standard deviation.
41 |
42 | Each will help us to understand the relationship between religion and attitudes towards sex.
43 |
44 |
45 |
46 |
47 | We need to start by opening the data. Run the code below to open the dataset and view a snapshot of the data.
48 |
49 |
50 | Count the variables logged after running this code block.
51 |
52 |
53 | How many variables are there in this dataset?
54 |
55 |
56 |
57 | Not quite, have another go!
58 |
59 |
60 |
61 | Not quite, have another go!
62 |
63 |
64 |
65 | Great job, there are 20 variables in this dataset!
66 |
67 |
68 |
69 | **Data Tip: Variables** contain information. In this case, they each contain information about people who completed the survey. They each have a name; sometimes, you can the information the variable contains from the name while other times it is less clear. What do you think the first variable 'agrp' refers to?
70 |
71 |
--------------------------------------------------------------------------------
/src/components/layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StaticQuery, graphql } from 'gatsby'
3 |
4 | import SEO from './seo'
5 | import { Link } from './link'
6 | import { H3 } from './typography'
7 | import Logo from '../../static/logo.svg'
8 |
9 | import '../styles/index.sass'
10 | import classes from '../styles/layout.module.sass'
11 |
12 | const Layout = ({ isHome, title, description, children }) => {
13 | return (
14 | {
32 | const meta = data.site.siteMetadata
33 | return (
34 | <>
35 |
36 |
37 | {!isHome && (
38 |
39 |
40 |
41 |
42 |
43 | )}
44 |
45 | {(title || description) && (
46 |
47 | {title && {title} }
48 | {description && (
49 | {description}
50 | )}
51 |
52 | )}
53 | {children}
54 |
55 |
56 |
88 |
89 | >
90 | )
91 | }}
92 | />
93 | )
94 | }
95 |
96 | export default Layout
97 |
--------------------------------------------------------------------------------
/src/styles/index.sass:
--------------------------------------------------------------------------------
1 | @import '../../theme'
2 |
3 | /* Reset */
4 |
5 | *, *:before, *:after
6 | box-sizing: border-box
7 | padding: 0
8 | margin: 0
9 | border: 0
10 | outline: 0
11 |
12 | html
13 | font-family: sans-serif
14 | text-rendering: optimizeSpeed
15 | -ms-text-size-adjust: 100%
16 | -webkit-text-size-adjust: 100%
17 |
18 | body
19 | margin: 0
20 |
21 | article, aside, details, figcaption, figure, footer, header, main, menu, nav,
22 | section, summary, progress
23 | display: block
24 |
25 | a
26 | background-color: transparent
27 | color: inherit
28 | text-decoration: none
29 |
30 | &:active,
31 | &:hover
32 | outline: 0
33 |
34 | abbr[title]
35 | border-bottom: none
36 | text-decoration: underline
37 | text-decoration: underline dotted
38 |
39 | b, strong
40 | font-weight: inherit
41 | font-weight: bolder
42 |
43 | small
44 | font-size: 80%
45 |
46 | sub, sup
47 | position: relative
48 | font-size: 65%
49 | line-height: 0
50 | vertical-align: baseline
51 |
52 | sup
53 | top: -0.5em
54 |
55 | sub
56 | bottom: -0.15em
57 |
58 | img
59 | border: 0
60 | height: auto
61 | max-width: 100%
62 |
63 | svg
64 | max-width: 100%
65 | color-interpolation-filters: sRGB
66 | fill: currentColor
67 |
68 | &:not(:root)
69 | overflow: hidden
70 |
71 | hr
72 | box-sizing: content-box
73 | overflow: visible
74 | height: 0
75 |
76 | pre
77 | overflow: auto
78 |
79 | code, pre
80 | font-family: monospace, monospace
81 | font-size: 1em
82 |
83 | table
84 | text-align: left
85 | width: 100%
86 | max-width: 100%
87 | border-collapse: collapse
88 | margin-bottom: 2rem
89 |
90 | td, th
91 | vertical-align: top
92 | padding: 0.5rem
93 | border-bottom: 1px solid var(--color-subtle-medium)
94 |
95 | code
96 | white-space: nowrap
97 |
98 | button
99 | appearance: none
100 | background: transparent
101 | cursor: pointer
102 |
103 | progress
104 | appearance: none
105 |
106 | /* Layout */
107 |
108 | html
109 | font-size: 11px
110 |
111 | @media(max-width: 767px)
112 | html
113 | font-size: 10px
114 |
115 | body
116 | font-family: var(--font-primary)
117 | font-size: var(--font-size-md)
118 | line-height: var(--line-height)
119 | color: var(--color-front)
120 | background: var(--color-back)
121 |
122 | p
123 | margin-bottom: 3rem
124 |
125 | ::selection
126 | background: var(--color-theme)
127 | color: var(--color-theme-contrast)
128 |
129 | /* Code */
130 |
131 | pre, code
132 | font-family: var(--font-code)
133 | font-weight: 500
134 | font-size: 1.25rem
135 | line-height: var(--line-height)
136 |
137 | pre
138 | margin-bottom: 3rem
139 |
140 | pre code
141 | display: block
142 | padding: 2rem !important
143 |
144 | /* Syntax highlighting */
145 |
146 | .CodeMirror.cm-s-default
147 | font-family: var(--font-code)
148 | font-size: var(--font-size-sm)
149 | background: var(--syntax-background)
150 | color: var(--syntax-text)
151 | word-wrap: break-word
152 |
153 | .CodeMirror-line
154 | padding: 0
155 |
156 | .CodeMirror-selected
157 | background: var(--syntax-selected-background)
158 |
159 | .CodeMirror-cursor
160 | border-left-color: currentColor
161 |
162 | .cm-variable-2
163 | color: inherit
164 | font-style: italic
165 |
166 | .cm-comment
167 | color: var(--syntax-comment)
168 |
169 | .cm-keyword, .cm-builtin
170 | color: var(--syntax-keyword)
171 |
172 | .cm-operator
173 | color: var(--syntax-operator)
174 |
175 | .cm-string
176 | color: var(--syntax-selector)
177 |
178 | .cm-number
179 | color: var(--syntax-number)
180 |
181 | .cm-def
182 | color: var(--syntax-function)
183 |
184 | .jp-RenderedText pre
185 | .ansi-cyan-fg.ansi-cyan-fg
186 | color: var(--ansi-cyan)
187 |
188 | .ansi-green-fg.ansi-green-fg
189 | color: var(--ansi-green)
190 |
191 | .ansi-red-fg.ansi-red-fg
192 | color: var(--ansi-red)
193 |
194 | /* Gatsby Images */
195 |
196 | .gatsby-resp-image-link
197 | border: 0
198 |
199 | .gatsby-resp-image-figure
200 | margin-bottom: 4rem
201 |
202 | .gatsby-resp-image-figcaption
203 | font-size: var(--font-size-xs)
204 | color: var(--color-front-light)
205 | padding-top: 1rem
206 | text-align: center
207 |
208 | code
209 | color: inherit
210 |
--------------------------------------------------------------------------------
/src/components/code.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StaticQuery, graphql } from 'gatsby'
3 |
4 | import { Hint } from './hint'
5 | import { Button } from './button'
6 |
7 | import classes from '../styles/code.module.sass'
8 |
9 | function getFiles({ allCode }) {
10 | return Object.assign(
11 | {},
12 | ...allCode.edges.map(({ node }) => ({
13 | [node.name]: node.code,
14 | }))
15 | )
16 | }
17 |
18 | function makeTest(template, testFile, solution) {
19 | // Escape quotation marks in the solution code, for cases where we
20 | // can only place the solution in regular quotes.
21 | const solutionEscaped = solution.replace(/"/g, '\\"')
22 | return template
23 | .replace(/\${solutionEscaped}/g, solutionEscaped)
24 | .replace(/\${solution}/g, solution)
25 | .replace(/\${test}/g, testFile)
26 | }
27 |
28 | class CodeBlock extends React.Component {
29 | state = { Juniper: null, showSolution: false, key: 0 }
30 |
31 | handleShowSolution() {
32 | this.setState({ showSolution: true })
33 | }
34 |
35 | handleReset() {
36 | // Using the key as a hack to force component to rerender
37 | this.setState({ showSolution: false, key: this.state.key + 1 })
38 | }
39 |
40 | updateJuniper() {
41 | // This type of stuff only really works in class components. I'm not
42 | // sure why, but I've tried with function components and hooks lots of
43 | // times and couldn't get it to work. So class component it is.
44 | if (!this.state.Juniper) {
45 | // We need a dynamic import here for SSR. Juniper's dependencies
46 | // include references to the global window object and I haven't
47 | // managed to fix this using webpack yet. If we imported Juniper
48 | // at the top level, Gatsby won't build.
49 | import('./juniper').then(Juniper => {
50 | this.setState({ Juniper: Juniper.default })
51 | })
52 | }
53 | }
54 |
55 | componentDidMount() {
56 | this.updateJuniper()
57 | }
58 |
59 | componentDidUpdate() {
60 | this.updateJuniper()
61 | }
62 |
63 | render() {
64 | const { Juniper, showSolution } = this.state
65 | const { id, source, solution, test, children } = this.props
66 | const sourceId = source || `exc_${id}`
67 | const solutionId = solution || `solution_${id}`
68 | const testId = test || `test_${id}`
69 | const juniperClassNames = {
70 | cell: classes.cell,
71 | input: classes.input,
72 | button: classes.button,
73 | output: classes.output,
74 | }
75 | const hintActions = [
76 | { text: 'Show solution', onClick: () => this.handleShowSolution() },
77 | { text: 'Reset', onClick: () => this.handleReset() },
78 | ]
79 |
80 | return (
81 | {
107 | const { testTemplate } = data.site.siteMetadata
108 | const { repo, branch, kernelType, debug, lang } = data.site.siteMetadata.juniper
109 | const files = getFiles(data)
110 | const sourceFile = files[sourceId]
111 | const solutionFile = files[solutionId]
112 | const testFile = files[testId]
113 | return (
114 |
115 | {Juniper && (
116 | (
125 | <>
126 | runCode()}>Run Code
127 | {testFile && (
128 |
131 | runCode(value =>
132 | makeTest(testTemplate, testFile, value)
133 | )
134 | }
135 | >
136 | Submit
137 |
138 | )}
139 | >
140 | )}
141 | >
142 | {showSolution ? solutionFile : sourceFile}
143 |
144 | )}
145 | {children}
146 |
147 | )
148 | }}
149 | />
150 | )
151 | }
152 | }
153 |
154 | export default CodeBlock
155 |
--------------------------------------------------------------------------------
/src/components/juniper.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import CodeMirror from 'codemirror'
4 | import { Widget } from '@phosphor/widgets'
5 | import { Kernel, ServerConnection } from '@jupyterlab/services'
6 | import { OutputArea, OutputAreaModel } from '@jupyterlab/outputarea'
7 | import { RenderMimeRegistry, standardRendererFactories } from '@jupyterlab/rendermime'
8 | import { window } from 'browser-monads'
9 |
10 | class Juniper extends React.Component {
11 | outputRef = null
12 | inputRef = null
13 | state = { content: null, cm: null, kernel: null, renderers: null, fromStorage: null }
14 |
15 | static defaultProps = {
16 | children: '',
17 | branch: 'master',
18 | url: 'https://mybinder.org',
19 | serverSettings: {},
20 | kernelType: 'python3',
21 | lang: 'python',
22 | theme: 'default',
23 | isolateCells: true,
24 | useBinder: true,
25 | storageKey: 'juniper',
26 | useStorage: true,
27 | storageExpire: 60,
28 | debug: true,
29 | msgButton: 'run',
30 | msgLoading: 'Loading...',
31 | msgError: 'Connecting failed. Please reload and try again.',
32 | classNames: {
33 | cell: 'juniper-cell',
34 | input: 'juniper-input',
35 | button: 'juniper-button',
36 | output: 'juniper-output',
37 | },
38 | }
39 |
40 | static propTypes = {
41 | children: PropTypes.string,
42 | repo: PropTypes.string.isRequired,
43 | branch: PropTypes.string,
44 | url: PropTypes.string,
45 | serverSettings: PropTypes.object,
46 | kernelType: PropTypes.string,
47 | lang: PropTypes.string,
48 | theme: PropTypes.string,
49 | isolateCells: PropTypes.bool,
50 | useBinder: PropTypes.bool,
51 | useStorage: PropTypes.bool,
52 | storageExpire: PropTypes.number,
53 | msgButton: PropTypes.string,
54 | msgLoading: PropTypes.string,
55 | msgError: PropTypes.string,
56 | classNames: PropTypes.shape({
57 | cell: PropTypes.string,
58 | input: PropTypes.string,
59 | button: PropTypes.string,
60 | output: PropTypes.string,
61 | }),
62 | actions: PropTypes.func,
63 | }
64 |
65 | componentDidMount() {
66 | this.setState({ content: this.props.children })
67 | const renderers = standardRendererFactories.filter(factory =>
68 | factory.mimeTypes.includes('text/latex') ? window.MathJax : true
69 | )
70 |
71 | const outputArea = new OutputArea({
72 | model: new OutputAreaModel({ trusted: true }),
73 | rendermime: new RenderMimeRegistry({ initialFactories: renderers }),
74 | })
75 |
76 | const cm = new CodeMirror(this.inputRef, {
77 | value: this.props.children.trim(),
78 | mode: this.props.lang,
79 | theme: this.props.theme,
80 | })
81 | this.setState({ cm })
82 |
83 | const runCode = wrapper => {
84 | const value = cm.getValue()
85 | this.execute(outputArea, wrapper ? wrapper(value) : value)
86 | }
87 | const setValue = value => cm.setValue(value)
88 | cm.setOption('extraKeys', { 'Shift-Enter': runCode })
89 | Widget.attach(outputArea, this.outputRef)
90 | this.setState({ runCode, setValue })
91 | }
92 |
93 | log(logFunction) {
94 | if (this.props.debug) {
95 | logFunction()
96 | }
97 | }
98 |
99 | componentWillReceiveProps({ children }) {
100 | if (children !== this.state.content && this.state.cm) {
101 | this.state.cm.setValue(children.trim())
102 | }
103 | }
104 |
105 | /**
106 | * Request a binder, e.g. from mybinder.org
107 | * @param {string} repo - Repository name in the format 'user/repo'.
108 | * @param {string} branch - The repository branch, e.g. 'master'.
109 | * @param {string} url - The binder reployment URL, including 'http(s)'.
110 | * @returns {Promise} - Resolved with Binder settings, rejected with Error.
111 | */
112 | requestBinder(repo, branch, url) {
113 | const binderUrl = `${url}/build/gh/${repo}/${branch}`
114 | this.log(() => console.info('building', { binderUrl }))
115 | return new Promise((resolve, reject) => {
116 | const es = new EventSource(binderUrl)
117 | es.onerror = err => {
118 | es.close()
119 | this.log(() => console.error('failed'))
120 | reject(new Error(err))
121 | }
122 | let phase = null
123 | es.onmessage = ({ data }) => {
124 | const msg = JSON.parse(data)
125 | if (msg.phase && msg.phase !== phase) {
126 | phase = msg.phase.toLowerCase()
127 | this.log(() => console.info(phase === 'ready' ? 'server-ready' : phase))
128 | }
129 | if (msg.phase === 'failed') {
130 | es.close()
131 | reject(new Error(msg))
132 | } else if (msg.phase === 'ready') {
133 | es.close()
134 | const settings = {
135 | baseUrl: msg.url,
136 | wsUrl: `ws${msg.url.slice(4)}`,
137 | token: msg.token,
138 | }
139 | resolve(settings)
140 | }
141 | }
142 | })
143 | }
144 |
145 | /**
146 | * Request kernel and estabish a server connection via the JupyerLab service
147 | * @param {object} settings - The server settings.
148 | * @returns {Promise} - A promise that's resolved with the kernel.
149 | */
150 | requestKernel(settings) {
151 | if (this.props.useStorage) {
152 | const timestamp = new Date().getTime() + this.props.storageExpire * 60 * 1000
153 | const json = JSON.stringify({ settings, timestamp })
154 | window.localStorage.setItem(this.props.storageKey, json)
155 | }
156 | const serverSettings = ServerConnection.makeSettings(settings)
157 | return Kernel.startNew({
158 | type: this.props.kernelType,
159 | name: this.props.kernelType,
160 | serverSettings,
161 | }).then(kernel => {
162 | this.log(() => console.info('ready'))
163 | return kernel
164 | })
165 | }
166 |
167 | /**
168 | * Get a kernel by requesting a binder or from localStorage / user settings
169 | * @returns {Promise}
170 | */
171 | getKernel() {
172 | if (this.props.useStorage) {
173 | const stored = window.localStorage.getItem(this.props.storageKey)
174 | if (stored) {
175 | this.setState({ fromStorage: true })
176 | const { settings, timestamp } = JSON.parse(stored)
177 | if (timestamp && new Date().getTime() < timestamp) {
178 | return this.requestKernel(settings)
179 | }
180 | window.localStorage.removeItem(this.props.storageKey)
181 | }
182 | }
183 | if (this.props.useBinder) {
184 | return this.requestBinder(this.props.repo, this.props.branch, this.props.url).then(
185 | settings => this.requestKernel(settings)
186 | )
187 | }
188 | return this.requestKernel(this.props.serverSettings)
189 | }
190 |
191 | /**
192 | * Render the kernel response in a JupyterLab output area
193 | * @param {OutputArea} outputArea - The cell's output area.
194 | * @param {string} code - The code to execute.
195 | */
196 | renderResponse(outputArea, code) {
197 | outputArea.future = this.state.kernel.requestExecute({ code })
198 | outputArea.model.add({
199 | output_type: 'stream',
200 | name: 'loading',
201 | text: this.props.msgLoading,
202 | })
203 | outputArea.model.clear(true)
204 | }
205 |
206 | /**
207 | * Process request to execute the code
208 | * @param {OutputArea} - outputArea - The cell's output area.
209 | * @param {string} code - The code to execute.
210 | */
211 | execute(outputArea, code) {
212 | this.log(() => console.info('executing'))
213 | if (this.state.kernel) {
214 | if (this.props.isolateCells) {
215 | this.state.kernel
216 | .restart()
217 | .then(() => this.renderResponse(outputArea, code))
218 | .catch(() => {
219 | this.log(() => console.error('failed'))
220 | this.setState({ kernel: null })
221 | outputArea.model.clear()
222 | outputArea.model.add({
223 | output_type: 'stream',
224 | name: 'failure',
225 | text: this.props.msgError,
226 | })
227 | })
228 | return
229 | }
230 | this.renderResponse(outputArea, code)
231 | return
232 | }
233 | this.log(() => console.info('requesting kernel'))
234 | const url = this.props.url.split('//')[1]
235 | const action = !this.state.fromStorage ? 'Launching' : 'Reconnecting to'
236 | outputArea.model.clear()
237 | outputArea.model.add({
238 | output_type: 'stream',
239 | name: 'stdout',
240 | text: `${action} Docker container on ${url}...`,
241 | })
242 | new Promise((resolve, reject) =>
243 | this.getKernel()
244 | .then(resolve)
245 | .catch(reject)
246 | )
247 | .then(kernel => {
248 | this.setState({ kernel })
249 | this.renderResponse(outputArea, code)
250 | })
251 | .catch(() => {
252 | this.log(() => console.error('failed'))
253 | this.setState({ kernel: null })
254 | if (this.props.useStorage) {
255 | this.setState({ fromStorage: false })
256 | window.localStorage.removeItem(this.props.storageKey)
257 | }
258 | outputArea.model.clear()
259 | outputArea.model.add({
260 | output_type: 'stream',
261 | name: 'failure',
262 | text: this.props.msgError,
263 | })
264 | })
265 | }
266 |
267 | render() {
268 | return (
269 |
270 |
{
273 | this.inputRef = x
274 | }}
275 | />
276 | {this.props.msgButton && (
277 |
278 | {this.props.msgButton}
279 |
280 | )}
281 | {this.props.actions && this.props.actions(this.state)}
282 |
{
284 | this.outputRef = x
285 | }}
286 | className={this.props.classNames.output}
287 | />
288 |
289 | )
290 | }
291 | }
292 |
293 | export default Juniper
294 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Online course starter: R
2 |
3 | ## Binder deploy
4 | Note that rebuilding the binder container can take a little bit of time (usually on the order of 5 or 10 minutes or so), since it is installing/compiling tidyverse for the container. You can always check the build status of the container by clicking the badge below and looking at the log.
5 |
6 | You can view the binder container here: Binder or at: [](https://mybinder.org/v2/gh/UKDataServiceOpen/r-course-starter/HEAD)
7 |
8 |
9 | ### Running the app
10 |
11 | To start the local development server, install [Gatsby](https://gatsbyjs.org)
12 | and then all other dependencies. This should serve up the app on
13 | `localhost:8000`.
14 |
15 | ```bash
16 | npm install -g gatsby-cli # Install Gatsby globally
17 | npm install # Install dependencies
18 | npm run dev # Run the development server
19 | ```
20 |
21 | ## 💡Introduction
22 | ### Adding Packages
23 |
24 | If you need to add packages, add the appropriate `install.packages()` statement
25 | into `binder/install.R`. When you do, check that the container was built
26 | properly by clicking the binder link above.
27 |
28 | Currently, `tidyverse` is installed in the binder container.
29 |
30 | ### `data/` folder
31 |
32 | If you want to access datasets in the data folder.This only works with .csv files. you can always refer to this
33 | folder as `data/`. For example, to use `data/pets.csv`:
34 |
35 | ```
36 | pets <- read.csv("data/pets.csv")
37 | ```
38 |
39 | Remember, if you need to add a dataset to the repo, it needs to be done in the
40 | `binder` branch into the `data/` folder.
41 |
42 | ## 🎨 Customization
43 |
44 | The app separates its source and content – so you usually shouldn't have to dig
45 | into the JavaScript source to change things. The following points of
46 | customization are available:
47 |
48 | | Location | Description |
49 | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
50 | | `meta.json` | General config settings, title, description etc. |
51 | | `theme.sass` | Color theme. |
52 | | `binder/install.R` | Packages to install. |
53 | | `binder/runtime.txt` | YYYY-MM-DD snapshot at MRAN that will be used for installing libraries. [See here](https://github.com/binder-examples/r) for details. |
54 | | `chapters` | The chapters, one Markdown file per chapter. |
55 | | `slides` | The slides, one Markdown file per slide deck. |
56 | | `static` | Static assets like images, will be copied to the root. |
57 |
58 | ### `meta.json`
59 |
60 | The following meta settings are available. **Note that you have to re-start
61 | Gatsby to see the changes if you're editing it while the server is running.**
62 |
63 | | Setting | Description |
64 | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
65 | | `courseId` | Unique ID of the course. Will be used when saving completed exercises to the browser's local storage. |
66 | | `title` | The title of the course. |
67 | | `slogan` | Course slogan, displayed in the page title on the front page. |
68 | | `description` | Course description. Used for site meta and in footer. |
69 | | `bio` | Author bio. Used in the footer. |
70 | | `siteUrl` | URL of the deployed site (without trailing slash). |
71 | | `twitter` | Author twitter handle, used in Twitter cards meta. |
72 | | `fonts` | [Google Fonts](https://fonts.google.com) to load. Should be the font part of the URL in the embed string, e.g. `Lato:400,400i,700,700i`. |
73 | | `testTemplate` | Template used to validate the answers. `${solution}` will be replaced with the user code and `${test}` with the contents of the test file. |
74 | | `juniper.repo` | Repo to build on Binder in `user/repo` format. Usually the same as this repo. |
75 | | `juniper.branch` | Branch to build. Ideally not `master`, so the image is not rebuilt every time you push. |
76 | | `juniper.lang` | Code language for syntax highlighting. |
77 | | `juniper.kernelType` | The name of the kernel to use. |
78 | | `juniper.debug` | Logs additional debugging info to the console. |
79 | | `showProfileImage` | Whether to show the profile image in the footer. If `true`, a file `static/profile.jpg` needs to be available. |
80 | | `footerLinks` | List of objects with `"text"` and `"url"` to display as links in the footer. |
81 | | `theme` | Currently only used for the progressive web app, e.g. as the theme color on mobile. For the UI theme, edit `theme.sass`. |
82 |
83 | ### Static assets
84 |
85 | All files added to `/static` will become available at the root of the deployed
86 | site. So `/static/image.jpg` can be referenced in your course as `/image.jpg`.
87 | The following assets need to be available and can be customized:
88 |
89 | | File | Description |
90 | | ----------------- | -------------------------------------------------------- |
91 | | `icon.png` | Custom [favicon](https://en.wikipedia.org/wiki/Favicon). |
92 | | `logo.svg` | The course logo. |
93 | | `profile.jpg` | Photo or profile image. |
94 | | `social.jpg` | Social image, displayed in Twitter and Facebook cards. |
95 | | `icon_check.svg` | "Check" icon displayed on "Mark as completed" button. |
96 | | `icon_slides.svg` | Icon displayed in the corner of a slides exercise. |
97 |
98 | ## ✏️ Content
99 |
100 | ### File formats
101 |
102 | #### Chapters
103 |
104 | Chapters are placed in [`/chapters`](/chapters) and are Markdown files
105 | consisting of `
` components. They'll be turned into pages, e.g.
106 | `/chapter1`. In their frontmatter block at the top of the file, they need to
107 | specify `type: chapter`, as well as the following meta:
108 |
109 | ```yaml
110 | ---
111 | title: The chapter title
112 | description: The chapter description
113 | prev: /chapter1 # exact path to previous chapter or null to not show a link
114 | next: /chapter3 # exact path to next chapter or null to not show a link
115 | id: 2 # unique identifier for chapter
116 | type: chapter # important: this creates a standalone page from the chapter
117 | ---
118 |
119 | ```
120 |
121 | #### Slides
122 |
123 | Slides are placed in [`/slides`](/slides) and are markdown files consisting of
124 | slide content, separated by `---`. They need to specify the following
125 | frontmatter block at the top of the file:
126 |
127 | ```yaml
128 | ---
129 | type: slides
130 | ---
131 |
132 | ```
133 |
134 | The **first and last slide** use a special layout and will display the headline
135 | in the center of the slide. **Speaker notes** (in this case, the script) can be
136 | added at the end of a slide, prefixed by `Notes:`. They'll then be shown on the
137 | right next to the slides. Here's an example slides file:
138 |
139 | ```markdown
140 | ---
141 | type: slides
142 | ---
143 |
144 | # Processing pipelines
145 |
146 | Notes: This is a slide deck about processing pipelines.
147 |
148 | ---
149 |
150 | # Next slide
151 |
152 | - Some bullet points here
153 | - And another bullet point
154 |
155 |
156 | ```
157 |
158 | ### Custom Elements
159 |
160 | When using custom elements, make sure to place a newline between the
161 | opening/closing tags and the children. Otherwise, Markdown content may not
162 | render correctly.
163 |
164 | #### ``
165 |
166 | Container of a single exercise.
167 |
168 | | Argument | Type | Description |
169 | | ------------ | --------------- | -------------------------------------------------------------- |
170 | | `id` | number / string | Unique exercise ID within chapter. |
171 | | `title` | string | Exercise title. |
172 | | `type` | string | Optional type. `"slides"` makes container wider and adds icon. |
173 | | **children** | - | The contents of the exercise. |
174 |
175 | ```markdown
176 |
177 |
178 | Content goes here...
179 |
180 |
181 | ```
182 |
183 | #### ``
184 |
185 | | Argument | Type | Description |
186 | | ------------ | --------------- | -------------------------------------------------------------------------------------------- |
187 | | `id` | number / string | Unique identifier of the code exercise. |
188 | | `source` | string | Name of the source file (without file extension). Defaults to `exc_${id}` if not set. |
189 | | `solution` | string | Name of the solution file (without file extension). Defaults to `solution_${id}` if not set. |
190 | | `test` | string | Name of the test file (without file extension). Defaults to `test_${id}` if not set. |
191 | | **children** | string | Optional hints displayed when the user clicks "Show hints". |
192 |
193 | ```markdown
194 |
195 |
196 | This is a hint!
197 |
198 |
199 | ```
200 |
201 | #### ``
202 |
203 | Container to display slides interactively using Reveal.js and a Markdown file.
204 |
205 | | Argument | Type | Description |
206 | | -------- | ------ | --------------------------------------------- |
207 | | `source` | string | Name of slides file (without file extension). |
208 |
209 | ```markdown
210 |
211 |
212 | ```
213 |
214 | #### ``
215 |
216 | Container for multiple-choice question.
217 |
218 | | Argument | Type | Description |
219 | | ------------ | --------------- | -------------------------------------------------------------------------------------------- |
220 | | `id` | string / number | Optional unique ID. Can be used if more than one choice question is present in one exercise. |
221 | | **children** | nodes | Only `` components for the options. |
222 |
223 | ```markdown
224 |
225 |
226 | You have selected option one! This is not good.
227 | Yay!
228 |
229 |
230 | ```
231 |
232 | #### ``
233 |
234 | A multiple-choice option.
235 |
236 | | Argument | Type | Description |
237 | | ------------ | ------ | ---------------------------------------------------------------------------------------------- |
238 | | `text` | string | The option text to be displayed. Supports inline HTML. |
239 | | `correct` | string | `"true"` if the option is the correct answer. |
240 | | **children** | string | The text to be displayed if the option is selected (explaining why it's correct or incorrect). |
241 |
242 | ### Adding tests
243 |
244 | To validate the code when the user hits "Submit", we're currently using a
245 | slightly hacky trick. Since the R code is sent back to the kernel as a string,
246 | we can manipulate it and add tests – for example, exercise `exc_01_02_01.R` will
247 | be validated using `test_01_02_01.R` (if available). The user code and test are
248 | combined using a string template. At the moment, the `testTemplate` in the
249 | `meta.json` looks like this:
250 |
251 | ```r
252 | success <- function(text) {
253 | cat(paste("\033[32m", text, "\033[0m", sep = ""))
254 | }
255 |
256 | .solution <- "${solutionEscaped}"
257 |
258 | ${solution}
259 |
260 | ${test}
261 | tryCatch({
262 | test()
263 | }, error = function(e) {
264 | cat(paste("\033[31m", e[1], "\033[0m", sep = ""))
265 | })
266 | ```
267 |
268 | If present, `${solution}` will be replaced with the string value of the
269 | submitted user code, and `${solutionEscaped}` with the code but with all `"`
270 | replaced by `\"`, so we can assign it to a variable as a string and check
271 | whether the submission includes something. We also insert the regular solution,
272 | so we can actually run it and check the objects it creates. `${test}` is
273 | replaced by the contents of the test file. The template also defines a `success`
274 | function, which prints a formatted green message and can be used in the tests.
275 | Finally, the `tryCatch` expression checks if the test function raises a `stop`
276 | and if so, it outputs the formatted error message. This also hides the full
277 | error traceback (which can easily leak the correct answers).
278 |
279 | A test file could then look like this:
280 |
281 | ```r
282 | test <- function() {
283 | if (some_var != length(mtcars)) {
284 | stop("Are you getting the correct length?")
285 | }
286 | if (!grepl("print(mtcars$gear)", .solution, fixed = TRUE)) {
287 | stop("Are you printing the correct variable?")
288 | }
289 | success("Well done!")
290 | }
291 | ```
292 |
293 | The string answer is available as `.solution`, and the test also has access to
294 | the solution code.
295 |
296 | ---
297 |
298 | For more details on how it all works behind the scenes, see
299 | [the original course repo](https://github.com/ines/spacy-course).
300 |
--------------------------------------------------------------------------------
/src/styles/reveal.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * reveal.js
3 | * http://revealjs.com
4 | * MIT licensed
5 | *
6 | * Copyright (C) 2019 Hakim El Hattab, http://hakim.se
7 | */
8 |
9 | /*********************************************
10 | * VIEW FRAGMENTS
11 | *********************************************/
12 | .reveal .slides section .fragment {
13 | opacity: 0;
14 | visibility: hidden;
15 | transition: all 0.2s ease;
16 | }
17 | .reveal .slides section .fragment.visible {
18 | opacity: 1;
19 | visibility: inherit;
20 | }
21 |
22 | .reveal .slides section .fragment.grow {
23 | opacity: 1;
24 | visibility: inherit;
25 | }
26 | .reveal .slides section .fragment.grow.visible {
27 | -webkit-transform: scale(1.3);
28 | transform: scale(1.3);
29 | }
30 |
31 | .reveal .slides section .fragment.shrink {
32 | opacity: 1;
33 | visibility: inherit;
34 | }
35 | .reveal .slides section .fragment.shrink.visible {
36 | -webkit-transform: scale(0.7);
37 | transform: scale(0.7);
38 | }
39 |
40 | .reveal .slides section .fragment.zoom-in {
41 | -webkit-transform: scale(0.1);
42 | transform: scale(0.1);
43 | }
44 | .reveal .slides section .fragment.zoom-in.visible {
45 | -webkit-transform: none;
46 | transform: none;
47 | }
48 |
49 | .reveal .slides section .fragment.fade-out {
50 | opacity: 1;
51 | visibility: inherit;
52 | }
53 | .reveal .slides section .fragment.fade-out.visible {
54 | opacity: 0;
55 | visibility: hidden;
56 | }
57 |
58 | .reveal .slides section .fragment.semi-fade-out {
59 | opacity: 1;
60 | visibility: inherit;
61 | }
62 | .reveal .slides section .fragment.semi-fade-out.visible {
63 | opacity: 0.5;
64 | visibility: inherit;
65 | }
66 |
67 | .reveal .slides section .fragment.strike {
68 | opacity: 1;
69 | visibility: inherit;
70 | }
71 | .reveal .slides section .fragment.strike.visible {
72 | text-decoration: line-through;
73 | }
74 |
75 | .reveal .slides section .fragment.fade-up {
76 | -webkit-transform: translate(0, 20%);
77 | transform: translate(0, 20%);
78 | }
79 | .reveal .slides section .fragment.fade-up.visible {
80 | -webkit-transform: translate(0, 0);
81 | transform: translate(0, 0);
82 | }
83 |
84 | .reveal .slides section .fragment.fade-down {
85 | -webkit-transform: translate(0, -20%);
86 | transform: translate(0, -20%);
87 | }
88 | .reveal .slides section .fragment.fade-down.visible {
89 | -webkit-transform: translate(0, 0);
90 | transform: translate(0, 0);
91 | }
92 |
93 | .reveal .slides section .fragment.fade-right {
94 | -webkit-transform: translate(-20%, 0);
95 | transform: translate(-20%, 0);
96 | }
97 | .reveal .slides section .fragment.fade-right.visible {
98 | -webkit-transform: translate(0, 0);
99 | transform: translate(0, 0);
100 | }
101 |
102 | .reveal .slides section .fragment.fade-left {
103 | -webkit-transform: translate(20%, 0);
104 | transform: translate(20%, 0);
105 | }
106 | .reveal .slides section .fragment.fade-left.visible {
107 | -webkit-transform: translate(0, 0);
108 | transform: translate(0, 0);
109 | }
110 |
111 | .reveal .slides section .fragment.fade-in-then-out,
112 | .reveal .slides section .fragment.current-visible {
113 | opacity: 0;
114 | visibility: hidden;
115 | }
116 | .reveal .slides section .fragment.fade-in-then-out.current-fragment,
117 | .reveal .slides section .fragment.current-visible.current-fragment {
118 | opacity: 1;
119 | visibility: inherit;
120 | }
121 |
122 | .reveal .slides section .fragment.fade-in-then-semi-out {
123 | opacity: 0;
124 | visibility: hidden;
125 | }
126 | .reveal .slides section .fragment.fade-in-then-semi-out.visible {
127 | opacity: 0.5;
128 | visibility: inherit;
129 | }
130 | .reveal .slides section .fragment.fade-in-then-semi-out.current-fragment {
131 | opacity: 1;
132 | visibility: inherit;
133 | }
134 |
135 | .reveal .slides section .fragment.highlight-red,
136 | .reveal .slides section .fragment.highlight-current-red,
137 | .reveal .slides section .fragment.highlight-green,
138 | .reveal .slides section .fragment.highlight-current-green,
139 | .reveal .slides section .fragment.highlight-blue,
140 | .reveal .slides section .fragment.highlight-current-blue {
141 | opacity: 1;
142 | visibility: inherit;
143 | }
144 |
145 | .reveal .slides section .fragment.highlight-red.visible {
146 | color: #ff2c2d;
147 | }
148 |
149 | .reveal .slides section .fragment.highlight-green.visible {
150 | color: #17ff2e;
151 | }
152 |
153 | .reveal .slides section .fragment.highlight-blue.visible {
154 | color: #1b91ff;
155 | }
156 |
157 | .reveal .slides section .fragment.highlight-current-red.current-fragment {
158 | color: #ff2c2d;
159 | }
160 |
161 | .reveal .slides section .fragment.highlight-current-green.current-fragment {
162 | color: #17ff2e;
163 | }
164 |
165 | .reveal .slides section .fragment.highlight-current-blue.current-fragment {
166 | color: #1b91ff;
167 | }
168 |
169 | /*********************************************
170 | * DEFAULT ELEMENT STYLES
171 | *********************************************/
172 | /* Fixes issue in Chrome where italic fonts did not appear when printing to PDF */
173 | .reveal:after {
174 | content: '';
175 | font-style: italic;
176 | }
177 |
178 | .reveal iframe {
179 | z-index: 1;
180 | }
181 |
182 | /** Prevents layering issues in certain browser/transition combinations */
183 | .reveal a {
184 | position: relative;
185 | }
186 |
187 | .reveal .stretch {
188 | max-width: none;
189 | max-height: none;
190 | }
191 |
192 | .reveal pre.stretch code {
193 | height: 100%;
194 | max-height: 100%;
195 | box-sizing: border-box;
196 | }
197 |
198 | /*********************************************
199 | * CONTROLS
200 | *********************************************/
201 | @-webkit-keyframes bounce-right {
202 | 0%,
203 | 10%,
204 | 25%,
205 | 40%,
206 | 50% {
207 | -webkit-transform: translateX(0);
208 | transform: translateX(0);
209 | }
210 | 20% {
211 | -webkit-transform: translateX(10px);
212 | transform: translateX(10px);
213 | }
214 | 30% {
215 | -webkit-transform: translateX(-5px);
216 | transform: translateX(-5px);
217 | }
218 | }
219 | @keyframes bounce-right {
220 | 0%,
221 | 10%,
222 | 25%,
223 | 40%,
224 | 50% {
225 | -webkit-transform: translateX(0);
226 | transform: translateX(0);
227 | }
228 | 20% {
229 | -webkit-transform: translateX(10px);
230 | transform: translateX(10px);
231 | }
232 | 30% {
233 | -webkit-transform: translateX(-5px);
234 | transform: translateX(-5px);
235 | }
236 | }
237 |
238 | @-webkit-keyframes bounce-down {
239 | 0%,
240 | 10%,
241 | 25%,
242 | 40%,
243 | 50% {
244 | -webkit-transform: translateY(0);
245 | transform: translateY(0);
246 | }
247 | 20% {
248 | -webkit-transform: translateY(10px);
249 | transform: translateY(10px);
250 | }
251 | 30% {
252 | -webkit-transform: translateY(-5px);
253 | transform: translateY(-5px);
254 | }
255 | }
256 |
257 | @keyframes bounce-down {
258 | 0%,
259 | 10%,
260 | 25%,
261 | 40%,
262 | 50% {
263 | -webkit-transform: translateY(0);
264 | transform: translateY(0);
265 | }
266 | 20% {
267 | -webkit-transform: translateY(10px);
268 | transform: translateY(10px);
269 | }
270 | 30% {
271 | -webkit-transform: translateY(-5px);
272 | transform: translateY(-5px);
273 | }
274 | }
275 |
276 | .reveal .controls {
277 | display: none;
278 | position: absolute;
279 | top: auto;
280 | bottom: 12px;
281 | right: 12px;
282 | left: auto;
283 | z-index: 1;
284 | color: #000;
285 | pointer-events: none;
286 | font-size: 10px;
287 | }
288 | .reveal .controls button {
289 | position: absolute;
290 | padding: 0;
291 | background-color: transparent;
292 | border: 0;
293 | outline: 0;
294 | cursor: pointer;
295 | color: currentColor;
296 | -webkit-transform: scale(0.9999);
297 | transform: scale(0.9999);
298 | transition: color 0.2s ease, opacity 0.2s ease, -webkit-transform 0.2s ease;
299 | transition: color 0.2s ease, opacity 0.2s ease, transform 0.2s ease;
300 | z-index: 2;
301 | pointer-events: auto;
302 | font-size: inherit;
303 | visibility: hidden;
304 | opacity: 0;
305 | -webkit-appearance: none;
306 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
307 | }
308 | .reveal .controls .controls-arrow:before,
309 | .reveal .controls .controls-arrow:after {
310 | content: '';
311 | position: absolute;
312 | top: 0;
313 | left: 0;
314 | width: 2.6em;
315 | height: 0.5em;
316 | border-radius: 0.25em;
317 | background-color: currentColor;
318 | transition: all 0.15s ease, background-color 0.8s ease;
319 | -webkit-transform-origin: 0.2em 50%;
320 | transform-origin: 0.2em 50%;
321 | will-change: transform;
322 | }
323 | .reveal .controls .controls-arrow {
324 | position: relative;
325 | width: 3.6em;
326 | height: 3.6em;
327 | }
328 | .reveal .controls .controls-arrow:before {
329 | -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
330 | transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
331 | }
332 | .reveal .controls .controls-arrow:after {
333 | -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
334 | transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
335 | }
336 | .reveal .controls .controls-arrow:hover:before {
337 | -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(40deg);
338 | transform: translateX(0.5em) translateY(1.55em) rotate(40deg);
339 | }
340 | .reveal .controls .controls-arrow:hover:after {
341 | -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-40deg);
342 | transform: translateX(0.5em) translateY(1.55em) rotate(-40deg);
343 | }
344 | .reveal .controls .controls-arrow:active:before {
345 | -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(36deg);
346 | transform: translateX(0.5em) translateY(1.55em) rotate(36deg);
347 | }
348 | .reveal .controls .controls-arrow:active:after {
349 | -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-36deg);
350 | transform: translateX(0.5em) translateY(1.55em) rotate(-36deg);
351 | }
352 | .reveal .controls .navigate-left {
353 | right: 6.4em;
354 | bottom: 3.2em;
355 | -webkit-transform: translateX(-10px);
356 | transform: translateX(-10px);
357 | }
358 | .reveal .controls .navigate-right {
359 | right: 0;
360 | bottom: 3.2em;
361 | -webkit-transform: translateX(10px);
362 | transform: translateX(10px);
363 | }
364 | .reveal .controls .navigate-right .controls-arrow {
365 | -webkit-transform: rotate(180deg);
366 | transform: rotate(180deg);
367 | }
368 | .reveal .controls .navigate-right.highlight {
369 | -webkit-animation: bounce-right 2s 50 both ease-out;
370 | animation: bounce-right 2s 50 both ease-out;
371 | }
372 | .reveal .controls .navigate-up {
373 | right: 3.2em;
374 | bottom: 6.4em;
375 | -webkit-transform: translateY(-10px);
376 | transform: translateY(-10px);
377 | }
378 | .reveal .controls .navigate-up .controls-arrow {
379 | -webkit-transform: rotate(90deg);
380 | transform: rotate(90deg);
381 | }
382 | .reveal .controls .navigate-down {
383 | right: 3.2em;
384 | bottom: 0;
385 | -webkit-transform: translateY(10px);
386 | transform: translateY(10px);
387 | }
388 | .reveal .controls .navigate-down .controls-arrow {
389 | -webkit-transform: rotate(-90deg);
390 | transform: rotate(-90deg);
391 | }
392 | .reveal .controls .navigate-down.highlight {
393 | -webkit-animation: bounce-down 2s 50 both ease-out;
394 | animation: bounce-down 2s 50 both ease-out;
395 | }
396 | .reveal .controls[data-controls-back-arrows='faded'] .navigate-left.enabled,
397 | .reveal .controls[data-controls-back-arrows='faded'] .navigate-up.enabled {
398 | opacity: 0.3;
399 | }
400 | .reveal .controls[data-controls-back-arrows='faded'] .navigate-left.enabled:hover,
401 | .reveal .controls[data-controls-back-arrows='faded'] .navigate-up.enabled:hover {
402 | opacity: 1;
403 | }
404 | .reveal .controls[data-controls-back-arrows='hidden'] .navigate-left.enabled,
405 | .reveal .controls[data-controls-back-arrows='hidden'] .navigate-up.enabled {
406 | opacity: 0;
407 | visibility: hidden;
408 | }
409 | .reveal .controls .enabled {
410 | visibility: visible;
411 | opacity: 0.9;
412 | cursor: pointer;
413 | -webkit-transform: none;
414 | transform: none;
415 | }
416 | .reveal .controls .enabled.fragmented {
417 | opacity: 0.5;
418 | }
419 | .reveal .controls .enabled:hover,
420 | .reveal .controls .enabled.fragmented:hover {
421 | opacity: 1;
422 | }
423 |
424 | .reveal[data-navigation-mode='linear'].has-horizontal-slides .navigate-up,
425 | .reveal[data-navigation-mode='linear'].has-horizontal-slides .navigate-down {
426 | display: none;
427 | }
428 |
429 | .reveal[data-navigation-mode='linear'].has-horizontal-slides .navigate-left,
430 | .reveal:not(.has-vertical-slides) .controls .navigate-left {
431 | bottom: 1.4em;
432 | right: 5.5em;
433 | }
434 |
435 | .reveal[data-navigation-mode='linear'].has-horizontal-slides .navigate-right,
436 | .reveal:not(.has-vertical-slides) .controls .navigate-right {
437 | bottom: 1.4em;
438 | right: 0.5em;
439 | }
440 |
441 | .reveal:not(.has-horizontal-slides) .controls .navigate-up {
442 | right: 1.4em;
443 | bottom: 5em;
444 | }
445 |
446 | .reveal:not(.has-horizontal-slides) .controls .navigate-down {
447 | right: 1.4em;
448 | bottom: 0.5em;
449 | }
450 |
451 | .reveal.has-dark-background .controls {
452 | color: #fff;
453 | }
454 |
455 | .reveal.has-light-background .controls {
456 | color: #000;
457 | }
458 |
459 | .reveal.no-hover .controls .controls-arrow:hover:before,
460 | .reveal.no-hover .controls .controls-arrow:active:before {
461 | -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
462 | transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
463 | }
464 |
465 | .reveal.no-hover .controls .controls-arrow:hover:after,
466 | .reveal.no-hover .controls .controls-arrow:active:after {
467 | -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
468 | transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
469 | }
470 |
471 | @media screen and (min-width: 500px) {
472 | .reveal .controls[data-controls-layout='edges'] {
473 | top: 0;
474 | right: 0;
475 | bottom: 0;
476 | left: 0;
477 | }
478 | .reveal .controls[data-controls-layout='edges'] .navigate-left,
479 | .reveal .controls[data-controls-layout='edges'] .navigate-right,
480 | .reveal .controls[data-controls-layout='edges'] .navigate-up,
481 | .reveal .controls[data-controls-layout='edges'] .navigate-down {
482 | bottom: auto;
483 | right: auto;
484 | }
485 | .reveal .controls[data-controls-layout='edges'] .navigate-left {
486 | top: 50%;
487 | left: 8px;
488 | margin-top: -1.8em;
489 | }
490 | .reveal .controls[data-controls-layout='edges'] .navigate-right {
491 | top: 50%;
492 | right: 8px;
493 | margin-top: -1.8em;
494 | }
495 | .reveal .controls[data-controls-layout='edges'] .navigate-up {
496 | top: 8px;
497 | left: 50%;
498 | margin-left: -1.8em;
499 | }
500 | .reveal .controls[data-controls-layout='edges'] .navigate-down {
501 | bottom: 8px;
502 | left: 50%;
503 | margin-left: -1.8em;
504 | }
505 | }
506 |
507 | /*********************************************
508 | * PROGRESS BAR
509 | *********************************************/
510 | .reveal .progress {
511 | position: absolute;
512 | display: none;
513 | height: 3px;
514 | width: 100%;
515 | bottom: 0;
516 | left: 0;
517 | z-index: 10;
518 | background-color: rgba(0, 0, 0, 0.2);
519 | color: #fff;
520 | }
521 |
522 | .reveal .progress:after {
523 | content: '';
524 | display: block;
525 | position: absolute;
526 | height: 10px;
527 | width: 100%;
528 | top: -10px;
529 | }
530 |
531 | .reveal .progress span {
532 | display: block;
533 | height: 100%;
534 | width: 0px;
535 | background-color: currentColor;
536 | transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
537 | }
538 |
539 | /*********************************************
540 | * SLIDE NUMBER
541 | *********************************************/
542 | .reveal .slide-number {
543 | position: absolute;
544 | display: block;
545 | right: 8px;
546 | bottom: 8px;
547 | z-index: 31;
548 | font-family: Helvetica, sans-serif;
549 | font-size: 12px;
550 | line-height: 1;
551 | color: #fff;
552 | background-color: rgba(0, 0, 0, 0.4);
553 | padding: 5px;
554 | }
555 |
556 | .reveal .slide-number a {
557 | color: currentColor;
558 | }
559 |
560 | .reveal .slide-number-delimiter {
561 | margin: 0 3px;
562 | }
563 |
564 | /*********************************************
565 | * SLIDES
566 | *********************************************/
567 | .reveal {
568 | position: relative;
569 | width: 100%;
570 | height: 100%;
571 | overflow: hidden;
572 | -ms-touch-action: pinch-zoom;
573 | touch-action: pinch-zoom;
574 | }
575 |
576 | .reveal .slides {
577 | position: absolute;
578 | width: 100%;
579 | height: 100%;
580 | top: 0;
581 | right: 0;
582 | bottom: 0;
583 | left: 0;
584 | margin: auto;
585 | pointer-events: none;
586 | overflow: visible;
587 | z-index: 1;
588 | text-align: center;
589 | -webkit-perspective: 600px;
590 | perspective: 600px;
591 | -webkit-perspective-origin: 50% 40%;
592 | perspective-origin: 50% 40%;
593 | }
594 |
595 | .reveal .slides > section {
596 | -webkit-perspective: 600px;
597 | perspective: 600px;
598 | }
599 |
600 | .reveal .slides > section,
601 | .reveal .slides > section > section {
602 | display: none;
603 | position: absolute;
604 | width: 100%;
605 | padding: 20px 0px;
606 | pointer-events: auto;
607 | z-index: 10;
608 | -webkit-transform-style: flat;
609 | transform-style: flat;
610 | transition: -webkit-transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985),
611 | -webkit-transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985),
612 | visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985),
613 | opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
614 | transition: transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985),
615 | transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985),
616 | visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985),
617 | opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
618 | }
619 |
620 | /* Global transition speed settings */
621 | .reveal[data-transition-speed='fast'] .slides section {
622 | transition-duration: 400ms;
623 | }
624 |
625 | .reveal[data-transition-speed='slow'] .slides section {
626 | transition-duration: 1200ms;
627 | }
628 |
629 | /* Slide-specific transition speed overrides */
630 | .reveal .slides section[data-transition-speed='fast'] {
631 | transition-duration: 400ms;
632 | }
633 |
634 | .reveal .slides section[data-transition-speed='slow'] {
635 | transition-duration: 1200ms;
636 | }
637 |
638 | .reveal .slides > section.stack {
639 | padding-top: 0;
640 | padding-bottom: 0;
641 | pointer-events: none;
642 | height: 100%;
643 | }
644 |
645 | .reveal .slides > section.present,
646 | .reveal .slides > section > section.present {
647 | display: block;
648 | z-index: 11;
649 | opacity: 1;
650 | }
651 |
652 | .reveal .slides > section:empty,
653 | .reveal .slides > section > section:empty,
654 | .reveal .slides > section[data-background-interactive],
655 | .reveal .slides > section > section[data-background-interactive] {
656 | pointer-events: none;
657 | }
658 |
659 | .reveal.center,
660 | .reveal.center .slides,
661 | .reveal.center .slides section {
662 | min-height: 0 !important;
663 | }
664 |
665 | /* Don't allow interaction with invisible slides */
666 | .reveal .slides > section.future,
667 | .reveal .slides > section > section.future,
668 | .reveal .slides > section.past,
669 | .reveal .slides > section > section.past {
670 | pointer-events: none;
671 | }
672 |
673 | .reveal.overview .slides > section,
674 | .reveal.overview .slides > section > section {
675 | pointer-events: auto;
676 | }
677 |
678 | .reveal .slides > section.past,
679 | .reveal .slides > section.future,
680 | .reveal .slides > section > section.past,
681 | .reveal .slides > section > section.future {
682 | opacity: 0;
683 | }
684 |
685 | /*********************************************
686 | * Mixins for readability of transitions
687 | *********************************************/
688 | /*********************************************
689 | * SLIDE TRANSITION
690 | * Aliased 'linear' for backwards compatibility
691 | *********************************************/
692 | .reveal.slide section {
693 | -webkit-backface-visibility: hidden;
694 | backface-visibility: hidden;
695 | }
696 |
697 | .reveal .slides > section[data-transition='slide'].past,
698 | .reveal .slides > section[data-transition~='slide-out'].past,
699 | .reveal.slide .slides > section:not([data-transition]).past {
700 | -webkit-transform: translate(-150%, 0);
701 | transform: translate(-150%, 0);
702 | }
703 |
704 | .reveal .slides > section[data-transition='slide'].future,
705 | .reveal .slides > section[data-transition~='slide-in'].future,
706 | .reveal.slide .slides > section:not([data-transition]).future {
707 | -webkit-transform: translate(150%, 0);
708 | transform: translate(150%, 0);
709 | }
710 |
711 | .reveal .slides > section > section[data-transition='slide'].past,
712 | .reveal .slides > section > section[data-transition~='slide-out'].past,
713 | .reveal.slide .slides > section > section:not([data-transition]).past {
714 | -webkit-transform: translate(0, -150%);
715 | transform: translate(0, -150%);
716 | }
717 |
718 | .reveal .slides > section > section[data-transition='slide'].future,
719 | .reveal .slides > section > section[data-transition~='slide-in'].future,
720 | .reveal.slide .slides > section > section:not([data-transition]).future {
721 | -webkit-transform: translate(0, 150%);
722 | transform: translate(0, 150%);
723 | }
724 |
725 | .reveal.linear section {
726 | -webkit-backface-visibility: hidden;
727 | backface-visibility: hidden;
728 | }
729 |
730 | .reveal .slides > section[data-transition='linear'].past,
731 | .reveal .slides > section[data-transition~='linear-out'].past,
732 | .reveal.linear .slides > section:not([data-transition]).past {
733 | -webkit-transform: translate(-150%, 0);
734 | transform: translate(-150%, 0);
735 | }
736 |
737 | .reveal .slides > section[data-transition='linear'].future,
738 | .reveal .slides > section[data-transition~='linear-in'].future,
739 | .reveal.linear .slides > section:not([data-transition]).future {
740 | -webkit-transform: translate(150%, 0);
741 | transform: translate(150%, 0);
742 | }
743 |
744 | .reveal .slides > section > section[data-transition='linear'].past,
745 | .reveal .slides > section > section[data-transition~='linear-out'].past,
746 | .reveal.linear .slides > section > section:not([data-transition]).past {
747 | -webkit-transform: translate(0, -150%);
748 | transform: translate(0, -150%);
749 | }
750 |
751 | .reveal .slides > section > section[data-transition='linear'].future,
752 | .reveal .slides > section > section[data-transition~='linear-in'].future,
753 | .reveal.linear .slides > section > section:not([data-transition]).future {
754 | -webkit-transform: translate(0, 150%);
755 | transform: translate(0, 150%);
756 | }
757 |
758 | /*********************************************
759 | * CONVEX TRANSITION
760 | * Aliased 'default' for backwards compatibility
761 | *********************************************/
762 | .reveal .slides section[data-transition='default'].stack,
763 | .reveal.default .slides section.stack {
764 | -webkit-transform-style: preserve-3d;
765 | transform-style: preserve-3d;
766 | }
767 |
768 | .reveal .slides > section[data-transition='default'].past,
769 | .reveal .slides > section[data-transition~='default-out'].past,
770 | .reveal.default .slides > section:not([data-transition]).past {
771 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
772 | transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
773 | }
774 |
775 | .reveal .slides > section[data-transition='default'].future,
776 | .reveal .slides > section[data-transition~='default-in'].future,
777 | .reveal.default .slides > section:not([data-transition]).future {
778 | -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
779 | transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
780 | }
781 |
782 | .reveal .slides > section > section[data-transition='default'].past,
783 | .reveal .slides > section > section[data-transition~='default-out'].past,
784 | .reveal.default .slides > section > section:not([data-transition]).past {
785 | -webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
786 | transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
787 | }
788 |
789 | .reveal .slides > section > section[data-transition='default'].future,
790 | .reveal .slides > section > section[data-transition~='default-in'].future,
791 | .reveal.default .slides > section > section:not([data-transition]).future {
792 | -webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
793 | transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
794 | }
795 |
796 | .reveal .slides section[data-transition='convex'].stack,
797 | .reveal.convex .slides section.stack {
798 | -webkit-transform-style: preserve-3d;
799 | transform-style: preserve-3d;
800 | }
801 |
802 | .reveal .slides > section[data-transition='convex'].past,
803 | .reveal .slides > section[data-transition~='convex-out'].past,
804 | .reveal.convex .slides > section:not([data-transition]).past {
805 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
806 | transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
807 | }
808 |
809 | .reveal .slides > section[data-transition='convex'].future,
810 | .reveal .slides > section[data-transition~='convex-in'].future,
811 | .reveal.convex .slides > section:not([data-transition]).future {
812 | -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
813 | transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
814 | }
815 |
816 | .reveal .slides > section > section[data-transition='convex'].past,
817 | .reveal .slides > section > section[data-transition~='convex-out'].past,
818 | .reveal.convex .slides > section > section:not([data-transition]).past {
819 | -webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
820 | transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
821 | }
822 |
823 | .reveal .slides > section > section[data-transition='convex'].future,
824 | .reveal .slides > section > section[data-transition~='convex-in'].future,
825 | .reveal.convex .slides > section > section:not([data-transition]).future {
826 | -webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
827 | transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
828 | }
829 |
830 | /*********************************************
831 | * CONCAVE TRANSITION
832 | *********************************************/
833 | .reveal .slides section[data-transition='concave'].stack,
834 | .reveal.concave .slides section.stack {
835 | -webkit-transform-style: preserve-3d;
836 | transform-style: preserve-3d;
837 | }
838 |
839 | .reveal .slides > section[data-transition='concave'].past,
840 | .reveal .slides > section[data-transition~='concave-out'].past,
841 | .reveal.concave .slides > section:not([data-transition]).past {
842 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
843 | transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
844 | }
845 |
846 | .reveal .slides > section[data-transition='concave'].future,
847 | .reveal .slides > section[data-transition~='concave-in'].future,
848 | .reveal.concave .slides > section:not([data-transition]).future {
849 | -webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
850 | transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
851 | }
852 |
853 | .reveal .slides > section > section[data-transition='concave'].past,
854 | .reveal .slides > section > section[data-transition~='concave-out'].past,
855 | .reveal.concave .slides > section > section:not([data-transition]).past {
856 | -webkit-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
857 | transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
858 | }
859 |
860 | .reveal .slides > section > section[data-transition='concave'].future,
861 | .reveal .slides > section > section[data-transition~='concave-in'].future,
862 | .reveal.concave .slides > section > section:not([data-transition]).future {
863 | -webkit-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
864 | transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
865 | }
866 |
867 | /*********************************************
868 | * ZOOM TRANSITION
869 | *********************************************/
870 | .reveal .slides section[data-transition='zoom'],
871 | .reveal.zoom .slides section:not([data-transition]) {
872 | transition-timing-function: ease;
873 | }
874 |
875 | .reveal .slides > section[data-transition='zoom'].past,
876 | .reveal .slides > section[data-transition~='zoom-out'].past,
877 | .reveal.zoom .slides > section:not([data-transition]).past {
878 | visibility: hidden;
879 | -webkit-transform: scale(16);
880 | transform: scale(16);
881 | }
882 |
883 | .reveal .slides > section[data-transition='zoom'].future,
884 | .reveal .slides > section[data-transition~='zoom-in'].future,
885 | .reveal.zoom .slides > section:not([data-transition]).future {
886 | visibility: hidden;
887 | -webkit-transform: scale(0.2);
888 | transform: scale(0.2);
889 | }
890 |
891 | .reveal .slides > section > section[data-transition='zoom'].past,
892 | .reveal .slides > section > section[data-transition~='zoom-out'].past,
893 | .reveal.zoom .slides > section > section:not([data-transition]).past {
894 | -webkit-transform: scale(16);
895 | transform: scale(16);
896 | }
897 |
898 | .reveal .slides > section > section[data-transition='zoom'].future,
899 | .reveal .slides > section > section[data-transition~='zoom-in'].future,
900 | .reveal.zoom .slides > section > section:not([data-transition]).future {
901 | -webkit-transform: scale(0.2);
902 | transform: scale(0.2);
903 | }
904 |
905 | /*********************************************
906 | * CUBE TRANSITION
907 | *
908 | * WARNING:
909 | * this is deprecated and will be removed in a
910 | * future version.
911 | *********************************************/
912 | .reveal.cube .slides {
913 | -webkit-perspective: 1300px;
914 | perspective: 1300px;
915 | }
916 |
917 | .reveal.cube .slides section {
918 | padding: 30px;
919 | min-height: 700px;
920 | -webkit-backface-visibility: hidden;
921 | backface-visibility: hidden;
922 | box-sizing: border-box;
923 | -webkit-transform-style: preserve-3d;
924 | transform-style: preserve-3d;
925 | }
926 |
927 | .reveal.center.cube .slides section {
928 | min-height: 0;
929 | }
930 |
931 | .reveal.cube .slides section:not(.stack):before {
932 | content: '';
933 | position: absolute;
934 | display: block;
935 | width: 100%;
936 | height: 100%;
937 | left: 0;
938 | top: 0;
939 | background: rgba(0, 0, 0, 0.1);
940 | border-radius: 4px;
941 | -webkit-transform: translateZ(-20px);
942 | transform: translateZ(-20px);
943 | }
944 |
945 | .reveal.cube .slides section:not(.stack):after {
946 | content: '';
947 | position: absolute;
948 | display: block;
949 | width: 90%;
950 | height: 30px;
951 | left: 5%;
952 | bottom: 0;
953 | background: none;
954 | z-index: 1;
955 | border-radius: 4px;
956 | box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
957 | -webkit-transform: translateZ(-90px) rotateX(65deg);
958 | transform: translateZ(-90px) rotateX(65deg);
959 | }
960 |
961 | .reveal.cube .slides > section.stack {
962 | padding: 0;
963 | background: none;
964 | }
965 |
966 | .reveal.cube .slides > section.past {
967 | -webkit-transform-origin: 100% 0%;
968 | transform-origin: 100% 0%;
969 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
970 | transform: translate3d(-100%, 0, 0) rotateY(-90deg);
971 | }
972 |
973 | .reveal.cube .slides > section.future {
974 | -webkit-transform-origin: 0% 0%;
975 | transform-origin: 0% 0%;
976 | -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg);
977 | transform: translate3d(100%, 0, 0) rotateY(90deg);
978 | }
979 |
980 | .reveal.cube .slides > section > section.past {
981 | -webkit-transform-origin: 0% 100%;
982 | transform-origin: 0% 100%;
983 | -webkit-transform: translate3d(0, -100%, 0) rotateX(90deg);
984 | transform: translate3d(0, -100%, 0) rotateX(90deg);
985 | }
986 |
987 | .reveal.cube .slides > section > section.future {
988 | -webkit-transform-origin: 0% 0%;
989 | transform-origin: 0% 0%;
990 | -webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg);
991 | transform: translate3d(0, 100%, 0) rotateX(-90deg);
992 | }
993 |
994 | /*********************************************
995 | * PAGE TRANSITION
996 | *
997 | * WARNING:
998 | * this is deprecated and will be removed in a
999 | * future version.
1000 | *********************************************/
1001 | .reveal.page .slides {
1002 | -webkit-perspective-origin: 0% 50%;
1003 | perspective-origin: 0% 50%;
1004 | -webkit-perspective: 3000px;
1005 | perspective: 3000px;
1006 | }
1007 |
1008 | .reveal.page .slides section {
1009 | padding: 30px;
1010 | min-height: 700px;
1011 | box-sizing: border-box;
1012 | -webkit-transform-style: preserve-3d;
1013 | transform-style: preserve-3d;
1014 | }
1015 |
1016 | .reveal.page .slides section.past {
1017 | z-index: 12;
1018 | }
1019 |
1020 | .reveal.page .slides section:not(.stack):before {
1021 | content: '';
1022 | position: absolute;
1023 | display: block;
1024 | width: 100%;
1025 | height: 100%;
1026 | left: 0;
1027 | top: 0;
1028 | background: rgba(0, 0, 0, 0.1);
1029 | -webkit-transform: translateZ(-20px);
1030 | transform: translateZ(-20px);
1031 | }
1032 |
1033 | .reveal.page .slides section:not(.stack):after {
1034 | content: '';
1035 | position: absolute;
1036 | display: block;
1037 | width: 90%;
1038 | height: 30px;
1039 | left: 5%;
1040 | bottom: 0;
1041 | background: none;
1042 | z-index: 1;
1043 | border-radius: 4px;
1044 | box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
1045 | -webkit-transform: translateZ(-90px) rotateX(65deg);
1046 | }
1047 |
1048 | .reveal.page .slides > section.stack {
1049 | padding: 0;
1050 | background: none;
1051 | }
1052 |
1053 | .reveal.page .slides > section.past {
1054 | -webkit-transform-origin: 0% 0%;
1055 | transform-origin: 0% 0%;
1056 | -webkit-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
1057 | transform: translate3d(-40%, 0, 0) rotateY(-80deg);
1058 | }
1059 |
1060 | .reveal.page .slides > section.future {
1061 | -webkit-transform-origin: 100% 0%;
1062 | transform-origin: 100% 0%;
1063 | -webkit-transform: translate3d(0, 0, 0);
1064 | transform: translate3d(0, 0, 0);
1065 | }
1066 |
1067 | .reveal.page .slides > section > section.past {
1068 | -webkit-transform-origin: 0% 0%;
1069 | transform-origin: 0% 0%;
1070 | -webkit-transform: translate3d(0, -40%, 0) rotateX(80deg);
1071 | transform: translate3d(0, -40%, 0) rotateX(80deg);
1072 | }
1073 |
1074 | .reveal.page .slides > section > section.future {
1075 | -webkit-transform-origin: 0% 100%;
1076 | transform-origin: 0% 100%;
1077 | -webkit-transform: translate3d(0, 0, 0);
1078 | transform: translate3d(0, 0, 0);
1079 | }
1080 |
1081 | /*********************************************
1082 | * FADE TRANSITION
1083 | *********************************************/
1084 | .reveal .slides section[data-transition='fade'],
1085 | .reveal.fade .slides section:not([data-transition]),
1086 | .reveal.fade .slides > section > section:not([data-transition]) {
1087 | -webkit-transform: none;
1088 | transform: none;
1089 | transition: opacity 0.5s;
1090 | }
1091 |
1092 | .reveal.fade.overview .slides section,
1093 | .reveal.fade.overview .slides > section > section {
1094 | transition: none;
1095 | }
1096 |
1097 | /*********************************************
1098 | * NO TRANSITION
1099 | *********************************************/
1100 | .reveal .slides section[data-transition='none'],
1101 | .reveal.none .slides section:not([data-transition]) {
1102 | -webkit-transform: none;
1103 | transform: none;
1104 | transition: none;
1105 | }
1106 |
1107 | /*********************************************
1108 | * PAUSED MODE
1109 | *********************************************/
1110 | .reveal .pause-overlay {
1111 | position: absolute;
1112 | top: 0;
1113 | left: 0;
1114 | width: 100%;
1115 | height: 100%;
1116 | background: black;
1117 | visibility: hidden;
1118 | opacity: 0;
1119 | z-index: 100;
1120 | transition: all 1s ease;
1121 | }
1122 |
1123 | .reveal .pause-overlay .resume-button {
1124 | position: absolute;
1125 | bottom: 20px;
1126 | right: 20px;
1127 | color: #ccc;
1128 | border-radius: 2px;
1129 | padding: 6px 14px;
1130 | border: 2px solid #ccc;
1131 | font-size: 16px;
1132 | background: transparent;
1133 | cursor: pointer;
1134 | }
1135 | .reveal .pause-overlay .resume-button:hover {
1136 | color: #fff;
1137 | border-color: #fff;
1138 | }
1139 |
1140 | .reveal.paused .pause-overlay {
1141 | visibility: visible;
1142 | opacity: 1;
1143 | }
1144 |
1145 | /*********************************************
1146 | * FALLBACK
1147 | *********************************************/
1148 | .no-transforms {
1149 | overflow-y: auto;
1150 | }
1151 |
1152 | .no-transforms .reveal {
1153 | overflow: visible;
1154 | }
1155 |
1156 | .no-transforms .reveal .slides {
1157 | position: relative;
1158 | width: 80%;
1159 | max-width: 1280px;
1160 | height: auto;
1161 | top: 0;
1162 | margin: 0 auto;
1163 | text-align: center;
1164 | }
1165 |
1166 | .no-transforms .reveal .controls,
1167 | .no-transforms .reveal .progress {
1168 | display: none;
1169 | }
1170 |
1171 | .no-transforms .reveal .slides section {
1172 | display: block;
1173 | opacity: 1;
1174 | position: relative;
1175 | height: auto;
1176 | min-height: 0;
1177 | top: 0;
1178 | left: 0;
1179 | margin: 10vh 0;
1180 | margin: 70px 0;
1181 | -webkit-transform: none;
1182 | transform: none;
1183 | }
1184 |
1185 | .reveal .no-transition,
1186 | .reveal .no-transition * {
1187 | transition: none !important;
1188 | }
1189 |
1190 | /*********************************************
1191 | * PER-SLIDE BACKGROUNDS
1192 | *********************************************/
1193 | .reveal .backgrounds {
1194 | position: absolute;
1195 | width: 100%;
1196 | height: 100%;
1197 | top: 0;
1198 | left: 0;
1199 | -webkit-perspective: 600px;
1200 | perspective: 600px;
1201 | }
1202 |
1203 | .reveal .slide-background {
1204 | display: none;
1205 | position: absolute;
1206 | width: 100%;
1207 | height: 100%;
1208 | opacity: 0;
1209 | visibility: hidden;
1210 | overflow: hidden;
1211 | background-color: rgba(0, 0, 0, 0);
1212 | transition: all 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
1213 | }
1214 |
1215 | .reveal .slide-background-content {
1216 | position: absolute;
1217 | width: 100%;
1218 | height: 100%;
1219 | background-position: 50% 50%;
1220 | background-repeat: no-repeat;
1221 | background-size: cover;
1222 | }
1223 |
1224 | .reveal .slide-background.stack {
1225 | display: block;
1226 | }
1227 |
1228 | .reveal .slide-background.present {
1229 | opacity: 1;
1230 | visibility: visible;
1231 | z-index: 2;
1232 | }
1233 |
1234 | .print-pdf .reveal .slide-background {
1235 | opacity: 1 !important;
1236 | visibility: visible !important;
1237 | }
1238 |
1239 | /* Video backgrounds */
1240 | .reveal .slide-background video {
1241 | position: absolute;
1242 | width: 100%;
1243 | height: 100%;
1244 | max-width: none;
1245 | max-height: none;
1246 | top: 0;
1247 | left: 0;
1248 | -o-object-fit: cover;
1249 | object-fit: cover;
1250 | }
1251 |
1252 | .reveal .slide-background[data-background-size='contain'] video {
1253 | -o-object-fit: contain;
1254 | object-fit: contain;
1255 | }
1256 |
1257 | /* Immediate transition style */
1258 | .reveal[data-background-transition='none'] > .backgrounds .slide-background,
1259 | .reveal > .backgrounds .slide-background[data-background-transition='none'] {
1260 | transition: none;
1261 | }
1262 |
1263 | /* Slide */
1264 | .reveal[data-background-transition='slide'] > .backgrounds .slide-background,
1265 | .reveal > .backgrounds .slide-background[data-background-transition='slide'] {
1266 | opacity: 1;
1267 | -webkit-backface-visibility: hidden;
1268 | backface-visibility: hidden;
1269 | }
1270 |
1271 | .reveal[data-background-transition='slide'] > .backgrounds .slide-background.past,
1272 | .reveal > .backgrounds .slide-background.past[data-background-transition='slide'] {
1273 | -webkit-transform: translate(-100%, 0);
1274 | transform: translate(-100%, 0);
1275 | }
1276 |
1277 | .reveal[data-background-transition='slide'] > .backgrounds .slide-background.future,
1278 | .reveal > .backgrounds .slide-background.future[data-background-transition='slide'] {
1279 | -webkit-transform: translate(100%, 0);
1280 | transform: translate(100%, 0);
1281 | }
1282 |
1283 | .reveal[data-background-transition='slide']
1284 | > .backgrounds
1285 | .slide-background
1286 | > .slide-background.past,
1287 | .reveal
1288 | > .backgrounds
1289 | .slide-background
1290 | > .slide-background.past[data-background-transition='slide'] {
1291 | -webkit-transform: translate(0, -100%);
1292 | transform: translate(0, -100%);
1293 | }
1294 |
1295 | .reveal[data-background-transition='slide']
1296 | > .backgrounds
1297 | .slide-background
1298 | > .slide-background.future,
1299 | .reveal
1300 | > .backgrounds
1301 | .slide-background
1302 | > .slide-background.future[data-background-transition='slide'] {
1303 | -webkit-transform: translate(0, 100%);
1304 | transform: translate(0, 100%);
1305 | }
1306 |
1307 | /* Convex */
1308 | .reveal[data-background-transition='convex'] > .backgrounds .slide-background.past,
1309 | .reveal > .backgrounds .slide-background.past[data-background-transition='convex'] {
1310 | opacity: 0;
1311 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
1312 | transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
1313 | }
1314 |
1315 | .reveal[data-background-transition='convex'] > .backgrounds .slide-background.future,
1316 | .reveal > .backgrounds .slide-background.future[data-background-transition='convex'] {
1317 | opacity: 0;
1318 | -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
1319 | transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
1320 | }
1321 |
1322 | .reveal[data-background-transition='convex']
1323 | > .backgrounds
1324 | .slide-background
1325 | > .slide-background.past,
1326 | .reveal
1327 | > .backgrounds
1328 | .slide-background
1329 | > .slide-background.past[data-background-transition='convex'] {
1330 | opacity: 0;
1331 | -webkit-transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0);
1332 | transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0);
1333 | }
1334 |
1335 | .reveal[data-background-transition='convex']
1336 | > .backgrounds
1337 | .slide-background
1338 | > .slide-background.future,
1339 | .reveal
1340 | > .backgrounds
1341 | .slide-background
1342 | > .slide-background.future[data-background-transition='convex'] {
1343 | opacity: 0;
1344 | -webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0);
1345 | transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0);
1346 | }
1347 |
1348 | /* Concave */
1349 | .reveal[data-background-transition='concave'] > .backgrounds .slide-background.past,
1350 | .reveal > .backgrounds .slide-background.past[data-background-transition='concave'] {
1351 | opacity: 0;
1352 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
1353 | transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
1354 | }
1355 |
1356 | .reveal[data-background-transition='concave'] > .backgrounds .slide-background.future,
1357 | .reveal > .backgrounds .slide-background.future[data-background-transition='concave'] {
1358 | opacity: 0;
1359 | -webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
1360 | transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
1361 | }
1362 |
1363 | .reveal[data-background-transition='concave']
1364 | > .backgrounds
1365 | .slide-background
1366 | > .slide-background.past,
1367 | .reveal
1368 | > .backgrounds
1369 | .slide-background
1370 | > .slide-background.past[data-background-transition='concave'] {
1371 | opacity: 0;
1372 | -webkit-transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0);
1373 | transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0);
1374 | }
1375 |
1376 | .reveal[data-background-transition='concave']
1377 | > .backgrounds
1378 | .slide-background
1379 | > .slide-background.future,
1380 | .reveal
1381 | > .backgrounds
1382 | .slide-background
1383 | > .slide-background.future[data-background-transition='concave'] {
1384 | opacity: 0;
1385 | -webkit-transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0);
1386 | transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0);
1387 | }
1388 |
1389 | /* Zoom */
1390 | .reveal[data-background-transition='zoom'] > .backgrounds .slide-background,
1391 | .reveal > .backgrounds .slide-background[data-background-transition='zoom'] {
1392 | transition-timing-function: ease;
1393 | }
1394 |
1395 | .reveal[data-background-transition='zoom'] > .backgrounds .slide-background.past,
1396 | .reveal > .backgrounds .slide-background.past[data-background-transition='zoom'] {
1397 | opacity: 0;
1398 | visibility: hidden;
1399 | -webkit-transform: scale(16);
1400 | transform: scale(16);
1401 | }
1402 |
1403 | .reveal[data-background-transition='zoom'] > .backgrounds .slide-background.future,
1404 | .reveal > .backgrounds .slide-background.future[data-background-transition='zoom'] {
1405 | opacity: 0;
1406 | visibility: hidden;
1407 | -webkit-transform: scale(0.2);
1408 | transform: scale(0.2);
1409 | }
1410 |
1411 | .reveal[data-background-transition='zoom']
1412 | > .backgrounds
1413 | .slide-background
1414 | > .slide-background.past,
1415 | .reveal
1416 | > .backgrounds
1417 | .slide-background
1418 | > .slide-background.past[data-background-transition='zoom'] {
1419 | opacity: 0;
1420 | visibility: hidden;
1421 | -webkit-transform: scale(16);
1422 | transform: scale(16);
1423 | }
1424 |
1425 | .reveal[data-background-transition='zoom']
1426 | > .backgrounds
1427 | .slide-background
1428 | > .slide-background.future,
1429 | .reveal
1430 | > .backgrounds
1431 | .slide-background
1432 | > .slide-background.future[data-background-transition='zoom'] {
1433 | opacity: 0;
1434 | visibility: hidden;
1435 | -webkit-transform: scale(0.2);
1436 | transform: scale(0.2);
1437 | }
1438 |
1439 | /* Global transition speed settings */
1440 | .reveal[data-transition-speed='fast'] > .backgrounds .slide-background {
1441 | transition-duration: 400ms;
1442 | }
1443 |
1444 | .reveal[data-transition-speed='slow'] > .backgrounds .slide-background {
1445 | transition-duration: 1200ms;
1446 | }
1447 |
1448 | /*********************************************
1449 | * OVERVIEW
1450 | *********************************************/
1451 | .reveal.overview {
1452 | -webkit-perspective-origin: 50% 50%;
1453 | perspective-origin: 50% 50%;
1454 | -webkit-perspective: 700px;
1455 | perspective: 700px;
1456 | }
1457 | .reveal.overview .slides {
1458 | -moz-transform-style: preserve-3d;
1459 | }
1460 | .reveal.overview .slides section {
1461 | height: 100%;
1462 | top: 0 !important;
1463 | opacity: 1 !important;
1464 | overflow: hidden;
1465 | visibility: visible !important;
1466 | cursor: pointer;
1467 | box-sizing: border-box;
1468 | }
1469 | .reveal.overview .slides section:hover,
1470 | .reveal.overview .slides section.present {
1471 | outline: 10px solid rgba(150, 150, 150, 0.4);
1472 | outline-offset: 10px;
1473 | }
1474 | .reveal.overview .slides section .fragment {
1475 | opacity: 1;
1476 | transition: none;
1477 | }
1478 | .reveal.overview .slides section:after,
1479 | .reveal.overview .slides section:before {
1480 | display: none !important;
1481 | }
1482 | .reveal.overview .slides > section.stack {
1483 | padding: 0;
1484 | top: 0 !important;
1485 | background: none;
1486 | outline: none;
1487 | overflow: visible;
1488 | }
1489 | .reveal.overview .backgrounds {
1490 | -webkit-perspective: inherit;
1491 | perspective: inherit;
1492 | -moz-transform-style: preserve-3d;
1493 | }
1494 | .reveal.overview .backgrounds .slide-background {
1495 | opacity: 1;
1496 | visibility: visible;
1497 | outline: 10px solid rgba(150, 150, 150, 0.1);
1498 | outline-offset: 10px;
1499 | }
1500 | .reveal.overview .backgrounds .slide-background.stack {
1501 | overflow: visible;
1502 | }
1503 |
1504 | .reveal.overview .slides section,
1505 | .reveal.overview-deactivating .slides section {
1506 | transition: none;
1507 | }
1508 |
1509 | .reveal.overview .backgrounds .slide-background,
1510 | .reveal.overview-deactivating .backgrounds .slide-background {
1511 | transition: none;
1512 | }
1513 |
1514 | /*********************************************
1515 | * RTL SUPPORT
1516 | *********************************************/
1517 | .reveal.rtl .slides,
1518 | .reveal.rtl .slides h1,
1519 | .reveal.rtl .slides h2,
1520 | .reveal.rtl .slides h3,
1521 | .reveal.rtl .slides h4,
1522 | .reveal.rtl .slides h5,
1523 | .reveal.rtl .slides h6 {
1524 | direction: rtl;
1525 | font-family: sans-serif;
1526 | }
1527 |
1528 | .reveal.rtl pre,
1529 | .reveal.rtl code {
1530 | direction: ltr;
1531 | }
1532 |
1533 | .reveal.rtl ol,
1534 | .reveal.rtl ul {
1535 | text-align: right;
1536 | }
1537 |
1538 | .reveal.rtl .progress span {
1539 | float: right;
1540 | }
1541 |
1542 | /*********************************************
1543 | * PARALLAX BACKGROUND
1544 | *********************************************/
1545 | .reveal.has-parallax-background .backgrounds {
1546 | transition: all 0.8s ease;
1547 | }
1548 |
1549 | /* Global transition speed settings */
1550 | .reveal.has-parallax-background[data-transition-speed='fast'] .backgrounds {
1551 | transition-duration: 400ms;
1552 | }
1553 |
1554 | .reveal.has-parallax-background[data-transition-speed='slow'] .backgrounds {
1555 | transition-duration: 1200ms;
1556 | }
1557 |
1558 | /*********************************************
1559 | * OVERLAY FOR LINK PREVIEWS AND HELP
1560 | *********************************************/
1561 | .reveal > .overlay {
1562 | position: absolute;
1563 | top: 0;
1564 | left: 0;
1565 | width: 100%;
1566 | height: 100%;
1567 | z-index: 1000;
1568 | background: rgba(0, 0, 0, 0.9);
1569 | opacity: 0;
1570 | visibility: hidden;
1571 | transition: all 0.3s ease;
1572 | }
1573 |
1574 | .reveal > .overlay.visible {
1575 | opacity: 1;
1576 | visibility: visible;
1577 | }
1578 |
1579 | .reveal > .overlay .spinner {
1580 | position: absolute;
1581 | display: block;
1582 | top: 50%;
1583 | left: 50%;
1584 | width: 32px;
1585 | height: 32px;
1586 | margin: -16px 0 0 -16px;
1587 | z-index: 10;
1588 | background-image: url(data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf%2F%2F%2F6%2Bvr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs%2FLy8vz8%2FAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ%2FV%2FnmOM82XiHRLYKhKP1oZmADdEAAAh%2BQQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY%2FCZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB%2BA4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6%2BHo7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq%2BB6QDtuetcaBPnW6%2BO7wDHpIiK9SaVK5GgV543tzjgGcghAgAh%2BQQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK%2B%2BG%2Bw48edZPK%2BM6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE%2BG%2BcD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm%2BFNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk%2BaV%2BoJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0%2FVNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc%2BXiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30%2FiI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE%2FjiuL04RGEBgwWhShRgQExHBAAh%2BQQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR%2BipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY%2BYip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd%2BMFCN6HAAIKgNggY0KtEBAAh%2BQQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1%2BvsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d%2BjYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg%2BygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0%2Bbm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h%2BKr0SJ8MFihpNbx%2B4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX%2BBP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA%3D%3D);
1589 | visibility: visible;
1590 | opacity: 0.6;
1591 | transition: all 0.3s ease;
1592 | }
1593 |
1594 | .reveal > .overlay header {
1595 | position: absolute;
1596 | left: 0;
1597 | top: 0;
1598 | width: 100%;
1599 | height: 40px;
1600 | z-index: 2;
1601 | border-bottom: 1px solid #222;
1602 | }
1603 |
1604 | .reveal > .overlay header a {
1605 | display: inline-block;
1606 | width: 40px;
1607 | height: 40px;
1608 | line-height: 36px;
1609 | padding: 0 10px;
1610 | float: right;
1611 | opacity: 0.6;
1612 | box-sizing: border-box;
1613 | }
1614 |
1615 | .reveal > .overlay header a:hover {
1616 | opacity: 1;
1617 | }
1618 |
1619 | .reveal > .overlay header a .icon {
1620 | display: inline-block;
1621 | width: 20px;
1622 | height: 20px;
1623 | background-position: 50% 50%;
1624 | background-size: 100%;
1625 | background-repeat: no-repeat;
1626 | }
1627 |
1628 | .reveal > .overlay header a.close .icon {
1629 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABkklEQVRYR8WX4VHDMAxG6wnoJrABZQPYBCaBTWAD2g1gE5gg6OOsXuxIlr40d81dfrSJ9V4c2VLK7spHuTJ/5wpM07QXuXc5X0opX2tEJcadjHuV80li/FgxTIEK/5QBCICBD6xEhSMGHgQPgBgLiYVAB1dpSqKDawxTohFw4JSEA3clzgIBPCURwE2JucBR7rhPJJv5OpJwDX+SfDjgx1wACQeJG1aChP9K/IMmdZ8DtESV1WyP3Bt4MwM6sj4NMxMYiqUWHQu4KYA/SYkIjOsm3BXYWMKFDwU2khjCQ4ELJUJ4SmClRArOCmSXGuKma0fYD5CbzHxFpCSGAhfAVSSUGDUk2BWZaff2g6GE15BsBQ9nwmpIGDiyHQddwNTMKkbZaf9fajXQca1EX44puJZUsnY0ObGmITE3GVLCbEhQUjGVt146j6oasWN+49Vph2w1pZ5EansNZqKBm1txbU57iRRcZ86RWMDdWtBJUHBHwoQPi1GV+JCbntmvok7iTX4/Up9mgyTc/FJYDTcndgH/AA5A/CHsyEkVAAAAAElFTkSuQmCC);
1630 | }
1631 |
1632 | .reveal > .overlay header a.external .icon {
1633 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAcElEQVRYR+2WSQoAIQwEzf8f7XiOMkUQxUPlGkM3hVmiQfQR9GYnH1SsAQlI4DiBqkCMoNb9y2e90IAEJPAcgdznU9+engMaeJ7Azh5Y1U67gAho4DqBqmB1buAf0MB1AlVBek83ZPkmJMGc1wAR+AAqod/B97TRpQAAAABJRU5ErkJggg==);
1634 | }
1635 |
1636 | .reveal > .overlay .viewport {
1637 | position: absolute;
1638 | display: -webkit-box;
1639 | display: -ms-flexbox;
1640 | display: flex;
1641 | top: 40px;
1642 | right: 0;
1643 | bottom: 0;
1644 | left: 0;
1645 | }
1646 |
1647 | .reveal > .overlay.overlay-preview .viewport iframe {
1648 | width: 100%;
1649 | height: 100%;
1650 | max-width: 100%;
1651 | max-height: 100%;
1652 | border: 0;
1653 | opacity: 0;
1654 | visibility: hidden;
1655 | transition: all 0.3s ease;
1656 | }
1657 |
1658 | .reveal > .overlay.overlay-preview.loaded .viewport iframe {
1659 | opacity: 1;
1660 | visibility: visible;
1661 | }
1662 |
1663 | .reveal > .overlay.overlay-preview.loaded .viewport-inner {
1664 | position: absolute;
1665 | z-index: -1;
1666 | left: 0;
1667 | top: 45%;
1668 | width: 100%;
1669 | text-align: center;
1670 | letter-spacing: normal;
1671 | }
1672 |
1673 | .reveal > .overlay.overlay-preview .x-frame-error {
1674 | opacity: 0;
1675 | transition: opacity 0.3s ease 0.3s;
1676 | }
1677 |
1678 | .reveal > .overlay.overlay-preview.loaded .x-frame-error {
1679 | opacity: 1;
1680 | }
1681 |
1682 | .reveal > .overlay.overlay-preview.loaded .spinner {
1683 | opacity: 0;
1684 | visibility: hidden;
1685 | -webkit-transform: scale(0.2);
1686 | transform: scale(0.2);
1687 | }
1688 |
1689 | .reveal > .overlay.overlay-help .viewport {
1690 | overflow: auto;
1691 | color: #fff;
1692 | }
1693 |
1694 | .reveal > .overlay.overlay-help .viewport .viewport-inner {
1695 | width: 600px;
1696 | margin: auto;
1697 | padding: 20px 20px 80px 20px;
1698 | text-align: center;
1699 | letter-spacing: normal;
1700 | }
1701 |
1702 | .reveal > .overlay.overlay-help .viewport .viewport-inner .title {
1703 | font-size: 20px;
1704 | }
1705 |
1706 | .reveal > .overlay.overlay-help .viewport .viewport-inner table {
1707 | border: 1px solid #fff;
1708 | border-collapse: collapse;
1709 | font-size: 16px;
1710 | }
1711 |
1712 | .reveal > .overlay.overlay-help .viewport .viewport-inner table th,
1713 | .reveal > .overlay.overlay-help .viewport .viewport-inner table td {
1714 | width: 200px;
1715 | padding: 14px;
1716 | border: 1px solid #fff;
1717 | vertical-align: middle;
1718 | }
1719 |
1720 | .reveal > .overlay.overlay-help .viewport .viewport-inner table th {
1721 | padding-top: 20px;
1722 | padding-bottom: 20px;
1723 | }
1724 |
1725 | /*********************************************
1726 | * PLAYBACK COMPONENT
1727 | *********************************************/
1728 | .reveal .playback {
1729 | position: absolute;
1730 | left: 15px;
1731 | bottom: 20px;
1732 | z-index: 30;
1733 | cursor: pointer;
1734 | transition: all 400ms ease;
1735 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
1736 | }
1737 |
1738 | .reveal.overview .playback {
1739 | opacity: 0;
1740 | visibility: hidden;
1741 | }
1742 |
1743 | /*********************************************
1744 | * CODE HIGHLGIHTING
1745 | *********************************************/
1746 | .reveal .hljs table {
1747 | margin: initial;
1748 | }
1749 |
1750 | .reveal .hljs-ln-code,
1751 | .reveal .hljs-ln-numbers {
1752 | padding: 0;
1753 | border: 0;
1754 | }
1755 |
1756 | .reveal .hljs-ln-numbers {
1757 | opacity: 0.6;
1758 | padding-right: 0.75em;
1759 | text-align: right;
1760 | vertical-align: top;
1761 | }
1762 |
1763 | .reveal .hljs[data-line-numbers]:not([data-line-numbers='']) tr:not(.highlight-line) {
1764 | opacity: 0.4;
1765 | }
1766 |
1767 | /*********************************************
1768 | * ROLLING LINKS
1769 | *********************************************/
1770 | .reveal .roll {
1771 | display: inline-block;
1772 | line-height: 1.2;
1773 | overflow: hidden;
1774 | vertical-align: top;
1775 | -webkit-perspective: 400px;
1776 | perspective: 400px;
1777 | -webkit-perspective-origin: 50% 50%;
1778 | perspective-origin: 50% 50%;
1779 | }
1780 |
1781 | .reveal .roll:hover {
1782 | background: none;
1783 | text-shadow: none;
1784 | }
1785 |
1786 | .reveal .roll span {
1787 | display: block;
1788 | position: relative;
1789 | padding: 0 2px;
1790 | pointer-events: none;
1791 | transition: all 400ms ease;
1792 | -webkit-transform-origin: 50% 0%;
1793 | transform-origin: 50% 0%;
1794 | -webkit-transform-style: preserve-3d;
1795 | transform-style: preserve-3d;
1796 | -webkit-backface-visibility: hidden;
1797 | backface-visibility: hidden;
1798 | }
1799 |
1800 | .reveal .roll:hover span {
1801 | background: rgba(0, 0, 0, 0.5);
1802 | -webkit-transform: translate3d(0px, 0px, -45px) rotateX(90deg);
1803 | transform: translate3d(0px, 0px, -45px) rotateX(90deg);
1804 | }
1805 |
1806 | .reveal .roll span:after {
1807 | content: attr(data-title);
1808 | display: block;
1809 | position: absolute;
1810 | left: 0;
1811 | top: 0;
1812 | padding: 0 2px;
1813 | -webkit-backface-visibility: hidden;
1814 | backface-visibility: hidden;
1815 | -webkit-transform-origin: 50% 0%;
1816 | transform-origin: 50% 0%;
1817 | -webkit-transform: translate3d(0px, 110%, 0px) rotateX(-90deg);
1818 | transform: translate3d(0px, 110%, 0px) rotateX(-90deg);
1819 | }
1820 |
1821 | /*********************************************
1822 | * SPEAKER NOTES
1823 | *********************************************/
1824 | .reveal aside.notes {
1825 | display: none;
1826 | }
1827 |
1828 | .reveal .speaker-notes {
1829 | display: none;
1830 | position: absolute;
1831 | width: 33.3333333333%;
1832 | height: 100%;
1833 | top: 0;
1834 | left: 100%;
1835 | padding: 14px 18px 14px 18px;
1836 | z-index: 1;
1837 | font-size: 18px;
1838 | line-height: 1.4;
1839 | border: 1px solid rgba(0, 0, 0, 0.05);
1840 | color: #222;
1841 | background-color: #f5f5f5;
1842 | overflow: auto;
1843 | box-sizing: border-box;
1844 | text-align: left;
1845 | font-family: Helvetica, sans-serif;
1846 | -webkit-overflow-scrolling: touch;
1847 | }
1848 | .reveal .speaker-notes .notes-placeholder {
1849 | color: #ccc;
1850 | font-style: italic;
1851 | }
1852 | .reveal .speaker-notes:focus {
1853 | outline: none;
1854 | }
1855 | .reveal .speaker-notes:before {
1856 | content: 'Speaker notes';
1857 | display: block;
1858 | margin-bottom: 10px;
1859 | opacity: 0.5;
1860 | }
1861 |
1862 | .reveal.show-notes {
1863 | max-width: 75%;
1864 | overflow: visible;
1865 | }
1866 |
1867 | .reveal.show-notes .speaker-notes {
1868 | display: block;
1869 | }
1870 |
1871 | @media screen and (min-width: 1600px) {
1872 | .reveal .speaker-notes {
1873 | font-size: 20px;
1874 | }
1875 | }
1876 |
1877 | @media screen and (max-width: 1024px) {
1878 | .reveal.show-notes {
1879 | border-left: 0;
1880 | max-width: none;
1881 | max-height: 70%;
1882 | max-height: 70vh;
1883 | overflow: visible;
1884 | }
1885 | .reveal.show-notes .speaker-notes {
1886 | top: 100%;
1887 | left: 0;
1888 | width: 100%;
1889 | height: 42.8571428571%;
1890 | height: 30vh;
1891 | border: 0;
1892 | }
1893 | }
1894 |
1895 | @media screen and (max-width: 600px) {
1896 | .reveal.show-notes {
1897 | max-height: 60%;
1898 | max-height: 60vh;
1899 | }
1900 | .reveal.show-notes .speaker-notes {
1901 | top: 100%;
1902 | height: 66.6666666667%;
1903 | height: 40vh;
1904 | }
1905 | .reveal .speaker-notes {
1906 | font-size: 14px;
1907 | }
1908 | }
1909 |
1910 | /*********************************************
1911 | * ZOOM PLUGIN
1912 | *********************************************/
1913 | .zoomed .reveal *,
1914 | .zoomed .reveal *:before,
1915 | .zoomed .reveal *:after {
1916 | -webkit-backface-visibility: visible !important;
1917 | backface-visibility: visible !important;
1918 | }
1919 |
1920 | .zoomed .reveal .progress,
1921 | .zoomed .reveal .controls {
1922 | opacity: 0;
1923 | }
1924 |
1925 | .zoomed .reveal .roll span {
1926 | background: none;
1927 | }
1928 |
1929 | .zoomed .reveal .roll span:after {
1930 | visibility: hidden;
1931 | }
1932 |
--------------------------------------------------------------------------------