Writing style sheets for b6+

When writing a style sheet for a slide set that uses the b6+ slide framework, it is necessary to know the kinds of markup and class attributes that b6+ relies on. B6+ also generates new class attributes during operation. This document explains what they are and gives examples of style rules.

One style sheet or two

In the b6+ framework, all slides of a slide set are contained in a single HTML file and that file can be viewed with slide mode activated or not. In slide mode, you see one slide at a time. Outside of slide mode (also called index mode), all slides are visible, and usually at a smaller size than in slide mode.

Typically, you will have a few style rules that apply only in slide mode and others that only apply in index mode. E.g., the rules that hide and show incremental elements (explained below) only apply in slide mode: In index mode, you don't interact with the slides and you normally want those elements visible at all times.

There are two ways to distinguish rules for slide mode from rules for index mode. One way is to rely on the fact that b6+ changes the class attribute of the BODY element in slide mode: It adds the value ‘full’ to it in slide mode and removes it again when going back to index mode. Thus you can write selectors that apply in only one mode. Example:

body.full h1 {color: red}         /* slide mode */
body:not(.full) h1 {color: green} /* index mode */

The other way is to put the style rules in separate files and use media queries to control which one is used for which mode. In slide mode, b6+ simulates a device that doesn't scroll, but displays multiple pages (like a printer) and thus it looks for style sheets designed for that.

For example, if you have a file ‘common.css’ with style that always applies, ‘slide.css’ with rules that only apply in slide mode, and ‘index.css’ with rules that only apply in index mode, you could write this:

<link rel=stylesheet href="common.css">
<link rel=stylesheet media="(overflow-block: paged)" href="slide.css">
<link rel=stylesheet media="(overflow-block: scroll)" href="index.css">

Fixed-size slides

It is not a requirement, but it may be useful to give the slide show a specific width and height.

If the BODY element has a fixed size, b6+ will scale it to fit the projector or screen. In that case you always know how much text fits on a slide, independent of how big (how many pixels) the projector is. Otherwise the size of the BODY depends on the projector and it may fit more or less text on a line, depending on how many pixels it has.

Because b6+ scales the slides to the projector, the chosen size matters less than the aspect ratio: It should ideally match the projector or screen, to avoid black bands.

Current video projectors and TV screens nearly all have a screen ratio of 16:9. (Computer monitors may also have a ratio of 16:10.) So 16:9 is probably the ratio to use.

The size does matter to determine how much text should fit on a slide. More than about 70 characters on one line makes the text hard to read, especially for people in the back of the room. (This assumes alphabetic characters, which is about 35 Chinese ideographs) Allowing for a bit of padding, or maybe some decorations, and an average kind of font, a width of about 40em would be a good choice.

Thus, you could use a rule like this:

body.full {width: 40em; height 22.5em}

(40 × 16/9 = 22.5) If you are using a separate style sheet for slide mode, the ‘.full’ can be omitted: body {width: 40em; height 22.5em}.

Slides with class=slide

B6+ supports two different syntaxes for slides. In the simplest form, all H1 elements that are children of BODY start a slide. Each slide includes everything until the next H1 element.

In the other syntax, all child elements of BODY that have a class of ‘slide’ are slides. It is typically easier to write style sheets for this.

E.g., if the slide set has a fixed size (see above), it is easy to give slides a border:

body.full .slide {box-sizing: border-box; width: 100%; height: 100%;
  border: thick solid red}

Incrementally displayed elements

B6+ supports ‘overlays’ or ‘built-up slides’. Typically, this is used to make elements of a list appear one by one on each click or key press. But whether elements appear, or something else changes, depends on the style sheet. All b6+ does is add or remove class attributes and if the style sheet doesn't have rules for those, nothing visually happens.

There are two ways to mark up incremental elements. The first depends on the class ‘next’.

The author of the slides may put class=next on elements and when a slide is displayed, b6+ finds all such elements on the slide. Then, on the first click or key press, it takes the first element and adds the class ‘active’ to it. On the next click or key press it adds class ‘active’ to the next element and changes ‘active’ to ‘visited’ on the previous element. And so on, until the last element.

When going backwards, b6+ removes the ‘visited’ class.

Thus, there is at most one ‘active’ element and all incremental elements before it are ‘visited’ and all incremental elements after it are not ‘visited’.

