transcript view: list button for touch devices

The 'r' keyboard shortcut is useless on mobile browsers, so the
transcript view can now also be toggled with a button next to the
speaker notes button (shown on coarse-pointer devices, like the
fullscreen and notes buttons, and highlighted while the transcript is
open).

Change-Id: I10909124850a5fba469a670115e87ef4f6c4376b
diff --git a/NEWS.md b/NEWS.md
index f9c044e..134bf68 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,6 @@
 # revealjs.ids (development version)
 
-- New transcript view, toggled with `r`: a single scrollable page containing every slide's content and speaker notes. Text-to-speech ("read aloud") browser extensions cannot walk a reveal.js deck (hidden slides are not part of the visible page text), but they can read the transcript from start to finish -- e.g. to have the whole talk including notes read aloud while practicing.
+- New transcript view, toggled with `r` (or, on touch devices, with a list button next to the speaker notes button): a single scrollable page containing every slide's content and speaker notes. Text-to-speech ("read aloud") browser extensions cannot walk a reveal.js deck (hidden slides are not part of the visible page text), but they can read the transcript from start to finish -- e.g. to have the whole talk including notes read aloud while practicing.
 
 - Slides titled "Overview" or "Überblick" are now automatically turned into a table of contents (ported from the old Google Docs slide workflow): their body content is replaced at presentation time with a clickable list of all `#` chapter headings. Adding `{data-overview-subheadings=true}` to the heading also includes the `##` sub-headings, laid out in two columns. References and appendix chapters (References/Literatur/Literaturverzeichnis/Appendix/Anhang) are excluded, together with their sub-slides.
 
diff --git a/R/revealjs_presentation.R b/R/revealjs_presentation.R
index e73ede9..d28fcd8 100644
--- a/R/revealjs_presentation.R
+++ b/R/revealjs_presentation.R
@@ -40,11 +40,13 @@
 #' useful for practicing a talk on a phone or tablet. It follows along as
 #' you change slides and only appears if the deck contains notes at all.
 #' 
-#' Pressing `r` toggles a transcript view: a single scrollable page
-#' containing every slide's content and speaker notes. Text-to-speech
-#' ("read aloud") browser extensions cannot walk a reveal.js deck, since
-#' hidden slides are not part of the visible page text -- the transcript
-#' gives them something they can read from start to finish.
+#' Pressing `r` -- or, on touch devices, tapping the list button next to
+#' the speaker notes button -- toggles a transcript view: a single
+#' scrollable page containing every slide's content and speaker notes.
+#' Text-to-speech ("read aloud") browser extensions cannot walk a
+#' reveal.js deck, since hidden slides are not part of the visible page
+#' text -- the transcript gives them something they can read from start
+#' to finish.
 #' 
 #' ## Slide timing
 #' 
diff --git a/README.Rmd b/README.Rmd
index 8c64a12..d5611cd 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -109,7 +109,7 @@
 
 -   `'s'` enable presentation mode with speaker notes when the Notes plugin is activated
 
-On mobile browsers (touch devices), a speech-bubble button next to the fullscreen button overlays the current slide with its speaker notes -- useful for practicing a talk on a phone or tablet, where the separate speaker view window of the `'s'` shortcut is awkward. The overlay shows the slide title, its planned time (if annotated with a timing comment), and the notes, and follows along as you change slides. It appears only if the deck contains notes at all.
+On mobile browsers (touch devices), a speech-bubble button next to the fullscreen button overlays the current slide with its speaker notes -- useful for practicing a talk on a phone or tablet, where the separate speaker view window of the `'s'` shortcut is awkward. The overlay shows the slide title, its planned time (if annotated with a timing comment), and the notes, and follows along as you change slides. It appears only if the deck contains notes at all. Next to it, a list button opens the transcript view (same as pressing `r`), so the whole talk including notes can be read (aloud) on mobile devices, too.
 
 -   `'r'` toggle the transcript view: a single scrollable page containing every slide's content and speaker notes, so text-to-speech ("read aloud") browser extensions can read the whole presentation -- they cannot walk a reveal.js deck themselves, since hidden slides are not part of the visible page text
 
diff --git a/README.md b/README.md
index 000c48d..1d5025f 100644
--- a/README.md
+++ b/README.md
@@ -135,7 +135,9 @@
 speaker view window of the `'s'` shortcut is awkward. The overlay shows
 the slide title, its planned time (if annotated with a timing comment),
 and the notes, and follows along as you change slides. It appears only
