ids theme: speaker notes overlay button for touch devices
The reveal.js speaker view ('s') opens a separate window, which is
awkward on mobile browsers. Add a button next to the fullscreen toggle
(shown on coarse-pointer devices only, like the fullscreen button) that
covers the slide with a full-screen overlay showing the current slide's
speaker notes, its title and planned time (from data-timing). The
overlay follows slide changes while open, closes with the X button or
Escape, is hidden in print output, and the button only appears when the
deck contains notes at all.
Change-Id: I50c6cd701115a973e8c6ba35448b13d5c722ab5a
diff --git a/inst/reveal.js-5.2.1/dist/theme/ids.css b/inst/reveal.js-5.2.1/dist/theme/ids.css
index ee3b816..8f95547 100644
--- a/inst/reveal.js-5.2.1/dist/theme/ids.css
+++ b/inst/reveal.js-5.2.1/dist/theme/ids.css
@@ -926,6 +926,92 @@
display: none !important;
}
+/*********************************************
+ * SPEAKER NOTES OVERLAY (touch devices)
+ *********************************************/
+#ids-notes {
+ display: none;
+ position: fixed;
+ left: 70px;
+ bottom: 6%;
+ z-index: 30;
+ width: 28px;
+ height: 28px;
+ border: 0;
+ padding: 0;
+ cursor: pointer;
+ opacity: 0.55;
+ background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23aaaaaa' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'/%3E%3C/svg%3E") center/contain no-repeat;
+}
+#ids-notes:active, #ids-notes:focus-visible {
+ opacity: 0.9;
+}
+#ids-notes.is-open {
+ opacity: 0.9;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f6a800' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'/%3E%3C/svg%3E");
+}
+
+@media (hover: none) and (pointer: coarse) {
+ #ids-notes {
+ display: block;
+ }
+}
+html.reveal-print #ids-notes {
+ display: none !important;
+}
+
+#ids-notes-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 100;
+ background: #fff;
+ color: #444444;
+ font-family: "Fira Sans", "Fira Sans Condensed", sans-serif;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ padding: max(24px, env(safe-area-inset-top)) max(24px, env(safe-area-inset-right))
+ max(24px, env(safe-area-inset-bottom)) max(24px, env(safe-area-inset-left));
+}
+#ids-notes-overlay[hidden] {
+ display: none;
+}
+#ids-notes-header {
+ display: flex;
+ align-items: baseline;
+ gap: 12px;
+ border-bottom: 1px solid #e0e0e0;
+ margin-bottom: 16px;
+ padding-bottom: 8px;
+}
+#ids-notes-timing {
+ flex-shrink: 0;
+ color: #f6a800;
+ font-weight: bold;
+ white-space: nowrap;
+}
+#ids-notes-title {
+ flex-grow: 1;
+ margin: 0;
+ font-size: 1.1em;
+}
+#ids-notes-close {
+ flex-shrink: 0;
+ border: 0;
+ background: none;
+ color: #888888;
+ font-size: 1.2em;
+ cursor: pointer;
+ padding: 8px;
+ margin: -8px;
+}
+#ids-notes-content {
+ font-size: 20px;
+ line-height: 1.5;
+}
+#ids-notes-content em {
+ color: #888888;
+}
+
.reveal .controls .controls-arrow:after, .reveal .controls .controls-arrow:before {
width: 13px !important; /* size */
height: 4px; /* line width */
diff --git a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
index 7370f80..55fdfaf 100644
--- a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
+++ b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
@@ -866,6 +866,15 @@
<footer id="ids-footer"><span>
$for(author)$$if(author.name)$$author.name$$else$$author$$endif$${ sep }, $endfor$ · $title$ · $date$</span></footer>
<button id="ids-fullscreen" aria-label="Toggle fullscreen" title="Fullscreen"></button>
+<button id="ids-notes" aria-label="Show speaker notes" title="Speaker notes"></button>
+<div id="ids-notes-overlay" hidden>
+ <header id="ids-notes-header">
+ <span id="ids-notes-timing" role="status"></span>
+ <h2 id="ids-notes-title"></h2>
+ <button id="ids-notes-close" aria-label="Close speaker notes">✕</button>
+ </header>
+ <div id="ids-notes-content"></div>
+</div>
<script>
// IDS theme: touch devices have no F11 and the browser chrome eats slide
// pixels, so offer a small fixed fullscreen toggle (styled in the theme;
@@ -894,6 +903,67 @@
});
})();
</script>
+<script>
+ // IDS theme: the reveal.js speaker view ('s') opens a separate window,
+ // which is awkward on mobile browsers. Offer a button (styled in the
+ // theme; only shown on coarse-pointer devices) that overlays the slide
+ // with the speaker notes of the current slide -- useful for practicing
+ // a talk on a phone or tablet.
+ (function () {
+ var btn = document.getElementById('ids-notes');
+ var overlay = document.getElementById('ids-notes-overlay');
+ if (!btn || !overlay || typeof Reveal === 'undefined') return;
+
+ // no need for the button if the deck has no notes at all
+ if (!document.querySelector('aside.notes, [data-notes]')) {
+ btn.style.display = 'none';
+ return;
+ }
+
+ function formatTiming(seconds) {
+ var m = Math.floor(seconds / 60);
+ var s = Math.round(seconds % 60);
+ return m + ':' + ('0' + s).slice(-2) + ' min';
+ }
+
+ function update() {
+ var slide = Reveal.getCurrentSlide();
+ if (!slide) return;
+ var heading = slide.querySelector('h1, h2, h3');
+ document.getElementById('ids-notes-title').textContent =
+ heading ? heading.textContent : '';
+ var timing = parseInt(slide.getAttribute('data-timing'));
+ document.getElementById('ids-notes-timing').textContent =
+ isNaN(timing) ? '' : formatTiming(timing);
+ var notes = slide.querySelectorAll('aside.notes, [data-notes]');
+ document.getElementById('ids-notes-content').innerHTML =
+ Array.prototype.map.call(notes, function (n) { return n.innerHTML; }).join('')
+ || '<p><em>No notes on this slide.</em></p>';
+ overlay.scrollTop = 0;
+ }
+
+ function setOpen(on) {
+ overlay.hidden = !on;
+ btn.classList.toggle('is-open', on);
+ if (on) update();
+ }
+
+ btn.addEventListener('click', function (e) {
+ e.stopPropagation();
+ setOpen(overlay.hidden);
+ });
+ document.getElementById('ids-notes-close').addEventListener('click', function (e) {
+ e.stopPropagation();
+ setOpen(false);
+ });
+ document.addEventListener('keydown', function (e) {
+ if (e.key === 'Escape' && !overlay.hidden) setOpen(false);
+ });
+ Reveal.addEventListener('slidechanged', function () {
+ if (!overlay.hidden) update();
+ });
+ })();
+</script>
$endif$
</body>
</html>