To simply make elements appear, the style sheet only needs to set ‘visibility: hidden’ on elements that have the class ‘next’ but have neither the class ‘visited’ nor the class ‘active’:

body.full .next:not(.visited):not(active) {visibility: hidden}

There are, of course, other ways to do this. And with suitable style rules, elements can also be grayed out, show an animation, be superimposed on each other, etc. The ‘active’ element can also be specially highlighted.

Instead of putting the class ‘next’ on all incremental elements, the author can in certain cases also use the class ‘overlay’ (or its alias ‘incremental’). It may be shorter if there are many incremental elements, but it is less flexible. Setting the class ‘overlay’ on an element indicates that all its children (but not the element itself) are incremental elements. E.g., if all items in a list are incremental elements, you can set class=overlay on the enclosing OL or UL.

To make the children of elements with class=overlay (and class=incremental) appear one by one, the style sheet needs a rule like this:

body.full .overlay > *:not(.visited):not(.active),
body.full .incremental > *:not(.visited):not(.active) {
  visibility: hidden}

Script-controlled dark mode

B6+ can provide a button and a key (‘D’) to switch to dark mode, without switching the whole browser or the whole desktop environment to dark mode. It cannot change the user preference ‘prefers-color-scheme’, so instead it sets the class ‘darkmode’ on the BODY, leaving it to the style sheet to change the colors when that class is set.

To know whether the style sheet contains rules for the class ‘darkmode’, b6+ checks if the style sheet has set the custom property ‘--has-darkmode: 1’ on BODY. So, a style sheet that has rules for darkmode on BODY, should also contain this rule:

body {--has-darkmode: 1}

Progress bar

B6+ doesn't itself show the progress through a slide set, but it sets three pieces of information that the slide set or its style sheet can make use of.

Slides may contain elements with a class of ‘slidenum’. In slide mode, b6+ sets the content of all such elements to the number of the current slide. It also sets the content of all elements with a class of ‘numslides’ to the total number of slides.

Slides may also contain elements with a class of ‘progress’. In slide mode, b6+ changes the ‘width’ property of such elements and sets it to a percentage corresponding to the current slide's position in the slide set. E.g., if the current slide is the 6th slide and there are 8 slides in total, the width will be 75%.

E.g., the slide set could contain <div class=progress></div> and the style sheet rules like this:

body.full .progress {position: absolute; top: 0; left: 0;
  height: 0.5em; z-index: 1; border-right: 0.5em solid green}

to display a green square along the top, which moves further to the right as each slide is displayed.

b6+ also sets the custom property ‘--progress’ on the BODY containing a number between 0 and 1, corresponding to the progress in the slide set. E.g., if the current slide is the 5th slide out of 8, BODY will get a ‘--progress’ property with the value 0.625.

Table of contents

In slide mode, the user can press the ‘C’ key and b6+ will show a clickable list of slides. This is in the form of a modal DIALOG element, containing a BUTTON (to close the dialog) and an OL list where each item contains a link to a slide.

The browser will provide a default style for the dialog, but the style sheet can override that. E.g., to make a wide dialog with the list in columns, and a semi-transparent, purple backdrop, use rules similar to this:

.toc {width: 95%; max-width: none; max-height: 95%}
.toc ol {margin: 0; columns: 18em; column-rule: thin solid; padding: 0}
.toc li {break-inside: avoid; margin-left: 2em}
.toc::backdrop {background:  hsl(270,85%,25%,0.5)}

(The ‘max-width’ is necessary, because the default browser style may have a ‘max-width’ that is less then 95%.)

Drawing on slides

In slide mode, a user can press the ‘W’ key and b6+ will create a canvas overlay on which the user can draw with the mouse. The color of the drawing is by default the foreground color of the slide, but the style sheet might want to set it to something else. The canvas has a class of ‘b6-canvas’, so a rule like the following sets the drawing color to red:

.b6-canvas {color: red}

Preview window

B6+ can display slides in a separate browser window. In that case, the original window continues to display the slides in index mode while the second window displays them in slide mode. The idea is that the second window can be moved to the projector while the original window serves as a preview window. If the author has put speaker notes between the slides, they are also visible in this window.

The normal style for index mode may be enough, but it is also possible to change the style to make the window more useful as a preview window. E.g., it may be useful to show not all slides, but only the current and next one, and display the speaker notes more prominently.