-if the deck contains notes at all.
+if the deck contains notes at all. Next to it, a list button opens the
+transcript view (same as pressing `r`), so the whole talk including
+notes can be read (aloud) on mobile devices, too.
 
 -   `'r'` toggle the transcript view: a single scrollable page containing
     every slide's content and speaker notes, so text-to-speech ("read
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 d1b8467..52f3cb7 100644
--- a/inst/reveal.js-5.2.1/dist/theme/ids.css
+++ b/inst/reveal.js-5.2.1/dist/theme/ids.css
@@ -951,12 +951,33 @@
   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");
 }
 
+#ids-transcript-btn {
+  display: none;
+  position: fixed;
+  left: 110px;
+  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%3Cline x1='8' y1='6' x2='21' y2='6'/%3E%3Cline x1='8' y1='12' x2='21' y2='12'/%3E%3Cline x1='8' y1='18' x2='21' y2='18'/%3E%3Cline x1='3' y1='6' x2='3.01' y2='6'/%3E%3Cline x1='3' y1='12' x2='3.01' y2='12'/%3E%3Cline x1='3' y1='18' x2='3.01' y2='18'/%3E%3C/svg%3E") center/contain no-repeat;
+}
+#ids-transcript-btn:active, #ids-transcript-btn:focus-visible, #ids-transcript-btn.is-open {
+  opacity: 0.9;
+}
+#ids-transcript-btn.is-open {
+  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%3Cline x1='8' y1='6' x2='21' y2='6'/%3E%3Cline x1='8' y1='12' x2='21' y2='12'/%3E%3Cline x1='8' y1='18' x2='21' y2='18'/%3E%3Cline x1='3' y1='6' x2='3.01' y2='6'/%3E%3Cline x1='3' y1='12' x2='3.01' y2='12'/%3E%3Cline x1='3' y1='18' x2='3.01' y2='18'/%3E%3C/svg%3E");
+}
+
 @media (hover: none) and (pointer: coarse) {
-  #ids-notes {
+  #ids-notes, #ids-transcript-btn {
     display: block;
   }
 }
-html.reveal-print #ids-notes {
+html.reveal-print #ids-notes, html.reveal-print #ids-transcript-btn {
   display: none !important;
 }
 
diff --git a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
index 003336b..9d49602 100644
--- a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
+++ b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
@@ -878,6 +878,7 @@
     <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>
+<button id="ids-transcript-btn" aria-label="Show transcript of all slides and notes" title="Transcript"></button>
 <div id="ids-notes-overlay" hidden>
   <header id="ids-notes-header">
     <span id="ids-notes-timing" role="status"></span>
@@ -1099,6 +1100,16 @@
         build();
         overlay.hidden = false;
       }
+      var btn = document.getElementById('ids-transcript-btn');
+      if (btn) btn.classList.toggle('is-open', !overlay.hidden);
+    }
+
+    var transcriptBtn = document.getElementById('ids-transcript-btn');
+    if (transcriptBtn) {
+      transcriptBtn.addEventListener('click', function (e) {
+        e.stopPropagation();
+        toggle();
+      });
     }
 
     document.addEventListener('keydown', function (e) {
diff --git a/man/revealjs_presentation.Rd b/man/revealjs_presentation.Rd
index f0b82b4..0e93d1a 100644
--- a/man/revealjs_presentation.Rd
+++ b/man/revealjs_presentation.Rd
@@ -184,11 +184,13 @@
 useful for practicing a talk on a phone or tablet. It follows along as
 you change slides and only appears if the deck contains notes at all.
 
-Pressing \code{r} toggles a transcript view: a single scrollable page
-containing every slide's content and speaker notes. Text-to-speech
-("read aloud") browser extensions cannot walk a reveal.js deck, since
-hidden slides are not part of the visible page text -- the transcript
-gives them something they can read from start to finish.
+Pressing \code{r} -- or, on touch devices, tapping the list button next to
+the speaker notes button -- toggles a transcript view: a single
+scrollable page containing every slide's content and speaker notes.
+Text-to-speech ("read aloud") browser extensions cannot walk a
+reveal.js deck, since hidden slides are not part of the visible page
+text -- the transcript gives them something they can read from start
+to finish.
 }
 
 }