Author(s): The user manual at the end has setup information and
+ instructions. To write slides, look at the slides in this
+ template.
+
+
Reader(s):
+
+ To start the slide show, press ‘A’. Return to
+ the index with ‘A’ or ‘Esc’. On a touch screen, use a 3-finger
+ touch. Double click to open a specific slide. In slide mode, press
+ ‘?’ (question mark) to get a list of available commands.
+
+ To start the slide show, press Shift+F5
+ (Command+Enter on Mac). Return to the index by pressing ‘Esc’. You
+ can also click to open a specific slide.
+
+
If it doesn't work: Slide mode requires a
+ recent browser with JavaScript. If you are using the ‘NoScript’ add-on (Firefox or the
+ Tor Browser), or changed the ‘site settings’ (Chrome, Vivaldi, Opera, Brave and some other
+ browsers), or the ‘permissions for this site’ (Edge), you may have
+ to explicitly allow JavaScript on these slides. Internet Explorer
+ is not supported.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Leaving slide mode.
+
+
+
+
+
%%Title%%
+ %%Author%%
+
TPAC 2025
+ Kobe, Japan & online
+ 10–14 November 2025
+
+
+
+
Notes for slide 1 can be put here.
+
+
+
+
+
Lists
+
This is an H3
+
+
Potatoes
+
Onions and olives
+
+
Another H3
+
+
North pole
+
East pole
+
+
+
+
+
Notes for slide 2 can be put here.
+
+
+
+
+
Lists with icons
+
+
♪ North pole
+
🍉 East pole
+
Metro pole
+
Straw pole
+
🐥 Pole tree
+
+
+
+
+
Notes.
+
+
+
+
+
Some elements
+
Words can be given a strong emphasis, which
+ makes them appear in bold
Combining class=incremental with class=in-place
+ yields elements that are displayed one by one, with each one
+ replacing the previous. Example:
+
+
+
+
X
+
+
+
+
O
+
X
+
+
+
+
X
O
+
X
+
+
+
+
X
O
+
X
+
O
+
+
+
X
X
O
+
X
+
O
+
+
+
X
X
O
+
X
O
+
O
+
+
+
X
X
O
+
X
O
+
O
X
+
+
+
(class=overlay is an alias for class=incremental.)
+
+
+
+
Notes.
+
+
+
+
+
‘Slipshow’ presentations
+
A slipshow presentation is one where you don't put each
+ topic on a separate slide, but add it to a long scroll, which
+ automatically moves up to display it.
Just progress (with space, arrow, click or swipe) to display
+ additional topics.
+
+
A topic
+
This new topic is added…
+
+
+
Another topic
+
When there is no more space, old content moves up…
+
+
+
+
Column head A
Column head B
+
+
+
A table
Just as an example…
+
A2
B2
+
A3
B3…
+
+
+
+
A topic with a list
+
The space for the list is reserved…
+
+
… but the list
+
is added separately…
+
+
+
One more bit of content…
+
And a final one.
+
+
+
+
Notes.
+
+
+
+
+
An image on the side
+
+
It seems the famous ‘lorem ipsum’ is based on a text by Cicero,
+ but with the lines mixed up. On a Cicero by the text, it seems the
+ ‘ipsum lorem’ is famous based with lines but mixed up.
+
+
+
+
Notes.
+
+
+
+
+
An image on the side
+
+
And again, with class slide side right.
+
It seems the famous ‘lorem ipsum’ is based on a text by Cicero,
+ but with the lines mixed up. On a Cicero by the text, it seems the
+ ‘ipsum lorem’ is famous based with lines but mixed up.
+
+
+
+
Notes.
+
+
+
+
+
A ‘bleeding’ image
+
+
Adding class cover to the image makes it stretch to
+ the edges of the slide.
+
+
+
+
Notes.
+
+
+
+
+
A ‘bleeding’ image
+
+
And also on the right side…
+
Hint: Add class=clear on the slide to omit the banner
+ and slide number.
+
+
+
+
Notes.
+
+
+
+
+
A big image on the side
+
+
It seems the famous ‘lorem ipsum’ is based on a text by Cicero,
+ but with the lines mixed up. On a Cicero by the text, it seems the
+ ‘ipsum lorem’ is famous based with lines but mixed up.
+
+
+
+
Notes.
+
+
+
+
+
A big image on the side
+
+
And again, with class ‘slide side right big’.
+
It seems the famous ‘lorem ipsum’ is based on a text by Cicero,
+ but with the lines mixed up…
+
+
+
+
Notes.
+
+
+
+
+
A big, ‘bleeding’ image
+
+
It seems the famous ‘lorem ipsum’ is based on a text by Cicero,
+ but with the lines mixed up. On a Cicero by the text, it seems
+ ‘ipsum lorem’ is famous based with lines but mixed up.
+
+
+
+
Notes.
+
+
+
+
+
A big, ‘bleeding’ image
+
+
And on the right side…
+
+
+
+
Notes.
+
+
+
+
+
A figure
+
+
+
+
+
The description of the pie chart, here as a table:
+
+
+
Name
Percentage
+
+
Andy
16%
+
Chloe
21%
+
Daniel
13%
+
Grace
20%
+
Sophia
30%
+
+
+
+
+
+
Notes.
+
+
+
+
+
In columns
+
+
+
Title
+
Children of an element with a class of columns are
+ distributed over two columns
+
+
+
Title
+
This is the second child, which goes into the right
+ column
+
+
+
Title
+
And this is the third one. Left column again.
+
+
Etc.
+
+
+
+
+
Notes.
+
+
+
+
+
place t l
+
place t
+
place t r
+
Here is something for the left
+ side, with class place l
+
class place puts an element
+ centered in a 3×3 grid
+
place r
+
place b l
+
place b
+
combine place with
+ top (or t),
+ right (or r),
+ bottom (or b) and
+ left (or l)
+
+
+
+
Notes.
+
+
+
+
+
Numbered lines of code
+
+Lines in a PRE can be numbered
+ (automatically)
+ * Give the PRE a class of "numbered"
+ * Works for up to 20 lines
+ (depending on font size)
+ six
+ seven
+ eight
+ nine
+ ten
+ eleven
+
+
+
+
Notes.
+
+
+
+
+
Striped tables
+
+
row 1
has no background
+
row 2
has a gray background
+
row 3
has no background
+
row 4
has a gray background
+
etc.
+
etc.
+
+
+
+
+
Notes.
+
+
+
+
+
Image overlay: cover
+
+
+
Careful, some images make the text hard to
+ read!
+
+
+
+
Notes.
+
+
+
+
+
Image overlay: fit
+
+
+
Careful, some images make the text
+ hard to read!
+
+
+
+
Notes.
+
+
+
+
+
White text
+
+
+
Use a class of slide darkmode for
+ white-on-black
A transition can be set globally, applying to all slides;
+
or
+ locally, applying only to the transition between this slide and the
+ next.
+
+
+
+
Notes.
+
+
+
+
+
Automatically scale text down
+
If the text doesn't fit, and you really cannot reduce it nor
+ split the slide, you can ask b6+ to scale the text down until it
+ does fit.
+
To do this, add class textfit to the slide.
+
Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ Too much text…
+ The last line.
This is a template for slides for TPAC 2025. It uses either
+ the Shower script (version 3.2) or
+ the b6+
+ script for the presentation. (To enable Shower, uncomment the
+ script tag in the HTML source.)
+
+
If you cannot put them
+ online yourself, you can download a zip (see below) with everything
+ needed to develop slides offline and ask Bert Bos for help uploading the
+ slides once they are ready.
If you develop your slides offline (or plan to
+ present them without a network), then download this zip file. Unpacking it creates the following directories and
+ files:
Make a directory for your own slides under
+ TPAC-2025. You can copy the
+ Overview.html file there as a starting point, or
+ just use it as an example. If you make any images, put them in that
+ directory as well.
+
+
If you are able to upload your slides, put your directory with
+ all that it contains under https://www.w3.org/2025/Talks/TPAC/. There is no need to
+ upload the Templates directory. It is is already
+ there.
+
+
Writing slides
+
+
Slides
+
+
Each slide is a section
+ element* with a class
+ of slide:
Inside the slides, use normal HTML elements (p, ul,
+ em, etc.).
+
+
*) Note for advanced users: Although not
+ shown in this template, it is in fact possible to use other
+ elements than section. One common choice
+ is div.
+
+
Speaker notes or comments
+
+
You can add additional text, such as speaker notes or
+ explanations, between the slides. They will be visible in index
+ mode but not in slide mode. Use elements with a class
+ of comment:
+
+
<section class="comment">
+ ... text here...
+</section>
+
+
Slide numbers
+
+
If a slide should not have a slide number and an image banner,
+ add the class clear:
On title slides, this only
+ removes the number, not the banner.
+
+
Adding the class clear on
+ the body element omits the slide number and the
+ side banner from all slides.
+
+
Title slides (cover slides)
+
+
For cover slides (the title slide or separator slides between
+ parts of a presentation), add a class cover. You
+ can combine cover and clear.
+ E.g.:
The class final is meant for a last slide,
+ e.g., for conclusions or thanks (but it may be used elsewhere,
+ too):
+
+
<section class="slide final clear">
+ <h2>Conclusions<h2>
+ …
+</section>
+
+
Illustrations on the left or right
+
+
Slides with narrower text and an illustration on the left or
+ right can be made by adding the class side to the
+ slide. Inside the slide there should be exactly one element that
+ also has a class of side (an image or some other
+ element). Two sizes are possible: normal (about 1/3 of the slide)
+ and big (about 2/3 of the slide).
<section class="slide side r big">
+ <img src="..." alt="..." class="side">
+ ... slide content here...
+</section>
+
+
The image can be stretched to the edges of the slide by adding a
+ class cover. The image is not deformed. It is
+ scaled to be big enough to cover the image area and then either the
+ sides are cropped (if it is too wide) or the top and
+ bottom*.
*) Note for advanced users: It is
+ possible to indicate which sides should be cropped: add an
+ attribute like style="object-position: 20% 60%" to
+ indicate that, of the amount to be cropped from the sides, 20%
+ should be cropped on the left and the remaining 80% on the right;
+ and of the amount to be cropped from the top and bottom, 60% should
+ come from the top and the remaining 40% from the bottom. Thus,
+ e.g., ‘0% 100%’ says never to crop anything from the left (0%) if
+ the image is too wide, and only to crop from the top (100%) if the
+ image is too tall. (The default corresponds to ‘50% 50%’, i.e.,
+ crop equal amounts from both sides.)
+
+
Figures
+
+
When information is in the form of an image (a diagram, a chart,
+ a screenshot, etc.), put it inside a figure
+ element. Add a figcaption if needed.
+
+
<figure><img src="..." alt="..."><figure>
+
+
If the image is not accessible, use a details
+ element instead and add a description, like this:
+
+
<details>
+ <summary><img src="..." alt="..."></summary>
+ ... the same data as in the image, but as text...
+</details>
+
+
The description becomes visible when the user clicks on the
+ image. (The slide above shows an image of a
+ pie chart that is described by a table with the same data.)
+
+
Automatic sizing of images
+
+
Setting the class autosize on an image
+ instructs b6+ to scale down the image if the slide's content is
+ otherwise too tall:
+
+
<img src="my-image.png" alt="..." class=autosize>
+
+
You can have several images with class autosize
+ on a slide and they are all scaled down by the same percentage.
+
+
Progress bar
+
+
If you want a progress bar during the slide presentation, add an
+ empty div with a class
+ of progress. It can be put before the first slide
+ or after the last, but there should be at most one such element in
+ the file:
+
+
<div class="progress"></div>
+
+
The progress bar will show as a thin red line along the bottom of
+ the slides. Its length indicates the position of the current slide
+ in the slide deck.
+
+
B6+ also sets a custom style
+ variable --progress with a value between 0 and 1
+ on the body. This may be useful if you write your
+ own style rules for a progress indicator.
+
+
Incremental display
+
+
To progressively reveal elements on a slide, put a class
+ of next on all elements that should not be visible
+ right away. They will become visible one by one as you press the
+ space bar or an arrow key. E.g.:
+
+
<ul>
+ <li>This item is visible when the slide appears
+ <li class="next">This item is not immediately visible
+ <li class="next">This is the third item to appear
+</ul>
+<p class="next">This is the last element to appear
+
+
By default, each new element appears with a short animation as
+ if it unfolds from left to right. Two alternative animations are
+ available: emerge makes the elements fade in
+ and quick omits the animation. The class can be
+ set on each incremental element:
+
+
<li class="next emerge">...
+
+
or on an ancestor, e.g.:
+
+
<ul class="emerge">
+ <li class="next">...
+
+
Three optional modifiers change how elements look before, after
+ or while they are the currently active
+ element: Strong makes the currently active element
+ red. Greeked replaces the elements that are not
+ yet visible by a gray bar. (Useful to show how many elements are
+ still invisible.) And dim grays out the elements
+ that are no longer the active element.
+
+
Like the animation, these modifiers can be set on the
+ incremental element itself or on an ancestor. The modifiers can
+ also be combined, e.g.:
With b6+ (but not with Shower) you can make slides that have
+ more incremental elements than fit on the slide. To display them,
+ the slide automatically scrolls to each element as it is
+ revealed.
+
+
This mode of presentation is called a ‘slipshow’ by its
+ inventor, Paul-Elliot Anglès d'Auriac.
+ (His program is on
+ GitHub.) B6+ only implements a simple variant.
+
+
Scrolling slides may be useful for long content or when you want
+ to add content while keeping some of the preceding content on
+ screen. (An alternative is updating elements
+ in place.)
+
+
Automatic slide shows
+
+
Slides can be made to advance automatically after a given time,
+ by setting a data-timing attribute on them with a
+ value of MM:SS (minutes and seconds), or a number
+ followed by an s (seconds), m
+ (minutes) or h (hours), e.g.:
E.g., the last one means that the slide is shown for a maximum
+ of 0.33 minutes (about 20 seconds) before the next slide appears.
+ (You can still advance the slide by hand before that.)
+
+
If the slide contains incremental
+ elements, the time is distributed equally over those elements.
+ E.g., if there are three incremental elements, the time is divided
+ by four so that the first incremental appears after a quarter of
+ the given time, the second after half the given time, and the last
+ one after three quarters. You can also set a time on an individual
+ incremental element, in which case that time is used for that
+ element. E.g.:
+
+
<section class=slide data-timing="8s">
+ <p>This slide has 3 incremental elements.
+ <ol>
+ <li class=next data-timing="10s">Shown after 8/4 = 2 seconds
+ <li class=next data-timing="15s">Shown 10 seconds later
+ <li class=next>An additional 15 seconds later
+ </ol>
+</section>
+
+
After 29 seconds (= 2 + 10 + 15 + 2), the next slide will
+ appear.
+
+
You can set a default time on the body element,
+ e.g.:
+
+
<body data-timing="20.2s">
+
+
This sets the time for all slides that do not have
+ a data-timing attribute of their own.
+
+
Setting the data-timing attribute to 0
+ indicates that the slide or element does not automatically
+ advance.
+
+
Two columns
+
+
To put elements side by side in two columns, make an element
+ (a div, ul or any other element) with
+ class columns. The first child of that element
+ will be put in the left column, the second child in the right
+ column. If there are more children, the third will be in the left
+ column again, the fourth in the right, etc.
+
+
<ul class="columns">
+ <li>First goes on the left</li>
+ <li>Second goes on the right</li>
+</ul>
+
+
Small text
+
+
Less important text can be shown in a smaller font by giving it
+ a class of note:
+
+
<p class="note">Note that this is harder to read</p>
+
+
Extra big text: shout
+
+
To make text extra big, give it a class
+ of shout, e.g.:
+
+
<p class=shout>Hurray!
+
+
(Sometimes this is referred to as
+ the ‘Takahashi method’: Instead of sentences or graphics, a slide
+ only contains one or two keywords. The narrative comes from the
+ speaker.)
+
+
Animated text: grow
+
+
To draw extra attention to some text or an image, it can be
+ animated. Adding a class of grow to it will make
+ it appear slowly. It will start small and one second after the
+ slide appears it will begin to grow and reach its normal size three
+ seconds later.
+
+
<p class=grow>See?
+
+
Automatic text fit
+
+
B6+ can automatically reduce the font size of a slide to fit
+ long text. To request this, add the class textfit
+ on a slide (or on the body, if all slides need
+ this). See the example slide.
+
+
(The way this works is that b6+ wraps the contents of the slide
+ in a div with the right font size. If you have
+ added your own style rules, make sure they still work.)
+
+
Be careful! The textfit feature can easily result in text that
+ is too small to read.
+
+
Automatic line numbering
+
+
Pre-formatted text (in a pre) can be given line
+ numbers by adding the class numbered:
+
+
<pre class="numbered">
+
+
No more than 20 lines will be numbered. (In the normal font
+ size, a slide fits 13 lines.)
+
+
Lists with icons
+
+
To replace the list bullets with icons, emojis, or images, make
+ a ul with a class of with-icons.
+ The first element of each li will be used instead
+ of the list bullet.
+
+
<ul class=with-icons>
+ <li><span>♪</span> North pole</li>
+ <li><img src="../Icons/toc.png" alt="toc button"> Metro pole</li>
+</ul>
+
+
3×3 Grid
+
+
It is possible to treat the slide as a 3×3 grid and put elements
+ in the four corners, in the middle of each edge, or in the center
+ of the slide. This is done by giving the elements a class
+ of place. On its own, place puts
+ the element in the center. By adding classes top, right,
+ bottom and left the element can be placed
+ in one of the eight other positions.
+
+
<div class="place">Put this in the center</div>
+<div class="place bottom">Put this bottom center</div>
+<div class="place top right">In the top right</div>
+
+
The direction classes can also be abbreviated to t, r,
+ b and l.
+
+
Image overlays (background images)
+
+
To put an image behind the text of a slide, use
+ an img with a class of cover:
+
+
<img class="cover" src="..." alt="...">
+
+
The image will be stretched to fill the whole of the text area.
+ If the image doesn't fit exactly (wrong aspect ratio), the image
+ will be cropped.
+
+
With a class of fit instead
+ of cover, the image will be scaled but without
+ cropping. Instead there may be white bands on the sides or
+ above/below the image, if it doesn't fit exactly.
+
+
<img class="fit" src="..." alt="...">
+
+
This works both for normal slides and title slides (slides with
+ a class of cover). The slide number
+ is not obscured by the image. (But you can use clear on the slide to hide it.)
+
+
It is advisable to add a class of darkmode
+ or lightmode to a slide with an image overlay. See
+ the next section.
+
+
Forcing white text or black text
+
+
The colors of the slides depend on whether ‘dark mode’ is in
+ effect in the operating system: slides have black text on a white
+ background if
+ dark mode is off and white on black if it is on. But when text is
+ overlaid on an image, it may be better to force the text to always
+ be white (if the image is dark) or black (on a lighter image). You
+ do that by setting a class on the slide:
+ darkmode (for white-on-black) or
+ lightmode (for white-on-black). E.g.:
+
+
<section class="slide darkmode">
+
+
Other colors (titles, list bullets, links, etc.) also
+ become fixed on such slides.
+
+
To make all slides white on black, set the
+ class darkmode on the body element.
+ In that case you can use the class lightmode on
+ individual slides to give them black-on-white text.
+
+
Inverting image colors in dark mode
+
+
Some images, such as diagrams, can be color-inverted and still
+ look good. You can give such images a
+ class can-invert and when the slides are displayed
+ in dark mode, the image colors will be inverted. (Try it on
+ the slide with a figure.)
+
+
<img class="can-invert" src="..." alt="...">
+
+
This also works for SVG images and other things.
+
+
Video and audio
+
+
Videos (with the video element) and audios
+ (with the audio element) on a slide can be made to
+ play automatically when the slide opens, by giving them
+ an autoplay attribute, e.g.:
+
+
<video src="myvideo.mp4" autoplay></video>
+
+
Videos can be used as background for the slide with
+ the cover or fit classes. (See
+ their description under ‘Image
+ overlays’.) The class fit is also useful
+ simply to make the video as large as possible if it is the only
+ thing on the slide.
+
+
However, if you do use one of those classes, it is best to use
+ the autoplay attribute and to avoid
+ the controls attribute, because the video controls
+ will capture all key presses and mouse clicks and thus you can't
+ use those to advance to the next slide anymore. (If you do want to
+ use controls, either present with
+ a second window (see below) or set
+ a data-timing attribute
+ on the slide, so that it advances to the next slide automatically
+ after a given time.)
+
+
Slide transitions
+
+
By default, each slide just replaces the previous one, but there
+ are several predefined slide transitions. You can set a transition
+ on the body element to apply it to all slides:
+
+
<body class="shower fade-in">
+
+
Or you can set it on individual slides, to apply only to the
+ transition between that slide and the next. (I.e., it doesn't
+ determine how the slide appears, but how
+ it disappears.)
+
+
<section class="slide wipe-left">
+
+
You can set both a global transition and local ones. The global
+ transition applies to slides that do not have an explicit
+ transition set locally.
+
+
+
fade-in
+
The new slide appears faint at first and gets more opaque
+ until it completely obscures the previous slide.
+
+
slide-in
+
The new slide moves in from the left, while the previous slide
+ moves back to the left.
+
+
slide-out
+
The current slide moves out to the left, revealing the new
+ slide.
+
+
move-left
+
The new slides move in from the right while the old slide
+ moves out to the left.
+
+
slide-up
+
The old slide moves up, revealing the new slide.
+
+
move-up
+
The old slide moves up and the new slide moves in from the
+ bottom.
+
+
flip-up
+
A 3D effect: the bottom of the old slide is lifted up and the
+ slide is turned over to reveal the new slide on its back
+ side.
+
+
flip-left
+
Another 3D effect, but in this case the right side of the
+ slide is lifted up and the slide is flipped over to the left,
+ revealing the new slide on the back side.
+
+
center-out
+
A small circle appears in the middle of the old slide and
+ reveals the new slide. The circle grows until it covers the whole
+ slide.
+
+
wipe-left
+
The new slide moves in from the right, until it covers the old
+ slide.
+
+
zigzag-left
+
A zigzag pattern moves in from the right. To the left is the
+ old slide, to the right the new one.
+
+
zigzag-right
+
A zigzag pattern moves in from the left. To the left is the
+ new slide, to the right the old one.
+
+
cut-in
+
The new slide moves in from the top left and covers the old
+ slide.
+
+
assemble
+
The contents of the new slide fly in from all directions.
+
+
+
Accessibility
+
+
When you present while using a screen reader, you cannot use the
+ screen reader's usual keystrokes to navigate, only
+ the keystrokes defined by the b6+ script.
+ However, the screen reader will speak each slide as soon as it
+ appears. The script creates an element with
+ attributes role=region
+ and aria-live=assertive for that purpose.
+
+
When you leave slide mode, the screen reader will say ‘stopped’.
+ To make it say something else (e.g., because you want a different
+ language than English), create an element with role=region and
+ aria-live=assertive yourself and put the text to speak in it.
+ E.g.:
The slides can be exported to PDF (or printed) in two ways:
+ multiple slides per page with comments interleaved, or one slide
+ per page without any comments. The latter may be useful to create a
+ PDF suitable for presenting, when it is not possible to use an HTML
+ browser.
+
+
+
portrait
When printing in portrait mode, the output
+ will contain as many slides per page as will fit and the comments
+ are printed between the slides. This corresponds to viewing the
+ slides in index mode.
+
+
landscape
When the output is in landscape mode, each
+ page consists of one slide, without page margins, and without the
+ comments between the slides.
+
+
+
Note: In landscape mode, the style sheet tries to set the size
+ of the output page to exactly the size of a slide, but not all user
+ agents that produce PDF respect that. (And, obviously, a printer is
+ limited to the available paper.) There may be some black margin to
+ the right and below each slide. Prince does respect the size. W3C
+ team can also use the ",pdfui" tool online.
+
+
Presenting
+
+
Mouse gestures and keystrokes
+
+
To present the slides, load them into a browser that supports
+ JavaScript and CSS and then either click the play (▶) button, press
+ the A key, double-click on a slide or touch the
+ screen with three fingers (on certain devices).
+
+
If you are using Shower instead of b6+, press
+ Shift+F5 (Command+Enter on Mac)
+ or click on a slide.
+
+
Navigate though the slides by clicking the left mouse button,
+ pressing the spacebar, the arrow keys or Page-up/Page-Down. The
+ Home and End keys jump to the first, resp. last
+ slide. F1 or F toggles full
+ screen mode. C shows a clicakble table of
+ contents. The ? (question mark) key shows a list
+ of available commands.
+
+
If you have automatically advancing
+ slides, you can pause the slide show with
+ the P key or the Play/Pause key,
+ if your keyboard has it. You can also navigate to a different slide
+ and resume from there.
+
+
To exit the presentation, press the A key or
+ the Esc key.
While in slide mode, you can press the D (only
+ with b6+) key to
+ switch between black-on-white and white-on-black. (This temporarily
+ adds or removes the darkmode class
+ on body, see ‘Forcing white
+ text or black text’.) When the
+ computer is already in dark mode, the key instead switches the
+ slides to light mode (and adds the class lightmode
+ on the body).
+
+
Using two screens or two windows
+
+
B6+ (but not Shower) can show the slides in a second window. The
+ first window can then be used to control the slide show and view
+ notes and next slides. If you have two screens that can show
+ different content (e.g., your computer's screen and a projector),
+ you can thus present the slides on one screen, and preview
+ the next slide and any notes on the other.
+
+
Open the second window by pressing the 2 key,
+ or the ⧉ button in index mode.
+
+
Drawing on the slides
+
+
After pressing the W key, you can draw on the
+ current slide with the mouse. Press W again to
+ clear the drawing.
+
+
The drawing is not permanent and there is always only one slide
+ with a drawing: As soon as you start drawing on another slide, the
+ previous slide is cleared.
+
+
The color of the drawing can be set with style rules, e.g., like
+ this:
+
+
.b6-canvas {color: red}
+
+
Clocks to show remaining time
+
+
When using a second screen, it is possible to show clocks on the
+ first screen with the remaining time, the time used so far, and the
+ real (wall clock) time. (The clocks are normally only shown on
+ the first screen, but they can be included in slides or overlaid
+ on slides, by adding suitable markup and/or CSS rules.)
+
+
By default, the clocks will count down from 30 minutes and show
+ a warning 5 minutes before the end. (In the style used
+ for this document, the clock turns from green to orange.) You can
+ set different times with the following classes:
+
+
+
duration=...
+
+
To set the duration to, e.g., 45 minutes,
+ add duration=45 to the class attribute of
+ the body. Example:
+
+
<body class="duration=45">
+
+
+
warn=...
+
+
To set the warning to, e.g., one minute, put this in the class
+ of the body:
+
+
<body class="warn=1">
+
+
+
+
B6+ has two kinds of clocks built-in, but also provides
+ primitive elements with which to build your own clock.
+
+
To get one of the built-in clocks, make an empty element with a
+ class of either fullclock
+ or clock. The former will display the real
+ (wall clock) time, the number of minutes so far, the number of
+ minutes left, a small ‘pie-chart’ showing the proportion of time
+ used, and four buttons: subtract one minute, add one minute, pause
+ the clock, and reset the clock. The simple clock will display the
+ pie chart and the four buttons (somewhat smaller) and the remaining
+ minutes.
+
+
When you make your own clock, you can make use of the following
+ classes and attributes:
+
+
+
hours-real, minutes-real, seconds-real
+
+
B6+ will fill all elements with a class
+ of hours-real with the current hour (wall-clock
+ time) and keep them up to date. The hour will always be two
+ digits and use a 24-hour clock: 00
+ to 23.
+
+
Similarly, all elements with a class
+ of minutes-real or seconds-real
+ will contain the current minutes or seconds, respectively, also
+ always as two digits, 00 to 59.
+ E.g.:
Ditto, but for the time still remaining. If the used time
+ exceeds the duration, these times will be shown as 00.
+
+
+
timeinc, timedec
+
+
An element with a class of timeinc will act as
+ a button that increments the duration, and thus the remaining
+ time, by one minute. timedec decrements the
+ duration by one minute. E.g.:
An element with this class acts as a toggle to pause &
+ resume the clocks. When the clocks are paused, the used time does
+ not progress and the remaining time does not diminish. (The real
+ time clocks continue, of course.) When the element is clicked
+ again, the clocks resume. E.g.:
+
+
<button class=timepause>pause</button>
+
+
+
timereset
+
+
An element with this class acts as a button to restart the
+ clocks, i.e., to set the used time to zero. Example:
+
+
<button class=timereset>reset</button>
+
+
+
+
In addition to setting the time in elements with the classes
+ mentioned above, b6+ also updates the style property, the class and
+ a data- attribute on the body. This is useful for
+ style rules to change the styles of elements based on the progress
+ of the slide show. In particular, b6+ sets the following:
+
+
+
--time-factor
+
This custom style property on the body will be set to a value
+ between 0.0 and 1.0, representing what fraction of the duration
+ has been used.
+
+
data-time-factor
+
This attribute on body will be set to the
+ percentage of the duration already used. This will be a whole
+ number with two digits between 00 and 99, or 100.
+
+
time-warning
+
When the remaining time is less than the warn time, b6+ will
+ add this class to the classes on body.
+
+
+
Starting in slide mode
+
+
Add ‘?full’ at the end of the URL (but before any fragment ID)
+ to open the slides in slide mode instead of index mode.
When you don't want the mouse pointer to remain on the screen in
+ slide mode, add the class hidemouse on
+ the body element. If the mouse does not move for
+ 5 seconds, the pointer is made invisible. It comes back as soon as
+ the mouse is moved.
+
+
<body class="hidemouse">
+
+
You can also set a different timeout in seconds. E.g, to set a
+ short timeout of 1.5 seconds:
+
+
<body class="hidemouse=1.5">
+
+
Ignore mouse clicks
+
+
Normally, a mouse click anywhere on a slide (other than on a
+ hyperlink or form element) has the effect of advancing to the next
+ slide or incremental element. If you don't want that, add the
+ class noclick on the body
+ element.
+
+
<body class="noclick">
+
+
Embedding slides in other documents
+
+
You can embed a single slide in another document with the help
+ of an <object> or <iframe>
+ element. To avoid that a keypress or click
+ accidentally changes the slide, you can disable navigation
+ and index mode: add ‘?full&static’ at the
+ end of the URL, followed by ‘#’ and the ID or number of the desired
+ slide. E.g.:
Adding ‘?static’ on its own to the URL is also possible: It
+ shows all slides in index mode and disables switching to slide
+ mode.
+
+
Note that using ‘?full&static’ on
+ an automatic slide show plays the whole
+ slide show without the possiblility to pause it.
+
+
Speaking guidelines
+
+
The page ‘Speaking guidelines’ of the TPAC 2025 site contains
+ recommendations for presenters.
+
+
+
+
+
+
+
+
diff --git a/presentations/tpac-2025/Templates/b6plus.js b/presentations/tpac-2025/Templates/b6plus.js
new file mode 100644
index 0000000..316a56e
--- /dev/null
+++ b/presentations/tpac-2025/Templates/b6plus.js
@@ -0,0 +1,3357 @@
+/* b6plus.js $Revision: 1.153 $
+ *
+ * Script to simulate projection mode on browsers that don't support
+ * media=projection or 'overflow-block: paged' (or ‘overflow-block:
+ * optional-paged’, from the 2014 Media Queries draft) but do support
+ * Javascript.
+ *
+ * Documentation and latest version:
+ *
+ * https://www.w3.org/Talks/Tools/b6plus/
+ *
+ * Brief usage instructions:
+ *
+ * Add the script to a page with
+ *
+ *
+ *
+ * The script assumes each slide starts with an H1 or is an element
+ * with class "slide". The slide must be a direct child of the BODY.
+ * If an H1 starts a slide, all elements until the next H1 are part of
+ * that slide, except for those with a class of "comment", which are
+ * hidden in slide mode.
+ *
+ * Elements with a class of "progress", "slidenum" or "numslides" are
+ * treated specially. They can be used to display progress in the
+ * slide show, as follows. Elements with a class of "numslides" will
+ * have their content replaced by the total number of slides in
+ * decimal. Elements with a class of "slidenum" will have their
+ * content replaced by the number of the currently displayed slide in
+ * decimal. The first slide is numbered 1. Elements with a class of
+ * "progress" will get a 'width' property whose value is a percentage
+ * between 0% and 100%, corresponding to the progress in the slide
+ * show: if there are M slide in total and the currently displayed
+ * slide is number N, the 'width' property will be N/M * 100%.
+ *
+ * There can be as many of these elements as desired. If they are
+ * defined as children of the BODY, they will be visible all the time.
+ * Otherwise their visibility depends on their parent.
+ *
+ * Usage:
+ *
+ * - Press A to toggle normal and slide mode. The script starts in
+ * normal mode.
+ *
+ * - Press Page-Down to go to the next slide. Press Page-Up, up arrow
+ * or left arrow to back-up one page.
+ *
+ * - Press Space, right arrow, down arrow or mouse button 1 to advance
+ * (incremental display or next slide)
+ *
+ * On touch screens, a tap with three fingers toggles slide mode, a
+ * wipe right goes back one slide, and wipe left advances.
+ *
+ * TODO: don't do anything if media = projection
+ *
+ * TODO: option to allow clicking in the left third of a slide to go
+ * back?
+ *
+ * TODO: Accessibility of the second window.
+ *
+ * TODO: Show an icon in the corner when sync mode is on?
+ *
+ * TODO: Allow a language for localized messages and clocks that is
+ * different from the slides' language?
+ *
+ * TODO: More or other syntaxes for commands in syncSlide()? "all" or
+ * "index" for "0"; "<", "previous" for "-"; ">", "next" for "+";
+ * "last" for "$"...
+ *
+ * TODO: Also fill elements with class=slidenum in the preview window.
+ *
+ * TODO: In the table of contents, indicate the current slide?
+ *
+ * TODO: The help box and the table of contents explicitly stop any
+ * automatic slide show, but it only resumes when the user
+ * subsequently navigates to another slide or incremental element.
+ *
+ * TODO: Use a DIALOG for the warning box instead of a DIV?
+ *
+ * TODO: Include more ideas from "slipshow" presentations? Allow
+ * incrementally displayed elements to align to the top or center of
+ * the slide, rather than only the bottom?
+ * https://presentation-slipshow-4b25b8.forge.apps.education.fr/
+ *
+ * TODO: Allow drawing on a slide in the preview window and
+ * automatically copy the drawing to the second window?
+ *
+ * TODO: Include a (structured and/or text) editor?
+ *
+ * TODO: When the 2nd window closes, reset the volume of audio and
+ * video in the 1st window to what it was before it was set to 0.01.
+ *
+ * TODO: An easy way to find out the URL of a slide when still in the
+ * index mode (for sharing, for bookmarking) without having to open
+ * the sldie fullscreen first. (Idea by Coralie.)
+ *
+ * TODO: A way to package a slide set as a single file for
+ * distribution.
+ *
+ * TODO: Do not set the 'width' on .progress, but rely on the style
+ * sheet to use the '--progress' property?
+ *
+ * TODO: Currently the speaker notes are not accessible in slide
+ * mode. Use the preview window for speech rather than the slide
+ * window? A keypress to speak the notes (while keeping them invisible)?
+ *
+ * TODO: Move repository to GitHub, to get more feedback?
+ *
+ * Originally derived from code by Dave Raggett.
+ *
+ * Author: Bert Bos
+ * Created: May 23, 2005 (b5)
+ * Modified: Jan 2012 (b5 -> b6)
+ * Modified: Oct 2016 (added jump to ID; fixes bugs with Home/End key handling)
+ * Modified: Apr 2018 (added touch events)
+ * Modified: May 2018 (support 'overflow-block' from Media Queries 4)
+ * Modified: Mar 2019 (support fixed aspect ratio, progress elements, b6 -> b6+)
+ * Modified: Aug 2020 (add class=visited to past elts in incremental display)
+ * Modified: Oct 2020 (start in slide mode if URL contains "?full")
+ * Modified: Apr 2021 (disable navigation if URL contains ‘?static’)
+ * Modified: May 2021 (rescale if window size changes while in slide mode)
+ * Modified: Jun 2021 (only one incremental item active, as in Shower since 3.1)
+ * Modified: Sep 2021 (a11y: added role=application and a live region)
+ * Modified: Dec 2021 (added noclick option; set slide number in URL if no ID)
+ * Modified: Dec 2021 (Added popup help tied to the "?" key)
+ * Modified: Apr 2022 (Added support for a second window, tied to the "2" key)
+ * Modified: Apr 2022 (forwarding of events in the second window to the first)
+ * Modified: Aug 2022 (help popup appears in the 2nd window if requested there)
+ * Modified: Nov 2022 (support server-sent events to sync slides)
+ * Modified: Nov 2022 (added clocks; localized to German, French and Dutch)
+ * Modified: Dec 2022 (protect against loading b6plus.js twice)
+ * Modified: Sep 2023 (show buttons in index mode to go to slide mode and more)
+ * Modified: Jan 2024 (swapped UI: 2nd window for slides, 1st for preview)
+ * Modified: Dec 2024 (ability to show a table of contents)
+ * Modified: Jan 2025 (data-timing attribute for automatic slide shows)
+ * Modified: Feb 2025 (scroll incremental elements into view: "slipshow")
+ * Modified: Feb 2025 (allow drawing on slides with the mouse)
+ * Modified: Mar 2025 (sync videos in preview window, support autoplay videos)
+ * Modified: Mar 2025 ("textfit" feature to reduce font size of long text)
+ * Modified: Mar 2025 (fullscreen automatically tries to use external screen)
+ * Modified: Mar 2025 ("autosize" feature to automatically reduce image size)
+ * Modified: Apr 2025 (hide speaker notes by default in index mode)
+ * Modified: Apr 2025 (Alt or Option key shows URLs of slides in index mode)
+ *
+ * Copyright 2005-2025 W3C, ERCIM
+ * See http://www.w3.org/Consortium/Legal/copyright-software
+ */
+
+(function() {
+
+"use strict";
+
+/* Localized strings */
+const translations = {
+ "Remaining time. To change, add class 'duration=n' to body" : {
+ de: "Restzeit. Um sie zu ändern, fügen Sie die Klasse 'duration=n' zu BODY hinzu",
+ fr: "Temps restant. Pour le changer, ajoutez la classe 'duration=n' à BODY",
+ nl: "Resterende tijd. Om de tijd te veranderen, voeg de class 'duration=N' toe aan BODY"},
+ "min": { // Abbreviation for "minutes"
+ de: "Min",
+ fr: "min",
+ nl: "min"},
+ "current time": {
+ de: "aktuelle Uhrzeit",
+ fr: "heure actuelle",
+ nl: "huidige tijd"},
+ "used": {
+ de: "verbraucht",
+ fr: "utilisé",
+ nl: "gebruikt"},
+ "remaining": { // As in "remaining time"
+ de: "Restzeit",
+ fr: "restant",
+ nl: "resterend"},
+ "pause": { // Label on a button to pause the clock
+ de: "Pause",
+ fr: "pause",
+ nl: "pauze"},
+ "resume": { // Label in a button to pause the clock
+ de: "fortsetzen",
+ fr: "reprendre",
+ nl: "hervatten"},
+ "+1 min": { // Label on a button to add 1 minute
+ de: "+1 Min",
+ fr: "+1 min",
+ nl: "+1 min"},
+ "−1 min": { // Label on a button to shorten time by 1 minute
+ de: "−1 Min",
+ fr: "−1 min",
+ nl: "−1 min"},
+ "restart": { // Label on a button to reset the clock
+ de: "Neustart",
+ fr: "réinitialiser",
+ nl: "herstart"},
+ "No navigation possible while sync mode is on.": {
+ de: "Bei aktiviertem Sync-Modus ist keine Navigation möglich.",
+ fr: "Aucune navigation possible lorsque le mode synchro est activé.",
+ nl: "Geen navigatie mogelijk terwijl de synchronisatiemodus is ingeschakeld."},
+ "Press S to toggle sync mode off.": {
+ de: "Drücken Sie S, um den Sync-Modus auszuschalten.",
+ fr: "Appuyez sur S pour désactiver le mode synchro.",
+ nl: "Druk op S om de synchronisatiemodus uit te schakelen."},
+ "Synchronization error.": {
+ de: "Synchronisierungsfehler",
+ fr: "Erreur de synchronisation.",
+ nl :"Synchronisatiefout."},
+ "You can try to turn synchronization back on with the S key.": {
+ de: "Sie können versuchen, die Synchronisation mit der Taste S wieder einzuschalten.",
+ fr: "Vous pouvez essayer de réactiver la synchronisation avec la touche S.",
+ nl: "U kunt proberen de synchronisatie weer in te schakelen met de S-toets."},
+ "An error occurred while trying to switch into fullscreen mode": {
+ de: "Beim Wechsel in den Vollbildmodus ist ein Fehler aufgetreten",
+ fr: "Une erreur s'est produite en essayant de passer en mode plein écran",
+ nl: "Er is een fout opgetreden bij het overschakelen naar volledig scherm"},
+ "Fullscreen mode is not possible": {
+ de: "Der Vollbildmodus ist nicht möglich",
+ fr: "Le mode plein écran est impossible",
+ nl: "Volledig scherm is niet mogelijk"},
+ "You can try again with the F or F1 key.": {
+ de: "Sie können es mit der Taste F oder F1 erneut versuchen.",
+ fr: "Vous pouvez réessayer avec la touche F ou F1.",
+ nl: "U kunt het opnieuw proberen met de toets F of F1."},
+ "Syncing turned OFF.\nPress S to turn syncing back on.": {
+ de: "Synchronisierung ausgeschaltet.\nDrücken Sie S, um die Synchronisierung wieder einzuschalten.",
+ fr: "Synchronisation désactivée\nAppuyez sur S pour réactiver la synchronisation,",
+ nl: "Synchroniseren uitgeschakeld\nDruk op S om het synchroniseren weer in te schakelen"},
+ "Syncing turned ON\nPress S to turn syncing off": {
+ de: "Synchronisierung eingeschaltet\nDrücken Sie S, um die Synchronisierung auszuschalten",
+ fr: "Synchronisation activée\nAppuyez sur S pour désactiver la synchronisation",
+ nl: "Synchronisatie ingeschakeld\nDruk op S om synchronisatie uit te schakelen"},
+ "Stopped.": {
+ de: "Gestoppt.",
+ fr: "Arrêté.",
+ nl: "Gestopt."},
+ "Mouse & keyboard commands": {
+ de: "Maus- und Tastaturbefehle",
+ fr: "Commandes de la souris et du clavier",
+ nl: "Muis- en toetsenbordopdrachten"},
+ "A, double click, 3-finger touch": {
+ de: "A, Doppelklick, 3-Finger-Touch",
+ fr: "A, double clic, toucher à 3 doigts",
+ nl: "A, dubbelklik, 3-vinger touch"},
+ "enter slide mode": {
+ de: "Dia-Modus einschalten",
+ fr: "passer en mode diapo",
+ nl: "naar de diamodus gaan"},
+ "A, Esc, 3-finger touch": {
+ de: "A, Esc, 3-Finger-Touch",
+ fr: "A, Esc, toucher à 3 doigts",
+ nl: "A, Esc, 3-vinger touch"},
+ "leave slide mode": {
+ de: "Dia-Modus ausschalten",
+ fr: "quiter le mode diapo",
+ nl: "diamodus verlaten"},
+ "space, →, ↓, swipe left": {
+ de: "Leertaste, →, ↓, links wischen",
+ fr: "espace, →, ↓, glisser vers la gauche",
+ nl: "spatie, →, ↓, veeg naar links",
+ },
+ "space, →, ↓, click": {
+ de: "Leertaste, →, ↓, click",
+ fr: "espace, →, ↓, clic",
+ nl: "spatie, →, ↓, klik"},
+ "next slide or incremental element": {
+ de: "nächstes Dia oder inkrementelles Element",
+ fr: "diapo suivante ou élément incrémentiel",
+ nl: "volgende dia of incrementeel element"},
+ "PgDn": {},
+ "PgDn, swipe left": {
+ de: "PgDn, links wischen",
+ fr: "PgDn, glisser vers la gauche",
+ nl: "PgDn, veeg naar links"},
+ "next slide": {
+ de: "nächstes Dia",
+ fr: "diapo suivante",
+ nl: "volgende dia"},
+ "PgUp, ←, ↑, swipe right": {
+ de: "PgUp, ←, ↑, rechts wischen",
+ fr: "PgUp, ←, ↑, glisser vers la droite",
+ nl: "PgUp, ←, ↑, veeg naar rechts"},
+ "previous slide": {
+ de: "vorheriges Dia",
+ fr: "diapo précédente",
+ nl: "vorige dia"},
+ "End": {},
+ "last slide": {
+ de: "letztes Dia",
+ fr: "dernière diapo",
+ nl: "laatste dia"},
+ "Home": {},
+ "first slide": {
+ de: "erstes Dia",
+ fr: "première diapo",
+ nl: "eerste dia"},
+ "F1, F": {},
+ "toggle fullscreen mode": {
+ de: "Vollbildmodus umschalten",
+ fr: "basculer le mode plein écran",
+ nl: "volledig scherm aan/uit",
+ },
+ "2": {},
+ "C": {},
+ "show slides in 2nd window": {
+ de: "abspielen in 2. Fenster",
+ fr: "lire dans 2e fenêtre",
+ nl: "afspelen in 2e venster"},
+ "?": {},
+ "this help": {
+ de: "diese Hilfe",
+ fr: "cette aide",
+ nl: "deze hulp"},
+ "S": {},
+ "toggle sync mode on/off": {
+ de: "Sync-Modus ein-/ausschalten",
+ fr: "activer/désactiver le mode synchro",
+ nl: "sync-modus aan/uit"},
+ "(More information in the b6+ manual)": {
+ de: "(Weitere Informationen im b6+ Handbuch)",
+ fr: "(Plus d'informations dans le manuel de b6+)",
+ nl: "(Meer informatie in de b6+ handleiding)"},
+ "▶\uFE0E": {},
+ "play slides or stop playing": {
+ de: "Dias abspielen oder halten",
+ fr: "lancer les diapos ou arrêter",
+ nl: "dia's afspelen of stoppen"},
+ "play/stop": {
+ de: "abspielen/halten",
+ fr: "lire/arrêter",
+ nl: "afspelen/stoppen"},
+ "⧉": {},
+ "play in 2nd window": {
+ de: "abspielen in 2. Fenster",
+ fr: "lire dans 2eme fenêtre",
+ nl: "afspelen in 2de venster"},
+ "play/stop slides in a 2nd window": {
+ de: "Dias abspielen/halten in einem zweiten Fenster",
+ fr: "lancer/arrêter les diapos dans une 2eme fenêtre",
+ nl: "dia's afspelen/stoppen in een 2de venster"},
+ "❮": {},
+ "back": {
+ de: "zurück",
+ fr: "précédent",
+ nl: "terug"},
+ "❯": {},
+ "forward": {
+ de: "vorwärts",
+ fr: "suivant",
+ nl: "vooruit"},
+ "?": {},
+ "help": {
+ de: "Hilfe",
+ fr: "aide",
+ nl: "help"},
+ "◑": {},
+ "dark mode": {
+ de: "Dunkelmodus",
+ fr: "mode sombre",
+ nl: "donkere modus"},
+ "toggle dark mode on/off": {
+ de: "Dunkelmodus ein- oder ausschalten",
+ fr: "activer ou désactiver le mode sombre",
+ nl: "schakel de donkere modus aan of uit"},
+ "notes": {
+ de: "Notizen",
+ fr: "notes",
+ nl: "notities"},
+ "(slide mode) table of contents (index mode) show/hide notes": {
+ de: "(Dia-modus) Inhaltsverzeichnis (Indexmodus) Notizen anzeigen/ausblenden",
+ fr: "(en mode diapo) table des matières (en mode index) afficher/masquer les notes",
+ nl: "(diamodus) inhoudsopgave (index-modus) notities weergeven/verbergen"},
+ "P, ⏯": {},
+ "pause/resume automatic slide show": {
+ de: "anhalten/fortsetzen der automatischen Dias",
+ fr: "pause/reprise du diapo automatique",
+ nl: "pauzeer/hervat automatisch afspelen"},
+ "W": {},
+ "start/stop drawing on the slide": {
+ de: "zeichnen auf dem Dia ein-/ausschalten",
+ fr: "dessiner sur la diapo activer/désactiver",
+ nl: "tekenen op de dia aan/uit"},
+ "F and F1 only work in the window with the slides. (Browser security restriction.)": {
+ de: "F und F1 funktonieren nur im Fenster mit den Dias. (Browsersicherheitsbeschränkung.)",
+ fr: "F et F1 ne fonctionnent que dans la fenêtre avec les diapo. (Restriction de sécurité du navigateur.)",
+ nl: "F en F1 werken alleen in het venster met de dia's. (Browser-beveiligingsbeperking.)"},
+ "Alt, Option": {},
+ "(index mode) show URL of slide": {
+ de: "(Indexmodus) URL des Dias anzeigen",
+ fr: "(en mode index) afficher l'URL de la diapo",
+ nl: "(index-modus) toon URL van dia"},
+ "🗊": {},
+ "show/hide notes": {
+ de: "Notizen anzeigen/ausblenden",
+ fr: "Afficher/masquer les notes",
+ nl: "Notities weergeven/verbergen"},
+};
+
+/* Logo for use on a dark background. (The border of the circle is
+ * light violet.) */
+const logo = '';
+
+/* Initial inner size of the second window. */
+const popupWidth = 800, popupHeight = 690;
+
+/* A random number for the URL of the 2nd window. */
+const randomnumber = Math.trunc(0x100000 * Math.random()).toString(36);
+
+/* Global variables */
+var curslide = null;
+var slidemode = false; // In slide show mode or normal mode?
+var switchInProgress = false; // True if waiting for finishToggleMode()
+var incrementals = null; // Array of incrementally displayed items
+var gesture = {}; // Info about touch/pointer gesture
+var numslides = 0; // Number of slides
+var stylesToLoad = 0; // # of load events to wait for
+var limit = 0; // A time limit used by toggleMode()
+var interactive = true; // Allow navigating to a different slide?
+var fullmode = false; // Whether "?full" was in the URL
+var progressElts = []; // Elements with class=progress
+var slidenumElts = []; // Elements with class=slidenum
+var numslidesElts = []; // Elements with class=numslides
+var liveregion = null; // Element [role=region][aria-live=assertive]
+var savedContent = ""; // Initial content of the liveregion
+var noclick = 0; // If != 0, mouse clicks do not advance slides
+var hideMouseTime = null; // If set, hide idle mouse pointer after N ms
+var helptext = null; // List of keyboard and mouse commands
+var toctext = null; // Table of contents
+var hideMouseID = null; // ID of timer to hide the mouse pointer
+var singleClickTimer = null; // Timeout to distinguish single & double click
+var secondwindow = null; // Optional second window for slides
+var firstwindow = null; // The window that opened this one
+var syncmode = false; // Sync mode
+var syncURL = null; // URL of sync server
+var eventsource = null; // Sync server object
+var startTime = 0; // Start time, used by displayed clocks
+var pauseStartTime = 0; // 0 = clocks not paused, > 0 = start of pause
+var clockElts = null; // Elements with class=clock
+var fullclockElts = null; // Elements with class=fullclock
+var realHoursElts = null; // Elements with wallclock time: hours
+var realMinutesElts = null; // Elements with wallclock time: minutes
+var realSecondsElts = null; // Elements with wallclock time: seconds
+var usedHoursElts = null; // Elements with used time: hours
+var usedMinutesElts = null; // Elements with used time: minutes
+var usedSecondsElts = null; // Elements with used time: seconds
+var leftHoursElts = null; // Elements with remaining time: hours
+var leftMinutesElts = null; // Elements with remaining time: minutes
+var leftSecondsElts = null; // Elements with remaining time: seconds
+var clockTimer = 0; // Interval timer for clocks
+var duration = 30 * 60 * 1000; // Default duration of a presentation 30 min
+var warnTime = 5 * 60 * 1000; // Warn 5 minutes before end of duration
+var language = null; // Language for localization
+var switchFullscreen = false; // True = toggle fullscreen but not slide mode
+var hasDarkMode = false; // Style sheet supports class=darkmode?
+var incrementalsBehavior = "symmetric"; // [Experimental]
+var slideTimer = null; // Timer for automatically advancing slides
+var slideTiming = 0; // Default time to advance slides, 0 means off
+var slideTimerPaused = false; // True = do not advance slides automatically
+var loopSlideShow = false; // Whether to wrap around to the first slide
+var scale = 1; // How much to scale a slide to fill the screen
+var canvas = null; // Canvas for drawing on slides
+var canvasContext = null; // Drawing context for the canvas
+var canvasX = 0; // Most recent mouse position on the canvas...
+var canvasY = 0; // ... used for drawing lines.
+var hoverOverlay = null; // For showing the URL of a slide in index mode
+var mouseX = 0, mouseY = 0; // Last known mouse pointer location
+var commentsVisible = true; // If speaker notes are currently visible
+var commentsWereVisible; // If notes were visible when 2nd window opened
+var commentsDefault = false; // If speaker notes should start out visible
+var visibleSlide; // The last slide used in toggleComments()
+var forceClocks = false; // Update clocks even when they are paused
+var clocksUpdateRequested = false; // True if a call is queued
+
+
+/* _ -- return translation for text, or text, if none is available */
+function _(text)
+{
+ return translations[text]?.[language] ?? text;
+}
+
+
+/* generateID -- make sure elt has a unique ID */
+function generateID(elt, slide)
+{
+ var nextid = 0; // For generating unique IDs
+
+ /* This doesn't guarantee that elt has a unique ID, but only that it
+ * is the first element in the document that has this ID. Which
+ * should be enough to make this element scroll into view when it is
+ * the target... */
+ if (!elt.id) elt.id = "s" + slide.b6slidenum
+ while (document.getElementById(elt.id) !== elt)
+ elt.id = "s" + slide.b6slidenum + "-" + ++nextid
+}
+
+
+/* cloneNodeWithoutID -- deep clone a node, but not any ID attributes */
+function cloneNodeWithoutID(elt)
+{
+ var clone, h;
+
+ clone = elt.cloneNode(false);
+ if (elt.nodeType === 1 /*Node.ELEMENT_NODE*/) {
+ clone.removeAttribute("id"); // If any
+ for (h = elt.firstChild; h; h = h.nextSibling)
+ clone.appendChild(cloneNodeWithoutID(h)); // Recursive
+ }
+ return clone;
+}
+
+
+/* updateClocks -- update clock elements */
+function updateClocks()
+{
+ var now, s0, m0, h0, s1, m1, h1, s2, m2, h2, used, left, factor;
+
+ // This function is called in an animation frame, set by
+ // requestClocksUpdate().
+
+ now = new Date();
+
+ s0 = now.getSeconds();
+ m0 = now.getMinutes();
+ h0 = now.getHours();
+
+ for (const e of realHoursElts)
+ e.textContent = h0.toString().padStart(2, "0");
+ for (const e of realMinutesElts)
+ e.textContent = m0.toString().padStart(2, "0");
+ for (const e of realSecondsElts)
+ e.textContent = s0.toString().padStart(2, "0");
+
+ // Only uodate clocks if they aren't paused, or if an update is forced.
+ if (pauseStartTime === 0 || forceClocks) {
+
+ if (forceClocks) forceClocks = false; // Reset
+
+ used = now.getTime() - startTime;
+ s1 = Math.trunc(used / 1000);
+ if (usedHoursElts.length != 0) { // Used hours are displayed
+ h1 = Math.trunc(s1 / 60 / 60); s1 -= h1 * 60 * 60;
+ m1 = Math.trunc(s1 / 60); s1 -= m1 * 60;
+ } else if (usedMinutesElts.length != 0) { // No hours, but minutes are shown
+ m1 = Math.trunc(s1 / 60); s1 -= m1 * 60;
+ }
+ for (const e of usedHoursElts)
+ e.textContent = h1.toString().padStart(2, "0");
+ for (const e of usedMinutesElts)
+ e.textContent = m1.toString().padStart(2, "0");
+ for (const e of usedSecondsElts)
+ e.textContent = s1.toString().padStart(2, "0");
+
+ left = Math.max(0, duration - used);
+ s2 = Math.trunc(left / 1000);
+ if (leftHoursElts.length != 0) { // Remaining hours are displayed
+ h2 = Math.trunc(s2 / 60 / 60); s2 -= 60 * 60 * h2;
+ m2 = Math.trunc(s2 / 60); s2 -= 60 * m2;
+ } else if (leftMinutesElts.length) { // No hours, but minutes are shown
+ m2 = Math.trunc(s2 / 60); s2 -= 60 * m2;
+ }
+ for (const e of leftHoursElts)
+ e.textContent = h2.toString().padStart(2, "0");
+ for (const e of leftMinutesElts)
+ e.textContent = m2.toString().padStart(2, "0");
+ for (const e of leftSecondsElts)
+ e.textContent = s2.toString().padStart(2, "0");
+
+ // Set a precise factor 0.0..1.0 in a CSS variable on all clock elements.
+ // Set an integer percentage 00..100 in a data attribute on BODY.
+ // If time left is <= warnTime, set class=time-warning on BODY.
+ factor = 1 - left/duration;
+ for (const e of clockElts) e.style.setProperty('--time-factor', factor);
+ for (const e of fullclockElts) e.style.setProperty('--time-factor', factor);
+ document.body.setAttribute("data-time-factor",
+ Math.trunc(100 * factor).toString().padStart(2, "0"));
+ if (left <= warnTime) document.body.classList.add("time-warning");
+ else document.body.classList.remove("time-warning");
+ }
+}
+
+
+/* requestClocksUpdate -- queue a call to updateClocks() if not already done */
+function requestClocksUpdate()
+{
+ if (clocksUpdateRequested) return;
+ clocksUpdateRequested = true;
+ requestAnimationFrame(() => {clocksUpdateRequested = false; updateClocks()});
+}
+
+
+/* addMinute -- add 1 minute to the duration */
+function addMinute(ev)
+{
+ duration += 60000;
+
+ if (firstwindow)
+ firstwindow.postMessage({event: "duration", v: duration});
+ else if (secondwindow?.closed === false)
+ secondwindow.postMessage({event: "duration", v: duration});
+
+ forceClocks = true;
+ requestClocksUpdate(); // Queue an update to the clocks
+
+ ev.stopPropagation();
+ ev.preventDefault();
+}
+
+
+/* subtractMinute -- subtract 1 minute from the duration */
+function subtractMinute(ev)
+{
+ duration = Math.max(0, duration - 60000);
+
+ if (firstwindow)
+ firstwindow.postMessage({event: "duration", v: duration});
+ else if (secondwindow?.closed === false)
+ secondwindow.postMessage({event: "duration", v: duration});
+
+ forceClocks = true;
+ requestClocksUpdate(); // Queue an update to the clocks
+
+ ev.stopPropagation();
+ ev.preventDefault();
+}
+
+
+/* pauseTime -- pause or resume the clocks */
+function pauseTime(ev)
+{
+ if (pauseStartTime) { // We're resuming, add paused time to startTime
+ startTime += Date.now() - pauseStartTime;
+ pauseStartTime = 0;
+ document.body.classList.remove("paused");
+ } else { // We're pausing, remember start time of pause
+ pauseStartTime = Date.now();
+ document.body.classList.add("paused");
+ }
+
+ forceClocks = true;
+ requestClocksUpdate(); // Queue an update to the clocks
+
+ if (firstwindow) {
+ firstwindow.postMessage({event: "startTime", v: startTime});
+ firstwindow.postMessage({event: "pauseStartTime", v: pauseStartTime});
+ } else if (secondwindow?.closed === false) {
+ secondwindow.postMessage({event: "startTime", v: startTime});
+ secondwindow.postMessage({event: "pauseStartTime", v: pauseStartTime});
+ }
+
+ ev.stopPropagation();
+ ev.preventDefault();
+}
+
+
+/* resetTime -- restart the clock */
+function resetTime(ev)
+{
+ startTime = Date.now();
+
+ if (firstwindow)
+ firstwindow.postMessage({event: "startTime", v: startTime});
+ else if (secondwindow?.closed === false)
+ secondwindow.postMessage({event: "startTime", v: startTime});
+
+ forceClocks = true;
+ requestClocksUpdate(); // Queue an update to the clocks
+
+ ev.stopPropagation();
+ ev.preventDefault();
+}
+
+
+/* ignoreEvent -- cancel an event */
+function ignoreEvent(ev)
+{
+ ev.stopPropagation();
+ ev.preventDefault();
+}
+
+
+/* initClocks -- find and initialize clock elements */
+function initClocks()
+{
+ var t;
+
+ // Get the duration and warn time of the presentation from body.class.
+ for (const c of document.body.classList) {
+ if ((t = c.match(/^duration=([0-9.]+)$/))) duration = 1000 * 60 * t[1];
+ if ((t = c.match(/^warn=([0-9.]+)$/))) warnTime = 1000 * 60 * t[1];
+ }
+
+ // Find and remember any clock elements. Turn the live
+ // HTMLCollection into a static array for efficiency, because we
+ // don't expect new clock elements to be created.
+ fullclockElts = Array.from(document.getElementsByClassName("fullclock"));
+ clockElts = Array.from(document.getElementsByClassName("clock"));
+
+ // If there are elements with class=fullclock or class=clock
+ // and that don't have child elements already, fill them with
+ // appropriate elements to make a clock.
+ for (const c of fullclockElts) {
+ c.setAttribute("aria-label", "clock");
+ if (!c.firstElementChild)
+ c.insertAdjacentHTML("beforeend", '' + _('current time') + '' +
+ '' +
+ '' +
+ '' + _('used') + '' +
+ '' +
+ '' + _('remaining') + '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '');
+ }
+ for (const c of clockElts) {
+ c.setAttribute("aria-label", "clock");
+ if (!c.firstElementChild)
+ c.insertAdjacentHTML("beforeend",
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '');
+ }
+
+ // Find all elements that will contain time.
+ realHoursElts = Array.from(document.getElementsByClassName("hours-real"));
+ realMinutesElts = Array.from(document.getElementsByClassName("minutes-real"));
+ realSecondsElts = Array.from(document.getElementsByClassName("seconds-real"));
+ usedHoursElts = Array.from(document.getElementsByClassName("hours-used"));
+ usedMinutesElts = Array.from(document.getElementsByClassName("minutes-used"));
+ usedSecondsElts = Array.from(document.getElementsByClassName("seconds-used"));
+ leftHoursElts = Array.from(document.getElementsByClassName("hours-remaining"));
+ leftMinutesElts = Array.from(document.getElementsByClassName("minutes-remaining"));
+ leftSecondsElts = Array.from(document.getElementsByClassName("seconds-remaining"));
+
+ // Find all elements that adjust the clock and install event handlers.
+ for (const e of document.getElementsByClassName("timeinc")) {
+ e.addEventListener("click", addMinute, true);
+ e.addEventListener("dblclick", ignoreEvent, true);
+ }
+ for (const e of document.getElementsByClassName("timedec")) {
+ e.addEventListener("click", subtractMinute, true);
+ e.addEventListener("dblclick", ignoreEvent, true);
+ }
+ for (const e of document.getElementsByClassName("timepause")) {
+ e.addEventListener("click", pauseTime, true);
+ e.addEventListener("dblclick", ignoreEvent, true);
+ }
+ for (const e of document.getElementsByClassName("timereset")) {
+ e.addEventListener("click", resetTime, true);
+ e.addEventListener("dblclick", ignoreEvent, true);
+ }
+
+ // Install a timer to update the clock elements once per second, if needed.
+ if (realHoursElts.length || realMinutesElts.length ||
+ realSecondsElts.length || usedHoursElts.length ||
+ usedMinutesElts.length || usedSecondsElts.length ||
+ leftHoursElts.length || leftMinutesElts.length ||
+ leftSecondsElts.length)
+ clockTimer = setInterval(requestClocksUpdate, 1000, false);
+
+ // Remember start time of presentation.
+ if (clockTimer) startTime = Date.now();
+}
+
+
+/* initIncrementals -- find incremental elements in current slide */
+function initIncrementals()
+{
+ var e = curslide;
+
+ // Collect all incremental elements into array incrementals.
+ //
+ // The functions nextSlideOrElt() and previousSlideOrElt() maintain
+ // the following invariant: If there are incrementals, there is at
+ // most one of them with a class of "active". If there is an
+ // "active" element, all incrementals before it, and only those,
+ // have a class of "visited". If there is an "active" element,
+ // incrementals[incrementals.cur] points to that element; if there
+ // is not, incrementals.cur is -1.
+ //
+ // incrementalsBehavior is an experimental variable to evaluate
+ // different behaviors when going backwards inside a slide with
+ // incremental elements:
+ //
+ // "freeze": When you leave a slide, the incremental elements that
+ // are currently displayed become frozen. When going back to that
+ // slide, those elements are still displayed but can no longer be
+ // removed by pressing the left arrow. This is the behavior of
+ // Shower.
+ //
+ // "reset": Every time you enter a slide, all incremental elements
+ // are in their hidden state. E.g., if you leave a slide with all
+ // elements visible and then go back, all elements are hidden again.
+ //
+ // "symmetric": When you return to a slide, the slide is exactly as
+ // you left it. Incremental elements that were displayed when you
+ // left the slide are still displayed and can be hidden by pressing
+ // the left arrow. This is currently the default.
+ //
+ // "forwardonly": When you enter a slide, all incremental elements
+ // are in their hidden state (as with "reset"). In addition,
+ // pressing the left arrow when some incremental elements are
+ // displayed, resets all elements to their hidden state.
+ //
+ // Note that with all of these except "symmetric", the left arrow
+ // acts very much like the PageUp key: when you go back to the
+ // previous slide, every next press of the left arrow goes back one
+ // slide.
+ //
+ incrementals = [];
+ incrementals.cur = -1;
+ do {
+ /* Go to the next node, in document source order. */
+ if (e.firstChild) {
+ e = e.firstChild;
+ } else {
+ while (e && !e.nextSibling) e = e.parentNode;
+ if (e) e = e.nextSibling;
+ }
+ if (!e) break; /* End of document */
+ if (e.nodeType != 1) continue; /* Not an element */
+ if (isStartOfSlide(e)) break; /* Reached the next slide */
+ if (e === liveregion) break; /* Do not search in the liveregion */
+
+ if (e.classList.contains("incremental") || e.classList.contains("overlay"))
+ for (const c of e.children)
+ if (incrementalsBehavior === "symmetric") {
+ if (c.classList.contains("active"))
+ incrementals.cur = incrementals.length; // Start at this element
+ incrementals.push(c);
+ } else if (incrementalsBehavior === "reset" ||
+ incrementalsBehavior == "forwardonly") {
+ c.classList.remove("active");
+ c.classList.remove("visited");
+ incrementals.push(c);
+ } else { // "freeze"
+ if (!c.classList.contains("visited") &&
+ !c.classList.contains("active"))
+ incrementals.push(c);
+ }
+ if (e.classList.contains("next")) { /* It is an incremental element */
+ if (incrementalsBehavior === "symmetric") {
+ if (e.classList.contains("active"))
+ incrementals.cur = incrementals.length; // Start at this element
+ incrementals.push(e);
+ } else if (incrementalsBehavior === "reset" ||
+ incrementalsBehavior == "forwardonly") {
+ e.classList.remove("active");
+ e.classList.remove("visited");
+ incrementals.push(e);
+ } else { // "freeze"
+ if (!e.classList.contains("visited") &&
+ !e.classList.contains("active"))
+ incrementals.push(e);
+ }
+ }
+ } while (1);
+}
+
+
+/* isStartOfSlide -- check if element has class=slide, page-break or is an H1 */
+function isStartOfSlide(elt)
+{
+ return elt.b6slidenum !== undefined || // shortcut: we already numbered it
+ (elt.nodeType == 1 && // it is an element
+ elt.parentNode == document.body &&
+ (elt.classList.contains("slide") ||
+ getComputedStyle(elt).getPropertyValue('page-break-before')=='always' ||
+ elt.nodeName == "H1"));
+}
+
+
+/* updateProgress -- update the progress bars and slide numbers, if any */
+function updateProgress()
+{
+ var p = curslide.b6slidenum / numslides;
+
+ /* Set the width of the progress bars */
+ for (const e of progressElts) e.style.width = 100 * p + "%";
+
+ /* Set the content of .slidenum elements to the current slide number */
+ for (const e of slidenumElts) e.textContent = curslide.b6slidenum;
+
+ /* Set a custom variable on BODY for use by style rules */
+ document.body.style.setProperty('--progress', p);
+}
+
+
+/* initProgress -- unhide .progress, .slidenum and .numslides elements */
+function initProgress()
+{
+ var s;
+
+ /* Find all elements that are progress bars, unhide them. */
+ if (interactive) {
+ progressElts = Array.from(document.getElementsByClassName("progress"));
+ for (const e of progressElts)
+ if (typeof e.b6savedstyle === "string") e.style.cssText = e.b6savedstyle;
+ }
+
+ /* Find all that should contain the current slide number, unhide them. */
+ slidenumElts = Array.from(document.getElementsByClassName("slidenum"));
+ for (const e of slidenumElts)
+ if (typeof e.b6savedstyle === "string") e.style.cssText = e.b6savedstyle;
+
+ /* Unhide all elements that contain the # of slides. */
+ for (const e of numslidesElts)
+ if (typeof e.b6savedstyle === "string") e.style.cssText = e.b6savedstyle;
+}
+
+
+/* numberSlides -- count slides, number them, and make sure they have IDs */
+function numberSlides()
+{
+ var s;
+
+ // Count slides and make sure all slides have an ID.
+ numslides = 0;
+ for (const h of document.body.children)
+ if (isStartOfSlide(h)) {
+ h.b6slidenum = ++numslides; // Save number in element
+ generateID(h, h); // If the slide has no ID, add one
+ for (const v of h.querySelectorAll('VIDEO, AUDIO'))
+ generateID(v, h); // Make sure all video elts have an ID
+ }
+
+ // Set content of all elements with class=numslides to the number of slides.
+ numslidesElts = Array.from(document.getElementsByClassName("numslides"));
+ for (const e of numslidesElts) e.textContent = numslides;
+
+ // Set the # of slides in a CSS counter on the BODY.
+ s = window.getComputedStyle(document.body).getPropertyValue("counter-reset");
+ if (s === "none") s = ""; else s += " ";
+ document.body.style.setProperty('counter-reset',s + 'numslides ' + numslides);
+}
+
+
+/* scaleImagesIfNeeded -- maybe shrink images with class=autosize */
+function scaleImagesIfNeeded(slide)
+{
+ var height, images, heights = [], f1, f2, f, saveHeight, saveMaxHeight;
+
+ // Only handle slides that consist of one element.
+ // TODO: Also handle other styles of markup.
+ if (! slide.classList.contains('slide')) return;
+
+ // Get all images to scale in the current slide, if any.
+ images = slide.querySelectorAll('img.autosize');
+ if (images.length === 0) return;
+
+ // Get the slide's height, which presumably is the desired height.
+ height = slide.getBoundingClientRect().height;
+
+ // Save the slide's inline height and max-height properties, if
+ // any, so we can restore them later. Then set them temporarily to
+ // auto and none.
+ saveHeight = slide.style.height;
+ saveMaxHeight = slide.style.maxHeight;
+ slide.style.height = 'auto';
+ slide.style.maxHeight = 'none';
+
+ // Get the height of the slide again, to see if it is bigger now.
+ if (slide.getBoundingClientRect().height > height) {
+
+ // Get the natural height of all images. Use the height of the
+ // slide if the image has none.
+ images.forEach((e, i) => heights[i] = e.naturalHeight || height);
+
+ // Find a factor 0.01 < f < 1.0 to multiply the height of each
+ // image by such that all slide content fits on the slide. (It may
+ // not be possible to find such a height: there may be too much
+ // non-image stuff on the slide already, or the author may have
+ // put class=autofit on images that don't influence the slide
+ // height.)
+ f1 = 0.01;
+ f2 = 1.0;
+ while (f2 > 1.00001 * f1) { // Until within 0.001%
+ f = (f1 + f2)/2;
+ images.forEach((e, i) => {
+ e.style.height = (f * heights[i]) + "px"});
+ if (slide.getBoundingClientRect().height > height) f2 = f; else f1 = f;
+ }
+ }
+
+ // Restore the element's style sttribute.
+ slide.style.height = saveHeight;
+ slide.style.maxHeight = saveMaxHeight;
+}
+
+
+/* scaleFontIfNeeded -- if the slide has class=textfit, maybe shrink the font */
+function scaleFontIfNeeded(slide)
+{
+ var style, height, f, f1, f2, wrapper, saveHeight, saveMaxHeight;
+
+ // The slide only needs to be checked if it has class=textfit or if
+ // the body has that class. And then only if the slide is one
+ // element. TODO: Also try to handle other markup?
+ if (! slide.classList.contains('slide') ||
+ (! document.body.classList.contains('textfit') &&
+ ! slide.classList.contains('textfit'))) return;
+
+ // Get the slide's height, which presumably is the desired height.
+ height = slide.getBoundingClientRect().height;
+
+ // Save the slide's inline height and max-height properties, if
+ // any, so we can restore them later. Then set them temporarily to
+ // auto and none.
+ saveHeight = slide.style.height;
+ saveMaxHeight = slide.style.maxHeight;
+ slide.style.height = 'auto';
+ slide.style.maxHeight = 'none';
+
+ // Get the height of the slide again, to see if it is bigger now.
+ if (slide.getBoundingClientRect().height > height) {
+ // Make a wrapper for the slide's content, if we didn't already.
+ // We'll then set a smaller font size on the wrapper to try and
+ // reduce the height of the slide.
+ wrapper = slide.firstChild;
+ if (!wrapper?.b6textfitwrapper) {
+ wrapper = document.createElement('div');
+ wrapper.setAttribute('class', 'b6textfitwrapper');
+ wrapper.style.display = 'contents'; // This DIV does not generate a box
+ while (slide.firstChild) wrapper.appendChild(slide.firstChild);
+ slide.appendChild(wrapper);
+ wrapper.b6textfitwrapper = true;
+ }
+
+ // Find new font size between 1% and 100% of the current size.
+ f1 = 0.01;
+ f2 = 1.0;
+ while (f2 > 1.00001 * f1) { // Until within 0.001%
+ f = (f1 + f2)/2;
+ wrapper.style.fontSize = 100 * f + '%';
+ if (slide.getBoundingClientRect().height > height) f2 = f; else f1 = f;
+ }
+ wrapper.style.setProperty('--font-scale-factor', f);
+ }
+
+ // Restore the element's style sttribute.
+ slide.style.height = saveHeight;
+ slide.style.maxHeight = saveMaxHeight;
+}
+
+
+/* textFit -- if the body or a slide has a class=textfit, make text fit */
+function textFit()
+{
+ for (const h of document.body.children)
+ if (isStartOfSlide(h)) scaleFontIfNeeded(h);
+}
+
+
+/* autosize -- shrink images with class=autosize if needed */
+function autosize()
+{
+ for (const h of document.body.children)
+ if (isStartOfSlide(h)) scaleImagesIfNeeded(h);
+}
+
+
+/* instrumentVideos -- add event handlers to all video and audio elements */
+function instrumentVideos()
+{
+ // Stop any videos and audios that have an autoplay attribute, but
+ // remember the attribute, so we can start the video/audio when
+ // its slide is shown.
+ for (const v of document.querySelectorAll('VIDEO, AUDIO')) {
+ v.b6autoplay = v.autoplay;
+ v.autoplay = false;
+ v.pause();
+ }
+
+ // If a second window is open, these event handlers help to
+ // synchronize the playback of videos and audios in both windows.
+ // When the user starts, pauses or seeks a video in one window, a
+ // message is sent to the other window to start, seek or pause the
+ // video there, too. See message() for how the message is handled in
+ // the receiving window.
+ for (const v of document.querySelectorAll('VIDEO, AUDIO'))
+ if (v.id !== "") {
+ v.addEventListener('pause', ev => {
+ if (ev.target.b6pausing) { // We paused because of a message
+ ev.target.b6pausing = false;
+ } else {
+ firstwindow?.postMessage({event: 'pause', id: v.id}, '*');
+ secondwindow?.postMessage({event: 'pause', id: v.id}, '*');
+ }
+ });
+ v.addEventListener('play', ev => {
+ if (ev.target.b6playing) { // We started play because of a message
+ ev.target.b6playing = false;
+ } else {
+ firstwindow?.postMessage({event: 'play', id: v.id}, '*');
+ secondwindow?.postMessage({event: 'play', id: v.id}, '*');
+ }
+ });
+ v.addEventListener('seeked', ev => {
+ if (ev.target.b6seeking) { // We seeked as a result of a message
+ ev.target.b6seeking = false;
+ } else {
+ firstwindow?.postMessage({event: 'seeked', id: v.id,
+ v: ev.target.currentTime}, '*');
+ secondwindow?.postMessage({event: 'seeked', id: v.id,
+ v: ev.target.currentTime}, '*');
+ }
+ });
+ v.addEventListener('volumechange', ev => {
+ // We only sync muted state, not volume (which is 0 in 1st window)
+ firstwindow?.postMessage({event: 'volumechange', id: ev.target.id,
+ v: ev.target.muted}, '*');
+ secondwindow?.postMessage({event: 'volumechange', id: ev.target.id,
+ v: ev.target.muted}, '*');
+ });
+ }
+}
+
+
+/* hideMouse -- make the mouse pointer invisible (only in slide mode) */
+function hideMouse()
+{
+ if (slidemode) document.body.style.cursor = 'none';
+ hideMouseID = 0; // 0 = timer has fired, cursor is hidden
+}
+
+
+/* hideMouseReset -- event handler for mousemove to reset the hideMouse timer */
+function hideMouseReset()
+{
+ if (hideMouseID === 0) { // Timer has fired and hid the cursor. Unhide it.
+ document.body.style.cursor = null;
+ hideMouseID = null; // null = cursor is visible
+ } else if (hideMouseID !== null) { // Timer hasn't fired yet. Remove it.
+ clearTimeout(hideMouseID);
+ hideMouseID = null; // null = cursor is visible
+ }
+
+ /* If still in slide mode, set a new timer; otherwise remove ourselves. */
+ if (slidemode) hideMouseID = setTimeout(hideMouse, hideMouseTime);
+ else document.removeEventListener('mousemove', hideMouseReset);
+}
+
+
+/* initHideMouse -- set a timeout to hide the mouse pointer when it is idle */
+function initHideMouse()
+{
+ if (hideMouseTime === null) return;
+
+ /* Add handler to restart the timer when the mouse moves. */
+ document.addEventListener('mousemove', hideMouseReset);
+
+ /* Remove old timer, unhide cursor if hidden, start new timer. */
+ hideMouseReset();
+}
+
+
+/* rewindVideos -- reset any autoplaying videos on the current slide */
+function rewindVideos()
+{
+ for (const v of curslide.querySelectorAll('VIDEO, AUDIO'))
+ if (v.b6autoplay) {
+ v.currentTime = 0;
+ v.play();
+ }
+}
+
+
+/* stopVideos -- stop any videos and audios on the current slide */
+function stopVideos()
+{
+ for (const v of curslide.querySelectorAll('VIDEO, AUDIO')) v.pause();
+}
+
+
+/* displaySlide -- make the current slide visible */
+function displaySlide()
+{
+ var h, url, m;
+
+ /* curslide has class=slide, page-break-before=always or is an H1 */
+ curslide.style.cssText = curslide.b6savedstyle;
+ curslide.classList.add("active"); // Compatibility with Shower
+ liveregion.innerHTML = ""; // Make it empty
+
+ if (!curslide.classList.contains('slide')) {
+ liveregion.appendChild(cloneNodeWithoutID(curslide));
+ /* Unhide all elements until the next slide. And copy the slide to
+ the live region so that it is spoken */
+ for (h = curslide.nextSibling; h && ! isStartOfSlide(h); h = h.nextSibling)
+ if (h !== liveregion) {
+ if (h.nodeType === 1) h.style.cssText = h.b6savedstyle;
+ liveregion.appendChild(cloneNodeWithoutID(h));
+ }
+
+ } else { // class=slide
+ /* Copy the contents of the slide to the live region so that it is spoken */
+ for (h = curslide.firstChild; h; h = h.nextSibling)
+ liveregion.appendChild(cloneNodeWithoutID(h));
+ }
+
+ updateProgress();
+ initIncrementals();
+
+ /* If there is a first window, tell it to scroll to the same slide. */
+ if (firstwindow)
+ firstwindow.postMessage({event: "slide", v: curslide.id}, "*");
+
+ /* Update the URL displayed in the location bar. */
+ history.replaceState({}, "", "#" + curslide.id)
+
+ /* Remove any existing slide timer. Then, unless the automatic slide
+ * show is paused, check if the slide has a data-timing attribute,
+ * or failing that, use the default (from the BODY). If the result
+ * is not 0, set a timeout. */
+ clearTimeout(slideTimer);
+ if (! slideTimerPaused &&
+ (m = curslide.dataset.timing !== undefined ?
+ timeToMillisec(curslide.dataset.timing) / (incrementals.length + 1) :
+ slideTiming / (incrementals.length + 1)))
+ slideTimer = setTimeout(nextSlideOrElt, m);
+
+ /* If there are any autoplay videos or audios, start them. */
+ rewindVideos();
+}
+
+
+/* hideSlide -- make the current slide invisible */
+function hideSlide()
+{
+ var h;
+
+ if (!curslide) return;
+
+ /* If any videos are playing, stop them. */
+ stopVideos();
+
+ /* curslide has class=slide, page-break-before=always or is an H1 */
+ curslide.classList.remove("active"); // Compatibility with Shower
+ curslide.classList.add("visited"); // Compatibility with Shower
+ curslide.style.visibility = "hidden";
+ curslide.style.position = "absolute";
+ curslide.style.top = "0";
+ for (h = curslide.nextSibling; h && ! isStartOfSlide(h); h = h.nextSibling)
+ if (h.nodeType === 1 /*Node.ELEMENT_NODE*/ && h !== liveregion) {
+ h.style.visibility = "hidden";
+ h.style.position = "absolute";
+ h.style.top = "0";
+ }
+}
+
+
+/* makeCurrent -- hide the previous slide, if any, and display elt */
+function makeCurrent(elt)
+{
+ console.assert(elt);
+ if (curslide != elt) {
+ hideSlide();
+ curslide = elt;
+ displaySlide();
+ }
+}
+
+
+/* fullscreen -- toggle fullscreen mode or turn it on ("on") or off ("off") */
+async function toggleFullscreen(onoff)
+{
+ var s, x;
+
+ switchFullscreen = true; // For the fullscreenchange event handler
+
+ if (onoff !== "on" && document.fullscreenElement)
+ document.exitFullscreen();
+ else if (onoff !== "off" && document.fullscreenEnabled)
+ try {
+ // If there is exactly one external screen, use that.
+ s = "getScreenDetails" in window &&
+ (x = (await getScreenDetails()).screens.filter((h) => !h.isInternal)) &&
+ x.length == 1 ? x[0] : screen;
+ await document.documentElement.requestFullscreen({navigationUI: "hide",
+ screen: s});
+ } catch (err) {
+ alert(_("An error occurred while trying to switch into fullscreen mode")
+ + ' (' + err.message + ' – ' + err.name + ")\n\n" +
+ _("You can try again with the F or F1 key."));
+ }
+ else if (onoff !== "off")
+ window.alert(_("Fullscreen mode is not possible"));
+}
+
+
+/* createHelpText -- fill the helptext element with help text */
+function createHelpText()
+{
+ var iframe, button;
+
+ /* Put the help text in an IFRAME so it is not affected by the slide style */
+ iframe = document.createElement('iframe');
+ iframe.setAttribute('title', _("Mouse & keyboard commands"));
+ iframe.srcdoc =
+ "" +
+ "" +
+ "" +
+ "" + _("Mouse & keyboard commands") + "" +
+ "" +
+ "
" + _("(More information in the b6+ manual)");
+ iframe.style.cssText = 'margin: 0; border: none; padding: 0; ' +
+ 'width: 100%; height: 100%';
+ button = document.createElement('button');
+ button.innerHTML = "\u274C\uFE0E"; // Cross mark
+ button.style.cssText = 'position:absolute; top: 0; right: 16px';
+ button.addEventListener('click',
+ ev => {helptext.remove(); ev.stopPropagation()});
+ // Unfortunately, when in fullscreen mode, the Escape key is
+ // captured by the browser to exit fullscreen mode and we never get
+ // it.
+ button.setAttribute("tabindex", 0);
+ button.addEventListener('keydown', ev => {
+ if (ev.key == "Escape") {helptext.remove(); ev.stopPropagation()}});
+ helptext = document.createElement('div');
+ helptext.appendChild(iframe);
+ helptext.appendChild(button);
+ helptext.style.cssText = 'position: fixed; width: 100%; height: 100%; ' +
+ 'top: 0; left: 0; z-index: 2; background: #000; color: #FFF; ' +
+ 'text-align: center; visibility: visible';
+ // In fullscreen mode, the Escape key is captured by the browser to
+ // leave fullscreen mode, so only the second Escape press closes the
+ // help text. But let's add it anyway.
+ helptext.setAttribute("tabindex", 0);
+ helptext.addEventListener('keydown', ev => {
+ if (ev.key == "Escape") {helptext.remove(); ev.stopPropagation()}});
+}
+
+
+/* help -- show information about available interactive commands */
+function help()
+{
+ // Works both on first and second windows
+ clearTimeout(slideTimer);
+ if (!helptext) createHelpText();
+ document.body.appendChild(helptext);
+ helptext.lastChild.focus(); // The button
+}
+
+
+/* getSlideTitle -- get the title of the slide that starts at elt */
+function getSlideTitle(elt)
+{
+ var title;
+
+ if (elt.nodeType == 1 && elt.nodeName.match(/^H[1-6]$/))
+ return elt.innerHTML;
+ else if (elt.firstChild && (title = getSlideTitle(elt.firstChild)))
+ return title;
+ else if (elt.nextSibling && !isStartOfSlide(elt.nextSibling))
+ return getSlideTitle(elt.nextSibling);
+ else
+ return null;
+}
+
+
+/* createTOCText -- fill the toctext element with the table of contents */
+function createTOCText()
+{
+ var button, style, items = "";
+
+ /* Collect the titles of all slides and make them A elements inside LI. */
+ for (const h of document.body.children)
+ if (isStartOfSlide(h)) {
+ let i = '#' + h.id.replaceAll('&', '&').replaceAll('"', '"');
+ let t = getSlideTitle(h); // Returns an HTML fragment or null
+ items += '
This is a template for slides for TPAC 2025.
+ +Author(s): The user manual at the end has setup information and + instructions. To write slides, look at the slides in this + template.
+ +Reader(s): + + To start the slide show, press ‘A’. Return to + the index with ‘A’ or ‘Esc’. On a touch screen, use a 3-finger + touch. Double click to open a specific slide. In slide mode, press + ‘?’ (question mark) to get a list of available commands. + + To start the slide show, press Shift+F5 + (Command+Enter on Mac). Return to the index by pressing ‘Esc’. You + can also click to open a specific slide.
+ +If it doesn't work: Slide mode requires a + recent browser with JavaScript. If you are using the ‘NoScript’ add-on (Firefox or the + Tor Browser), or changed the ‘site settings’ (Chrome, Vivaldi, Opera, Brave and some other + browsers), or the ‘permissions for this site’ (Edge), you may have + to explicitly allow JavaScript on these slides. Internet Explorer + is not supported.
+