This can be done by writing style rules that select for the class ‘has-2nd-window’. B6+ sets that class on the BODY when the window is in index mode and there is a second window in slide mode. E.g., here is a rule to put an outline border around the current slide in the preview window:

body.has-2nd-window .slide.active {outline: thin solid red}

Clocks and time elements

When b6+ finds elements with class ‘hours-real’, ‘minutes-real’, ‘seconds-real’, ‘hours-remaining’, ‘minutes-remaining’, ‘seconds-remaining’, ‘hours-used’, ‘minutes-used’, or ‘seconds-used’ it will replace their contents with numbers representing the current time, the elapsed time or the remaining time, and it will keep them updated. (See the manual for how it determines the remaining time.)

When b6+ finds elements with class ‘timepause’, ‘timedec’, ‘timeinc’, or ‘timereset’ it will attach handlers to them that can be used to pause, change or reset the time elements.

Additionally, when it find an empty element with a class of ‘clock’, it will fill it with time elements like the above. E.g., if it finds

<div class=clock></div>

it will turn that into:

<div class="clock" role="widget" aria-label="clock">
 <time title="Remaining time. To change, add class 'duration=n' to body">
  <b class="minutes-remaining">12</b> min
  </time>
 <span><span></span></span>
 <button class="timepause">
  <span title="resume">▶︎</span>
  <span title="pause">⏸︎</span>
 </button>
 <button class="timedec" title="−1 min">−1</button>
 <button class="timeinc" title="+1 min">+1</button>
 <button class="timereset" title="restart">↺</button>
</div>

The spaces and newlines are just for readability. They do not exist in reality. The text may be in a different language. The two nested SPAN elements are explained below.

Similarly, if b6+ finds an empty element with class ‘fullclock’ in the slide set, e.g.,

<div class=fullclock></div>

it will turn that into:

<div class="fullclock" role="widget" aria-label="clock">
 <i>current time</i>
 <time>
  <b class="hours-real">18</b>:<b class="minutes-real">47</b>
 </time>
 <span><span></span></span>
 <i>used</i>
 <time>
  <b class="minutes-used">00</b> min
 </time>
 <i>remaining</i>
 <time>
  <b class="minutes-remaining">29</b> min
 </time>
 <button class="timepause">pause</button>
 <button class="timedec">−1 min</button>
 <button class="timeinc">+1 min</button>
 <button class="timereset">restart</button>
</div>

Here, too, there are in reality no spaces and newlines, the text may be in a different language, and the numbers are just an example.

When the user stops the clock (using the pause button), b6+ sets the class ‘paused’ on the BODY. The style sheet may use that to indicate that the clock is paused, e.g., by changing the clock's color.

B6+ also sets the class ‘time-warning’ on BODY when the remaining time is below a given threshold.

And it sets the property ‘--time-factor' on the BODY with a number between 0 and 1, corresponding to the elapsed time divided by the total duration. (The value will not go beyond 1.)

One way to use the ‘--time-factor’ property is to draw a progress bar, a rotating dial, or similar, using the two empty SPAN elements in the markup above. E.g., here one of the SPANs shows a ‘pie chart’ of the used time in the form of a white circle with a black segment that increases as ‘--time-factor’ grows:

