Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08f42a5f48 | ||
|
|
d5310d5a3f | ||
|
|
5217c574e9 | ||
|
|
348f4a9738 | ||
|
|
d54f1ca8b5 | ||
|
|
32ce52706f | ||
|
|
a1b75c0946 | ||
|
|
8f59da412c | ||
|
|
32ed2d1c14 | ||
|
|
6f58be6cbd | ||
|
|
4127c1e590 | ||
|
|
bdfdfca2e5 | ||
|
|
446c39b372 | ||
|
|
8df9535afa | ||
|
|
67d96a44de | ||
|
|
1c22e4e60b | ||
|
|
a5bedfbae4 | ||
|
|
b5c35eda24 | ||
|
|
164df8986f | ||
|
|
5a18c546ce | ||
|
|
7177c346ab | ||
|
|
c28e2e2eba | ||
|
|
e60f60261b | ||
|
|
744af1d8c7 | ||
|
|
54da406971 | ||
|
|
0635e99b5d | ||
|
|
da60b6af36 | ||
|
|
197c165a0a | ||
|
|
3327018a87 | ||
|
|
534c424d88 | ||
|
|
64530c20af | ||
|
|
de1524838f | ||
|
|
260bb174a2 | ||
|
|
8743f0776b | ||
|
|
1e139c16ec | ||
|
|
9bef40bcf3 | ||
|
|
8856ec7274 | ||
|
|
91889da042 | ||
|
|
747215042c | ||
|
|
f8496af3c9 | ||
|
|
0ad8577621 | ||
|
|
f796b14b70 | ||
|
|
b4bda2f163 | ||
|
|
0f1391e1b7 |
@@ -1,6 +1,6 @@
|
||||
# ThothII — Project State
|
||||
|
||||
Last updated: 2026-09-06.
|
||||
Last updated: 2026-09-24 (11-slide deck, consolidated project opening and final contacts).
|
||||
|
||||
This file is the short operational snapshot. Stable commands and the architecture mental model
|
||||
live in `AGENTS.md`; current design and runtime contracts live under `docs/architecture/`,
|
||||
@@ -12,6 +12,69 @@ Authentik, internal catalog/embedding services, and the PSD workspace repository
|
||||
`docs/operations/server-upgrade-gitea-workspace-v2.md`. Treat its operator gates and rollback
|
||||
requirements as mandatory; do not replace the running server stack in place.
|
||||
|
||||
## Presentation publishing
|
||||
|
||||
The deck now has 11 slides. The cover remains; “Where we started” is folded into
|
||||
“What we wanted to build”, now slide 2. Its speaker notes open with the four goals,
|
||||
and its existing popups include the disconnected sources and missing capabilities.
|
||||
Speaker notes now read as continuous prose without titles. On slide 2 and slides
|
||||
7–10, parenthesized cues identify the corresponding popup button at the start of
|
||||
its paragraph or dedicated popup text. The closing slide has no popup cues.
|
||||
The console reading area also omits slide and popup headings; popup controls
|
||||
remain below the preview. Edits preserve the content and keep the spoken text close
|
||||
to its previous length; actual delivery time still depends on rehearsal. The former
|
||||
penultimate “One datamart — many questions answered” slide is removed. The ThothII
|
||||
tour leads directly to “Thank you”, with both contact emails in bold 32px type.
|
||||
|
||||
Slide 8 now has four popups: Home page, Patient data (original PNG 3),
|
||||
Dashboard catalogue (PNG 6), and Brugada dashboard (PNG 7), numbered 1–4.
|
||||
Each has dedicated presenter notes; the opening explains the portal's breadth,
|
||||
the limited time, and Marco and Sara's availability for an in-person or remote
|
||||
follow-up. The overview uses a two-by-two grid; unused PNGs remain on disk.
|
||||
|
||||
Presentation PNG source (user instruction, 2026-09-23): all PNGs to import come from
|
||||
`/Users/mp/Desktop/ScreenshotPresentation/ThothIIScreens/`. This is the verified on-disk
|
||||
path the user refers to as `desktop/screenshot/presentation/thothIIscreens`.
|
||||
Copy supplied files unchanged into `presentation/deck/screenshots/thothii/`, preserving
|
||||
their embedded annotations. The 12 source PNGs retain their `01` through `12` filenames.
|
||||
The shortened slide 10 tour uses PNGs 01, 02, 05, 07, 08 and 09, numbered 1–6
|
||||
in the interface; unused images remain on disk. The 284-word presenter script
|
||||
covers all eight phases, explicitly introduces Human in the Loop, and includes
|
||||
the invitation to a longer presentation at the conference or remotely.
|
||||
The planned duration is 2:50, to be confirmed by rehearsal.
|
||||
Slide 11 prominently displays Marco Pancotti's and Sara Paratico's email addresses.
|
||||
Local preview uses `python3 -m http.server 8000 --bind 127.0.0.1 --directory presentation`.
|
||||
The first popup uses
|
||||
`01-StartingPoint.png`; popup 2 uses `02-Disambiguation01.png`. Refresh the image
|
||||
cache version when replacing a PNG. During the current editing session, refresh both
|
||||
operational browser windows after every modification, as explicitly requested.
|
||||
Popup 3 uses `05-CloseSchemaLinking.png`, popup 4 uses `07-CTE01.png`,
|
||||
popup 5 uses `08-FinalSQL.png`, and popup 6 uses `09-DatamartProduction.png`.
|
||||
The HTML references PNGs by relative URL: they are external static assets, not
|
||||
compiled or embedded. Keep `presentation/deck/screenshots/thothii/` with the deck;
|
||||
the Desktop source folder is not a runtime dependency.
|
||||
On 2026-09-23, the complete source folder was backed up to
|
||||
`/Users/mp/Desktop/ThothIIScreens-backup-20260923-FkCqV3/` and verified by SHA-256.
|
||||
The four unused PNGs (original prefixes 02, 05, 09, and 10) were removed from the
|
||||
source folder and, on 2026-09-24, from the deck after verifying backup equality.
|
||||
The backup retains all 16 original PNGs under their original names. Renumbering
|
||||
preserved each file's bytes; the existing Starting point images in the source and
|
||||
deck differ and were each retained unchanged.
|
||||
On 2026-09-24, annotated PNGs 08–12 were updated directly in the deck and verified
|
||||
visually; these deck copies are newer than the Desktop source copies. Preserve
|
||||
them when importing images. Their image cache version is `v=5`. Presenter notes
|
||||
for popups 09–12 are also updated in `presentation/deck/index.html`. The two Snagit
|
||||
comment boxes in PNG 12 share the same horizontal centre (1676.5 px in the
|
||||
3830 px source image); their text is centred within each box.
|
||||
|
||||
The user confirmed the public AritmoLab presentation and presenter console working on
|
||||
2026-09-20. Updates use the `feat/ai-etl-presentation` branch on Gitea: commit and push
|
||||
locally, then the user runs `git pull --ff-only origin feat/ai-etl-presentation` in
|
||||
`/var/www/aritmolab/presentation` on the remote server. No SSH tunnel is available;
|
||||
the user can operate an authenticated remote terminal in VS Code. Nginx routes are
|
||||
already configured, so ordinary slide updates need no Nginx reload. Exact URLs,
|
||||
paths, checks and cache behavior: [Presentation publishing](docs/operations/presentation-publishing.md).
|
||||
|
||||
## Current product shape
|
||||
|
||||
ThothII is a human-in-the-loop datamart builder with three independently built layers:
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# Updating the AritmoLab presentation
|
||||
|
||||
Last confirmed by the user: 2026-09-20, after deploying commit `5217c574`.
|
||||
The user reported both the presentation and presenter console working. This is
|
||||
the static presentation deployment, separate from the ThothII application stack.
|
||||
|
||||
## Locations
|
||||
|
||||
- Local worktree: `/Users/mp/projects/ThothII-presentazione`.
|
||||
- Source: `presentation/deck/` and `presentation/presenter/`.
|
||||
- Git remote: `origin`, `https://git.tylconsulting.it/mptyl/ThothII.git`.
|
||||
- Publication branch: `feat/ai-etl-presentation`.
|
||||
- Remote checkout: `/var/www/aritmolab/presentation`.
|
||||
|
||||
Presentation:
|
||||
|
||||
```text
|
||||
https://aritmolab.policlinicosandonato.it/presentation/
|
||||
```
|
||||
|
||||
Presenter console:
|
||||
|
||||
```text
|
||||
https://aritmolab.policlinicosandonato.it/presentation/presenter/
|
||||
```
|
||||
|
||||
Give these URLs as copyable plain text when providing instructions: the user
|
||||
reported difficulty clicking Markdown links.
|
||||
|
||||
## Routine update
|
||||
|
||||
1. In the local worktree, review and validate the requested presentation changes,
|
||||
then commit and push the intended files to `origin/feat/ai-etl-presentation`.
|
||||
Include new images, styles, scripts and presenter files. A remote pull cannot
|
||||
receive changes that remain uncommitted or unpushed. Leave unrelated changes alone.
|
||||
2. The user connects to the remote server and runs:
|
||||
|
||||
```bash
|
||||
cd /var/www/aritmolab/presentation
|
||||
git branch --show-current
|
||||
git status --short
|
||||
```
|
||||
|
||||
Continue only when the branch is `feat/ai-etl-presentation` and there are no
|
||||
unexpected local changes. If either differs, inspect before updating; do not
|
||||
reset, discard files or merge the presentation branch into another branch.
|
||||
|
||||
```bash
|
||||
git pull --ff-only origin feat/ai-etl-presentation
|
||||
git log -1 --oneline
|
||||
```
|
||||
|
||||
3. Refresh both browser windows. Static responses advertise
|
||||
`Cache-Control: public, max-age=300`, so a browser may reuse assets for five
|
||||
minutes. Use a hard refresh if the old version remains visible; bump asset
|
||||
query versions when changing cached JavaScript or CSS.
|
||||
4. When network access is available, verify the expected version at both URLs,
|
||||
including screenshots and synchronization of presenter and audience popups.
|
||||
Distinguish a successful Git push from a completed remote deployment.
|
||||
|
||||
Changes to slides, notes, images, CSS and JavaScript need no build, application
|
||||
restart or Nginx reload. A reload is necessary only if Nginx configuration changes.
|
||||
|
||||
## Remote access
|
||||
|
||||
The site was reachable from the user's Mac while connected to the VPN.
|
||||
Do not use SSH tunnels for this deployment. A non-interactive direct SSH attempt
|
||||
on 2026-09-20 failed authentication. The user offered an authenticated SSH session
|
||||
inside VS Code, then chose to execute the remote commands personally. The normal
|
||||
workflow is to push locally and provide the remote pull command to the user;
|
||||
do not assume autonomous remote access or ask for passwords in chat.
|
||||
|
||||
## Nginx routing already configured
|
||||
|
||||
The existing `/presentation` route redirects to `/presentation/`.
|
||||
The user supplied the original deck alias and subsequently confirmed successful
|
||||
setup of the two additional routes for the console:
|
||||
|
||||
| URL prefix | Filesystem alias |
|
||||
| --- | --- |
|
||||
| `/presentation/` | `/var/www/aritmolab/presentation/presentation/deck/` |
|
||||
| `/presentation/deck/` | `/var/www/aritmolab/presentation/presentation/deck/` |
|
||||
| `/presentation/presenter/` | `/var/www/aritmolab/presentation/presentation/presenter/` |
|
||||
|
||||
These are sibling `location ^~` blocks within the site's `server` block. The
|
||||
specific `deck/` route supports the console's relative links to fonts and slide
|
||||
iframes. The configuration filename was not recorded.
|
||||
|
||||
The presentation is intentionally public static content, bypassing Django and
|
||||
Authentik. Preserve its existing routing and protections: directory listings off,
|
||||
`index index.html`, GET/HEAD only, five-minute cache, `nosniff`, `SAMEORIGIN`, and
|
||||
`noindex, nofollow, noarchive`. Do not expose the entire repository or change
|
||||
unrelated application routes.
|
||||
|
||||
Only if routing needs diagnosis:
|
||||
|
||||
```bash
|
||||
sudo nginx -T 2>/dev/null | grep -n -A 12 -B 3 '/presentation'
|
||||
```
|
||||
|
||||
Only after an intentional configuration edit:
|
||||
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Artificial-brain controls and clearly separated explanatory callouts. */
|
||||
.arit.flow-slide .fcol.ai-hit { flex-basis: 62px; }
|
||||
.arit.flow-slide .brain-btn {
|
||||
width: 56px; height: 56px; border-radius: 9px;
|
||||
border: 1.5px solid var(--bordeaux); background: var(--surface);
|
||||
box-shadow: 0 3px 8px oklch(26% .02 15 / .1);
|
||||
}
|
||||
.arit.flow-slide .fcol.ai > .brain-btn { top: -54px; right: 6px; }
|
||||
.arit.flow-slide .brain-btn:hover, .arit.flow-slide .brain-btn:focus-visible {
|
||||
transform: none; background: var(--bordeaux-tint);
|
||||
box-shadow: 0 0 0 4px var(--bordeaux-tint);
|
||||
}
|
||||
.arit.flow-slide .brain-btn[aria-pressed="true"] { outline: 3px solid var(--bordeaux); outline-offset: 4px; }
|
||||
.artificial-brain { display: block; width: 46px; height: 46px; }
|
||||
.arit.flow-slide .brain-btn .ai-label {
|
||||
position: absolute; left: 50%; top: -24px; transform: translateX(-50%);
|
||||
display: grid; place-items: center; width: 42px; height: 25px;
|
||||
border: 1.5px solid var(--bordeaux); border-radius: 5px 5px 0 0;
|
||||
background: var(--bordeaux); color: var(--surface);
|
||||
font: 800 18px/1 var(--sans); letter-spacing: .04em;
|
||||
}
|
||||
.arit.flow-slide .brain-btn .ai-number {
|
||||
display: none;
|
||||
}
|
||||
/* The first AI control is in normal flow, but still anchors its AI label. */
|
||||
.arit.flow-slide .fcol.ai-hit .brain-btn { position: relative; top: auto; right: auto; }
|
||||
.ai-pop.is-ai .ai-pop-card {
|
||||
box-sizing: border-box;
|
||||
width: min(540px, calc(100vw - 48px)); max-height: calc(100vh - 48px);
|
||||
overflow-y: auto; border: 2px solid var(--bordeaux); background: var(--surface);
|
||||
transform: none; transition: none;
|
||||
}
|
||||
.ai-pop.is-ai .ai-line line { stroke: var(--bordeaux); stroke-width: 4; }
|
||||
.ai-pop.is-ai .ai-line circle { fill: var(--bordeaux); }
|
||||
.ai-pop-title .artificial-brain { width: 40px; height: 40px; }
|
||||
.ai-pop-title #aiPopBrain { flex: 0 0 44px; width: 44px; height: 44px; border: 0; background: none; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.arit.flow-slide .brain-btn { transition: none; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" stroke="#8f1204" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M29 9h-9l-8 8v8l-5 5v12l8 8h6l5 5h6V15zm6 0h9l8 8v8l5 5v12l-8 8h-6l-5 5h-6" fill="#f4dfe1"/>
|
||||
<path d="M20 17v9h12m-19 9h10v10m-7-1h7m21-27v9H32m19 9H41v10m7-1h-7M26 8V4m12 4V4M7 30H3m54 0h4M26 55v5m12-5v5"/>
|
||||
<rect x="25" y="27" width="14" height="14" rx="2" fill="#fdfcfc"/>
|
||||
<path d="M29 23v4m6-4v4m-6 14v4m6-4v4m-14-14h4m-4 6h4m14-6h4m-4 6h4"/>
|
||||
<circle cx="20" cy="17" r="2.5" fill="#fdfcfc"/><circle cx="44" cy="17" r="2.5" fill="#fdfcfc"/>
|
||||
<circle cx="23" cy="46" r="2.5" fill="#fdfcfc"/><circle cx="41" cy="46" r="2.5" fill="#fdfcfc"/>
|
||||
<path d="M30 32h4v4h-4z" fill="#8f1204" stroke="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 782 B |
@@ -0,0 +1,386 @@
|
||||
/* AritmoLab deck theme — consolidated light layout (variant 1) */
|
||||
:root {
|
||||
--bordeaux: #cb333b; --bordeaux-hover: #b7252d; --bordeaux-deep: #8f1204;
|
||||
--bordeaux-tint: #f4dfe1;
|
||||
--surface: #fdfcfc; --surface-alt: #f6f5f5; --surface-sunken: #ecebeb;
|
||||
--ink: #2a2426; --ink-muted: #6b6568;
|
||||
--line: #e3e0e1; --line-strong: #c9c5c6;
|
||||
--sans: 'Source Sans 3', system-ui, sans-serif;
|
||||
--display: 'Source Serif 4', Georgia, serif;
|
||||
--mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
.reveal { font-family: var(--sans); }
|
||||
.reveal .slides { text-align: left; }
|
||||
.reveal .slides section.arit {
|
||||
width: 100%; height: 100%; box-sizing: border-box;
|
||||
display: flex; flex-direction: column;
|
||||
background: var(--surface); color: var(--ink);
|
||||
text-align: left; padding: 0; font-family: var(--sans); font-size: 16px;
|
||||
}
|
||||
.reveal .arit *, .reveal .arit *::before, .reveal .arit *::after { box-sizing: border-box; }
|
||||
|
||||
/* ---------- chrome ---------- */
|
||||
.arit .head {
|
||||
flex: 0 0 104px; height: 104px; display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 40px; background: var(--surface); border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.arit .head img { height: 80px; display: block; }
|
||||
.arit .head-org { font: 700 11.5px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-muted); }
|
||||
.arit .band {
|
||||
height: 46px; display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 40px; background: var(--bordeaux); color: var(--surface); flex: 0 0 46px;
|
||||
}
|
||||
.arit .band-section { font: 700 13px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; }
|
||||
.arit .band select.jump {
|
||||
background: transparent; color: #fff; border: none;
|
||||
padding: 6px 12px; font: 700 11.5px/1.2 var(--sans);
|
||||
letter-spacing: .04em; cursor: pointer;
|
||||
}
|
||||
.arit .band select.jump option { color: var(--ink); background: #fff; }
|
||||
.arit .foot {
|
||||
flex: 0 0 34px; height: 34px; display: flex; align-items: center; justify-content: space-between; gap: 24px;
|
||||
padding: 0 40px; border-top: 1px solid var(--line);
|
||||
font: 600 10px/1.4 var(--sans); letter-spacing: .03em; color: var(--ink-muted);
|
||||
}
|
||||
.arit .foot span { white-space: nowrap; flex-shrink: 0; }
|
||||
.presenter-preview .arit .foot { display: none; }
|
||||
.arit .foot span:first-child { overflow: hidden; text-overflow: ellipsis; flex-shrink: 1; min-width: 0; }
|
||||
.arit .foot span:first-child { overflow: hidden; text-overflow: ellipsis; flex-shrink: 1; }
|
||||
.arit .sbody { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 30px 48px 22px; text-align: left; }
|
||||
.arit.title-slide .sbody { justify-content: center; }
|
||||
.arit.center-v .sbody { justify-content: center; }
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
.arit .kicker { font: 700 12px/1 var(--sans); letter-spacing: .16em; text-transform: uppercase; color: var(--bordeaux); margin-bottom: 14px; }
|
||||
.arit h1 { font: 700 46px/1.15 var(--sans); letter-spacing: -.01em; color: var(--ink); margin: 0; }
|
||||
.arit h2 { font: 700 30px/1.15 var(--sans); letter-spacing: -.01em; color: var(--ink); margin: 0 0 8px; }
|
||||
.arit .lead { font: 400 16px/1.5 var(--sans); color: var(--ink-muted); margin: 8px 0 0; }
|
||||
.arit.title-slide .lead { font-size: 19px; text-align: center; }
|
||||
.arit .hgap { height: 16px; flex: 0 0 16px; }
|
||||
|
||||
/* title slide */
|
||||
.arit .title-meta { display: flex; align-items: center; gap: 14px; margin-top: 28px; }
|
||||
.arit .title-meta .pill {
|
||||
display: inline-flex; align-items: center; height: 34px; padding: 0 18px;
|
||||
border-radius: 9999px; background: var(--bordeaux); color: #fdfcfc;
|
||||
font: 700 12.5px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
|
||||
}
|
||||
.arit .title-meta .what { font: 400 14.5px/1.5 var(--sans); color: var(--ink-muted); }
|
||||
.arit .speakers { margin-top: 22px; display: flex; flex-direction: column; gap: 8px; }
|
||||
.arit .speakers span { font: 600 15.5px/1.4 var(--sans); color: var(--ink); }
|
||||
.arit .event { margin: 16px 0 0; font: italic 400 15px/1.5 var(--display); color: var(--ink-muted); }
|
||||
.arit .event-where { margin: 8px 0 0; font: 600 14px/1.4 var(--sans); color: var(--ink-muted); }
|
||||
|
||||
/* quote */
|
||||
.arit .quote {
|
||||
position: relative; margin: 14px 0 20px; padding: 20px 26px 18px 68px; border: 1px solid var(--line);
|
||||
background: var(--surface-alt); border-radius: 8px;
|
||||
font: italic 400 19px/1.55 var(--display); color: var(--ink);
|
||||
}
|
||||
.arit .quote::before { content: '\201C'; position: absolute; left: 22px; top: 10px;
|
||||
font: 700 52px/1 var(--display); color: var(--bordeaux); }
|
||||
|
||||
/* EN translations alongside Italian clinical text */
|
||||
.arit .tr-grid { display: grid; grid-template-columns: 1.12fr 1fr; gap: 24px; align-items: center; }
|
||||
.arit .tr-grid .it, .arit .tr-grid .en { margin: 0; }
|
||||
.arit .tr-grid .en { padding-left: 20px; border-left: 1px solid var(--line-strong);
|
||||
font: 400 19px/1.5 var(--sans); color: var(--bordeaux-deep); }
|
||||
.arit .en-tag { display: inline-block; margin-right: 8px; padding: 2.5px 8px; border-radius: 4px;
|
||||
background: var(--bordeaux); color: #fdfcfc; font: 700 9.5px/1 var(--sans); letter-spacing: .12em;
|
||||
vertical-align: 2px; }
|
||||
.arit .points .g { color: var(--bordeaux-deep); }
|
||||
.arit .chipc .g { font-weight: 400; opacity: .78; }
|
||||
.arit .code .en { font: 400 13px/1.5 var(--sans); color: var(--bordeaux-deep); }
|
||||
.arit .code .en::before { content: 'EN'; display: inline-block; margin-right: 8px; padding: 2px 6px;
|
||||
border-radius: 3px; background: var(--bordeaux); color: #fdfcfc;
|
||||
font: 700 8.5px/1 var(--sans); letter-spacing: .1em; }
|
||||
|
||||
/* ---------- slide "The problem": challenges + outputs rail + ThothII right ---------- */
|
||||
.arit.problem .sbody { padding-top: 20px; padding-bottom: 14px; }
|
||||
.arit.problem .kicker { margin-bottom: 10px; }
|
||||
.arit.problem .quote { margin: 0 0 10px; padding: 12px 22px 10px 64px; font-size: 16.5px; }
|
||||
.arit.problem .tr-grid .en { font-size: 16.5px; }
|
||||
.arit.problem .stage { padding: 11px 10px; }
|
||||
.arit.problem .stage b { font-size: 13px; text-transform: uppercase; }
|
||||
.arit.problem .stage span { font-size: 10.5px; text-transform: uppercase; letter-spacing: .02em; }
|
||||
.arit.problem .stage.hot { background: #fdf1f2; border-color: var(--bordeaux); }
|
||||
.arit .brain-ai { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px;
|
||||
border-radius: 9999px; background: #fff; border: 1.5px solid var(--bordeaux); }
|
||||
.arit .brain-ai svg { width: 24px; height: 24px; color: var(--bordeaux); }
|
||||
.arit.problem .stage span.brain-ai { display: inline-flex; margin: 3px auto 0; }
|
||||
.arit.problem .midrow { display: flex; gap: 0; margin-top: 8px; align-items: stretch; }
|
||||
.arit.problem .concepts { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
||||
.arit.problem .concepts .citem { flex: 1; }
|
||||
.arit .concepts .lbl, .arit .rail .lbl { display: block; font: 700 10px/1 var(--mono); letter-spacing: .14em;
|
||||
text-transform: uppercase; color: var(--bordeaux); margin: 0 0 5px; }
|
||||
.arit .rail .lbl { padding-left: 16px; }
|
||||
.arit .citem { display: flex; gap: 12px; align-items: baseline; padding: 5px 0; border-bottom: 1px solid var(--line); }
|
||||
.arit .citem:last-child { border-bottom: none; }
|
||||
.arit .citem .n { font: 700 11px/1 var(--mono); color: var(--bordeaux); }
|
||||
.arit .citem b { font: 700 12.5px/1.3 var(--sans); color: var(--ink); text-transform: uppercase; letter-spacing: .03em; }
|
||||
.arit .citem span.d { font: 400 11px/1.4 var(--sans); color: var(--ink-muted); margin-left: 8px; }
|
||||
.arit .railwrap { position: relative; display: flex; justify-content: flex-end; }
|
||||
.arit .conn-drop, .arit .t-conn { position: absolute; width: 2px; background: var(--bordeaux); }
|
||||
.arit .conn-elbow { position: absolute; height: 2px; background: var(--bordeaux); }
|
||||
.arit .t-head { position: absolute; width: 0; height: 0; border: 5px solid transparent;
|
||||
border-bottom: 7px solid var(--bordeaux); transform: translateX(-50%); }
|
||||
.arit .rail { width: 540px; border-left: 2px solid var(--bordeaux); padding-top: 12px; }
|
||||
.arit .rail .row { display: flex; align-items: center; gap: 12px; padding: 5px 14px 5px 16px; position: relative; }
|
||||
.arit .rail .row::before { content: ''; position: absolute; left: -2px; top: 50%; width: 14px; height: 2px; background: var(--bordeaux); }
|
||||
.arit .rail .row .oicon { flex: 0 0 26px; width: 26px; height: 26px; color: var(--bordeaux); display: block; }
|
||||
.arit .rail .row b { font: 700 13px/1.2 var(--sans); color: var(--ink); text-transform: uppercase; letter-spacing: .03em; }
|
||||
.arit .rail .row span.d { font: 400 11px/1.3 var(--sans); color: var(--ink-muted); }
|
||||
.arit.problem .thoth-wrap { position: relative; justify-content: flex-end; margin-top: 10px; }
|
||||
.arit.problem .thoth-box { padding: 8px 14px; font-size: 11px; text-transform: uppercase; letter-spacing: .02em; }
|
||||
.arit.problem .thoth-box .brain-ai { margin-right: 9px; vertical-align: middle; }
|
||||
.arit .quote small { display: block; margin-top: 6px; font: 600 11px/1.4 var(--sans); font-style: normal;
|
||||
letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted); }
|
||||
|
||||
/* mini pipeline (problem / thoth slides) */
|
||||
.arit .pipeline { display: flex; align-items: stretch; gap: 12px; }
|
||||
.arit .stage { flex: 1; padding: 15px 12px; text-align: center; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-alt); }
|
||||
.arit .stage b { display: block; font: 700 14px/1.2 var(--sans); margin-bottom: 4px; }
|
||||
.arit .stage span { display: block; font: 400 11.5px/1.35 var(--sans); color: var(--ink-muted); }
|
||||
.arit .stage .tag { display: inline-block; margin-top: 6px; padding: 3px 10px; border-radius: 9999px;
|
||||
background: var(--bordeaux); color: #fdfcfc; font: 700 9.5px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; }
|
||||
.arit .stage.ai { background: var(--bordeaux-tint); border-color: var(--bordeaux); }
|
||||
.arit .arrow { align-self: center; color: var(--line-strong); font: 700 20px/1 var(--sans); }
|
||||
.arit .thoth-wrap { display: flex; justify-content: flex-end; margin-top: 18px; }
|
||||
.arit .thoth-col { width: 540px; text-align: center; }
|
||||
.arit .thoth-arrow { color: var(--line-strong); font: 700 18px/1 var(--sans); margin: 2px 0; }
|
||||
.arit .thoth-box { border: 1px solid var(--bordeaux); border-radius: 8px; padding: 12px 16px;
|
||||
background: var(--surface-alt); font: 600 13px/1.4 var(--sans); }
|
||||
.arit .thoth-box .tag { margin-right: 8px; }
|
||||
|
||||
/* points */
|
||||
.arit .points { list-style: none; margin: 0; padding: 0; }
|
||||
.arit .points li { position: relative; padding: 11px 0 11px 26px; border-bottom: 1px solid var(--line);
|
||||
font: 400 15px/1.45 var(--sans); color: var(--ink); }
|
||||
.arit .points li::before { content: ''; position: absolute; left: 0; top: 18px;
|
||||
width: 10px; height: 10px; border-radius: 9999px; background: var(--bordeaux); }
|
||||
.arit .points code, .arit .stat-note code, .arit .ai-pop-list code {
|
||||
font: 600 0.92em var(--mono); color: var(--bordeaux-deep); background: var(--surface-alt);
|
||||
padding: 1px 5px; border-radius: 4px; }
|
||||
|
||||
/* split + code panel */
|
||||
.arit .split { display: flex; gap: 40px; flex: 1; min-height: 0; }
|
||||
.arit .split .left { flex: 0 0 430px; display: flex; flex-direction: column; }
|
||||
.arit .split .right { flex: 1; min-width: 0; display: flex; }
|
||||
.arit.nlp .split .right { align-items: center; }
|
||||
.arit.nlp .code { flex: 0 0 auto; }
|
||||
.arit .code { width: 100%; display: flex; flex-direction: column; overflow: hidden;
|
||||
border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface-sunken); }
|
||||
.arit .code-head { flex: 0 0 38px; display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 16px; border-bottom: 1px solid var(--line); font: 600 11px/1 var(--mono); color: var(--ink-muted); }
|
||||
.arit .code pre { flex: 1; padding: 16px 18px; overflow: hidden; margin: 0;
|
||||
font: 400 13px/1.6 var(--mono); color: var(--ink); text-align: left; box-shadow: none; }
|
||||
.arit .code b { font-weight: 700; color: var(--bordeaux-deep); }
|
||||
.arit .code i { font-style: normal; color: #2f6f4f; }
|
||||
|
||||
/* stats — editorial numeral band */
|
||||
.arit .stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); }
|
||||
.arit .stat { padding: 8px 24px; display: flex; flex-direction: column; gap: 10px; }
|
||||
.arit .stat:first-child { padding-left: 0; }
|
||||
.arit .stat + .stat { border-left: 1px solid var(--line); }
|
||||
.arit .stat .num { font: 700 62px/1 var(--sans); color: var(--bordeaux); letter-spacing: -.02em; }
|
||||
.arit .stat .lbl { font: 600 14.5px/1.35 var(--sans); color: var(--ink); }
|
||||
.arit .stat .sub { font: 400 12px/1.45 var(--sans); color: var(--ink-muted); }
|
||||
.arit .stat-note { margin-top: 26px; font: 400 13px/1.5 var(--sans); color: var(--ink-muted); }
|
||||
|
||||
/* slide "What the text miner reads": slim numbers strip + elliptical component cycle */
|
||||
.arit.stats .sbody { padding-top: 22px; }
|
||||
.arit.stats .strip { display: flex; justify-content: space-between; align-items: baseline;
|
||||
border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 9px 2px; }
|
||||
.arit.stats .sstat { display: flex; align-items: baseline; gap: 9px; }
|
||||
.arit.stats .sstat .n { font: 700 25px/1 var(--sans); color: var(--bordeaux); letter-spacing: -.02em; }
|
||||
.arit.stats .sstat .l { font: 600 11.5px/1.2 var(--sans); color: var(--ink); }
|
||||
.arit.stats .cycle { position: relative; width: 1184px; height: 270px; margin: 4px auto 0; }
|
||||
.arit.stats .cycle-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.arit.stats .vcomp { position: absolute; transform: translate(-50%, -50%); width: 250px;
|
||||
text-align: center; background: var(--surface); padding: 4px 6px; }
|
||||
.arit.stats .vcomp .cicon { width: 46px; height: 46px; color: var(--bordeaux); display: block; margin: 0 auto 7px; }
|
||||
.arit.stats .vcomp b { display: block; font: 700 14.5px/1.25 var(--sans); text-transform: uppercase;
|
||||
letter-spacing: .04em; color: var(--ink); }
|
||||
.arit.stats .vcomp span.d { display: block; margin-top: 4px; font: 400 11.5px/1.4 var(--sans); color: var(--ink-muted); }
|
||||
.arit.stats .note-row { display: flex; gap: 30px; margin-top: 6px; }
|
||||
.arit.stats .nitem { flex: 1; position: relative; padding-left: 16px; font: 400 14px/1.45 var(--sans); color: var(--ink); }
|
||||
.arit.stats .nitem::before { content: ''; position: absolute; left: 0; top: 7px; width: 8px; height: 8px;
|
||||
border-radius: 9999px; background: var(--bordeaux); }
|
||||
.arit.stats .nitem b { color: var(--bordeaux-deep); }
|
||||
|
||||
/* divider */
|
||||
.arit.divider .sbody { justify-content: center; }
|
||||
.arit .numeral { font: 700 110px/1 var(--sans); color: var(--bordeaux); }
|
||||
.arit .divider-h { font: 700 40px/1.15 var(--sans); margin-top: 10px; }
|
||||
.arit .cue { margin-top: 22px; font: 600 12px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-muted); }
|
||||
|
||||
/* ontology slide: EN-first category pills + output panel */
|
||||
.arit .onto-split { flex: 0 0 auto; display: grid; grid-template-columns: 1.25fr 1fr;
|
||||
gap: 26px; align-items: stretch; margin-top: 14px; }
|
||||
.arit .tier-h { display: flex; align-items: center; font: 700 10px/1 var(--mono); letter-spacing: .14em;
|
||||
text-transform: uppercase; color: var(--bordeaux-deep); margin: 22px 0 9px; }
|
||||
.arit .onto-left .tier-h:first-child { margin-top: 0; }
|
||||
.arit .tier-h .cnt { margin-left: auto; font: 700 9px/1 var(--mono); color: var(--bordeaux);
|
||||
border: 1px solid var(--bordeaux); border-radius: 9999px; padding: 2px 7px; }
|
||||
.arit .cats { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.arit .cat { display: inline-flex; align-items: baseline; gap: 7px; border: 1px solid var(--bordeaux);
|
||||
border-radius: 9999px; padding: 6px 13px; background: #fff; white-space: nowrap; }
|
||||
.arit .cat b { font: 700 13px/1 var(--sans); color: var(--ink); }
|
||||
.arit .cat .it { font: 400 11px/1 var(--sans); color: var(--ink-muted); }
|
||||
.arit .cat.fill { background: var(--bordeaux-tint); }
|
||||
.arit .onto-points { margin-top: 18px; }
|
||||
.arit .onto-left .onto-points:first-child { margin-top: 0; }
|
||||
.arit .onto-left { display: flex; flex-direction: column; }
|
||||
.arit .onto-left .roi { margin-top: auto; }
|
||||
.arit .onto-left .onto-points { margin-bottom: 14px; }
|
||||
.arit .oitem { display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--line); }
|
||||
.arit .oitem:last-child { border-bottom: none; }
|
||||
.arit .oitem .n { flex: 0 0 22px; font: 700 11px/1.6 var(--mono); color: var(--bordeaux); }
|
||||
.arit .oitem b { font: 700 13px/1.35 var(--sans); color: var(--ink); }
|
||||
.arit .oitem .d { font: 400 12.5px/1.45 var(--sans); color: var(--ink-muted); }
|
||||
.arit .out-panel { border: 1px solid var(--bordeaux); background: var(--bordeaux-tint); border-radius: 10px;
|
||||
padding: 15px 18px 8px; align-self: stretch; display: flex; flex-direction: column; }
|
||||
.arit .out-h { display: block; font: 700 10px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
|
||||
color: var(--bordeaux-deep); margin-bottom: 10px; }
|
||||
.arit .out-item { display: flex; gap: 10px; padding: 13px 0; border-bottom: 1px solid rgba(143,18,4,.18); flex: 1; }
|
||||
.arit .out-item:last-child { border-bottom: none; }
|
||||
.arit .out-item .n { flex: 0 0 22px; font: 700 11px/1.6 var(--mono); color: var(--bordeaux-deep); }
|
||||
.arit .out-item b { font: 700 13px/1.35 var(--sans); color: var(--bordeaux-deep); }
|
||||
.arit .out-item .d { display: block; margin-top: 2px; font: 400 12.5px/1.45 var(--sans); color: var(--ink); }
|
||||
|
||||
/* flow */
|
||||
.arit .flow { margin-top: 16px; display: flex; align-items: stretch; gap: 14px; }
|
||||
.arit .zone { border-radius: 8px; padding: 30px 14px 14px; position: relative; }
|
||||
.arit .zone > .zlabel { position: absolute; top: 8px; left: 14px;
|
||||
font: 700 10px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; }
|
||||
.arit .zone.existed { flex: 0 0 168px; background: var(--surface-alt); border: 1px dashed var(--line-strong); }
|
||||
.arit .zone.existed > .zlabel { color: var(--ink-muted); }
|
||||
.arit .zone.built { flex: 1; background: #faf4f5; border: 1px solid #e7c3c6; }
|
||||
.arit .zone.built > .zlabel { color: var(--bordeaux); }
|
||||
.arit .fcol { flex: 1; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
|
||||
.arit .fbox { background: #fff; border: 1px solid var(--line-strong); border-radius: 6px; padding: 10px; text-align: center; }
|
||||
.arit .fbox b { display: block; font: 700 13.5px/1.25 var(--sans); }
|
||||
.arit .fbox span { display: block; margin-top: 3px; font: 400 11px/1.3 var(--sans); color: var(--ink-muted); }
|
||||
.arit .fbox.src { background: var(--surface-alt); border-color: var(--line); }
|
||||
.arit .fbox.src.future { background: transparent; border: 1.5px dashed var(--line-strong); }
|
||||
.arit .fbox.src.future b { color: var(--ink-muted); font-weight: 700; }
|
||||
.arit .fbox.src.future .plus { display: inline-flex; width: 16px; height: 16px; margin-right: 5px; vertical-align: -3px;
|
||||
align-items: center; justify-content: center; border-radius: 9999px; border: 1.5px solid var(--bordeaux);
|
||||
color: var(--bordeaux); font: 800 11px/1 var(--sans); }
|
||||
.arit .fbox.end { background: var(--bordeaux); border-color: var(--bordeaux); color: #fff; padding: 14px 10px; }
|
||||
.arit .fbox.end span { color: rgba(255,255,255,.85); }
|
||||
.arit .farrow { align-self: center; color: var(--bordeaux); font: 700 15px/1 var(--sans); }
|
||||
.arit .fcol.ai { position: relative; padding-top: 12px; }
|
||||
.arit .brain {
|
||||
position: absolute; top: -6px; right: 6px; z-index: 5;
|
||||
width: 30px; height: 30px; border-radius: 9999px;
|
||||
background: #fff; border: 1.5px solid var(--bordeaux);
|
||||
display: flex; align-items: center; justify-content: center; font-size: 15px;
|
||||
}
|
||||
.arit .fcap { font: italic 400 11px/1.4 var(--sans); color: var(--ink-muted); text-align: center; }
|
||||
.arit .fcol.ai-hit { flex: 0 0 44px; align-items: center; justify-content: center; gap: 8px; }
|
||||
.arit .fcol.ai-hit .brain-btn { position: static; }
|
||||
|
||||
/* clickable brains */
|
||||
button.brain-btn { cursor: pointer; padding: 0; transition: transform .15s ease, box-shadow .15s ease; }
|
||||
button.brain-btn:hover, button.brain-btn:focus-visible { transform: scale(1.18); box-shadow: 0 0 0 5px rgba(203,51,59,.14); outline: none; }
|
||||
|
||||
/* popup */
|
||||
.ai-pop { position: fixed; inset: 0; z-index: 100; background: rgba(42,36,38,.12);
|
||||
display: block; opacity: 0; pointer-events: none; transition: opacity .2s ease; }
|
||||
.ai-pop.open { opacity: 1; pointer-events: auto; }
|
||||
.ai-line { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.ai-line line { stroke: var(--bordeaux-deep); stroke-width: 5; stroke-linecap: round; filter: drop-shadow(0 0 2px #fff); }
|
||||
.ai-line circle { fill: var(--bordeaux-deep); stroke: #fff; stroke-width: 1.5; }
|
||||
.ai-pop-card { position: absolute; width: min(580px, 92vw); background: #fff; border: 1px solid var(--line);
|
||||
border-radius: 12px; box-shadow: 0 18px 50px rgba(42,36,38,.28); padding: 24px 28px 22px;
|
||||
transform: translateY(14px) scale(.95); transition: transform .24s cubic-bezier(.2,.9,.3,1.15); text-align: left; }
|
||||
.ai-pop.open .ai-pop-card { transform: none; }
|
||||
.ai-pop-kicker { font: 700 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--bordeaux); margin-bottom: 10px; }
|
||||
.ai-pop-title { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
||||
.ai-pop-title .brain { position: static; }
|
||||
.ai-pop-title .brain-btn { cursor: default; pointer-events: none; }
|
||||
.ai-pop-title b { font: 700 18px/1.3 var(--sans); color: var(--ink); font-family: var(--sans); }
|
||||
.ai-pop-card p { font: 400 14px/1.65 var(--sans); color: var(--ink); margin: 0; text-align: left; }
|
||||
.ai-pop-card p.lead { margin-bottom: 14px; }
|
||||
.ai-pop-list { list-style: none; display: flex; flex-direction: column; gap: 10px; margin: 0; padding: 0; }
|
||||
.ai-pop-list li { position: relative; padding-left: 20px; font: 400 13.5px/1.5 var(--sans); color: var(--ink); }
|
||||
.ai-pop-list li::before { content: ''; position: absolute; left: 0; top: 7px; width: 8px; height: 8px; border-radius: 9999px; background: var(--bordeaux); }
|
||||
.ai-pop-list b { font-weight: 800; }
|
||||
.ai-pop-list .n { color: var(--bordeaux-deep); font-weight: 800; }
|
||||
.roi { margin-top: 14px; border: 1.5px solid var(--bordeaux); background: #faf4f5; border-radius: 8px;
|
||||
padding: 10px 14px; font: 400 13.5px/1.55 var(--sans); color: var(--ink); }
|
||||
.roi-quiet { border-color: var(--line-strong); border-width: 1px; background: var(--surface-alt); }
|
||||
.roi .k { display: block; margin-bottom: 4px; font: 800 10.5px/1 var(--mono); letter-spacing: .14em;
|
||||
text-transform: uppercase; color: var(--bordeaux-deep); }
|
||||
.ai-pop-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; border: none;
|
||||
border-radius: 9999px; background: var(--surface-alt); font: 700 15px/1 var(--sans); color: var(--ink-muted); cursor: pointer; }
|
||||
.ai-pop-x:hover { background: var(--bordeaux-tint); color: var(--bordeaux-deep); }
|
||||
|
||||
/* chips on slides (wording TBD) */
|
||||
.arit .chip.tbd { color: var(--bordeaux-deep); border: 1px solid var(--bordeaux); background: #fff;
|
||||
border-radius: 9999px; padding: 6px 12px; font: 700 10.5px/1 var(--sans); letter-spacing: .06em; }
|
||||
|
||||
/* where we started — scattered orbit map */
|
||||
.arit .scatter { position: relative; flex: 1; min-height: 0; }
|
||||
.arit .todo-stack { position: absolute; z-index: 2; }
|
||||
.arit .todo { display: flex; align-items: center; gap: 12px; border: 2px dashed var(--bordeaux);
|
||||
border-radius: 8px; background: #fff; padding: 8px 12px; margin-bottom: 8px;
|
||||
box-shadow: 0 2px 8px rgba(42,36,38,.06); cursor: pointer; }
|
||||
.arit .todo .q { font: 800 22px/1 var(--sans); color: var(--bordeaux-deep); }
|
||||
.arit .todo .tx { display: flex; flex-direction: column; }
|
||||
.arit .todo b { font: 700 13.5px/1.3 var(--sans); color: var(--bordeaux-deep); }
|
||||
.arit .todo .tx span { font: 400 12px/1.4 var(--sans); color: var(--ink-muted); margin-top: 2px; }
|
||||
.arit .missing-title { position: absolute; font: 700 30px/1.2 var(--sans); color: var(--bordeaux-deep); }
|
||||
.arit .isle { position: absolute; display: flex; flex-direction: column; align-items: center; gap: 2px;
|
||||
cursor: pointer; transform: rotate(var(--rot, 0deg)); transition: transform .18s ease;
|
||||
border: none; background: none; font-family: var(--sans); width: 230px; }
|
||||
.arit .isle:hover, .arit .isle:focus-visible { transform: rotate(var(--rot, 0deg)) scale(1.05); outline: none; }
|
||||
.arit .isle .ico { width: var(--size, 120px); height: var(--size, 120px); color: var(--bordeaux); display: block; }
|
||||
.arit .isle .nm { font: 700 16px/1.3 var(--sans); color: var(--ink); margin-top: 4px; }
|
||||
.arit .isle .sc { font: 400 13.5px/1.4 var(--sans); color: var(--ink-muted); max-width: 240px; }
|
||||
.arit .hint { margin-top: 12px; font: italic 400 12px/1.4 var(--sans); color: var(--ink-muted); }
|
||||
|
||||
/* snapshot tours + chart placeholders */
|
||||
.arit .shots { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: 1fr 1fr;
|
||||
gap: 14px; flex: 1; min-height: 0; align-content: center; }
|
||||
.arit .shot { border: 1.5px dashed var(--line-strong); border-radius: 8px;
|
||||
background: var(--surface-alt); display: flex; align-items: center; justify-content: center;
|
||||
font: italic 400 12px/1.4 var(--sans); color: var(--ink-muted); min-height: 0; overflow: hidden; }
|
||||
.arit .charts { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; flex: 1; min-height: 0; align-content: center; }
|
||||
.arit .chart { border: 1.5px solid var(--line); border-radius: 8px; background: #fff; min-height: 0;
|
||||
display: flex; flex-direction: column; overflow: hidden; }
|
||||
.arit .chart b { display: block; padding: 12px 16px; font: 700 14px/1.3 var(--sans); color: var(--ink);
|
||||
border-bottom: 1px solid var(--line); }
|
||||
.arit .chart span { flex: 1; display: flex; align-items: center; justify-content: center;
|
||||
font: italic 400 12px/1.4 var(--sans); color: var(--ink-muted); text-align: center; padding: 0 20px; }
|
||||
|
||||
/* pillars strip (vision slide) */
|
||||
.arit .pillars { margin-top: auto; padding-top: 14px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
||||
.arit .pillars .agents { font: italic 600 12px/1.4 var(--sans); color: var(--bordeaux-deep);
|
||||
border: 1.5px dashed var(--bordeaux); border-radius: 8px; padding: 7px 12px; background: #faf4f5; }
|
||||
.arit .pillars .pillar { border: 1px solid var(--line-strong); border-radius: 9999px;
|
||||
padding: 6px 12px; font: 600 11.5px/1 var(--sans); color: var(--ink-muted); }
|
||||
.arit .pillars .pillar b { color: var(--ink); font-weight: 700; }
|
||||
|
||||
/* Reveal sets inline "display: block" on the presented section — beat it
|
||||
(author !important wins over inline) so our flex column layout applies */
|
||||
.reveal .slides section.arit.present { display: flex !important; }
|
||||
|
||||
/* hide reveal default noise on our chrome */
|
||||
.reveal .slides section.arit aside.notes { display: none; }
|
||||
|
||||
/* vision slide: tighter vertical rhythm to keep pillars clear of the footer */
|
||||
.arit.flow-slide .kicker { margin-bottom: 10px; }
|
||||
.arit.flow-slide h2 { margin-bottom: 6px; }
|
||||
.arit.flow-slide .lead { margin-top: 6px; font-size: 15px; }
|
||||
.arit.flow-slide .flow { margin-top: 10px; }
|
||||
.arit.flow-slide .zone { padding: 26px 14px 12px; }
|
||||
.arit.flow-slide .fbox { padding: 8px 10px; }
|
||||
.arit.flow-slide .fbox.end { padding: 12px 10px; }
|
||||
.arit.flow-slide .fcol { gap: 6px; }
|
||||
.arit.flow-slide .pillars { padding-top: 8px; }
|
||||
|
||||
/* popup: lead + list (missing-pieces popups) */
|
||||
.ai-pop-card p.lead { font-style: italic; color: var(--ink-muted); }
|
||||
@@ -0,0 +1,11 @@
|
||||
/* Embedded fonts — Source Sans 3 & Source Serif 4 (SIL Open Font License) */
|
||||
@font-face { font-family: 'Source Sans 3'; font-style: normal; font-weight: 400; font-display: swap;
|
||||
src: url('fonts/ss3-400.woff2') format('woff2'); }
|
||||
@font-face { font-family: 'Source Sans 3'; font-style: normal; font-weight: 600; font-display: swap;
|
||||
src: url('fonts/ss3-600.woff2') format('woff2'); }
|
||||
@font-face { font-family: 'Source Sans 3'; font-style: normal; font-weight: 700; font-display: swap;
|
||||
src: url('fonts/ss3-700.woff2') format('woff2'); }
|
||||
@font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 400; font-display: swap;
|
||||
src: url('fonts/ss4-400.woff2') format('woff2'); }
|
||||
@font-face { font-family: 'Source Serif 4'; font-style: italic; font-weight: 400; font-display: swap;
|
||||
src: url('fonts/ss4-italic-400.woff2') format('woff2'); }
|
||||
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,76 @@
|
||||
/* Opt-in, same-browser synchronization. The regular deck has no channel or session. */
|
||||
(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const session = params.get('session');
|
||||
const role = params.get('view');
|
||||
if (!session || !['preview', 'audience'].includes(role)) return;
|
||||
const channel = new BroadcastChannel(`aritmolab-presenter:${session}`);
|
||||
const sender = crypto.randomUUID();
|
||||
const storageKey = `aritmolab-state:${session}`;
|
||||
let applying = false;
|
||||
let pending = false;
|
||||
const send = (type, extra = {}) => channel.postMessage({ type, role, sender, ...extra });
|
||||
const api = window.PresentationControls;
|
||||
const valid = state => state && Number.isInteger(state.index) && state.index >= 0 && state.index < Reveal.getTotalSlides();
|
||||
function save(state) { try { localStorage.setItem(storageKey, JSON.stringify(state)); } catch {} }
|
||||
function publish() {
|
||||
if (applying || pending) return;
|
||||
pending = true;
|
||||
queueMicrotask(() => {
|
||||
pending = false;
|
||||
const state = api.snapshot();
|
||||
save(state);
|
||||
send('state', { state });
|
||||
});
|
||||
}
|
||||
function apply(state) {
|
||||
if (!valid(state)) return;
|
||||
applying = true;
|
||||
Reveal.slide(state.index);
|
||||
api.closePopup();
|
||||
if (state.popup) api.openPopup(state.popup);
|
||||
applying = false;
|
||||
save(api.snapshot());
|
||||
}
|
||||
function command(message) {
|
||||
if (message.action === 'next') Reveal.next();
|
||||
if (message.action === 'previous') Reveal.prev();
|
||||
if (message.action === 'slide' && valid({ index: message.index })) Reveal.slide(message.index);
|
||||
if (message.action === 'popup' && message.popup) api.openPopup(message.popup);
|
||||
if (message.action === 'close') api.closePopup();
|
||||
publish();
|
||||
}
|
||||
function ready() {
|
||||
Reveal.configure({ overview: false, pause: false });
|
||||
if (role === 'preview') {
|
||||
// Remove only the presenter's footer and its 34px, preserving the slide body geometry.
|
||||
document.documentElement.classList.add('presenter-preview');
|
||||
Reveal.configure({ height: 686 });
|
||||
}
|
||||
try { apply(JSON.parse(localStorage.getItem(storageKey))); } catch {}
|
||||
addEventListener('presentationchange', publish);
|
||||
Reveal.on('slidechanged', publish);
|
||||
channel.onmessage = ({ data }) => {
|
||||
if (!data || data.sender === sender) return;
|
||||
if (data.type === 'state') apply(data.state);
|
||||
if (data.type === 'command' && role === 'preview') command(data);
|
||||
if (data.type === 'request' && role === 'preview') publish();
|
||||
};
|
||||
if (role === 'preview') publish();
|
||||
else send('request');
|
||||
send('presence');
|
||||
setInterval(() => send('presence'), 1000);
|
||||
addEventListener('pagehide', () => send('departed'));
|
||||
if (role === 'audience') {
|
||||
document.title = 'AritmoLab | Audience';
|
||||
const button = document.createElement('button');
|
||||
button.textContent = 'Full screen';
|
||||
button.setAttribute('aria-label', 'Enter audience full screen');
|
||||
Object.assign(button.style, { position: 'fixed', right: '16px', top: '16px', zIndex: '200', padding: '10px 18px', borderRadius: '6px', border: '1px solid #b9afb1', background: '#faf7f7', color: '#482d34', cursor: 'pointer', font: '15px system-ui' });
|
||||
button.onclick = () => document.documentElement.requestFullscreen().catch(() => { button.textContent = 'Use the browser full-screen command'; });
|
||||
document.addEventListener('fullscreenchange', () => { button.hidden = Boolean(document.fullscreenElement); });
|
||||
document.body.append(button);
|
||||
}
|
||||
}
|
||||
if (Reveal.isReady()) ready(); else Reveal.on('ready', ready);
|
||||
})();
|
||||
@@ -0,0 +1,49 @@
|
||||
/* Ordered tour stops; screenshot originals keep their full aspect ratio. */
|
||||
.arit .screenshot-tour {
|
||||
display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px; flex: 1; min-height: 0; margin-top: 10px;
|
||||
}
|
||||
.arit .screenshot-tour-four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.arit .screenshot-tour button {
|
||||
display: flex; flex-direction: column; min-width: 0; min-height: 0; padding: 0;
|
||||
overflow: hidden; border: 1px solid var(--line-strong); border-radius: 8px;
|
||||
background: var(--surface-alt); color: var(--ink); cursor: pointer; text-align: left;
|
||||
}
|
||||
.arit .screenshot-tour button:hover { border-color: var(--bordeaux); background: var(--bordeaux-tint); }
|
||||
.arit .screenshot-tour button:focus-visible { outline: 3px solid var(--bordeaux); outline-offset: 3px; }
|
||||
.arit .screenshot-tour img {
|
||||
display: block; width: 100%; height: 0; flex: 1; min-height: 0; max-width: none; max-height: none;
|
||||
margin: 0; object-fit: contain; border: 0; box-shadow: none; background: #fff;
|
||||
}
|
||||
.arit .screenshot-tour span {
|
||||
display: flex; align-items: center; gap: 10px; padding: 9px 12px;
|
||||
border-top: 1px solid var(--line); font: 600 16px/1.2 var(--sans);
|
||||
}
|
||||
.arit .screenshot-tour b { color: var(--bordeaux); font: 700 22px/1 var(--sans); }
|
||||
.arit .screenshot-tour-compact {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-rows: repeat(2, minmax(0, 1fr)); gap: 10px;
|
||||
}
|
||||
.arit .screenshot-tour-compact span { gap: 8px; padding: 6px 10px; font-size: 14px; }
|
||||
.arit .screenshot-tour-compact b { font-size: 18px; }
|
||||
.screenshot-dialog {
|
||||
box-sizing: border-box; width: calc(100vw - 24px); height: calc(100vh - 24px);
|
||||
max-width: none; max-height: none; padding: 8px; margin: auto;
|
||||
border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface);
|
||||
color: var(--ink); font-family: var(--sans); overflow: hidden;
|
||||
}
|
||||
.screenshot-dialog[open] { display: flex; flex-direction: column; gap: 6px; }
|
||||
.screenshot-dialog::backdrop { background: rgb(42 36 38 / 70%); }
|
||||
.screenshot-dialog header { display: flex; align-items: center; gap: 12px; min-height: 32px; flex: 0 0 auto; }
|
||||
.screenshot-dialog h2 { margin: 0 auto 0 4px; font: 600 16px/1.2 var(--sans); }
|
||||
.screenshot-dialog button {
|
||||
border: 1px solid var(--line-strong); border-radius: 4px; background: var(--surface);
|
||||
color: var(--ink); padding: 5px 12px; font: 600 14px/1.2 var(--sans); cursor: pointer;
|
||||
}
|
||||
.screenshot-dialog button:hover { background: var(--bordeaux-tint); }
|
||||
.screenshot-dialog button:focus-visible { outline: 3px solid var(--bordeaux); outline-offset: 1px; }
|
||||
.screenshot-stage { position: relative; flex: 1; min-height: 0; }
|
||||
.screenshot-stage img { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.screenshot-stage img { object-fit: contain; }
|
||||
.screenshot-dialog .screenshot-error { margin: auto; text-align: center; }
|
||||
@media print { .screenshot-dialog { display: none !important; } }
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Uses the deck's shared popup state, so preview and audience stay synchronized. */
|
||||
(() => {
|
||||
const dialog = document.createElement('dialog');
|
||||
dialog.className = 'screenshot-dialog';
|
||||
dialog.setAttribute('aria-labelledby', 'screenshot-title');
|
||||
dialog.innerHTML = `<header><h2 id="screenshot-title"></h2><button type="button" aria-label="Close screenshot">Close · Esc</button></header>
|
||||
<div class="screenshot-stage">
|
||||
<img alt="">
|
||||
</div>
|
||||
<p class="screenshot-error" hidden>Screenshot unavailable. Close and reopen to retry.</p>`;
|
||||
document.body.append(dialog);
|
||||
const title = dialog.querySelector('h2');
|
||||
const image = dialog.querySelector('img');
|
||||
const error = dialog.querySelector('.screenshot-error');
|
||||
let active = null;
|
||||
let trigger = null;
|
||||
const close = () => {
|
||||
active = null;
|
||||
if (dialog.open) dialog.close();
|
||||
if (trigger?.closest('section.present')) trigger.focus({ preventScroll: true });
|
||||
trigger = null;
|
||||
};
|
||||
const requestClose = () => window.PresentationControls.closePopup();
|
||||
dialog.querySelector('button').addEventListener('click', requestClose);
|
||||
dialog.addEventListener('cancel', event => { event.preventDefault(); requestClose(); });
|
||||
dialog.addEventListener('click', event => { if (event.target === dialog) requestClose(); });
|
||||
image.addEventListener('error', () => { image.hidden = true; error.hidden = false; });
|
||||
window.ScreenshotTour = {
|
||||
get active() { return active; },
|
||||
open(button) {
|
||||
trigger = button;
|
||||
active = { kind: 'screenshot', key: button.dataset.screenshot };
|
||||
const total = button.closest('.screenshot-tour').querySelectorAll('[data-screenshot]').length;
|
||||
title.textContent = `${button.dataset.screenshotNumber} / ${total} · ${button.dataset.screenshotTitle}`;
|
||||
image.hidden = false;
|
||||
error.hidden = true;
|
||||
image.alt = button.dataset.screenshotTitle;
|
||||
image.src = button.querySelector('img').src;
|
||||
if (!dialog.open) dialog.showModal();
|
||||
window.dispatchEvent(new Event('presentationchange'));
|
||||
},
|
||||
close,
|
||||
};
|
||||
})();
|
||||
|
After Width: | Height: | Size: 630 KiB |
|
After Width: | Height: | Size: 680 KiB |
|
After Width: | Height: | Size: 551 KiB |
|
After Width: | Height: | Size: 721 KiB |
|
After Width: | Height: | Size: 723 KiB |
|
After Width: | Height: | Size: 834 KiB |
|
After Width: | Height: | Size: 596 KiB |
|
After Width: | Height: | Size: 470 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 976 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 959 KiB |
|
After Width: | Height: | Size: 948 KiB |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 802 KiB |
|
After Width: | Height: | Size: 820 KiB |
|
After Width: | Height: | Size: 925 KiB |
|
After Width: | Height: | Size: 707 KiB |
|
After Width: | Height: | Size: 472 KiB |
@@ -0,0 +1,127 @@
|
||||
/* A rectangular optical enlargement, anchored to the original diagram element. */
|
||||
.arit .flow .lens-target {
|
||||
display: block; width: 100%; color: inherit; font: inherit; cursor: pointer;
|
||||
appearance: none;
|
||||
transition: transform 180ms cubic-bezier(.16, 1, .3, 1), box-shadow 180ms ease,
|
||||
border-color 180ms ease, opacity 220ms ease;
|
||||
}
|
||||
.arit .flow .lens-target:hover {
|
||||
transform: translateY(-2px); border-color: var(--bordeaux);
|
||||
box-shadow: 0 5px 12px oklch(26% .02 15 / .13);
|
||||
}
|
||||
.arit .flow .lens-target:focus-visible, .arit .slide-lens-close:focus-visible {
|
||||
outline: 3px solid var(--bordeaux); outline-offset: 4px;
|
||||
}
|
||||
.arit .flow .zone.built, .arit .flow > .ai-hit, .arit .flow .fbox:not(.lens-target) {
|
||||
transition: opacity 220ms ease;
|
||||
}
|
||||
.arit.lens-active .flow .zone.built:not(:has(.lens-source)), .arit.lens-active .flow > .ai-hit,
|
||||
.arit.lens-active .flow .zone.built:has(.lens-source) :is(.fbox:not(.lens-source), .farrow, .brain-btn, .fcap),
|
||||
.arit.lens-active .flow .zone.existed:not(.lens-source) { opacity: .18; }
|
||||
.arit .flow .lens-source {
|
||||
border-color: var(--bordeaux); box-shadow: 0 0 0 3px var(--bordeaux-tint);
|
||||
}
|
||||
.arit .oitem.lens-target {
|
||||
width: 100%; color: inherit; font: inherit; text-align: left;
|
||||
background: transparent; border: 0; border-bottom: 1px solid var(--line);
|
||||
appearance: none; cursor: pointer;
|
||||
transition: background 180ms ease, opacity 220ms ease;
|
||||
}
|
||||
.arit .oitem.lens-target:last-child { border-bottom: 0; }
|
||||
.arit .oitem.lens-target:hover, .arit .oitem.lens-source { background: var(--bordeaux-tint); }
|
||||
.arit .oitem.lens-target:focus-visible { outline: 3px solid var(--bordeaux); outline-offset: 3px; }
|
||||
.arit.crisis .crisis-points { margin-bottom: 4px; }
|
||||
.arit.crisis .oitem { padding: 12px 10px; gap: 14px; }
|
||||
.arit.crisis .oitem .n { flex-basis: 28px; font-size: 15px; }
|
||||
.arit.crisis .oitem b { display: block; font-size: 18px; }
|
||||
.arit.crisis .oitem .d { display: block; margin-top: 3px; font-size: 16px; }
|
||||
.arit.crisis .sbody > .roi { padding: 12px 18px; font-size: 22px; line-height: 1.3; font-weight: 600; }
|
||||
.arit.crisis .sbody > .roi .k { margin-bottom: 8px; font-size: 14px; line-height: 1.2; }
|
||||
.arit.crisis.lens-active .crisis-points .oitem:not(.lens-source),
|
||||
.arit.crisis.lens-active .sbody > .roi { opacity: .18; }
|
||||
.arit.crisis.lens-active .sbody > :is(h2, .kicker) { opacity: 0; }
|
||||
.arit.validation .onto-split :is(.oitem, .roi, .out-panel) { transition: opacity 220ms ease; }
|
||||
.arit.validation.lens-active .onto-split :is(.oitem:not(.lens-source), .roi, .out-panel:not(.lens-source)) { opacity: .18; }
|
||||
.arit.validation .out-panel.lens-target {
|
||||
appearance: none; width: 100%; color: inherit; font: inherit; text-align: left;
|
||||
cursor: pointer;
|
||||
transition: transform 180ms cubic-bezier(.16, 1, .3, 1), box-shadow 180ms ease,
|
||||
background 180ms ease, opacity 220ms ease;
|
||||
}
|
||||
.arit.validation .out-panel.lens-target:hover {
|
||||
transform: translateY(-2px); background: var(--surface-alt);
|
||||
box-shadow: 0 0 0 3px var(--bordeaux-tint), 0 6px 16px oklch(26% .02 15 / .14);
|
||||
}
|
||||
.arit.validation .out-panel.lens-target:focus-visible {
|
||||
outline: 3px solid var(--bordeaux); outline-offset: 4px;
|
||||
}
|
||||
.arit.validation .out-panel.lens-source { box-shadow: 0 0 0 3px var(--bordeaux-tint); }
|
||||
.arit.validation .out-panel.lens-target .out-item:last-of-type { border-bottom: none; }
|
||||
.arit .slide-lens-guides {
|
||||
position: absolute; inset: 0; width: 100%; height: 100%;
|
||||
pointer-events: none; z-index: 8; overflow: visible;
|
||||
}
|
||||
.arit .slide-lens-guides path {
|
||||
fill: none; stroke: var(--bordeaux); stroke-width: 1; opacity: .45;
|
||||
}
|
||||
.arit .slide-lens {
|
||||
position: absolute; z-index: 9; transform-origin: 0 0;
|
||||
border: 3px solid var(--bordeaux); border-radius: 9px;
|
||||
background: var(--surface-alt); padding: 9px;
|
||||
box-shadow: 0 22px 48px oklch(26% .02 15 / .2),
|
||||
0 4px 10px oklch(26% .02 15 / .12),
|
||||
inset 0 0 0 2px var(--surface), inset 0 0 0 3px var(--line-strong);
|
||||
}
|
||||
/* A beveled rim and a restrained reflection suggest a lens without an icon. */
|
||||
.arit .slide-lens::after {
|
||||
content: ''; position: absolute; inset: 5px; pointer-events: none;
|
||||
border-radius: 4px; border: 1px solid oklch(99% .004 20 / .85);
|
||||
background: linear-gradient(125deg, oklch(99% .004 20 / .45), transparent 26%,
|
||||
transparent 78%, oklch(99% .004 20 / .25));
|
||||
}
|
||||
.arit .slide-lens-content { overflow: hidden; width: 100%; height: 100%; border-radius: 2px; }
|
||||
.arit .slide-lens .slide-lens-copy {
|
||||
margin: 0; transform-origin: 0 0; border-radius: 1px;
|
||||
border-color: transparent; position: static;
|
||||
}
|
||||
.arit .slide-lens .slide-lens-copy.zone { position: relative; }
|
||||
.arit .slide-lens.has-details .slide-lens-content { overflow-y: auto; }
|
||||
.arit .slide-lens-detail { padding: 16px 18px; text-align: left; color: var(--ink); }
|
||||
.arit .slide-lens-detail h3 {
|
||||
margin: 0 0 12px; font: 700 30px/1.15 var(--sans); color: var(--bordeaux-deep);
|
||||
}
|
||||
.arit .slide-lens-detail p { margin: 0 0 10px; font: 400 17px/1.35 var(--sans); }
|
||||
.arit .slide-lens-detail p:last-child { margin-bottom: 0; }
|
||||
.arit .slide-lens-detail p b {
|
||||
display: block; margin-bottom: 4px; font-weight: 700; color: var(--bordeaux-deep);
|
||||
}
|
||||
.arit .slide-lens-detail .detail-intro { font-size: 19px; line-height: 1.3; }
|
||||
.arit .slide-lens-detail .detail-example { padding-top: 12px; border-top: 1px solid var(--line-strong); }
|
||||
.arit .slide-lens-caption {
|
||||
position: absolute; left: 0; top: -28px; color: var(--bordeaux-deep);
|
||||
font: 700 11px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase;
|
||||
}
|
||||
.arit .slide-lens-close {
|
||||
position: absolute; right: -15px; top: -15px; z-index: 1;
|
||||
display: grid; place-items: center; width: 30px; height: 30px;
|
||||
border: 1px solid var(--bordeaux); border-radius: 5px;
|
||||
padding: 0; background: var(--surface); color: var(--bordeaux-deep);
|
||||
font: 400 22px/1 var(--sans); cursor: pointer;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.arit.validation .out-panel.lens-target { transition: none; }
|
||||
.arit.validation .out-panel.lens-target:hover { transform: none; }
|
||||
.arit.validation .onto-split :is(.oitem, .roi, .out-panel) { transition: none; }
|
||||
.arit .flow .zone, .arit .flow > .ai-hit, .arit .flow .fbox { transition: none; }
|
||||
.arit .flow .lens-target:hover { transform: none; }
|
||||
}
|
||||
@media print {
|
||||
.arit.crisis.lens-active .sbody > :is(h2, .kicker) { opacity: 1; }
|
||||
.arit.crisis.lens-active .crisis-points .oitem:not(.lens-source),
|
||||
.arit.crisis.lens-active .sbody > .roi { opacity: 1; }
|
||||
.arit.validation.lens-active .onto-split :is(.oitem, .roi, .out-panel) { opacity: 1; }
|
||||
.arit .slide-lens, .arit .slide-lens-guides { display: none !important; }
|
||||
.arit.lens-active .flow .zone, .arit.lens-active .flow > .ai-hit,
|
||||
.arit.lens-active .flow .fbox,
|
||||
.arit.lens-active .flow .zone.built:has(.lens-source) :is(.fbox, .farrow, .brain-btn, .fcap) { opacity: 1; }
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Reuse with a source button carrying its id and data-lens="source-id".
|
||||
Content is cloned from the source, with optional explanatory content in a template. */
|
||||
(() => {
|
||||
let current = null;
|
||||
const changed = () => window.dispatchEvent(new Event('presentationchange'));
|
||||
const reducedMotion = () => matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
function layout() {
|
||||
if (!current) return;
|
||||
const { source, slide, frame, copy, guides, caption, detailed } = current;
|
||||
const sr = slide.getBoundingClientRect();
|
||||
const rect = source.getBoundingClientRect();
|
||||
const scale = sr.width / slide.offsetWidth;
|
||||
const area = (slide.querySelector('.flow') || slide.querySelector('.sbody')).getBoundingClientRect();
|
||||
const centered = source.dataset.lensPlacement === 'center';
|
||||
const x = (rect.left - sr.left) / scale, y = (rect.top - sr.top) / scale;
|
||||
const w = rect.width / scale, h = rect.height / scale;
|
||||
const isGroup = source.classList.contains('zone');
|
||||
const areaTop = isGroup || detailed ? 160 : (area.top - sr.top) / scale;
|
||||
const areaHeight = isGroup || detailed ? slide.offsetHeight - areaTop - 50 : area.height / scale;
|
||||
// Choose the roomier side so the original stays visible, including middle/right nodes.
|
||||
const margin = 65, gap = 38;
|
||||
const roomLeft = x - gap - margin;
|
||||
const roomRight = slide.offsetWidth - margin - (x + w + gap);
|
||||
const onRight = roomRight >= roomLeft;
|
||||
const maxWidth = centered ? Math.min(760, slide.offsetWidth - 2 * margin)
|
||||
: Math.min(slide.offsetWidth * .58, Math.max(roomLeft, roomRight));
|
||||
const zoom = Math.min(4, (maxWidth - 24) / w, (areaHeight - 40) / h);
|
||||
let width = w * zoom + 24, height = h * zoom + 24;
|
||||
if (detailed) {
|
||||
width = maxWidth;
|
||||
Object.assign(copy.style, { width: `${width - 24}px`, height: 'auto', transform: 'none' });
|
||||
height = Math.min(copy.scrollHeight + 24, areaHeight - 16);
|
||||
}
|
||||
const left = centered ? (slide.offsetWidth - width) / 2 : onRight
|
||||
? Math.min(slide.offsetWidth - width - margin, Math.max(slide.offsetWidth * .36, x + w + gap))
|
||||
: Math.max(margin, Math.min(slide.offsetWidth * .36, x - gap - width));
|
||||
const top = areaTop + (areaHeight - height) / 2 + 10;
|
||||
Object.assign(frame.style, { left: `${left}px`, top: `${top}px`, width: `${width}px`, height: `${height}px` });
|
||||
if (!detailed) Object.assign(copy.style, { width: `${w}px`, height: `${h}px`, transform: `scale(${zoom})` });
|
||||
const zoneTitle = source.dataset.lensCaption || source.closest('.zone')?.querySelector('.zlabel')?.textContent || 'Detail';
|
||||
caption.textContent = detailed ? zoneTitle : `${zoneTitle} · ${zoom.toFixed(1)}×`;
|
||||
guides.setAttribute('viewBox', `0 0 ${slide.offsetWidth} ${slide.offsetHeight}`);
|
||||
const sourceEdge = onRight ? x + w : x;
|
||||
const lensEdge = onRight ? left : left + width;
|
||||
guides.querySelector('path').setAttribute('d', `M ${sourceEdge} ${y} L ${lensEdge} ${top} M ${sourceEdge} ${y + h} L ${lensEdge} ${top + height}`);
|
||||
current.origin = `translate(${x - left}px, ${y - top}px) scale(${w / width}, ${h / height})`;
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!current) return;
|
||||
const { slide, source, trigger, frame, guides, animation } = current;
|
||||
animation?.cancel();
|
||||
const restoreFocus = frame.contains(document.activeElement);
|
||||
frame.remove(); guides.remove();
|
||||
slide.classList.remove('lens-active'); source.classList.remove('lens-source');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
current = null;
|
||||
if (restoreFocus && slide.classList.contains('present')) trigger.focus({ preventScroll: true });
|
||||
changed();
|
||||
}
|
||||
|
||||
function open(trigger) {
|
||||
const source = document.getElementById(trigger.dataset.lens);
|
||||
const slide = source?.closest('section');
|
||||
if (!source || !slide?.classList.contains('present')) return;
|
||||
close();
|
||||
const details = source.querySelector('template.lens-details');
|
||||
const title = source.dataset.lensTitle || source.querySelector('b').textContent;
|
||||
const copy = document.createElement('div');
|
||||
if (details) {
|
||||
copy.className = 'slide-lens-detail';
|
||||
const heading = document.createElement('h3');
|
||||
heading.textContent = title;
|
||||
copy.append(heading, details.content.cloneNode(true));
|
||||
} else {
|
||||
copy.className = source.className;
|
||||
copy.classList.remove('lens-target', 'lens-source');
|
||||
copy.innerHTML = source.innerHTML;
|
||||
}
|
||||
copy.querySelectorAll('[id]').forEach(el => el.removeAttribute('id'));
|
||||
copy.querySelectorAll('button').forEach(el => el.remove());
|
||||
copy.classList.add('slide-lens-copy');
|
||||
const frame = document.createElement('div');
|
||||
frame.id = 'slide-lens'; frame.className = 'slide-lens';
|
||||
frame.classList.toggle('has-details', Boolean(details));
|
||||
frame.setAttribute('role', 'region');
|
||||
frame.setAttribute('aria-label', details ? `${title} explained` : `${title} enlarged`);
|
||||
const caption = document.createElement('div'); caption.className = 'slide-lens-caption';
|
||||
const content = document.createElement('div'); content.className = 'slide-lens-content';
|
||||
content.append(copy);
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.className = 'slide-lens-close'; closeButton.textContent = '×';
|
||||
closeButton.setAttribute('aria-label', 'Close detail');
|
||||
frame.append(caption, content, closeButton);
|
||||
const guides = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
guides.classList.add('slide-lens-guides'); guides.setAttribute('aria-hidden', 'true');
|
||||
guides.append(document.createElementNS(guides.namespaceURI, 'path'));
|
||||
slide.append(guides, frame);
|
||||
current = { source, slide, trigger, frame, copy, guides, caption, detailed: Boolean(details) };
|
||||
layout();
|
||||
source.classList.add('lens-source'); slide.classList.add('lens-active');
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
if (!reducedMotion()) {
|
||||
current.animation = frame.animate([
|
||||
{ transform: current.origin, opacity: .5 },
|
||||
{ transform: 'none', opacity: 1 },
|
||||
], { duration: 480, easing: 'cubic-bezier(.16, 1, .3, 1)' });
|
||||
guides.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 480 });
|
||||
}
|
||||
changed();
|
||||
}
|
||||
window.SlideLens = {
|
||||
open, close, layout,
|
||||
get active() { return current ? { kind: 'lens', key: current.source.id } : null; },
|
||||
};
|
||||
addEventListener('resize', layout);
|
||||
document.addEventListener('transitionend', event => {
|
||||
if (event.target === current?.source && event.propertyName === 'transform') layout();
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,21 @@
|
||||
## Contributing
|
||||
Please keep the [issue tracker](https://github.com/hakimel/reveal.js/issues) limited to **bug reports**.
|
||||
|
||||
|
||||
### General Questions and Support
|
||||
If you have questions about how to use reveal.js the best place to ask is in the [Discussions](https://github.com/hakimel/reveal.js/discussions). Anything that isn't a bug report should be posted as a dicussion instead.
|
||||
|
||||
|
||||
### Bug Reports
|
||||
When reporting a bug make sure to include information about which browser and operating system you are on as well as the necessary steps to reproduce the issue. If possible please include a link to a sample presentation where the bug can be tested.
|
||||
|
||||
|
||||
### Pull Requests
|
||||
- Should be submitted from a feature/topic branch (not your master)
|
||||
- Should follow the coding style of the file you work in, most importantly:
|
||||
- Tabs to indent
|
||||
- Single-quoted strings
|
||||
|
||||
|
||||
### Plugins
|
||||
Please do not submit plugins as pull requests. They should be maintained in their own separate repository. More information here: https://github.com/hakimel/reveal.js/wiki/Plugin-Guidelines
|
||||
@@ -0,0 +1 @@
|
||||
github: [hakimel]
|
||||
@@ -0,0 +1,31 @@
|
||||
name: tests
|
||||
|
||||
on:
|
||||
- push
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version:
|
||||
- 18
|
||||
- 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- run: npm install
|
||||
- run: npm run build --if-present
|
||||
- run: npm test
|
||||
env:
|
||||
CI: true
|
||||
@@ -0,0 +1,11 @@
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
*.eml
|
||||
out/
|
||||
.DS_Store
|
||||
.svn
|
||||
log/*.log
|
||||
tmp/**
|
||||
node_modules/
|
||||
.sass-cache
|
||||
@@ -0,0 +1,5 @@
|
||||
/test
|
||||
/examples
|
||||
.github
|
||||
.sass-cache
|
||||
gulpfile.js
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2011-2024 Hakim El Hattab, http://hakim.se, and reveal.js contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,50 @@
|
||||
<p align="center">
|
||||
<a href="https://revealjs.com">
|
||||
<img src="https://hakim-static.s3.amazonaws.com/reveal-js/logo/v1/reveal-black-text-sticker.png" alt="reveal.js" width="500">
|
||||
</a>
|
||||
<br><br>
|
||||
<a href="https://github.com/hakimel/reveal.js/actions"><img src="https://github.com/hakimel/reveal.js/workflows/tests/badge.svg"></a>
|
||||
<a href="https://slides.com/"><img src="https://s3.amazonaws.com/static.slid.es/images/slides-github-banner-320x40.png?1" alt="Slides" width="160" height="20"></a>
|
||||
</p>
|
||||
|
||||
reveal.js is an open source HTML presentation framework. It enables anyone with a web browser to create beautiful presentations for free. Check out the live demo at [revealjs.com](https://revealjs.com/).
|
||||
|
||||
The framework comes with a powerful feature set including [nested slides](https://revealjs.com/vertical-slides/), [Markdown support](https://revealjs.com/markdown/), [Auto-Animate](https://revealjs.com/auto-animate/), [PDF export](https://revealjs.com/pdf-export/), [speaker notes](https://revealjs.com/speaker-view/), [LaTeX typesetting](https://revealjs.com/math/), [syntax highlighted code](https://revealjs.com/code/) and an [extensive API](https://revealjs.com/api/).
|
||||
|
||||
---
|
||||
|
||||
Want to create reveal.js presentation in a graphical editor? Try <https://slides.com>. It's made by the same people behind reveal.js.
|
||||
|
||||
---
|
||||
|
||||
### Sponsors
|
||||
Hakim's open source work is supported by <a href="https://github.com/sponsors/hakimel">GitHub sponsors</a>. Special thanks to:
|
||||
<div align="center">
|
||||
<table>
|
||||
<td align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=revealjs&utm_source=github">
|
||||
<div>
|
||||
<img src="https://user-images.githubusercontent.com/629429/151508669-efb4c3b3-8fe3-45eb-8e47-e9510b5f0af1.svg" width="290" alt="WorkOS">
|
||||
</div>
|
||||
<b>Your app, enterprise-ready.</b>
|
||||
<div>
|
||||
<sub>Start selling to enterprise customers with just a few lines of code. Add Single Sign-On (and more) in minutes instead of months.</sup>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Getting started
|
||||
- 🚀 [Install reveal.js](https://revealjs.com/installation)
|
||||
- 👀 [View the demo presentation](https://revealjs.com/demo)
|
||||
- 📖 [Read the documentation](https://revealjs.com/markup/)
|
||||
- 🖌 [Try the visual editor for reveal.js at Slides.com](https://slides.com/)
|
||||
- 🎬 [Watch the reveal.js video course (paid)](https://revealjs.com/course)
|
||||
|
||||
---
|
||||
<div align="center">
|
||||
MIT licensed | Copyright © 2011-2024 Hakim El Hattab, https://hakim.se
|
||||
</div>
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Layout helpers.
|
||||
*/
|
||||
|
||||
// Stretch an element vertically based on available space
|
||||
.reveal .stretch,
|
||||
.reveal .r-stretch {
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.reveal pre.stretch code,
|
||||
.reveal pre.r-stretch code {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// Text that auto-fits its container
|
||||
.reveal .r-fit-text {
|
||||
display: inline-block; // https://github.com/rikschennink/fitty#performance
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Stack multiple elements on top of each other
|
||||
.reveal .r-stack {
|
||||
display: grid;
|
||||
grid-template-rows: 100%;
|
||||
}
|
||||
|
||||
.reveal .r-stack > * {
|
||||
grid-area: 1/1;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
// Horizontal and vertical stacks
|
||||
.reveal .r-vstack,
|
||||
.reveal .r-hstack {
|
||||
display: flex;
|
||||
|
||||
img, video {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.reveal .r-vstack {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.reveal .r-hstack {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// Naming based on tailwindcss
|
||||
.reveal .items-stretch { align-items: stretch; }
|
||||
.reveal .items-start { align-items: flex-start; }
|
||||
.reveal .items-center { align-items: center; }
|
||||
.reveal .items-end { align-items: flex-end; }
|
||||
|
||||
.reveal .justify-between { justify-content: space-between; }
|
||||
.reveal .justify-around { justify-content: space-around; }
|
||||
.reveal .justify-start { justify-content: flex-start; }
|
||||
.reveal .justify-center { justify-content: center; }
|
||||
.reveal .justify-end { justify-content: flex-end; }
|
||||
@@ -0,0 +1,166 @@
|
||||
|
||||
@media print {
|
||||
html:not(.print-pdf) {
|
||||
overflow: visible;
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
html:not(.print-pdf) .reveal {
|
||||
background: #fff;
|
||||
font-size: 20pt;
|
||||
|
||||
.controls,
|
||||
.state-background,
|
||||
.progress,
|
||||
.backgrounds,
|
||||
.slide-number {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
p, td, li {
|
||||
font-size: 20pt!important;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
color: #000!important;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
text-align: left;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
h1 { font-size: 28pt !important; }
|
||||
h2 { font-size: 24pt !important; }
|
||||
h3 { font-size: 22pt !important; }
|
||||
h4 { font-size: 22pt !important; font-variant: small-caps; }
|
||||
h5 { font-size: 21pt !important; }
|
||||
h6 { font-size: 20pt !important; font-style: italic; }
|
||||
|
||||
a:link,
|
||||
a:visited {
|
||||
color: #000 !important;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
ul, ol, div, p {
|
||||
visibility: visible;
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: block;
|
||||
overflow: visible;
|
||||
margin: 0;
|
||||
text-align: left !important;
|
||||
}
|
||||
pre,
|
||||
table {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
pre code {
|
||||
padding: 20px;
|
||||
}
|
||||
blockquote {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.slides {
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-top: 0 !important;
|
||||
padding: 0 !important;
|
||||
zoom: 1 !important;
|
||||
transform: none !important;
|
||||
|
||||
overflow: visible !important;
|
||||
display: block !important;
|
||||
|
||||
text-align: left !important;
|
||||
perspective: none;
|
||||
|
||||
perspective-origin: 50% 50%;
|
||||
}
|
||||
.slides section {
|
||||
visibility: visible !important;
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
display: block !important;
|
||||
overflow: visible !important;
|
||||
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-top: 0 !important;
|
||||
padding: 60px 20px !important;
|
||||
z-index: auto !important;
|
||||
|
||||
opacity: 1 !important;
|
||||
|
||||
page-break-after: always !important;
|
||||
|
||||
transform-style: flat !important;
|
||||
transform: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
.slides section.stack {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.slides section:last-of-type {
|
||||
page-break-after: avoid !important;
|
||||
}
|
||||
.slides section .fragment {
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.r-fit-text {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
section img {
|
||||
display: block;
|
||||
margin: 15px 0px;
|
||||
background: rgba(255,255,255,1);
|
||||
border: 1px solid #666;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
section small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
max-height: 100%;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
font-size: 15pt;
|
||||
}
|
||||
|
||||
.hljs .hljs-ln-numbers {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hljs td {
|
||||
font-size: inherit !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* This stylesheet is used to print reveal.js
|
||||
* presentations to PDF.
|
||||
*
|
||||
* https://revealjs.com/pdf-export/
|
||||
*/
|
||||
|
||||
html.reveal-print {
|
||||
* {
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
& {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 auto !important;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
float: none !important;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Remove any elements not needed in print. */
|
||||
.nestedarrow,
|
||||
.reveal .controls,
|
||||
.reveal .progress,
|
||||
.reveal .playback,
|
||||
.reveal.overview,
|
||||
.state-background {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.reveal pre code {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.reveal {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
.reveal .slides {
|
||||
position: static;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
zoom: 1 !important;
|
||||
pointer-events: initial;
|
||||
|
||||
left: auto;
|
||||
top: auto;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
|
||||
overflow: visible;
|
||||
display: block;
|
||||
|
||||
perspective: none;
|
||||
perspective-origin: 50% 50%;
|
||||
}
|
||||
|
||||
.reveal .slides .pdf-page {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.reveal .slides .pdf-page:last-of-type {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.reveal .slides section {
|
||||
visibility: visible !important;
|
||||
display: block !important;
|
||||
position: absolute !important;
|
||||
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
min-height: 1px;
|
||||
|
||||
opacity: 1 !important;
|
||||
|
||||
transform-style: flat !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.reveal section.stack {
|
||||
position: relative !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
page-break-after: avoid !important;
|
||||
height: auto !important;
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
.reveal img {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Slide backgrounds are placed inside of their slide when exporting to PDF */
|
||||
.reveal .backgrounds {
|
||||
display: none;
|
||||
}
|
||||
.reveal .slide-background {
|
||||
display: block !important;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: auto !important;
|
||||
}
|
||||
|
||||
/* Display slide speaker notes when 'showNotes' is enabled */
|
||||
.reveal.show-notes {
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
}
|
||||
.reveal .speaker-notes-pdf {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* Layout option which makes notes appear on a separate page */
|
||||
.reveal .speaker-notes-pdf[data-layout="separate-page"] {
|
||||
position: relative;
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
padding: 20px;
|
||||
page-break-after: always;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Display slide numbers when 'slideNumber' is enabled */
|
||||
.reveal .slide-number-pdf {
|
||||
display: block;
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* This accessibility tool is not useful in PDF and breaks it visually */
|
||||
.aria-status {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
## Dependencies
|
||||
|
||||
Themes are written using Sass to keep things modular and reduce the need for repeated selectors across files. Make sure that you have the reveal.js development environment installed before proceeding: https://revealjs.com/installation/#full-setup
|
||||
|
||||
## Creating a Theme
|
||||
|
||||
To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled from Sass to CSS (see the [gulpfile](https://github.com/hakimel/reveal.js/blob/master/gulpfile.js)) when you run `npm run build -- css-themes`.
|
||||
|
||||
Each theme file does four things in the following order:
|
||||
|
||||
1. **Include [/css/theme/template/mixins.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/mixins.scss)**
|
||||
Shared utility functions.
|
||||
|
||||
2. **Include [/css/theme/template/settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss)**
|
||||
Declares a set of custom variables that the template file (step 4) expects. Can be overridden in step 3.
|
||||
|
||||
3. **Override**
|
||||
This is where you override the default theme. Either by specifying variables (see [settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss) for reference) or by adding any selectors and styles you please.
|
||||
|
||||
4. **Include [/css/theme/template/theme.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/theme.scss)**
|
||||
The template theme file which will generate final CSS output based on the currently defined variables.
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Beige theme for reveal.js.
|
||||
*
|
||||
* Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/league-gothic/league-gothic.css);
|
||||
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$mainColor: #333;
|
||||
$headingColor: #333;
|
||||
$headingTextShadow: none;
|
||||
$backgroundColor: #f7f3de;
|
||||
$linkColor: #8b743d;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
$selectionBackgroundColor: rgba(79, 64, 28, 0.99);
|
||||
$heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15);
|
||||
|
||||
$overlayElementBgColor: 0, 0, 0;
|
||||
$overlayElementFgColor: 240, 240, 240;
|
||||
|
||||
// Background generator
|
||||
@mixin bodyBackground() {
|
||||
@include radial-gradient( rgba(247,242,211,1), rgba(255,255,255,1) );
|
||||
}
|
||||
|
||||
// Change text colors against dark slide backgrounds
|
||||
@include dark-bg-text-color(#fff);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Black compact & high contrast reveal.js theme, with headers not in capitals.
|
||||
*
|
||||
* By Peter Kehl. Based on black.(s)css by Hakim El Hattab, http://hakim.se
|
||||
*
|
||||
* - Keep the source similar to black.css - for easy comparison.
|
||||
* - $mainFontSize controls code blocks, too (although under some ratio).
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/source-sans-pro/source-sans-pro.css);
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$backgroundColor: #000000;
|
||||
|
||||
$mainColor: #fff;
|
||||
$headingColor: #fff;
|
||||
|
||||
$mainFontSize: 42px;
|
||||
$mainFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingTextShadow: none;
|
||||
$headingLetterSpacing: normal;
|
||||
$headingTextTransform: uppercase;
|
||||
$headingFontWeight: 600;
|
||||
$linkColor: #42affa;
|
||||
$linkColorHover: lighten( $linkColor, 15% );
|
||||
$selectionBackgroundColor: lighten( $linkColor, 25% );
|
||||
|
||||
$heading1Size: 2.5em;
|
||||
$heading2Size: 1.6em;
|
||||
$heading3Size: 1.3em;
|
||||
$heading4Size: 1.0em;
|
||||
|
||||
// Change text colors against light slide backgrounds
|
||||
@include light-bg-text-color(#000);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Black theme for reveal.js. This is the opposite of the 'white' theme.
|
||||
*
|
||||
* By Hakim El Hattab, http://hakim.se
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/source-sans-pro/source-sans-pro.css);
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$backgroundColor: #191919;
|
||||
|
||||
$mainColor: #fff;
|
||||
$headingColor: #fff;
|
||||
|
||||
$mainFontSize: 42px;
|
||||
$mainFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingTextShadow: none;
|
||||
$headingLetterSpacing: normal;
|
||||
$headingTextTransform: uppercase;
|
||||
$headingFontWeight: 600;
|
||||
$linkColor: #42affa;
|
||||
$linkColorHover: lighten( $linkColor, 15% );
|
||||
$selectionBackgroundColor: rgba( $linkColor, 0.75 );
|
||||
|
||||
$heading1Size: 2.5em;
|
||||
$heading2Size: 1.6em;
|
||||
$heading3Size: 1.3em;
|
||||
$heading4Size: 1.0em;
|
||||
|
||||
// Change text colors against light slide backgrounds
|
||||
@include light-bg-text-color(#222);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Blood theme for reveal.js
|
||||
* Author: Walther http://github.com/Walther
|
||||
*
|
||||
* Designed to be used with highlight.js theme
|
||||
* "monokai_sublime.css" available from
|
||||
* https://github.com/isagalaev/highlight.js/
|
||||
*
|
||||
* For other themes, change $codeBackground accordingly.
|
||||
*
|
||||
*/
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
// Include theme-specific fonts
|
||||
|
||||
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,700,300italic,700italic);
|
||||
|
||||
// Colors used in the theme
|
||||
$blood: #a23;
|
||||
$coal: #222;
|
||||
$codeBackground: #23241f;
|
||||
|
||||
$backgroundColor: $coal;
|
||||
|
||||
// Main text
|
||||
$mainFont: Ubuntu, 'sans-serif';
|
||||
$mainColor: #eee;
|
||||
|
||||
// Headings
|
||||
$headingFont: Ubuntu, 'sans-serif';
|
||||
$headingTextShadow: 2px 2px 2px $coal;
|
||||
|
||||
// h1 shadow, borrowed humbly from
|
||||
// (c) Default theme by Hakim El Hattab
|
||||
$heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15);
|
||||
|
||||
// Links
|
||||
$linkColor: $blood;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
|
||||
// Text selection
|
||||
$selectionBackgroundColor: $blood;
|
||||
$selectionColor: #fff;
|
||||
|
||||
// Change text colors against dark slide backgrounds
|
||||
@include light-bg-text-color(#222);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
|
||||
// some overrides after theme template import
|
||||
|
||||
.reveal p {
|
||||
font-weight: 300;
|
||||
text-shadow: 1px 1px $coal;
|
||||
}
|
||||
|
||||
section.has-light-background {
|
||||
p, h1, h2, h3, h4 {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.reveal h1,
|
||||
.reveal h2,
|
||||
.reveal h3,
|
||||
.reveal h4,
|
||||
.reveal h5,
|
||||
.reveal h6 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.reveal p code {
|
||||
background-color: $codeBackground;
|
||||
display: inline-block;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.reveal small code {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Dracula Dark theme for reveal.js.
|
||||
* Based on https://draculatheme.com
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
$systemFontsSansSerif: -apple-system,
|
||||
BlinkMacSystemFont,
|
||||
avenir next,
|
||||
avenir,
|
||||
segoe ui,
|
||||
helvetica neue,
|
||||
helvetica,
|
||||
Cantarell,
|
||||
Ubuntu,
|
||||
roboto,
|
||||
noto,
|
||||
arial,
|
||||
sans-serif;
|
||||
$systemFontsMono: Menlo,
|
||||
Consolas,
|
||||
Monaco,
|
||||
Liberation Mono,
|
||||
Lucida Console,
|
||||
monospace;
|
||||
|
||||
/**
|
||||
* Dracula colors by Zeno Rocha
|
||||
* https://draculatheme.com/contribute
|
||||
*/
|
||||
html * {
|
||||
color-profile: sRGB;
|
||||
rendering-intent: auto;
|
||||
}
|
||||
|
||||
$background: #282A36;
|
||||
$foreground: #F8F8F2;
|
||||
$selection: #44475A;
|
||||
$comment: #6272A4;
|
||||
$red: #FF5555;
|
||||
$orange: #FFB86C;
|
||||
$yellow: #F1FA8C;
|
||||
$green: #50FA7B;
|
||||
$purple: #BD93F9;
|
||||
$cyan: #8BE9FD;
|
||||
$pink: #FF79C6;
|
||||
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$mainColor: $foreground;
|
||||
$headingColor: $purple;
|
||||
$headingTextShadow: none;
|
||||
$headingTextTransform: none;
|
||||
$backgroundColor: $background;
|
||||
$linkColor: $pink;
|
||||
$linkColorHover: $cyan;
|
||||
$selectionBackgroundColor: $selection;
|
||||
$inlineCodeColor: $green;
|
||||
$listBulletColor: $cyan;
|
||||
|
||||
$mainFont: $systemFontsSansSerif;
|
||||
$codeFont: "Fira Code", $systemFontsMono;
|
||||
|
||||
// Change text colors against light slide backgrounds
|
||||
@include light-bg-text-color($background);
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
|
||||
// Define additional color effects based on Dracula spec
|
||||
// https://spec.draculatheme.com/
|
||||
:root {
|
||||
--r-bold-color: #{$orange};
|
||||
--r-italic-color: #{$yellow};
|
||||
--r-inline-code-color: #{$inlineCodeColor};
|
||||
--r-list-bullet-color: #{$listBulletColor};
|
||||
}
|
||||
|
||||
.reveal {
|
||||
strong, b {
|
||||
color: var(--r-bold-color);
|
||||
}
|
||||
em, i, blockquote {
|
||||
color: var(--r-italic-color);
|
||||
}
|
||||
code {
|
||||
color: var(--r-inline-code-color);
|
||||
}
|
||||
// Dracula colored list bullets and numbers
|
||||
ul, ol {
|
||||
li::marker {
|
||||
color: var(--r-list-bullet-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* League theme for reveal.js.
|
||||
*
|
||||
* This was the default theme pre-3.0.0.
|
||||
*
|
||||
* Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/league-gothic/league-gothic.css);
|
||||
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$headingTextShadow: 0px 0px 6px rgba(0,0,0,0.2);
|
||||
$heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15);
|
||||
|
||||
// Background generator
|
||||
@mixin bodyBackground() {
|
||||
@include radial-gradient( rgba(28,30,32,1), rgba(85,90,95,1) );
|
||||
}
|
||||
|
||||
// Change text colors against light slide backgrounds
|
||||
@include light-bg-text-color(#222);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Solarized Dark theme for reveal.js.
|
||||
* Author: Achim Staebler
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/league-gothic/league-gothic.css);
|
||||
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
|
||||
|
||||
/**
|
||||
* Solarized colors by Ethan Schoonover
|
||||
*/
|
||||
|
||||
// Solarized colors
|
||||
$base03: #002b36;
|
||||
$base02: #073642;
|
||||
$base01: #586e75;
|
||||
$base00: #657b83;
|
||||
$base0: #839496;
|
||||
$base1: #93a1a1;
|
||||
$base2: #eee8d5;
|
||||
$base3: #fdf6e3;
|
||||
$yellow: #b58900;
|
||||
$orange: #cb4b16;
|
||||
$red: #dc322f;
|
||||
$magenta: #d33682;
|
||||
$violet: #6c71c4;
|
||||
$blue: #268bd2;
|
||||
$cyan: #2aa198;
|
||||
$green: #859900;
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$mainColor: $base1;
|
||||
$headingColor: $base2;
|
||||
$headingTextShadow: none;
|
||||
$backgroundColor: $base03;
|
||||
$linkColor: $blue;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
$selectionBackgroundColor: $magenta;
|
||||
|
||||
// Change text colors against light slide backgrounds
|
||||
@include light-bg-text-color(#222);
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Black theme for reveal.js.
|
||||
*
|
||||
* Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(https://fonts.googleapis.com/css?family=Montserrat:700);
|
||||
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic);
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$backgroundColor: #111;
|
||||
|
||||
$mainFont: 'Open Sans', sans-serif;
|
||||
$linkColor: #e7ad52;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
$headingFont: 'Montserrat', Impact, sans-serif;
|
||||
$headingTextShadow: none;
|
||||
$headingLetterSpacing: -0.03em;
|
||||
$headingTextTransform: none;
|
||||
$selectionBackgroundColor: #e7ad52;
|
||||
|
||||
// Change text colors against light slide backgrounds
|
||||
@include light-bg-text-color(#222);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* A simple theme for reveal.js presentations, similar
|
||||
* to the default theme. The accent color is brown.
|
||||
*
|
||||
* This theme is Copyright (C) 2012-2013 Owen Versteeg, http://owenversteeg.com - it is MIT licensed.
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$mainFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
|
||||
$mainColor: #000;
|
||||
$headingFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
|
||||
$headingColor: #383D3D;
|
||||
$headingTextShadow: none;
|
||||
$headingTextTransform: none;
|
||||
$backgroundColor: #F0F1EB;
|
||||
$linkColor: #51483D;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
$selectionBackgroundColor: #26351C;
|
||||
|
||||
$overlayElementBgColor: 0, 0, 0;
|
||||
$overlayElementFgColor: 240, 240, 240;
|
||||
|
||||
.reveal a {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
// Change text colors against dark slide backgrounds
|
||||
@include dark-bg-text-color(#fff);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* A simple theme for reveal.js presentations, similar
|
||||
* to the default theme. The accent color is darkblue.
|
||||
*
|
||||
* This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed.
|
||||
* reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(https://fonts.googleapis.com/css?family=News+Cycle:400,700);
|
||||
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$mainFont: 'Lato', sans-serif;
|
||||
$mainColor: #000;
|
||||
$headingFont: 'News Cycle', Impact, sans-serif;
|
||||
$headingColor: #000;
|
||||
$headingTextShadow: none;
|
||||
$headingTextTransform: none;
|
||||
$backgroundColor: #fff;
|
||||
$linkColor: #00008B;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
$selectionBackgroundColor: rgba(0, 0, 0, 0.99);
|
||||
|
||||
$overlayElementBgColor: 0, 0, 0;
|
||||
$overlayElementFgColor: 240, 240, 240;
|
||||
|
||||
// Change text colors against dark slide backgrounds
|
||||
@include dark-bg-text-color(#fff);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Sky theme for reveal.js.
|
||||
*
|
||||
* Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(https://fonts.googleapis.com/css?family=Quicksand:400,700,400italic,700italic);
|
||||
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700);
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$mainFont: 'Open Sans', sans-serif;
|
||||
$mainColor: #333;
|
||||
$headingFont: 'Quicksand', sans-serif;
|
||||
$headingColor: #333;
|
||||
$headingLetterSpacing: -0.08em;
|
||||
$headingTextShadow: none;
|
||||
$backgroundColor: #f7fbfc;
|
||||
$linkColor: #3b759e;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
$selectionBackgroundColor: #134674;
|
||||
|
||||
$overlayElementBgColor: 0, 0, 0;
|
||||
$overlayElementFgColor: 240, 240, 240;
|
||||
|
||||
// Fix links so they are not cut off
|
||||
.reveal a {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
// Background generator
|
||||
@mixin bodyBackground() {
|
||||
@include radial-gradient( #add9e4, #f7fbfc );
|
||||
}
|
||||
|
||||
// Change text colors against dark slide backgrounds
|
||||
@include dark-bg-text-color(#fff);
|
||||
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Solarized Light theme for reveal.js.
|
||||
* Author: Achim Staebler
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/league-gothic/league-gothic.css);
|
||||
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
|
||||
|
||||
|
||||
/**
|
||||
* Solarized colors by Ethan Schoonover
|
||||
*/
|
||||
html * {
|
||||
color-profile: sRGB;
|
||||
rendering-intent: auto;
|
||||
}
|
||||
|
||||
// Solarized colors
|
||||
$base03: #002b36;
|
||||
$base02: #073642;
|
||||
$base01: #586e75;
|
||||
$base00: #657b83;
|
||||
$base0: #839496;
|
||||
$base1: #93a1a1;
|
||||
$base2: #eee8d5;
|
||||
$base3: #fdf6e3;
|
||||
$yellow: #b58900;
|
||||
$orange: #cb4b16;
|
||||
$red: #dc322f;
|
||||
$magenta: #d33682;
|
||||
$violet: #6c71c4;
|
||||
$blue: #268bd2;
|
||||
$cyan: #2aa198;
|
||||
$green: #859900;
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$mainColor: $base00;
|
||||
$headingColor: $base01;
|
||||
$headingTextShadow: none;
|
||||
$backgroundColor: $base3;
|
||||
$linkColor: $blue;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
$selectionBackgroundColor: $magenta;
|
||||
|
||||
$overlayElementBgColor: 0, 0, 0;
|
||||
$overlayElementFgColor: 240, 240, 240;
|
||||
|
||||
// Background generator
|
||||
// @mixin bodyBackground() {
|
||||
// @include radial-gradient( rgba($base3,1), rgba(lighten($base3, 20%),1) );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* White compact & high contrast reveal.js theme, with headers not in capitals.
|
||||
*
|
||||
* By Peter Kehl. Based on white.(s)css by Hakim El Hattab, http://hakim.se
|
||||
*
|
||||
* - Keep the source similar to black.css - for easy comparison.
|
||||
* - $mainFontSize controls code blocks, too (although under some ratio).
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/source-sans-pro/source-sans-pro.css);
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$backgroundColor: #fff;
|
||||
|
||||
$mainColor: #000;
|
||||
$headingColor: #000;
|
||||
|
||||
$mainFontSize: 42px;
|
||||
$mainFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingTextShadow: none;
|
||||
$headingLetterSpacing: normal;
|
||||
$headingTextTransform: uppercase;
|
||||
$headingFontWeight: 600;
|
||||
$linkColor: #2a76dd;
|
||||
$linkColorHover: lighten( $linkColor, 15% );
|
||||
$selectionBackgroundColor: lighten( $linkColor, 25% );
|
||||
|
||||
$heading1Size: 2.5em;
|
||||
$heading2Size: 1.6em;
|
||||
$heading3Size: 1.3em;
|
||||
$heading4Size: 1.0em;
|
||||
|
||||
$overlayElementBgColor: 0, 0, 0;
|
||||
$overlayElementFgColor: 240, 240, 240;
|
||||
|
||||
// Change text colors against dark slide backgrounds
|
||||
@include dark-bg-text-color(#fff);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* White theme for reveal.js. This is the opposite of the 'black' theme.
|
||||
*
|
||||
* By Hakim El Hattab, http://hakim.se
|
||||
*/
|
||||
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../template/mixins";
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/source-sans-pro/source-sans-pro.css);
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$backgroundColor: #fff;
|
||||
|
||||
$mainColor: #222;
|
||||
$headingColor: #222;
|
||||
|
||||
$mainFontSize: 42px;
|
||||
$mainFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingTextShadow: none;
|
||||
$headingLetterSpacing: normal;
|
||||
$headingTextTransform: uppercase;
|
||||
$headingFontWeight: 600;
|
||||
$linkColor: #2a76dd;
|
||||
$linkColorHover: lighten( $linkColor, 15% );
|
||||
$selectionBackgroundColor: lighten( $linkColor, 25% );
|
||||
|
||||
$heading1Size: 2.5em;
|
||||
$heading2Size: 1.6em;
|
||||
$heading3Size: 1.3em;
|
||||
$heading4Size: 1.0em;
|
||||
|
||||
$overlayElementBgColor: 0, 0, 0;
|
||||
$overlayElementFgColor: 240, 240, 240;
|
||||
|
||||
// Change text colors against dark slide backgrounds
|
||||
@include dark-bg-text-color(#fff);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../template/theme";
|
||||
// ---------------------------------------------
|
||||
@@ -0,0 +1,30 @@
|
||||
// Exposes theme's variables for easy re-use in CSS for plugin authors
|
||||
|
||||
:root {
|
||||
--r-background-color: #{$backgroundColor};
|
||||
--r-main-font: #{$mainFont};
|
||||
--r-main-font-size: #{$mainFontSize};
|
||||
--r-main-color: #{$mainColor};
|
||||
--r-block-margin: #{$blockMargin};
|
||||
--r-heading-margin: #{$headingMargin};
|
||||
--r-heading-font: #{$headingFont};
|
||||
--r-heading-color: #{$headingColor};
|
||||
--r-heading-line-height: #{$headingLineHeight};
|
||||
--r-heading-letter-spacing: #{$headingLetterSpacing};
|
||||
--r-heading-text-transform: #{$headingTextTransform};
|
||||
--r-heading-text-shadow: #{$headingTextShadow};
|
||||
--r-heading-font-weight: #{$headingFontWeight};
|
||||
--r-heading1-text-shadow: #{$heading1TextShadow};
|
||||
--r-heading1-size: #{$heading1Size};
|
||||
--r-heading2-size: #{$heading2Size};
|
||||
--r-heading3-size: #{$heading3Size};
|
||||
--r-heading4-size: #{$heading4Size};
|
||||
--r-code-font: #{$codeFont};
|
||||
--r-link-color: #{$linkColor};
|
||||
--r-link-color-dark: #{darken($linkColor , 15% )};
|
||||
--r-link-color-hover: #{$linkColorHover};
|
||||
--r-selection-background-color: #{$selectionBackgroundColor};
|
||||
--r-selection-color: #{$selectionColor};
|
||||
--r-overlay-element-bg-color: #{$overlayElementBgColor};
|
||||
--r-overlay-element-fg-color: #{$overlayElementFgColor};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
@mixin vertical-gradient( $top, $bottom ) {
|
||||
background: $top;
|
||||
background: -moz-linear-gradient( top, $top 0%, $bottom 100% );
|
||||
background: -webkit-gradient( linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom) );
|
||||
background: -webkit-linear-gradient( top, $top 0%, $bottom 100% );
|
||||
background: -o-linear-gradient( top, $top 0%, $bottom 100% );
|
||||
background: -ms-linear-gradient( top, $top 0%, $bottom 100% );
|
||||
background: linear-gradient( top, $top 0%, $bottom 100% );
|
||||
}
|
||||
|
||||
@mixin horizontal-gradient( $top, $bottom ) {
|
||||
background: $top;
|
||||
background: -moz-linear-gradient( left, $top 0%, $bottom 100% );
|
||||
background: -webkit-gradient( linear, left top, right top, color-stop(0%,$top), color-stop(100%,$bottom) );
|
||||
background: -webkit-linear-gradient( left, $top 0%, $bottom 100% );
|
||||
background: -o-linear-gradient( left, $top 0%, $bottom 100% );
|
||||
background: -ms-linear-gradient( left, $top 0%, $bottom 100% );
|
||||
background: linear-gradient( left, $top 0%, $bottom 100% );
|
||||
}
|
||||
|
||||
@mixin radial-gradient( $outer, $inner, $type: circle ) {
|
||||
background: $outer;
|
||||
background: -moz-radial-gradient( center, $type cover, $inner 0%, $outer 100% );
|
||||
background: -webkit-gradient( radial, center center, 0px, center center, 100%, color-stop(0%,$inner), color-stop(100%,$outer) );
|
||||
background: -webkit-radial-gradient( center, $type cover, $inner 0%, $outer 100% );
|
||||
background: -o-radial-gradient( center, $type cover, $inner 0%, $outer 100% );
|
||||
background: -ms-radial-gradient( center, $type cover, $inner 0%, $outer 100% );
|
||||
background: radial-gradient( center, $type cover, $inner 0%, $outer 100% );
|
||||
}
|
||||
|
||||
@mixin light-bg-text-color( $color ) {
|
||||
section.has-light-background {
|
||||
&, h1, h2, h3, h4, h5, h6 {
|
||||
color: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dark-bg-text-color( $color ) {
|
||||
section.has-dark-background {
|
||||
&, h1, h2, h3, h4, h5, h6 {
|
||||
color: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Base settings for all themes that can optionally be
|
||||
// overridden by the super-theme
|
||||
|
||||
// Background of the presentation
|
||||
$backgroundColor: #2b2b2b;
|
||||
|
||||
// Primary/body text
|
||||
$mainFont: 'Lato', sans-serif;
|
||||
$mainFontSize: 40px;
|
||||
$mainColor: #eee;
|
||||
|
||||
// Vertical spacing between blocks of text
|
||||
$blockMargin: 20px;
|
||||
|
||||
// Headings
|
||||
$headingMargin: 0 0 $blockMargin 0;
|
||||
$headingFont: 'League Gothic', Impact, sans-serif;
|
||||
$headingColor: #eee;
|
||||
$headingLineHeight: 1.2;
|
||||
$headingLetterSpacing: normal;
|
||||
$headingTextTransform: uppercase;
|
||||
$headingTextShadow: none;
|
||||
$headingFontWeight: normal;
|
||||
$heading1TextShadow: $headingTextShadow;
|
||||
|
||||
$heading1Size: 3.77em;
|
||||
$heading2Size: 2.11em;
|
||||
$heading3Size: 1.55em;
|
||||
$heading4Size: 1.00em;
|
||||
|
||||
$codeFont: monospace;
|
||||
|
||||
// Links and actions
|
||||
$linkColor: #13DAEC;
|
||||
$linkColorHover: lighten( $linkColor, 20% );
|
||||
|
||||
// Text selection
|
||||
$selectionBackgroundColor: #FF5E99;
|
||||
$selectionColor: #fff;
|
||||
|
||||
// Colors used for UI elements that are overlaid on top of
|
||||
// the presentation
|
||||
$overlayElementBgColor: 240, 240, 240;
|
||||
$overlayElementFgColor: 0, 0, 0;
|
||||
|
||||
// Generates the presentation background, can be overridden
|
||||
// to return a background image or gradient
|
||||
@mixin bodyBackground() {
|
||||
background: $backgroundColor;
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
// Base theme template for reveal.js
|
||||
|
||||
/*********************************************
|
||||
* GLOBAL STYLES
|
||||
*********************************************/
|
||||
|
||||
@import "./exposer";
|
||||
|
||||
.reveal-viewport {
|
||||
@include bodyBackground();
|
||||
background-color: var(--r-background-color);
|
||||
}
|
||||
|
||||
.reveal {
|
||||
font-family: var(--r-main-font);
|
||||
font-size: var(--r-main-font-size);
|
||||
font-weight: normal;
|
||||
color: var(--r-main-color);
|
||||
}
|
||||
|
||||
.reveal ::selection {
|
||||
color: var(--r-selection-color);
|
||||
background: var(--r-selection-background-color);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.reveal ::-moz-selection {
|
||||
color: var(--r-selection-color);
|
||||
background: var(--r-selection-background-color);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.reveal .slides section,
|
||||
.reveal .slides section>section {
|
||||
line-height: 1.3;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*********************************************
|
||||
* HEADERS
|
||||
*********************************************/
|
||||
|
||||
.reveal h1,
|
||||
.reveal h2,
|
||||
.reveal h3,
|
||||
.reveal h4,
|
||||
.reveal h5,
|
||||
.reveal h6 {
|
||||
margin: var(--r-heading-margin);
|
||||
color: var(--r-heading-color);
|
||||
|
||||
font-family: var(--r-heading-font);
|
||||
font-weight: var(--r-heading-font-weight);
|
||||
line-height: var(--r-heading-line-height);
|
||||
letter-spacing: var(--r-heading-letter-spacing);
|
||||
|
||||
text-transform: var(--r-heading-text-transform);
|
||||
text-shadow: var(--r-heading-text-shadow);
|
||||
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.reveal h1 {font-size: var(--r-heading1-size); }
|
||||
.reveal h2 {font-size: var(--r-heading2-size); }
|
||||
.reveal h3 {font-size: var(--r-heading3-size); }
|
||||
.reveal h4 {font-size: var(--r-heading4-size); }
|
||||
|
||||
.reveal h1 {
|
||||
text-shadow: var(--r-heading1-text-shadow);
|
||||
}
|
||||
|
||||
|
||||
/*********************************************
|
||||
* OTHER
|
||||
*********************************************/
|
||||
|
||||
.reveal p {
|
||||
margin: var(--r-block-margin) 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* Remove trailing margins after titles */
|
||||
.reveal h1:last-child,
|
||||
.reveal h2:last-child,
|
||||
.reveal h3:last-child,
|
||||
.reveal h4:last-child,
|
||||
.reveal h5:last-child,
|
||||
.reveal h6:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Ensure certain elements are never larger than the slide itself */
|
||||
.reveal img,
|
||||
.reveal video,
|
||||
.reveal iframe {
|
||||
max-width: 95%;
|
||||
max-height: 95%;
|
||||
}
|
||||
.reveal strong,
|
||||
.reveal b {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.reveal em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.reveal ol,
|
||||
.reveal dl,
|
||||
.reveal ul {
|
||||
display: inline-block;
|
||||
|
||||
text-align: left;
|
||||
margin: 0 0 0 1em;
|
||||
}
|
||||
|
||||
.reveal ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.reveal ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.reveal ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
.reveal ul ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
.reveal ul ul,
|
||||
.reveal ul ol,
|
||||
.reveal ol ol,
|
||||
.reveal ol ul {
|
||||
display: block;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.reveal dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.reveal dd {
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.reveal blockquote {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 70%;
|
||||
margin: var(--r-block-margin) auto;
|
||||
padding: 5px;
|
||||
|
||||
font-style: italic;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0px 0px 2px rgba(0,0,0,0.2);
|
||||
}
|
||||
.reveal blockquote p:first-child,
|
||||
.reveal blockquote p:last-child {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.reveal q {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.reveal pre {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 90%;
|
||||
margin: var(--r-block-margin) auto;
|
||||
|
||||
text-align: left;
|
||||
font-size: 0.55em;
|
||||
font-family: var(--r-code-font);
|
||||
line-height: 1.2em;
|
||||
|
||||
word-wrap: break-word;
|
||||
|
||||
box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.reveal code {
|
||||
font-family: var(--r-code-font);
|
||||
text-transform: none;
|
||||
tab-size: 2;
|
||||
}
|
||||
|
||||
.reveal pre code {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
.reveal .code-wrapper {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.reveal .code-wrapper code {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.reveal table {
|
||||
margin: auto;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.reveal table th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.reveal table th,
|
||||
.reveal table td {
|
||||
text-align: left;
|
||||
padding: 0.2em 0.5em 0.2em 0.5em;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.reveal table th[align="center"],
|
||||
.reveal table td[align="center"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reveal table th[align="right"],
|
||||
.reveal table td[align="right"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.reveal table tbody tr:last-child th,
|
||||
.reveal table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.reveal sup {
|
||||
vertical-align: super;
|
||||
font-size: smaller;
|
||||
}
|
||||
.reveal sub {
|
||||
vertical-align: sub;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.reveal small {
|
||||
display: inline-block;
|
||||
font-size: 0.6em;
|
||||
line-height: 1.2em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.reveal small * {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.reveal img {
|
||||
margin: var(--r-block-margin) 0;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************
|
||||
* LINKS
|
||||
*********************************************/
|
||||
|
||||
.reveal a {
|
||||
color: var(--r-link-color);
|
||||
text-decoration: none;
|
||||
transition: color .15s ease;
|
||||
}
|
||||
.reveal a:hover {
|
||||
color: var(--r-link-color-hover);
|
||||
text-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.reveal .roll span:after {
|
||||
color: #fff;
|
||||
// background: darken( var(--r-link-color), 15% );
|
||||
background: var(--r-link-color-dark);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*********************************************
|
||||
* Frame helper
|
||||
*********************************************/
|
||||
|
||||
.reveal .r-frame {
|
||||
border: 4px solid var(--r-main-color);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.reveal a .r-frame {
|
||||
transition: all .15s linear;
|
||||
}
|
||||
|
||||
.reveal a:hover .r-frame {
|
||||
border-color: var(--r-link-color);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
|
||||
/*********************************************
|
||||
* NAVIGATION CONTROLS
|
||||
*********************************************/
|
||||
|
||||
.reveal .controls {
|
||||
color: var(--r-link-color);
|
||||
}
|
||||
|
||||
|
||||
/*********************************************
|
||||
* PROGRESS BAR
|
||||
*********************************************/
|
||||
|
||||
.reveal .progress {
|
||||
background: rgba(0,0,0,0.2);
|
||||
color: var(--r-link-color);
|
||||
}
|
||||
|
||||
/*********************************************
|
||||
* PRINT BACKGROUND
|
||||
*********************************************/
|
||||
@media print {
|
||||
.backgrounds {
|
||||
background-color: var(--r-background-color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>reveal.js – The HTML Presentation Framework</title>
|
||||
|
||||
<meta name="description" content="A framework for easily creating beautiful presentations using HTML">
|
||||
<meta name="author" content="Hakim El Hattab">
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="dist/reset.css">
|
||||
<link rel="stylesheet" href="dist/reveal.css">
|
||||
<link rel="stylesheet" href="dist/theme/black.css" id="theme">
|
||||
|
||||
<!-- Theme used for syntax highlighting of code -->
|
||||
<link rel="stylesheet" href="plugin/highlight/monokai.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="reveal">
|
||||
|
||||
<!-- Any section element inside of this container is displayed as a slide -->
|
||||
<div class="slides">
|
||||
<section>
|
||||
<a href="https://revealjs.com">
|
||||
<img src="https://static.slid.es/reveal/logo-v1/reveal-white-text.svg" alt="reveal.js logo" style="height: 180px; margin: 0 auto 4rem auto; background: transparent;" class="demo-logo">
|
||||
</a>
|
||||
<h3>The HTML Presentation Framework</h3>
|
||||
<p>
|
||||
<small>Created by <a href="http://hakim.se">Hakim El Hattab</a> and <a href="https://github.com/hakimel/reveal.js/graphs/contributors">contributors</a></small>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Hello There</h2>
|
||||
<p>
|
||||
reveal.js enables you to create beautiful interactive slide decks using HTML. This presentation will show you examples of what it can do.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Example of nested vertical slides -->
|
||||
<section>
|
||||
<section>
|
||||
<h2>Vertical Slides</h2>
|
||||
<p>Slides can be nested inside of each other.</p>
|
||||
<p>Use the <em>Space</em> key to navigate through all slides.</p>
|
||||
<br>
|
||||
<a href="#/2/1" class="navigate-down">
|
||||
<img class="r-frame" style="background: rgba(255,255,255,0.1);" width="178" height="238" data-src="https://static.slid.es/reveal/arrow.png" alt="Down arrow">
|
||||
</a>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Basement Level 1</h2>
|
||||
<p>Nested slides are useful for adding additional detail underneath a high level horizontal slide.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Basement Level 2</h2>
|
||||
<p>That's it, time to go back up.</p>
|
||||
<br>
|
||||
<a href="#/2">
|
||||
<img class="r-frame" style="background: rgba(255,255,255,0.1); transform: rotate(180deg);" width="178" height="238" data-src="https://static.slid.es/reveal/arrow.png" alt="Up arrow">
|
||||
</a>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Slides</h2>
|
||||
<p>
|
||||
Not a coder? Not a problem. There's a fully-featured visual editor for authoring these, try it out at <a href="https://slides.com" target="_blank">https://slides.com</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section data-visibility="hidden">
|
||||
<h2>Hidden Slides</h2>
|
||||
<p>
|
||||
This slide is visible in the source, but hidden when the presentation is viewed. You can show all hidden slides by setting the `showHiddenSlides` config option to `true`.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section data-auto-animate>
|
||||
<h2 data-id="code-title">Pretty Code</h2>
|
||||
<pre data-id="code-animation"><code class="hljs javascript" data-trim data-line-numbers>
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function Example() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
...
|
||||
);
|
||||
}
|
||||
</code></pre>
|
||||
<p>Code syntax highlighting courtesy of <a href="https://highlightjs.org/usage/">highlight.js</a>.</p>
|
||||
</section>
|
||||
|
||||
<section data-auto-animate>
|
||||
<h2 data-id="code-title">With Animations</h2>
|
||||
<pre data-id="code-animation"><code class="hljs javascript" data-trim data-line-numbers="|4,8-11|17|22-24"><script type="text/template">
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function Example() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>You clicked {count} times</p>
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Click me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SecondExample() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>You clicked {count} times</p>
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Click me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</script></code></pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Point of View</h2>
|
||||
<p>
|
||||
Press <strong>ESC</strong> to enter the slide overview.
|
||||
</p>
|
||||
<p>
|
||||
Hold down the <strong>alt</strong> key (<strong>ctrl</strong> in Linux) and click on any element to zoom towards it using <a href="http://lab.hakim.se/zoom-js">zoom.js</a>. Click again to zoom back out.
|
||||
</p>
|
||||
<p>
|
||||
(NOTE: Use ctrl + click in Linux.)
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section data-auto-animate data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)">
|
||||
<h2>Auto-Animate</h2>
|
||||
<p>Automatically animate matching elements across slides with <a href="https://revealjs.com/auto-animate/">Auto-Animate</a>.</p>
|
||||
<div class="r-hstack justify-center">
|
||||
<div data-id="box1" style="background: #999; width: 50px; height: 50px; margin: 10px; border-radius: 5px;"></div>
|
||||
<div data-id="box2" style="background: #999; width: 50px; height: 50px; margin: 10px; border-radius: 5px;"></div>
|
||||
<div data-id="box3" style="background: #999; width: 50px; height: 50px; margin: 10px; border-radius: 5px;"></div>
|
||||
</div>
|
||||
</section>
|
||||
<section data-auto-animate data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)">
|
||||
<div class="r-hstack justify-center">
|
||||
<div data-id="box1" data-auto-animate-delay="0" style="background: cyan; width: 150px; height: 100px; margin: 10px;"></div>
|
||||
<div data-id="box2" data-auto-animate-delay="0.1" style="background: magenta; width: 150px; height: 100px; margin: 10px;"></div>
|
||||
<div data-id="box3" data-auto-animate-delay="0.2" style="background: yellow; width: 150px; height: 100px; margin: 10px;"></div>
|
||||
</div>
|
||||
<h2 style="margin-top: 20px;">Auto-Animate</h2>
|
||||
</section>
|
||||
<section data-auto-animate data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)">
|
||||
<div class="r-stack">
|
||||
<div data-id="box1" style="background: cyan; width: 300px; height: 300px;"></div>
|
||||
<div data-id="box2" style="background: magenta; width: 200px; height: 200px;"></div>
|
||||
<div data-id="box3" style="background: yellow; width: 100px; height: 100px;"></div>
|
||||
</div>
|
||||
<h2 style="margin-top: 20px;">Auto-Animate</h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Touch Optimized</h2>
|
||||
<p>
|
||||
Presentations look great on touch devices, like mobile phones and tablets. Simply swipe through your slides.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section data-markdown>
|
||||
<script type="text/template">
|
||||
## Markdown Support
|
||||
|
||||
Write content using inline or external Markdown.
|
||||
Instructions and more info available in the [docs](https://revealjs.com/markdown/).
|
||||
|
||||
```html []
|
||||
<section data-markdown>
|
||||
## Markdown Support
|
||||
|
||||
Write content using inline or external Markdown.
|
||||
Instructions and more info available in the [docs](https://revealjs.com/markdown/).
|
||||
</section>
|
||||
```
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>Add the <code>r-fit-text</code> class to auto-size text</p>
|
||||
<h2 class="r-fit-text">FIT TEXT</h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="fragments">
|
||||
<h2>Fragments</h2>
|
||||
<p>Hit the next arrow...</p>
|
||||
<p class="fragment">... to step through ...</p>
|
||||
<p><span class="fragment">... a</span> <span class="fragment">fragmented</span> <span class="fragment">slide.</span></p>
|
||||
|
||||
<aside class="notes">
|
||||
This slide has fragments which are also stepped through in the notes window.
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Fragment Styles</h2>
|
||||
<p>There's different types of fragments, like:</p>
|
||||
<p class="fragment grow">grow</p>
|
||||
<p class="fragment shrink">shrink</p>
|
||||
<p class="fragment fade-out">fade-out</p>
|
||||
<p>
|
||||
<span style="display: inline-block;" class="fragment fade-right">fade-right, </span>
|
||||
<span style="display: inline-block;" class="fragment fade-up">up, </span>
|
||||
<span style="display: inline-block;" class="fragment fade-down">down, </span>
|
||||
<span style="display: inline-block;" class="fragment fade-left">left</span>
|
||||
</p>
|
||||
<p class="fragment fade-in-then-out">fade-in-then-out</p>
|
||||
<p class="fragment fade-in-then-semi-out">fade-in-then-semi-out</p>
|
||||
<p>Highlight <span class="fragment highlight-red">red</span> <span class="fragment highlight-blue">blue</span> <span class="fragment highlight-green">green</span></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="transitions">
|
||||
<h2>Transition Styles</h2>
|
||||
<p>
|
||||
You can select from different transitions, like: <br>
|
||||
<a href="?transition=none#/transitions">None</a> -
|
||||
<a href="?transition=fade#/transitions">Fade</a> -
|
||||
<a href="?transition=slide#/transitions">Slide</a> -
|
||||
<a href="?transition=convex#/transitions">Convex</a> -
|
||||
<a href="?transition=concave#/transitions">Concave</a> -
|
||||
<a href="?transition=zoom#/transitions">Zoom</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="themes">
|
||||
<h2>Themes</h2>
|
||||
<p>
|
||||
reveal.js comes with a few themes built in: <br>
|
||||
<!-- Hacks to swap themes after the page has loaded. Not flexible and only intended for the reveal.js demo deck. -->
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/black.css'); return false;">Black (default)</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/white.css'); return false;">White</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/league.css'); return false;">League</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/sky.css'); return false;">Sky</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/beige.css'); return false;">Beige</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/simple.css'); return false;">Simple</a> <br>
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/serif.css'); return false;">Serif</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/blood.css'); return false;">Blood</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/night.css'); return false;">Night</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/moon.css'); return false;">Moon</a> -
|
||||
<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/solarized.css'); return false;">Solarized</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section data-background="#dddddd">
|
||||
<h2>Slide Backgrounds</h2>
|
||||
<p>
|
||||
Set <code>data-background="#dddddd"</code> on a slide to change the background color. All CSS color formats are supported.
|
||||
</p>
|
||||
<a href="#" class="navigate-down">
|
||||
<img class="r-frame" style="background: rgba(255,255,255,0.1);" width="178" height="238" data-src="https://static.slid.es/reveal/arrow.png" alt="Down arrow">
|
||||
</a>
|
||||
</section>
|
||||
<section data-background-gradient="linear-gradient(to bottom, #283b95, #17b2c3)">
|
||||
<h2>Gradient Backgrounds</h2>
|
||||
<pre><code class="hljs html wrap"><section data-background-gradient=
|
||||
"linear-gradient(to bottom, #ddd, #191919)"></code></pre>
|
||||
</section>
|
||||
<section data-background="https://static.slid.es/reveal/image-placeholder.png">
|
||||
<h2>Image Backgrounds</h2>
|
||||
<pre><code class="hljs html"><section data-background="image.png"></code></pre>
|
||||
</section>
|
||||
<section data-background="https://static.slid.es/reveal/image-placeholder.png" data-background-repeat="repeat" data-background-size="100px">
|
||||
<h2>Tiled Backgrounds</h2>
|
||||
<pre><code class="hljs html" style="word-wrap: break-word;"><section data-background="image.png" data-background-repeat="repeat" data-background-size="100px"></code></pre>
|
||||
</section>
|
||||
<section data-background-video="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4" data-background-color="#000000">
|
||||
<div style="background-color: rgba(0, 0, 0, 0.9); color: #fff; padding: 20px;">
|
||||
<h2>Video Backgrounds</h2>
|
||||
<pre><code class="hljs html" style="word-wrap: break-word;"><section data-background-video="video.mp4,video.webm"></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
<section data-background="http://i.giphy.com/90F8aUepslB84.gif">
|
||||
<h2>... and GIFs!</h2>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section data-transition="slide" data-background="#4d7e65" data-background-transition="zoom">
|
||||
<h2>Background Transitions</h2>
|
||||
<p>
|
||||
Different background transitions are available via the backgroundTransition option. This one's called "zoom".
|
||||
</p>
|
||||
<pre><code class="hljs javascript">Reveal.configure({ backgroundTransition: 'zoom' })</code></pre>
|
||||
</section>
|
||||
|
||||
<section data-transition="slide" data-background="#b5533c" data-background-transition="zoom">
|
||||
<h2>Background Transitions</h2>
|
||||
<p>
|
||||
You can override background transitions per-slide.
|
||||
</p>
|
||||
<pre><code class="hljs html" style="word-wrap: break-word;"><section data-background-transition="zoom"></code></pre>
|
||||
</section>
|
||||
|
||||
<section data-background-iframe="https://hakim.se" data-background-interactive>
|
||||
<div style="position: absolute; width: 40%; right: 0; box-shadow: 0 1px 4px rgba(0,0,0,0.5), 0 5px 25px rgba(0,0,0,0.2); background-color: rgba(0, 0, 0, 0.9); color: #fff; padding: 20px; font-size: 20px; text-align: left;">
|
||||
<h2>Iframe Backgrounds</h2>
|
||||
<p>Since reveal.js runs on the web, you can easily embed other web content. Try interacting with the page in the background.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Marvelous List</h2>
|
||||
<ul>
|
||||
<li>No order here</li>
|
||||
<li>Or here</li>
|
||||
<li>Or here</li>
|
||||
<li>Or here</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Fantastic Ordered List</h2>
|
||||
<ol>
|
||||
<li>One is smaller than...</li>
|
||||
<li>Two is smaller than...</li>
|
||||
<li>Three!</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Tabular Tables</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th>Value</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Apples</td>
|
||||
<td>$1</td>
|
||||
<td>7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lemonade</td>
|
||||
<td>$2</td>
|
||||
<td>18</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bread</td>
|
||||
<td>$3</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Clever Quotes</h2>
|
||||
<p>
|
||||
These guys come in two forms, inline: <q cite="http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations">The nice thing about standards is that there are so many to choose from</q> and block:
|
||||
</p>
|
||||
<blockquote cite="http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations">
|
||||
“For years there has been a theory that millions of monkeys typing at random on millions of typewriters would
|
||||
reproduce the entire works of Shakespeare. The Internet has proven this theory to be untrue.”
|
||||
</blockquote>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Intergalactic Interconnections</h2>
|
||||
<p>
|
||||
You can link between slides internally,
|
||||
<a href="#/2/3">like this</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Speaker View</h2>
|
||||
<p>There's a <a href="https://revealjs.com/speaker-view/">speaker view</a>. It includes a timer, preview of the upcoming slide as well as your speaker notes.</p>
|
||||
<p>Press the <em>S</em> key to try it out.</p>
|
||||
|
||||
<aside class="notes">
|
||||
Oh hey, these are some notes. They'll be hidden in your presentation, but you can see them if you open the speaker notes window (hit 's' on your keyboard).
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Export to PDF</h2>
|
||||
<p>Presentations can be <a href="https://revealjs.com/pdf-export/">exported to PDF</a>, here's an example:</p>
|
||||
<iframe data-src="https://www.slideshare.net/slideshow/embed_code/42840540" width="445" height="355" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:3px solid #666; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Global State</h2>
|
||||
<p>
|
||||
Set <code>data-state="something"</code> on a slide and <code>"something"</code>
|
||||
will be added as a class to the document element when the slide is open. This lets you
|
||||
apply broader style changes, like switching the page background.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section data-state="customevent">
|
||||
<h2>State Events</h2>
|
||||
<p>
|
||||
Additionally custom events can be triggered on a per slide basis by binding to the <code>data-state</code> name.
|
||||
</p>
|
||||
<pre><code class="javascript" data-trim contenteditable style="font-size: 18px;">
|
||||
Reveal.on( 'customevent', function() {
|
||||
console.log( '"customevent" has fired' );
|
||||
} );
|
||||
</code></pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Take a Moment</h2>
|
||||
<p>
|
||||
Press B or . on your keyboard to pause the presentation. This is helpful when you're on stage and want to take distracting slides off the screen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Much more</h2>
|
||||
<ul>
|
||||
<li>Right-to-left support</li>
|
||||
<li><a href="https://revealjs.com/api/">Extensive JavaScript API</a></li>
|
||||
<li><a href="https://revealjs.com/auto-slide/">Auto-progression</a></li>
|
||||
<li><a href="https://revealjs.com/backgrounds/#parallax-background">Parallax backgrounds</a></li>
|
||||
<li><a href="https://revealjs.com/keyboard/">Custom keyboard bindings</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section style="text-align: left;">
|
||||
<h1>THE END</h1>
|
||||
<p>
|
||||
- <a href="https://slides.com">Try the online editor</a> <br>
|
||||
- <a href="https://github.com/hakimel/reveal.js">Source code & documentation</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="dist/reveal.js"></script>
|
||||
<script src="plugin/zoom/zoom.js"></script>
|
||||
<script src="plugin/notes/notes.js"></script>
|
||||
<script src="plugin/search/search.js"></script>
|
||||
<script src="plugin/markdown/markdown.js"></script>
|
||||
<script src="plugin/highlight/highlight.js"></script>
|
||||
<script>
|
||||
|
||||
// Also available as an ES module, see:
|
||||
// https://revealjs.com/initialization/
|
||||
Reveal.initialize({
|
||||
controls: true,
|
||||
progress: true,
|
||||
center: true,
|
||||
hash: true,
|
||||
|
||||
// Learn about plugins: https://revealjs.com/plugins/
|
||||
plugins: [ RevealZoom, RevealNotes, RevealSearch, RevealMarkdown, RevealHighlight ]
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,526 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>reveal.js - 500 slides</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" href="../dist/reveal.css">
|
||||
<link rel="stylesheet" href="../dist/theme/black.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="reveal">
|
||||
<div class="slides">
|
||||
<section><h1>1</h1></section>
|
||||
<section><h1>2</h1></section>
|
||||
<section><h1>3</h1></section>
|
||||
<section><h1>4</h1></section>
|
||||
<section><h1>5</h1></section>
|
||||
<section><h1>6</h1></section>
|
||||
<section><h1>7</h1></section>
|
||||
<section><h1>8</h1></section>
|
||||
<section><h1>9</h1></section>
|
||||
<section><h1>10</h1></section>
|
||||
<section><h1>11</h1></section>
|
||||
<section><h1>12</h1></section>
|
||||
<section><h1>13</h1></section>
|
||||
<section><h1>14</h1></section>
|
||||
<section><h1>15</h1></section>
|
||||
<section><h1>16</h1></section>
|
||||
<section><h1>17</h1></section>
|
||||
<section><h1>18</h1></section>
|
||||
<section><h1>19</h1></section>
|
||||
<section><h1>20</h1></section>
|
||||
<section><h1>21</h1></section>
|
||||
<section><h1>22</h1></section>
|
||||
<section><h1>23</h1></section>
|
||||
<section><h1>24</h1></section>
|
||||
<section><h1>25</h1></section>
|
||||
<section><h1>26</h1></section>
|
||||
<section><h1>27</h1></section>
|
||||
<section><h1>28</h1></section>
|
||||
<section><h1>29</h1></section>
|
||||
<section><h1>30</h1></section>
|
||||
<section><h1>31</h1></section>
|
||||
<section><h1>32</h1></section>
|
||||
<section><h1>33</h1></section>
|
||||
<section><h1>34</h1></section>
|
||||
<section><h1>35</h1></section>
|
||||
<section><h1>36</h1></section>
|
||||
<section><h1>37</h1></section>
|
||||
<section><h1>38</h1></section>
|
||||
<section><h1>39</h1></section>
|
||||
<section><h1>40</h1></section>
|
||||
<section><h1>41</h1></section>
|
||||
<section><h1>42</h1></section>
|
||||
<section><h1>43</h1></section>
|
||||
<section><h1>44</h1></section>
|
||||
<section><h1>45</h1></section>
|
||||
<section><h1>46</h1></section>
|
||||
<section><h1>47</h1></section>
|
||||
<section><h1>48</h1></section>
|
||||
<section><h1>49</h1></section>
|
||||
<section><h1>50</h1></section>
|
||||
<section><h1>51</h1></section>
|
||||
<section><h1>52</h1></section>
|
||||
<section><h1>53</h1></section>
|
||||
<section><h1>54</h1></section>
|
||||
<section><h1>55</h1></section>
|
||||
<section><h1>56</h1></section>
|
||||
<section><h1>57</h1></section>
|
||||
<section><h1>58</h1></section>
|
||||
<section><h1>59</h1></section>
|
||||
<section><h1>60</h1></section>
|
||||
<section><h1>61</h1></section>
|
||||
<section><h1>62</h1></section>
|
||||
<section><h1>63</h1></section>
|
||||
<section><h1>64</h1></section>
|
||||
<section><h1>65</h1></section>
|
||||
<section><h1>66</h1></section>
|
||||
<section><h1>67</h1></section>
|
||||
<section><h1>68</h1></section>
|
||||
<section><h1>69</h1></section>
|
||||
<section><h1>70</h1></section>
|
||||
<section><h1>71</h1></section>
|
||||
<section><h1>72</h1></section>
|
||||
<section><h1>73</h1></section>
|
||||
<section><h1>74</h1></section>
|
||||
<section><h1>75</h1></section>
|
||||
<section><h1>76</h1></section>
|
||||
<section><h1>77</h1></section>
|
||||
<section><h1>78</h1></section>
|
||||
<section><h1>79</h1></section>
|
||||
<section><h1>80</h1></section>
|
||||
<section><h1>81</h1></section>
|
||||
<section><h1>82</h1></section>
|
||||
<section><h1>83</h1></section>
|
||||
<section><h1>84</h1></section>
|
||||
<section><h1>85</h1></section>
|
||||
<section><h1>86</h1></section>
|
||||
<section><h1>87</h1></section>
|
||||
<section><h1>88</h1></section>
|
||||
<section><h1>89</h1></section>
|
||||
<section><h1>90</h1></section>
|
||||
<section><h1>91</h1></section>
|
||||
<section><h1>92</h1></section>
|
||||
<section><h1>93</h1></section>
|
||||
<section><h1>94</h1></section>
|
||||
<section><h1>95</h1></section>
|
||||
<section><h1>96</h1></section>
|
||||
<section><h1>97</h1></section>
|
||||
<section><h1>98</h1></section>
|
||||
<section><h1>99</h1></section>
|
||||
<section><h1>100</h1></section>
|
||||
<section><h1>101</h1></section>
|
||||
<section><h1>102</h1></section>
|
||||
<section><h1>103</h1></section>
|
||||
<section><h1>104</h1></section>
|
||||
<section><h1>105</h1></section>
|
||||
<section><h1>106</h1></section>
|
||||
<section><h1>107</h1></section>
|
||||
<section><h1>108</h1></section>
|
||||
<section><h1>109</h1></section>
|
||||
<section><h1>110</h1></section>
|
||||
<section><h1>111</h1></section>
|
||||
<section><h1>112</h1></section>
|
||||
<section><h1>113</h1></section>
|
||||
<section><h1>114</h1></section>
|
||||
<section><h1>115</h1></section>
|
||||
<section><h1>116</h1></section>
|
||||
<section><h1>117</h1></section>
|
||||
<section><h1>118</h1></section>
|
||||
<section><h1>119</h1></section>
|
||||
<section><h1>120</h1></section>
|
||||
<section><h1>121</h1></section>
|
||||
<section><h1>122</h1></section>
|
||||
<section><h1>123</h1></section>
|
||||
<section><h1>124</h1></section>
|
||||
<section><h1>125</h1></section>
|
||||
<section><h1>126</h1></section>
|
||||
<section><h1>127</h1></section>
|
||||
<section><h1>128</h1></section>
|
||||
<section><h1>129</h1></section>
|
||||
<section><h1>130</h1></section>
|
||||
<section><h1>131</h1></section>
|
||||
<section><h1>132</h1></section>
|
||||
<section><h1>133</h1></section>
|
||||
<section><h1>134</h1></section>
|
||||
<section><h1>135</h1></section>
|
||||
<section><h1>136</h1></section>
|
||||
<section><h1>137</h1></section>
|
||||
<section><h1>138</h1></section>
|
||||
<section><h1>139</h1></section>
|
||||
<section><h1>140</h1></section>
|
||||
<section><h1>141</h1></section>
|
||||
<section><h1>142</h1></section>
|
||||
<section><h1>143</h1></section>
|
||||
<section><h1>144</h1></section>
|
||||
<section><h1>145</h1></section>
|
||||
<section><h1>146</h1></section>
|
||||
<section><h1>147</h1></section>
|
||||
<section><h1>148</h1></section>
|
||||
<section><h1>149</h1></section>
|
||||
<section><h1>150</h1></section>
|
||||
<section><h1>151</h1></section>
|
||||
<section><h1>152</h1></section>
|
||||
<section><h1>153</h1></section>
|
||||
<section><h1>154</h1></section>
|
||||
<section><h1>155</h1></section>
|
||||
<section><h1>156</h1></section>
|
||||
<section><h1>157</h1></section>
|
||||
<section><h1>158</h1></section>
|
||||
<section><h1>159</h1></section>
|
||||
<section><h1>160</h1></section>
|
||||
<section><h1>161</h1></section>
|
||||
<section><h1>162</h1></section>
|
||||
<section><h1>163</h1></section>
|
||||
<section><h1>164</h1></section>
|
||||
<section><h1>165</h1></section>
|
||||
<section><h1>166</h1></section>
|
||||
<section><h1>167</h1></section>
|
||||
<section><h1>168</h1></section>
|
||||
<section><h1>169</h1></section>
|
||||
<section><h1>170</h1></section>
|
||||
<section><h1>171</h1></section>
|
||||
<section><h1>172</h1></section>
|
||||
<section><h1>173</h1></section>
|
||||
<section><h1>174</h1></section>
|
||||
<section><h1>175</h1></section>
|
||||
<section><h1>176</h1></section>
|
||||
<section><h1>177</h1></section>
|
||||
<section><h1>178</h1></section>
|
||||
<section><h1>179</h1></section>
|
||||
<section><h1>180</h1></section>
|
||||
<section><h1>181</h1></section>
|
||||
<section><h1>182</h1></section>
|
||||
<section><h1>183</h1></section>
|
||||
<section><h1>184</h1></section>
|
||||
<section><h1>185</h1></section>
|
||||
<section><h1>186</h1></section>
|
||||
<section><h1>187</h1></section>
|
||||
<section><h1>188</h1></section>
|
||||
<section><h1>189</h1></section>
|
||||
<section><h1>190</h1></section>
|
||||
<section><h1>191</h1></section>
|
||||
<section><h1>192</h1></section>
|
||||
<section><h1>193</h1></section>
|
||||
<section><h1>194</h1></section>
|
||||
<section><h1>195</h1></section>
|
||||
<section><h1>196</h1></section>
|
||||
<section><h1>197</h1></section>
|
||||
<section><h1>198</h1></section>
|
||||
<section><h1>199</h1></section>
|
||||
<section><h1>200</h1></section>
|
||||
<section><h1>201</h1></section>
|
||||
<section><h1>202</h1></section>
|
||||
<section><h1>203</h1></section>
|
||||
<section><h1>204</h1></section>
|
||||
<section><h1>205</h1></section>
|
||||
<section><h1>206</h1></section>
|
||||
<section><h1>207</h1></section>
|
||||
<section><h1>208</h1></section>
|
||||
<section><h1>209</h1></section>
|
||||
<section><h1>210</h1></section>
|
||||
<section><h1>211</h1></section>
|
||||
<section><h1>212</h1></section>
|
||||
<section><h1>213</h1></section>
|
||||
<section><h1>214</h1></section>
|
||||
<section><h1>215</h1></section>
|
||||
<section><h1>216</h1></section>
|
||||
<section><h1>217</h1></section>
|
||||
<section><h1>218</h1></section>
|
||||
<section><h1>219</h1></section>
|
||||
<section><h1>220</h1></section>
|
||||
<section><h1>221</h1></section>
|
||||
<section><h1>222</h1></section>
|
||||
<section><h1>223</h1></section>
|
||||
<section><h1>224</h1></section>
|
||||
<section><h1>225</h1></section>
|
||||
<section><h1>226</h1></section>
|
||||
<section><h1>227</h1></section>
|
||||
<section><h1>228</h1></section>
|
||||
<section><h1>229</h1></section>
|
||||
<section><h1>230</h1></section>
|
||||
<section><h1>231</h1></section>
|
||||
<section><h1>232</h1></section>
|
||||
<section><h1>233</h1></section>
|
||||
<section><h1>234</h1></section>
|
||||
<section><h1>235</h1></section>
|
||||
<section><h1>236</h1></section>
|
||||
<section><h1>237</h1></section>
|
||||
<section><h1>238</h1></section>
|
||||
<section><h1>239</h1></section>
|
||||
<section><h1>240</h1></section>
|
||||
<section><h1>241</h1></section>
|
||||
<section><h1>242</h1></section>
|
||||
<section><h1>243</h1></section>
|
||||
<section><h1>244</h1></section>
|
||||
<section><h1>245</h1></section>
|
||||
<section><h1>246</h1></section>
|
||||
<section><h1>247</h1></section>
|
||||
<section><h1>248</h1></section>
|
||||
<section><h1>249</h1></section>
|
||||
<section><h1>250</h1></section>
|
||||
<section><h1>251</h1></section>
|
||||
<section><h1>252</h1></section>
|
||||
<section><h1>253</h1></section>
|
||||
<section><h1>254</h1></section>
|
||||
<section><h1>255</h1></section>
|
||||
<section><h1>256</h1></section>
|
||||
<section><h1>257</h1></section>
|
||||
<section><h1>258</h1></section>
|
||||
<section><h1>259</h1></section>
|
||||
<section><h1>260</h1></section>
|
||||
<section><h1>261</h1></section>
|
||||
<section><h1>262</h1></section>
|
||||
<section><h1>263</h1></section>
|
||||
<section><h1>264</h1></section>
|
||||
<section><h1>265</h1></section>
|
||||
<section><h1>266</h1></section>
|
||||
<section><h1>267</h1></section>
|
||||
<section><h1>268</h1></section>
|
||||
<section><h1>269</h1></section>
|
||||
<section><h1>270</h1></section>
|
||||
<section><h1>271</h1></section>
|
||||
<section><h1>272</h1></section>
|
||||
<section><h1>273</h1></section>
|
||||
<section><h1>274</h1></section>
|
||||
<section><h1>275</h1></section>
|
||||
<section><h1>276</h1></section>
|
||||
<section><h1>277</h1></section>
|
||||
<section><h1>278</h1></section>
|
||||
<section><h1>279</h1></section>
|
||||
<section><h1>280</h1></section>
|
||||
<section><h1>281</h1></section>
|
||||
<section><h1>282</h1></section>
|
||||
<section><h1>283</h1></section>
|
||||
<section><h1>284</h1></section>
|
||||
<section><h1>285</h1></section>
|
||||
<section><h1>286</h1></section>
|
||||
<section><h1>287</h1></section>
|
||||
<section><h1>288</h1></section>
|
||||
<section><h1>289</h1></section>
|
||||
<section><h1>290</h1></section>
|
||||
<section><h1>291</h1></section>
|
||||
<section><h1>292</h1></section>
|
||||
<section><h1>293</h1></section>
|
||||
<section><h1>294</h1></section>
|
||||
<section><h1>295</h1></section>
|
||||
<section><h1>296</h1></section>
|
||||
<section><h1>297</h1></section>
|
||||
<section><h1>298</h1></section>
|
||||
<section><h1>299</h1></section>
|
||||
<section><h1>300</h1></section>
|
||||
<section><h1>301</h1></section>
|
||||
<section><h1>302</h1></section>
|
||||
<section><h1>303</h1></section>
|
||||
<section><h1>304</h1></section>
|
||||
<section><h1>305</h1></section>
|
||||
<section><h1>306</h1></section>
|
||||
<section><h1>307</h1></section>
|
||||
<section><h1>308</h1></section>
|
||||
<section><h1>309</h1></section>
|
||||
<section><h1>310</h1></section>
|
||||
<section><h1>311</h1></section>
|
||||
<section><h1>312</h1></section>
|
||||
<section><h1>313</h1></section>
|
||||
<section><h1>314</h1></section>
|
||||
<section><h1>315</h1></section>
|
||||
<section><h1>316</h1></section>
|
||||
<section><h1>317</h1></section>
|
||||
<section><h1>318</h1></section>
|
||||
<section><h1>319</h1></section>
|
||||
<section><h1>320</h1></section>
|
||||
<section><h1>321</h1></section>
|
||||
<section><h1>322</h1></section>
|
||||
<section><h1>323</h1></section>
|
||||
<section><h1>324</h1></section>
|
||||
<section><h1>325</h1></section>
|
||||
<section><h1>326</h1></section>
|
||||
<section><h1>327</h1></section>
|
||||
<section><h1>328</h1></section>
|
||||
<section><h1>329</h1></section>
|
||||
<section><h1>330</h1></section>
|
||||
<section><h1>331</h1></section>
|
||||
<section><h1>332</h1></section>
|
||||
<section><h1>333</h1></section>
|
||||
<section><h1>334</h1></section>
|
||||
<section><h1>335</h1></section>
|
||||
<section><h1>336</h1></section>
|
||||
<section><h1>337</h1></section>
|
||||
<section><h1>338</h1></section>
|
||||
<section><h1>339</h1></section>
|
||||
<section><h1>340</h1></section>
|
||||
<section><h1>341</h1></section>
|
||||
<section><h1>342</h1></section>
|
||||
<section><h1>343</h1></section>
|
||||
<section><h1>344</h1></section>
|
||||
<section><h1>345</h1></section>
|
||||
<section><h1>346</h1></section>
|
||||
<section><h1>347</h1></section>
|
||||
<section><h1>348</h1></section>
|
||||
<section><h1>349</h1></section>
|
||||
<section><h1>350</h1></section>
|
||||
<section><h1>351</h1></section>
|
||||
<section><h1>352</h1></section>
|
||||
<section><h1>353</h1></section>
|
||||
<section><h1>354</h1></section>
|
||||
<section><h1>355</h1></section>
|
||||
<section><h1>356</h1></section>
|
||||
<section><h1>357</h1></section>
|
||||
<section><h1>358</h1></section>
|
||||
<section><h1>359</h1></section>
|
||||
<section><h1>360</h1></section>
|
||||
<section><h1>361</h1></section>
|
||||
<section><h1>362</h1></section>
|
||||
<section><h1>363</h1></section>
|
||||
<section><h1>364</h1></section>
|
||||
<section><h1>365</h1></section>
|
||||
<section><h1>366</h1></section>
|
||||
<section><h1>367</h1></section>
|
||||
<section><h1>368</h1></section>
|
||||
<section><h1>369</h1></section>
|
||||
<section><h1>370</h1></section>
|
||||
<section><h1>371</h1></section>
|
||||
<section><h1>372</h1></section>
|
||||
<section><h1>373</h1></section>
|
||||
<section><h1>374</h1></section>
|
||||
<section><h1>375</h1></section>
|
||||
<section><h1>376</h1></section>
|
||||
<section><h1>377</h1></section>
|
||||
<section><h1>378</h1></section>
|
||||
<section><h1>379</h1></section>
|
||||
<section><h1>380</h1></section>
|
||||
<section><h1>381</h1></section>
|
||||
<section><h1>382</h1></section>
|
||||
<section><h1>383</h1></section>
|
||||
<section><h1>384</h1></section>
|
||||
<section><h1>385</h1></section>
|
||||
<section><h1>386</h1></section>
|
||||
<section><h1>387</h1></section>
|
||||
<section><h1>388</h1></section>
|
||||
<section><h1>389</h1></section>
|
||||
<section><h1>390</h1></section>
|
||||
<section><h1>391</h1></section>
|
||||
<section><h1>392</h1></section>
|
||||
<section><h1>393</h1></section>
|
||||
<section><h1>394</h1></section>
|
||||
<section><h1>395</h1></section>
|
||||
<section><h1>396</h1></section>
|
||||
<section><h1>397</h1></section>
|
||||
<section><h1>398</h1></section>
|
||||
<section><h1>399</h1></section>
|
||||
<section><h1>400</h1></section>
|
||||
<section><h1>401</h1></section>
|
||||
<section><h1>402</h1></section>
|
||||
<section><h1>403</h1></section>
|
||||
<section><h1>404</h1></section>
|
||||
<section><h1>405</h1></section>
|
||||
<section><h1>406</h1></section>
|
||||
<section><h1>407</h1></section>
|
||||
<section><h1>408</h1></section>
|
||||
<section><h1>409</h1></section>
|
||||
<section><h1>410</h1></section>
|
||||
<section><h1>411</h1></section>
|
||||
<section><h1>412</h1></section>
|
||||
<section><h1>413</h1></section>
|
||||
<section><h1>414</h1></section>
|
||||
<section><h1>415</h1></section>
|
||||
<section><h1>416</h1></section>
|
||||
<section><h1>417</h1></section>
|
||||
<section><h1>418</h1></section>
|
||||
<section><h1>419</h1></section>
|
||||
<section><h1>420</h1></section>
|
||||
<section><h1>421</h1></section>
|
||||
<section><h1>422</h1></section>
|
||||
<section><h1>423</h1></section>
|
||||
<section><h1>424</h1></section>
|
||||
<section><h1>425</h1></section>
|
||||
<section><h1>426</h1></section>
|
||||
<section><h1>427</h1></section>
|
||||
<section><h1>428</h1></section>
|
||||
<section><h1>429</h1></section>
|
||||
<section><h1>430</h1></section>
|
||||
<section><h1>431</h1></section>
|
||||
<section><h1>432</h1></section>
|
||||
<section><h1>433</h1></section>
|
||||
<section><h1>434</h1></section>
|
||||
<section><h1>435</h1></section>
|
||||
<section><h1>436</h1></section>
|
||||
<section><h1>437</h1></section>
|
||||
<section><h1>438</h1></section>
|
||||
<section><h1>439</h1></section>
|
||||
<section><h1>440</h1></section>
|
||||
<section><h1>441</h1></section>
|
||||
<section><h1>442</h1></section>
|
||||
<section><h1>443</h1></section>
|
||||
<section><h1>444</h1></section>
|
||||
<section><h1>445</h1></section>
|
||||
<section><h1>446</h1></section>
|
||||
<section><h1>447</h1></section>
|
||||
<section><h1>448</h1></section>
|
||||
<section><h1>449</h1></section>
|
||||
<section><h1>450</h1></section>
|
||||
<section><h1>451</h1></section>
|
||||
<section><h1>452</h1></section>
|
||||
<section><h1>453</h1></section>
|
||||
<section><h1>454</h1></section>
|
||||
<section><h1>455</h1></section>
|
||||
<section><h1>456</h1></section>
|
||||
<section><h1>457</h1></section>
|
||||
<section><h1>458</h1></section>
|
||||
<section><h1>459</h1></section>
|
||||
<section><h1>460</h1></section>
|
||||
<section><h1>461</h1></section>
|
||||
<section><h1>462</h1></section>
|
||||
<section><h1>463</h1></section>
|
||||
<section><h1>464</h1></section>
|
||||
<section><h1>465</h1></section>
|
||||
<section><h1>466</h1></section>
|
||||
<section><h1>467</h1></section>
|
||||
<section><h1>468</h1></section>
|
||||
<section><h1>469</h1></section>
|
||||
<section><h1>470</h1></section>
|
||||
<section><h1>471</h1></section>
|
||||
<section><h1>472</h1></section>
|
||||
<section><h1>473</h1></section>
|
||||
<section><h1>474</h1></section>
|
||||
<section><h1>475</h1></section>
|
||||
<section><h1>476</h1></section>
|
||||
<section><h1>477</h1></section>
|
||||
<section><h1>478</h1></section>
|
||||
<section><h1>479</h1></section>
|
||||
<section><h1>480</h1></section>
|
||||
<section><h1>481</h1></section>
|
||||
<section><h1>482</h1></section>
|
||||
<section><h1>483</h1></section>
|
||||
<section><h1>484</h1></section>
|
||||
<section><h1>485</h1></section>
|
||||
<section><h1>486</h1></section>
|
||||
<section><h1>487</h1></section>
|
||||
<section><h1>488</h1></section>
|
||||
<section><h1>489</h1></section>
|
||||
<section><h1>490</h1></section>
|
||||
<section><h1>491</h1></section>
|
||||
<section><h1>492</h1></section>
|
||||
<section><h1>493</h1></section>
|
||||
<section><h1>494</h1></section>
|
||||
<section><h1>495</h1></section>
|
||||
<section><h1>496</h1></section>
|
||||
<section><h1>497</h1></section>
|
||||
<section><h1>498</h1></section>
|
||||
<section><h1>499</h1></section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../dist/reveal.js"></script>
|
||||
<script>
|
||||
Reveal.initialize({
|
||||
transition: 'linear'
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
Source: https://freesound.org/people/fennelliott/sounds/379419/
|
||||
License: CC0 (public domain)
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,225 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>reveal.js - Auto Animate</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" href="../dist/reveal.css">
|
||||
<link rel="stylesheet" href="../dist/theme/black.css" id="theme">
|
||||
<link rel="stylesheet" href="../plugin/highlight/monokai.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="reveal">
|
||||
|
||||
<div class="slides">
|
||||
<section data-auto-animate data-auto-animate-unmatched="fade">
|
||||
<h3>Auto-Animate Example</h3>
|
||||
<p>This will fade out</p>
|
||||
<img src="assets/image1.png" style="height: 100px;">
|
||||
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
|
||||
function Example() {
|
||||
const [count, setCount] = useState(0);
|
||||
}
|
||||
</code></pre>
|
||||
</section>
|
||||
<section data-auto-animate data-auto-animate-unmatched="fade">
|
||||
<h3>Auto-Animate Example</h3>
|
||||
<p style="opacity: 0.2; margin-top: 100px;">This will fade out</p>
|
||||
<p>This element is unmatched</p>
|
||||
<img src="assets/image1.png" style="height: 150px;">
|
||||
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
|
||||
function Example() {
|
||||
New line!
|
||||
const [count, setCount] = useState(0);
|
||||
}
|
||||
</code></pre>
|
||||
</section>
|
||||
|
||||
<section data-auto-animate>
|
||||
<p data-id="text-props" style="background: #555; line-height: 1em; letter-spacing: 0em;">Line Height & Letter Spacing</p>
|
||||
</section>
|
||||
<section data-auto-animate>
|
||||
<p data-id="text-props" style="background: #555; line-height: 3em; letter-spacing: 0.2em;">Line Height & Letter Spacing</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section data-auto-animate>
|
||||
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function Example() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
...
|
||||
);
|
||||
}
|
||||
</code></pre>
|
||||
</section>
|
||||
<section data-auto-animate>
|
||||
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
|
||||
function Example() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>You clicked {count} times</p>
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Click me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</code></pre>
|
||||
</section>
|
||||
<section data-auto-animate>
|
||||
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
|
||||
function Example() {
|
||||
// A comment!
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>You clicked {count} times</p>
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Click me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</code></pre>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section data-auto-animate>
|
||||
<h3>Swapping list items</h3>
|
||||
<ul>
|
||||
<li>One</li>
|
||||
<li>Two</li>
|
||||
<li>Three</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section data-auto-animate>
|
||||
<h3>Swapping list items</h3>
|
||||
<ul>
|
||||
<li>Two</li>
|
||||
<li>One</li>
|
||||
<li>Three</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section data-auto-animate>
|
||||
<h3>Swapping list items</h3>
|
||||
<ul>
|
||||
<li>Two</li>
|
||||
<li>Three</li>
|
||||
<li>One</li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section data-auto-animate style="height: 600px">
|
||||
<h3 style="opacity: 0.3; font-size: 18px;">SLIDE 1</h3>
|
||||
<h2 data-id="title" style="margin-top: 260px;">Animate Anything</h2>
|
||||
<div data-id="1" style="background: white; position: absolute; top: 150px; left: 16%; width: 60px; height: 60px;"></div>
|
||||
<div data-id="2" style="background: white; position: absolute; top: 150px; left: 36%; width: 60px; height: 60px;"></div>
|
||||
<div data-id="3" style="background: white; position: absolute; top: 150px; left: 56%; width: 60px; height: 60px;"></div>
|
||||
<div data-id="4" style="background: white; position: absolute; top: 150px; left: 76%; width: 60px; height: 60px;"></div>
|
||||
</section>
|
||||
<section data-auto-animate style="height: 600px">
|
||||
<h3 style="opacity: 0.3; font-size: 18px;">SLIDE 2</h3>
|
||||
<h2 data-id="title" style="margin-top: 500px">With Auto Animate</h2>
|
||||
<div data-id="1" style="background: cyan; position: absolute; bottom: 190px; left: 16%; width: 60px; height: 60px;"></div>
|
||||
<div data-id="2" style="background: magenta; position: absolute; bottom: 190px; left: 36%; width: 60px; height: 160px;"></div>
|
||||
<div data-id="3" style="background: yellow; position: absolute; bottom: 190px; left: 56%; width: 60px; height: 260px;"></div>
|
||||
<div data-id="4" style="background: red; position: absolute; bottom: 190px; left: 76%; width: 60px; height: 360px;"></div>
|
||||
</section>
|
||||
<section data-auto-animate style="height: 600px">
|
||||
<h3 style="opacity: 0.3; font-size: 18px;">SLIDE 3</h3>
|
||||
<h2 data-id="title" style="margin-top: 500px; opacity: 0;">With Auto Animate</h2>
|
||||
<div data-id="1" style="background: cyan; position: absolute; top: 50%; left: 50%; width: 400px; height: 400px; margin: -200px 0 0 -200px; border-radius: 400px;"></div>
|
||||
<div data-id="2" style="background: magenta; position: absolute; top: 50%; left: 50%; width: 300px; height: 300px; margin: -150px 0 0 -150px; border-radius: 400px;"></div>
|
||||
<div data-id="3" style="background: yellow; position: absolute; top: 50%; left: 50%; width: 200px; height: 200px; margin: -100px 0 0 -100px; border-radius: 400px;"></div>
|
||||
<div data-id="4" style="background: red; position: absolute; top: 50%; left: 50%; width: 100px; height: 100px; margin: -50px 0 0 -50px; border-radius: 400px;"></div>
|
||||
</section>
|
||||
<section data-auto-animate style="height: 600px">
|
||||
<h3 style="opacity: 0.3; font-size: 18px;">SLIDE 3</h3>
|
||||
<h2 data-id="title" style="margin-top: 500px; opacity: 0;">With Auto Animate</h2>
|
||||
<div data-id="1" style="background: red; position: absolute; top: 250px; left: 16%; width: 60px; height: 60px;"></div>
|
||||
<div data-id="2" style="background: yellow; position: absolute; top: 250px; left: 36%; width: 60px; height: 60px;"></div>
|
||||
<div data-id="3" style="background: magenta; position: absolute; top: 250px; left: 56%; width: 60px; height: 60px;"></div>
|
||||
<div data-id="4" style="background: cyan; position: absolute; top: 250px; left: 76%; width: 60px; height: 60px;"></div>
|
||||
</section>
|
||||
|
||||
<section data-auto-animate data-auto-animate-id="a">
|
||||
<h2>data-auto-animate-id="a"</h2>
|
||||
<h3>A1</h3>
|
||||
</section>
|
||||
<section data-auto-animate data-auto-animate-id="a">
|
||||
<h2>data-auto-animate-id="a"</h2>
|
||||
<h3>A1</h3>
|
||||
<h3>A2</h3>
|
||||
</section>
|
||||
<section data-auto-animate data-auto-animate-id="b">
|
||||
<h2>data-auto-animate-id="b"</h2>
|
||||
<h3>B1</h3>
|
||||
</section>
|
||||
<section data-auto-animate data-auto-animate-id="b">
|
||||
<h2>data-auto-animate-id="b"</h2>
|
||||
<h3>B1</h3>
|
||||
<h3>B2</h3>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="stacked-slide-1" data-auto-animate>
|
||||
<a href="#/stacked-slide-1">Slide 1</a><br>
|
||||
<a href="#/stacked-slide-2">Slide 2</a><br>
|
||||
<a href="#/stacked-slide-3">Slide 3</a><br>
|
||||
<a href="#/stacked-slide-4">Slide 4</a><br>
|
||||
<div data-id="anim" style="background: indigo; padding: 8px; width: 50px; height: 50px; position: absolute; left: 0px;">A</div>
|
||||
</section>
|
||||
<section id="stacked-slide-2" data-auto-animate>
|
||||
<a href="#/stacked-slide-1">Slide 1</a><br>
|
||||
<a href="#/stacked-slide-2">Slide 2</a><br>
|
||||
<a href="#/stacked-slide-3">Slide 3</a><br>
|
||||
<a href="#/stacked-slide-4">Slide 4</a><br>
|
||||
<div data-id="anim" style="background: indigo; padding: 8px; width: 50px; height: 50px; position: absolute; left: 25%;">A</div>
|
||||
</section>
|
||||
<section id="stacked-slide-3" data-auto-animate>
|
||||
<a href="#/stacked-slide-1">Slide 1</a><br>
|
||||
<a href="#/stacked-slide-2">Slide 2</a><br>
|
||||
<a href="#/stacked-slide-3">Slide 3</a><br>
|
||||
<a href="#/stacked-slide-4">Slide 4</a><br>
|
||||
<div data-id="anim" style="background: indigo; padding: 8px; width: 50px; height: 50px; position: absolute; left: 50%;">A</div>
|
||||
</section>
|
||||
<section id="stacked-slide-4" data-auto-animate>
|
||||
<a href="#/stacked-slide-1">Slide 1</a><br>
|
||||
<a href="#/stacked-slide-2">Slide 2</a><br>
|
||||
<a href="#/stacked-slide-3">Slide 3</a><br>
|
||||
<a href="#/stacked-slide-4">Slide 4</a><br>
|
||||
<div data-id="anim" style="background: indigo; padding: 8px; width: 50px; height: 50px; position: absolute; left: 75%;">A</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="../dist/reveal.js"></script>
|
||||
<script src="../plugin/highlight/highlight.js"></script>
|
||||
<script>
|
||||
Reveal.initialize({
|
||||
center: true,
|
||||
hash: true,
|
||||
plugins: [ RevealHighlight ]
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,141 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>reveal.js - Slide Backgrounds</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" href="../dist/reveal.css">
|
||||
<link rel="stylesheet" href="../dist/theme/serif.css" id="theme">
|
||||
<style type="text/css" media="screen">
|
||||
.slides section.has-dark-background,
|
||||
.slides section.has-dark-background h2 {
|
||||
color: #fff;
|
||||
}
|
||||
.slides section.has-light-background,
|
||||
.slides section.has-light-background h2 {
|
||||
color: #222;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="reveal">
|
||||
|
||||
<div class="slides">
|
||||
|
||||
<section data-background="#00ffff">
|
||||
<h2>data-background: #00ffff</h2>
|
||||
</section>
|
||||
|
||||
<section data-background="#bb00bb">
|
||||
<h2>data-background: #bb00bb</h2>
|
||||
</section>
|
||||
|
||||
<section data-background-color="lightblue">
|
||||
<h2>data-background: lightblue</h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section data-background="#ff0000">
|
||||
<h2>data-background: #ff0000</h2>
|
||||
</section>
|
||||
<section data-background="rgba(0, 0, 0, 0.2)">
|
||||
<h2>data-background: rgba(0, 0, 0, 0.2)</h2>
|
||||
</section>
|
||||
<section data-background="salmon">
|
||||
<h2>data-background: salmon</h2>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section data-background="rgba(0, 100, 100, 0.2)">
|
||||
<section>
|
||||
<h2>Background applied to stack</h2>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Background applied to stack</h2>
|
||||
</section>
|
||||
<section data-background="rgb(66, 66, 66)">
|
||||
<h2>Background applied to slide inside of stack</h2>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section data-background-transition="slide" data-background="assets/image1.png">
|
||||
<h2>Background image</h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section data-background-transition="slide" data-background="assets/image1.png">
|
||||
<h2>Background image</h2>
|
||||
</section>
|
||||
<section data-background-transition="slide" data-background="assets/image1.png">
|
||||
<h2>Background image</h2>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section data-background="assets/image2.png" data-background-size="100px" data-background-repeat="repeat" data-background-color="#111">
|
||||
<h2>Background image</h2>
|
||||
<pre>data-background-size="100px" data-background-repeat="repeat" data-background-color="#111"</pre>
|
||||
</section>
|
||||
|
||||
<section data-background="#888888">
|
||||
<h2>Same background twice (1/2)</h2>
|
||||
</section>
|
||||
<section data-background="#888888">
|
||||
<h2>Same background twice (2/2)</h2>
|
||||
</section>
|
||||
|
||||
<section data-background-video="https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.mp4,https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.webm">
|
||||
<h2>Video background</h2>
|
||||
</section>
|
||||
|
||||
<section data-background-iframe="https://slides.com/news/make-better-presentations/embed?style=hidden&autoSlide=4000">
|
||||
<h2>Iframe background</h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section data-background="#417203">
|
||||
<h2>Same background twice vertical (1/2)</h2>
|
||||
</section>
|
||||
<section data-background="#417203">
|
||||
<h2>Same background twice vertical (2/2)</h2>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section data-background="#934f4d">
|
||||
<h2>Same background from horizontal to vertical (1/3)</h2>
|
||||
</section>
|
||||
<section>
|
||||
<section data-background="#934f4d">
|
||||
<h2>Same background from horizontal to vertical (2/3)</h2>
|
||||
</section>
|
||||
<section data-background="#934f4d">
|
||||
<h2>Same background from horizontal to vertical (3/3)</h2>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="../dist/reveal.js"></script>
|
||||
<script>
|
||||
|
||||
// Full list of configuration options:
|
||||
// https://revealjs.revealjs.com/config/
|
||||
Reveal.initialize({
|
||||
center: true,
|
||||
|
||||
transition: 'linear',
|
||||
// transitionSpeed: 'slow',
|
||||
// backgroundTransition: 'slide'
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>reveal.js - Barebones</title>
|
||||
<link rel="stylesheet" href="../dist/reveal.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="reveal">
|
||||
<div class="slides">
|
||||
|
||||
<section>
|
||||
<h2>Barebones Presentation</h2>
|
||||
<p>This example contains the bare minimum includes and markup required to run a reveal.js presentation.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>No Theme</h2>
|
||||
<p>There's no theme included, so it will fall back on browser defaults.</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../dist/reveal.js"></script>
|
||||
<script>
|
||||
Reveal.initialize();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,160 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>reveal.js - Layout Helpers</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" href="../dist/reveal.css">
|
||||
<link rel="stylesheet" href="../dist/theme/white.css" id="theme">
|
||||
<link rel="stylesheet" href="../plugin/highlight/monokai.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="reveal">
|
||||
|
||||
<div class="slides">
|
||||
|
||||
<section>
|
||||
<h2>Layout Helper Examples</h2>
|
||||
<ul>
|
||||
<li><a href="#/fit-text">Big Text</a></li>
|
||||
<li><a href="#/stretch">Stretch</a></li>
|
||||
<li><a href="#/stack">Stack</a></li>
|
||||
<li><a href="#/hstack">HStack</a></li>
|
||||
<li><a href="#/vstack">VStack</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="fit-text">
|
||||
<h2>Fit Text</h2>
|
||||
<p>Resizes text to be as large as possible within its container.</p>
|
||||
<pre><code class="html" data-trim data-line-numbers>
|
||||
<h2 class="r-fit-text">FIT</h2>
|
||||
</code></pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="r-fit-text">FIT</h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="r-fit-text">HELLO WORLD</h2>
|
||||
<h2 class="r-fit-text">BOTH THESE TITLES USE FIT-TEXT</h2>
|
||||
</section>
|
||||
|
||||
<section id="stretch">
|
||||
<h2>Stretch</h2>
|
||||
<p>Makes an element as tall as possible while remaining within the slide bounds.</p>
|
||||
<pre><code class="html" data-trim data-line-numbers>
|
||||
<h2>Stretch Example</h2>
|
||||
<img src="assets/image2.png" class="r-stretch">
|
||||
<p>Image byline</p>
|
||||
</code></pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Stretch Example</h2>
|
||||
<img src="assets/image2.png" class="r-stretch">
|
||||
<p>Image byline</p>
|
||||
</section>
|
||||
|
||||
<section id="stack">
|
||||
<h2>Stack</h2>
|
||||
<p>Stacks multiple elements on top of each other, for use with fragments.</p>
|
||||
<pre><code class="html" data-trim data-line-numbers>
|
||||
<div class="r-stack">
|
||||
<img class="fragment" width="450" height="300" src="...">
|
||||
<img class="fragment" width="300" height="450" src="...">
|
||||
<img class="fragment" width="400" height="400" src="...">
|
||||
</div>
|
||||
</code></pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Stack Example</h2>
|
||||
<div class="r-stack">
|
||||
<p class="fragment fade-in-then-out">One</p>
|
||||
<p class="fragment fade-in-then-out">Two</p>
|
||||
<p class="fragment fade-in-then-out">Three</p>
|
||||
<p class="fragment fade-in-then-out">Four</p>
|
||||
</div>
|
||||
<div class="r-stack">
|
||||
<img src="https://placekitten.com/450/300" width="450" height="300" class="fragment">
|
||||
<img src="https://placekitten.com/300/450" width="300" height="450" class="fragment">
|
||||
<img src="https://placekitten.com/400/400" width="400" height="400" class="fragment">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Stack Example</h2>
|
||||
<p>fade-in-then-out fragments</p>
|
||||
<div class="r-stack">
|
||||
<img src="https://placekitten.com/450/300" width="450" height="300" class="fragment fade-in-then-out">
|
||||
<img src="https://placekitten.com/300/450" width="300" height="450" class="fragment fade-in-then-out">
|
||||
<img src="https://placekitten.com/400/400" width="400" height="400" class="fragment fade-in-then-out">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="hstack">
|
||||
<h2>HStack</h2>
|
||||
<p>Stacks multiple elements horizontally.</p>
|
||||
<pre><code class="html" data-trim data-line-numbers>
|
||||
<div class="r-hstack">
|
||||
<img width="450" height="300" src="...">
|
||||
<img width="300" height="450" src="...">
|
||||
<img width="400" height="400" src="...">
|
||||
</div>
|
||||
</code></pre>
|
||||
</section>
|
||||
|
||||
<section data-auto-animate>
|
||||
<h2>HStack Example</h2>
|
||||
<div class="r-hstack">
|
||||
<p style="padding: 0.50em; background: #eee; margin: 0.25em">One</p>
|
||||
<p style="padding: 0.75em; background: #eee; margin: 0.25em">Two</p>
|
||||
<p style="padding: 1.00em; background: #eee; margin: 0.25em">Three</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="vstack">
|
||||
<h2>VStack</h2>
|
||||
<p>Stacks multiple elements vertically.</p>
|
||||
<pre><code class="html" data-trim data-line-numbers>
|
||||
<div class="r-vstack">
|
||||
<img width="450" height="300" src="...">
|
||||
<img width="300" height="450" src="...">
|
||||
<img width="400" height="400" src="...">
|
||||
</div>
|
||||
</code></pre>
|
||||
</section>
|
||||
|
||||
<section data-auto-animate>
|
||||
<h2>VStack Example</h2>
|
||||
<div class="r-vstack">
|
||||
<p style="padding: 0.50em; background: #eee; margin: 0.25em">One</p>
|
||||
<p style="padding: 0.75em; background: #eee; margin: 0.25em">Two</p>
|
||||
<p style="padding: 1.00em; background: #eee; margin: 0.25em">Three</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="../dist/reveal.js"></script>
|
||||
<script src="../plugin/highlight/highlight.js"></script>
|
||||
<script>
|
||||
Reveal.initialize({
|
||||
center: true,
|
||||
hash: true,
|
||||
plugins: [ RevealHighlight ]
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,161 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>reveal.js - Markdown Example</title>
|
||||
|
||||
<link rel="stylesheet" href="../dist/reveal.css">
|
||||
<link rel="stylesheet" href="../dist/theme/white.css" id="theme">
|
||||
|
||||
<link rel="stylesheet" href="../plugin/highlight/monokai.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="reveal">
|
||||
|
||||
<div class="slides">
|
||||
|
||||
<!-- Use external markdown resource, separate slides by three newlines; vertical slides by two newlines -->
|
||||
<section data-markdown="markdown.md" data-separator="^\n\n\n" data-separator-vertical="^\n\n"></section>
|
||||
|
||||
<!-- Slides are separated by three dashes (the default) -->
|
||||
<section data-markdown>
|
||||
<script type="text/template">
|
||||
## Demo 1
|
||||
Slide 1
|
||||
---
|
||||
## Demo 1
|
||||
Slide 2
|
||||
---
|
||||
## Demo 1
|
||||
Slide 3
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- Slides are separated by regexp matching newline + three dashes + newline, vertical slides identical but two dashes -->
|
||||
<section data-markdown data-separator="^\n---\n$" data-separator-vertical="^\n--\n$">
|
||||
<script type="text/template">
|
||||
## Demo 2
|
||||
Slide 1.1
|
||||
|
||||
--
|
||||
|
||||
## Demo 2
|
||||
Slide 1.2
|
||||
|
||||
---
|
||||
|
||||
## Demo 2
|
||||
Slide 2
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- No "extra" slides, since the separator can't be matched ("---" will become horizontal rulers) -->
|
||||
<section data-markdown data-separator="$x">
|
||||
<script type="text/template">
|
||||
A
|
||||
|
||||
---
|
||||
|
||||
B
|
||||
|
||||
---
|
||||
|
||||
C
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- Slide attributes -->
|
||||
<section data-markdown>
|
||||
<script type="text/template">
|
||||
<!-- .slide: data-background="#000000" -->
|
||||
## Slide attributes
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- Element attributes -->
|
||||
<section data-markdown>
|
||||
<script type="text/template">
|
||||
## Element attributes
|
||||
- Item 1 <!-- .element: class="fragment" data-fragment-index="2" -->
|
||||
- Item 2 <!-- .element: class="fragment" data-fragment-index="1" -->
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- Code -->
|
||||
<section data-markdown>
|
||||
<script type="text/template">
|
||||
```php [1|3-5]
|
||||
public function foo()
|
||||
{
|
||||
$foo = array(
|
||||
'bar' => 'bar'
|
||||
)
|
||||
}
|
||||
```
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- add optional line count offset, in this case 287 -->
|
||||
<section data-markdown>
|
||||
<script type="text/template">
|
||||
## echo.c
|
||||
|
||||
```c [287: 2|4,6]
|
||||
/* All of the options in this arg are valid, so handle them. */
|
||||
p = arg + 1;
|
||||
do {
|
||||
if (*p == 'n')
|
||||
nflag = 0;
|
||||
if (*p == 'e')
|
||||
eflag = '\\';
|
||||
} while (*++p);
|
||||
```
|
||||
[source](https://git.busybox.net/busybox/tree/coreutils/echo.c?h=1_36_stable#n287)
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- Images -->
|
||||
<section data-markdown>
|
||||
<script type="text/template">
|
||||

|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- Math -->
|
||||
<section data-markdown>
|
||||
## The Lorenz Equations
|
||||
`\[\begin{aligned}
|
||||
\dot{x} & = \sigma(y-x) \\
|
||||
\dot{y} & = \rho x - y - xz \\
|
||||
\dot{z} & = -\beta z + xy
|
||||
\end{aligned} \]`
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../dist/reveal.js"></script>
|
||||
<script src="../plugin/markdown/markdown.js"></script>
|
||||
<script src="../plugin/highlight/highlight.js"></script>
|
||||
<script src="../plugin/notes/notes.js"></script>
|
||||
<script src="../plugin/math/math.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
Reveal.initialize({
|
||||
controls: true,
|
||||
progress: true,
|
||||
history: true,
|
||||
center: true,
|
||||
|
||||
plugins: [ RevealMarkdown, RevealHighlight, RevealNotes, RevealMath.KaTeX ]
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
# Markdown Demo
|
||||
|
||||
|
||||
|
||||
## External 1.1
|
||||
|
||||
Content 1.1
|
||||
|
||||
Note: This will only appear in the speaker notes window.
|
||||
|
||||
|
||||
## External 1.2
|
||||
|
||||
Content 1.2
|
||||
|
||||
|
||||
|
||||
## External 2
|
||||
|
||||
Content 2.1
|
||||
|
||||
|
||||
|
||||
## External 3.1
|
||||
|
||||
Content 3.1
|
||||
|
||||
|
||||
## External 3.2
|
||||
|
||||
Content 3.2
|
||||
|
||||
|
||||
## External 3.3 (Image)
|
||||
|
||||

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