.fullclock > span, .clock > span {display: inline-block;
  width: 3em; height: 3em; border-radius: 50%; background: #FFF;
  background: conic-gradient(
      #000 calc(var(--time-factor) * 360deg),
      #FFF calc(var(--time-factor) * 360deg),
      #FFF 360deg), #FFF}

And the following example uses both of the SPANs to draw a clock hand that rotates as the ‘--time-factor’ advances:

.fullclock > span, .clock > span {display: inline-block;
  width: 3em; height: 3em; border-radius: 50%; background: #FFF;
  position: relative; background: #FFF}
.fullclock > span > span, .clock > span > span {height: 2px;
  width: 50%; background: #000; position: absolute; top: calc(50% - 1px);
  left: 50%; transform-origin: 0 1px;
  transform: rotate(calc(var(--time-factor) * 360deg - 90deg))}

UI buttons

B6+ inserts a handful of buttons at the start of the slide set, which looks like this:

<div class="b6-ui">
 <button class="b6-playbutton" title="play slides or stop playing">
  <span>▶︎</span> <span>play/<wbr>stop</span>
 </button>
 <button class="b6-secondwindowbutton" title="play/stop slides in a 2nd window">
  <span>⧉</span> <span>play in 2nd window</span>
 </button>
 <button class="b6-prevbutton" title="previous slide">
  <span>❮</span> <span>back</span>
 </button>
 <button class="b6-nextbutton" title="next slide or element">
  <span>❯</span> <span>forward</span>
 </button>
 <button class="b6-helpbutton" title="help">
  <span>?</span> <span>help</span>
 </button>
</div>

You may want to give the buttons some style, but especially it may be useful to keep the buttons from scrolling off the screen when the window is used as a preview window, i.e., when the ‘has-2nd-window’ class is set on BODY. E.g.:

body.has-2nd-window .b6-ui {position: sticky; top: 0.5em; z-index: 1}

Paused automatic slide shows

If b6+ finds a ‘data-timing’ attribute containing a time (on BODY, on a slide, or on an incremental element), it will automatically advance the slide show (or just that slide or element). The user can stop the automatic slide show with the ‘P’ or ‘Pause’ key. And to indicate that an automatic slide show is currently in manual mode, b6+ puts the class ‘manual’ on BODY.

A style sheet may use that to show the current status. E.g., here is a style rule that shows a pause symbol in the middle of the slide when an automatic slide show is in manual mode:

.manual::before {position: absolute; z-index: 1;
  content: url("data:image/svg+xml,%lt;svg%20version='1.1'%20viewBox='0%200%2036%2036'%20xmlns='http://www.w3.org/2000/svg'>%lt;circle%20style='opacity:0.6;stroke:%23fff;stroke-width:2'%20cx='18'%20cy='18'%20r='17'/>%lt;path%20style='opacity:0.6;fill:%23fff'%20d='m20,10h6v16h-6zm-10,0h6v16h-6z'/>%lt;/svg>");
  top: calc(50% - 2em); left: calc(50% - 2em); width: 4em; height: 4em}

Slide transitions

In slide mode, b6+ not only hides all slides except one, it also sets class=active on the currently displayed slide. And when a different slide is displayed, the one that was active before gets the class ‘visited’ and the class ‘active’ is removed.

But unlike for incremental elements, the ‘visited’ class is not removed when you go back to a previous slide. It is thus possible to change the style of slides that the user has already seen before.

But the classes are most useful for making animated slide transitions. E.g., here is a style to make a slide gradually fade in.

body.full .slide.visited {animation: delay 1s 1}
body.full .slide + .active {animation: fade-in 1s 1}
@keyframes delay {
  from {visibility: visible}
  to {visibility: visible}
}
@keyframes fade-in {
  from {opacity: 0}
  to {opacity: 1}
}

The first rule keeps the old slide from disappearing immediately. B6+ sets ‘visibility: hidden’ on the slide at the same time as it adds the ‘visited’ class, which makes the slide invisible. This rule overrides that property by means of an animation that lasts one second. The slide thus only disappears when the animation ends.

The second rule sets an animation on the new slide, the one with class ‘active’. This animation also lasts 1 second and slowly changes the ‘opacity’ property from 0 to 1. (The normal ‘opacity’ is 1, i.e., the slide is normally fully opaque, and this animation overrides that during 1 second.)

Slides inside an IFRAME

B6+ detects if the slides are shown inside an IFRAME, EMBED or OBJECT element inside another document, rather than as a top-level document. It changes all links in slides so that clicking them makes their target replace the containing document, rather than open inside the IFRAME. (Concretely it adds the target=_parent attribute). And it also sets the class ‘framed’ on the BODY, so that the style sheet can adapt the style if needed.

E.g., the style sheet might give the slides rounded corners and set the background behind the slides to black, so that the corners are black, but then make the background transparent when the slides are inside an IFRAME in another document, to better blend in with that document:

body.full .slide {border-radius: 0.5em}
body.full {background: black}
body.framed {background: transparent}

Distinguishing b6+ from other frameworks

A style sheet may support different slide frameworks, e.g., both b6+ and Shower. In that case there may be a need to include style rules that only apply to slides that use b6+. To help with that, b6+ adds the class ‘b6plus’ on the BODY. Thus, a rule like this:

body.b6plus h1 {color: red}

makes H1 elements red, but only if the slide set uses b6+.

The class is present as soon as b6+ is loaded, i.e., it is present both in index mode and in slide mode.

Created 14 February 2025 by Bert Bos
Last modified $Date: 2025/02/21 13:51:32 $ by $Author: bbos $