EPUBs for TTRPG Adventures

When I started publishing adventures last year, I decided to add EPUB and HTML versions to offer options accessible to screen reader users. I was curious about how many people actually use EPUBs to read adventures and Symbolic City put up a poll about it. 54% never use EPUBs while the rest sometimes do. But some of the comments mentioned that EPUB is very rarely an option.

My educational and professional background is in publishing and I’ve been working with EPUBs for over a decade at this point, so I thought I would share my workflow for putting together EPUBs for my adventures. My big caveat is that this workflow does require some comfort with your computer’s command line, or at least a willingness to learn. This method uses Pandoc. If you’re not trying to learn the command line, I also have sections on tools, formatting tips, and accessibility.

As a side-note, an EPUB is just a packaged website with an extra bit of metadata. It’s all just HTML and CSS.

The exact text of this blog post is released under CC BY-SA. You have my permission to use text from this page to create content (including documentation) and publish it, as long as there is attribution and it is used under the same license.

Preparing the Manuscript

I write my adventures in Markdown using Obsidian. For PDF layout, I use Affinity Publisher. Once I’m done with the print layout, I go back to the manuscript and make any updates I made in Affinity to the manuscript. Ideally the manuscript should be final by the time it gets into Affinity, but there are always constraints when it comes to layout, so I usually have some edits to carry over.

Cross Reference Cleanup

In the manuscript, I include cross-references (hyperlinking mentions of monsters, room names, treasures, etc) and any art and maps as well. I make sure that these hyperlinks are all lowercase and have hyphens where spaces would be.

I make things like rooms, treasures, and monsters with their own headings, so they can be linked to. So for example, this is how I marked up a third-level heading for a dungeon room (A1 Statues Room).

### A1 Statues Room

(The # character indicates a heading level. So # is a top level heading, ## is a second level heading, and so on).

To link to this A1 Statues Room from our example, the spaces must be replaced by hyphens and the text must be lowercase. The example below follows markdown formatting:

Broken pottery. 3 backpacks: 3 stale rations, 3 torches. 
Door north to [A1 Statue Room](#a1-statue-room). 

The space replacement and lowercasing must be done because when Pandoc converts the manuscript to EPUB, this is how it will export the ID’s of the headings.

To make these links all lower case, you can use this find and replace regular expression:

\(#(.*?)\)

Replace with

\(#\L$1\)

Regular expressions are basically find and replace using variables. It can be easily researched online. You can use RegExPal to test them out.

Metadata

At the top of the manuscript, I add metadata:

---
title:
- type: main
  text: ADD YOUR TITLE HERE
- type: subtitle
  text: ADD A SUBTITLE HERE IF PRESENT
creator:
- role: author
  ADD YOUR NAME
- role: illustrator
  ADD AS MANY ROLES AS YOU LIKE
publisher:  PUBLISHER NAME
rights: copyright 2024 YOUR NAME or any other license like Creative Commons goes here
lang: en-US
cover-image: cover.jpg
css: style.css
...
---

Running Pandoc

Make sure your manuscript, cover image, any referenced internal images, and stylesheets are in the same folder. In your command line, navigate to the folder. Run pandoc:

$ pandoc -o file.epub manuscript.md

Quality Assurance

Open up the exported EPUB file in the reading system of your choice. I use ibooks for this. Flip through it. Click through links. Make sure it looks like you want it to and that the links are working. Validate the EPUB using EPUBCheck and Ace by DAISY. Put it online!

Remediation

Sometimes you find errors and issues. I typically remediate EPUBs by unzipping the EPUB package and using a code editor. I use eCanCrusher (see the section EPUB Packaging) to unzip and Sublime Text to edit. There are other tools for editing EPUBs described below.

Appendix A: Tools

EPUB Packaging

  • eCanCrusher. You can use this to unzip EPUB packages and edit their constituent parts, or zip a folder to make an EPUB package. You can use this to make manual edits to the EPUB. The main difficulty of creating an EPUB is making the OPF metadata file. It’s such a tedious little file that should’ve been replaced by JSON a long time ago and that’s mostly what you’re automating with Pandoc.
  • Kindle Previewer. Kindle formats are like EPUBs but worse and not open source. Kindle Previewer creates a Kindle file once you run the conversion.
  • InDesign has a much improved EPUB export. It used to be trash.
  • Google Docs has an EPUB export! It does a decent job.

EPUB Editors

  • Sigil. I used to use this in my first job like 11 years ago. Not sure how it functions now, but it worked well enough back then.
  • Calibre. I do not like Calibre, but it’s been like 10 years since I’ve used it and some people swear by it, so I’ve added it.

Desktop EPUB Readers

  • Adobe Digital Editions is trash.
  • iBooks is fine.
  • Thorium
  • Is Readium still a thing?
  • Maybe get an e-reader instead. I have a BOOX and a Kobo.

Validators

EPUBCheck and Ace by DAISY are the industry standard validation tools, both maintained by a single developer, it’s wild.

  • EPUBChecker is my favorite GUI for EPUBCHECK. I know it’s in German, but there’s an English tab. Unless you speak German, in which case you can stay on the German tab.
  • Ace by DAISY has its own GUI. I’m still waiting for this issue to get resolved, but again, it’s just one guy and I hope you’re not using MathML in your adventures.
  • Make sure the styles in your EPUB pass color contrast. Use WebAIM’s contrast checker. This is also useful when making any art that contains text or information. Make sure that color is not the only thing that is indicating something. Use other visual cues for people with colorblindness.

Appendix B: Accessibility

Alt Text and Extended (or Long) Descriptions

Alt text should be a reasonable substitute for an image. Note that the common recommendation for alt text is around 125 characters.

125 characters is way too short for a map, so for my EPUBs, I write extended descriptions. I either put these in a details HTML element after the image or provide a link explicitly stating that there is an extended description. It’s not just people with visual impairments that can benefit from a description of a map.

Use rich formatting in extended descriptions. By that I mean, use multiple paragraphs, lists, tables, and even subheadings. The reason people recommend 125 characters for alt text is because a screen reader user usually can’t navigate inside of a line of alt text. Using an extended description, you can provide a rich navigational experience using lists, paragraphs, and subheadings.

Try describing the map like a diagram. Here are some guides to authoring alt text.

Extended Description Tips

Start a new paragraph when introducing a new idea.

For complex images, consider the clock face approach: Starting at the top of the image for 12:00, describe each element clockwise. Because extended descriptions are also available to sighted users who may have difficulty parsing complex diagrams, this method may be helpful for referencing different sections of the image.

For diagrams or room descriptions, consider nested lists. For example:

  • Room 12: The Kitchen
    • Backpack
      • 2 rations, 1 dagger

Consider using tables to represent datasets in a map. For example,

Room Treasure Monster
1 Golden goose statuette 300 Geese
2 The friends we made along the way Envy

Extended Description Example

Here is an excerpt from the EPUB and HTML version of my adventure The Devil’s Millhopper. This is the extended description for the map.

Extended description for the Map of the Limestone Pits
  1. Fossil Cave. 30 by 20 foot room, irregular. Dry. Connects to room 3a by a ledge.
  2. The Devil's Millhopper. Funnel. 40 by 50 circular room, completely underwater. Branches off to three tunnels, rooms 3a, 3b, and 3c.
  3. Three tunnels branching off from the Devil's Millhopper, room 2.
    1. Completely underwater room. 20 by 30 foot room, irregular. Connects to The Devil's Millhopper, room 2, and room 1, the Fossil Cave, via a ledge.
    2. Completely underwater room. 20 by 10 foot room, irregular. Connects to The Devil's Millhopper, room 2, and room 6, the Root Chamber, via a ledge.
    3. Completely underwater room. 20 by 20 foot room, irregular. Connects to The Devil's Millhopper, room 2, and room 4, Floatsam Cave, via a tunnel.
  4. Floatsam cave. Completely underwater room. 30 by 30 foot irregular room. Wooden planks and barrels float on the surface. Connects to 3c Underwater Tunnel through a tunnel, room 5 the Square Chamber via a ledge, and room 7 Lower Flooded Chamber via a locked door.
  5. Square Chamber. 25 by 25 foot square room. Connects to room 4 Flotsam Cave by a ledge and room 6 the Root Chamber by a 50 foot corridor.
  6. Root chamber. 30 by 20 foot irregular room. Connects to 3b Underwater tunnel by a ledge. Contains a statue in the top left (northwest) corner. Connects to room 11, the Gator Pit, by a secret door and short staircase down. Connects to room 5, Square chamber, via a 50 foot corridor.

Inset: A side view: The Devil's Millhopper funnel (room 2) opens up at the bottom to the underwater tunnels (rooms 3a and 3c), showing them completely underwater. The Tunnel 3a leads to room 1, which is on dry land. The tunnel 3c leads to room 4, which is also underwater but has space above to breathe. A locked door leads to room 7.

Full disclosure, I don’t know how well I did on this one. I’m happy to take any feedback for any alt text or extended descriptions in my adventures.

Alternatives to Extended Descriptions

Another viable method is to extend the room descriptions in your dungeons in the EPUB version. Since there are no print constraints, you can include things like connections and dimensions that you may have eschewed in the print version and relied on the map for.

Appendix C: Formatting Tips

Tables

Tables! Don’t make them too complicated. Navigating a table with a screen reader can be difficult. Usually adventure tables are pretty straightforward though so this shouldn’t be too much of a problem. If a table gets really big though, consider breaking it up.

Heading Hierachy

Remember that headings do not indicate importance. They indicate hierarchy and nesting. A screen reader user can navigate between headings, so if there are any skipped headings (for example, there is an h3 (that is, a heading of level 3) nested directly under an h1 without an h2 in between), the screen reader is unable to access that h3!

Drop Caps

Drop caps are cool! They’re not in the CSS I included but if you want to add them, use the first-letter pseudo-class. Do not use spans. These break up the word for screen reader users. For example,

<p><span class="dropcap">T</span>his</p>

will be read as “T, his” rather than “This” by a screen reader. Do not break up words using spans.

CSS

Unzipping the EPUB package will let you see all of the HTML and CSS. I have included the CSS I use in my books below.

It’s based on some custom rules I came up with, Pandoc’s default styles, most of which I removed, and Jiminy Panoz’s Blitz CSS framework which was sunsetted back in 2020. The Blitz styles are still fine to use, I just wasn’t able to find a more recent EPUB CSS framework. The classes used in Blitz would have to be manually added in some way, but they’re all there for you to pick and choose. None are exported by default in the Pandoc export, but the element level styles are all retained.

Maybe someday I’ll make an adventure-specific EPUB CSS framework. That could be neat. Basic CSS for stat blocks and the like.

Select to Reveal CSS
@charset "UTF-8";
/*!
  Blitz — CSS framework for reflowable eBooks
  Version 1.5.2 by Jiminy Panoz
  Codename: Cool Under Heat
  License: MIT (https://opensource.org/licenses/MIT)
*/
/* NAMESPACES */
@namespace h "http://www.w3.org/1999/xhtml/";
@namespace epub "http://www.idpf.org/2007/ops";
/* if you need to style epub:type */
@namespace m "http://www.w3.org/1998/Math/MathML/";
/* if you need to style MathML */
@namespace svg "http://www.w3.org/2000/svg";
/* if you need to style SVG */
html {
  /* Don't use it for styling, used as selector which can take a punch if anything goes wrong above */
}
/* Begin CSS */
/* RESET */
/* So here's the trick, we must reset to manage a number of problems once and for all: 
- HTML5 backwards compatibility (EPUB 3 file in EPUB 2 app); 
- user settings (e.g. line-height on Kobo and Kindle); 
- CSS bloat (DRY); 
- KFX for which a reset using `border: 0` seems to disable support; 
- etc.
It all started as a normalize and became a reset given the magnitude of the task.                                                          
*/
article,
address,
aside,
blockquote,
canvas,
dd,
details,
div,
dl,
dt,
figure,
figcaption,
footer,
h1,
h2,
h3,
h4,
h5,
h6,
header,
hr,
li,
main,
nav,
ol,
p,
pre,
section,
summary,
ul {
  margin: 0;
  padding: 0;
  /* RS may apply vertical padding to el such as p */
  font-size: 1em;
  /* Font size in pixel disable the user setting in legacy RMSDK */
  line-height: inherit;
  /* Kindle ignores it, Kobo needs it. If you don’t use inherit, the user setting may be disabled on some Kobo devices */
  text-indent: 0;
  font-style: normal;
  font-weight: normal;
}
/* This is absolutely necessary for backwards compatibility */
article,
aside,
figure,
figcaption,
footer,
header,
main,
nav,
section {
  display: block;
}

/* [Opinionated] Default to prevent RS from justifying all of these! */
h1,
h2,
h3,
h4,
h5,
h6,
dt,
pre {
  text-align: left;
}
/* Following EPUB 3 spec by the letter (applies to RS but let’s make sure it is respected because we never know) */
nav[epub|type~="toc"] ol {
  list-style: none !important;
}
/* Kindle does not follow the EPUB 3 spec. */
@media amzn-kf8, amzn-mobi {
  nav ol {
    list-style-type: none !important;
  }
}
/* [Opinionated] Default to prevent bloat in case linear="no" is rendered as linear="yes" */
nav[epub|type~="landmarks"],
nav[epub|type~="page-list"] {
}
a,
abbr,
b,
bdi,
bdo,
cite,
code,
data,
del,
dfn,
em,
i,
ins,
kbd,
mark,
q,
rp,
rt,
rtc,
ruby,
s,
samp,
small,
span,
strong,
sub,
sup,
time,
var {
  font-size: inherit;
  vertical-align: baseline;
  font-style: inherit;
  /* Taking nesting of inline elements into account (e.g. sup nested in em) */
  font-weight: inherit;
  /* Taking nestiog of inline elements into account (e.g. em nested in strong) */
  color: inherit;
  text-decoration: none;
}
q {
  quotes: none;
}
/* Trying to prevent blank page if element with margin-bottom at the end of xhtml */
body > :last-child,
body > section > :last-child {
  margin-bottom: 0;
}
/* PAGE LAYOUT */
@page {
  margin: 30px 30px 20px 30px;
  /* Recommended by Barnes & Noble in this old spec: https://simg1.imagesbn.com/pimages/pubit/support/pubit_epub_formatting_guide.pdf */
  padding: 0;
}
body {
  font-size: 100%;
  line-height: 1.5;
  margin: 0;
  /* RS will override margins anyways */
  padding: 0;
}
/* TYPOGRAPHY */
h1,
h2,
h3,
h4,
h5,
h6,
blockquote p cite,
dt,
pre,
address,
table,
caption,
th,
td,
.align-left,
.align-center,
.align-right,
.caption,
.no-hyphens {
  adobe-hyphenate: none;
  /* proprietary for Legacy RMSDK */
  -ms-hyphens: none;
  -moz-hyphens: none;
  -webkit-hyphens: none;
  -epub-hyphens: none;
  hyphens: none;
}
h1,
h2,
h3,
h4,
h5,
h6,
dt,
hr {
  page-break-inside: avoid;
  break-inside: avoid;
  page-break-after: avoid;
  break-after: avoid;
}
@media amzn-kf8 {
  h1,
  h2,
  h3,
  h4,
  h5,
  h6,
  dt,
  hr {
    page-break-inside: auto;
    break-inside: auto;
    /* Fix blank bug because of page-break-inside: avoid… */
  }
}
h1 {
  font-size: 1.4375em;
  line-height: 1.04347826;
  margin-top: 0em;
  margin-bottom: 2.08695652em;
}
@media amzn-kf8 {
  h1 {
    line-height: 1.2;
    /* Minimum value Kindle supports */
  }
}
@media amzn-mobi {
  h1 {
    font-size: xx-large;
    /* Keywords offer more precision for mobi 7*/
    margin: 0 0 3em 0;
    /* mobi7 doesn’t support decimal values */
  }
}
h2 {
  font-size: 1.3125em;
  line-height: 1.14285714;
  margin-top: 2.28571429em;
  margin-bottom: 1.14285714em;
}
@media amzn-kf8 {
  h2 {
    line-height: 1.2;
    /* Minimum value Kindle supports */
  }
}
@media amzn-mobi {
  h2 {
    font-size: x-large;
    margin: 2em 0 1em 0;
  }
}
h3 {
  font-size: 1.125em;
  line-height: 1.33333333;
  margin-top: 1.33333333em;
  margin-bottom: 1.33333333em;
}
@media amzn-mobi {
  h3 {
    font-size: large;
    margin: 1em 0;
  }
}
h4 {
  font-size: 1em;
  line-height: 1.5;
  margin-top: 1.5em;
  margin-bottom: 0em;
}
@media amzn-mobi {
  h4 {
    font-size: medium;
    margin: 1em 0 0 0;
  }
}
h5 {
  /* Styles */
}
h6 {
  /* Styles */
}
p {
  text-indent: 1em;
}
.footnote {
  font-size: 0.9375em;
  line-height: 1.6;
  text-indent: 0;
}
@media amzn-mobi {
  .footnote {
    font-size: medium;
  }
}
blockquote {
  margin: 1.5em 5%;
}
@media amzn-mobi {
  blockquote {
    margin: 1em 5%;
  }
}
blockquote p {
  text-indent: 0;
  font-style: italic;
}
blockquote p i,
blockquote p em,
blockquote p cite {
  font-style: normal;
}
address {
  /* Styles */
}
/* MICRO TYPOGRAPHY */
a {
  text-decoration: underline;
  /* Note: KF8 will force this value unless you use "el.class a" */
  font-weight: bold;
  color: inherit;
  -webkit-text-fill-color: inherit;
  /* iBooks override (iOS 9 + El Capitan in night mode) */
  /* inherit = text color */
}
abbr {
  /* Note: Kindle doesn't support that */
}
i,
cite,
dfn,
em {
  font-style: italic;
}
/* Get back to normal when italic nested in italic */
i i,
i cite,
i dfn,
i em,
cite i,
cite cite,
cite dfn,
cite em,
dfn i,
dfn cite,
dfn dfn,
dfn em,
em i,
em cite,
em dfn,
em em {
  font-style: normal;
}
b,
strong {
  font-weight: bold;
}
del,
s {
  text-decoration: line-through;
}
mark {
  background-color: yellow;
  color: inherit;
}
ins {
  /* Styles */
}
small {
  font-size: 0.8125em;
}
@media amzn-mobi {
  small {
    font-size: small;
  }
}
/* Styling is improved to prevent sub from affecting line-height */
sub {
  font-size: 75%;
  line-height: 1.2;
  vertical-align: sub;
  /* Fallback */
  vertical-align: -20%;
}
@media amzn-mobi {
  sub {
    font-size: x-small;
  }
}
/* Styling is improved to prevent sup from affecting line-height */
sup {
  font-size: 75%;
  line-height: 1.2;
  vertical-align: super;
  /* Fallback */
  vertical-align: 33%;
}
@media amzn-mobi {
  sup {
    font-size: x-small;
  }
}
/* i18n */
/* Ruby text */
rt {
  font-size: 50%;
  /* Line-height may need to be adjusted to fit ruby text. */
  text-transform: full-size-kana;
  /* Increases the legibility of Japanese ruby characters. */
}
/* FIGURES + IMAGES  */
figure {
  page-break-inside: avoid;
  break-inside: avoid;
  margin: 1.5em 0;
}
@media amzn-kf8 {
  figure {
    page-break-inside: auto;
    break-inside: auto;
    /* Fix blank bug because of page-break-inside: avoid… */
  }
}
figcaption,
.caption {
  font-size: 0.9375em;
  line-height: 1.6;
  text-indent: 0;
}
img {
  width: auto;
  max-width: 100%;
  /* Note: KF8 doesn't support max-width hence "width: auto;" as fallback */
  height: auto;
  object-fit: contain;
  vertical-align: bottom;
  /* Remove gap after the image */
}
/* Note: portrait image styling + figcaption is a nightmare */
/* See https://github.com/jstallent/ImagesSingleFile for the css hack */
img.portrait {
  width: auto;
  max-width: 100%;
  /* Note: KF8 doesn't support max-width hence "width: auto;" as fallback */
  height: 100%;
  /* We try to prevent blank page after */
  max-height: 95%;
  /* Max value iBooks enforces */
}
.float-left img,
.float-right img {
  width: 100%;
  /* If it’s auto, image in floating container will overflow on Kobo iOS + Kindle */
}
@supports (height: 99vh) {
  img.portrait {
    height: 99vh;
  }
}
/* LISTS */
ul,
ol {
  margin: 1.5em 0;
  padding-left: 2em;
}
@media amzn-mobi {
  ul,
  ol {
    margin: 1em 0;
  }
}
ol ol,
ol ul,
ul ol,
ul ul {
  padding-left: 1em;
}
ul {
  list-style-type: disc;
  list-style-position: outside;
}
ul ul {
  list-style-type: square;
}
ol {
  list-style-type: decimal;
  list-style-position: outside;
}
ol ol {
  list-style-type: lower-roman;
}
/* DEFINITION LISTS */
dl {
  margin: 1.5em 0 1.5em 5%;
}
@media amzn-mobi {
  dl {
    margin: 1em 5%;
  }
}
dt {
  margin: 1.5em 0 0 0;
  font-weight: bold;
}
@media amzn-mobi {
  dt {
    margin-top: 1em;
  }
}
dd {
  /* Styles */
}
dt > dfn {
  font-style: normal;
  font-weight: bold;
}
/* HORIZONTAL RULES — CONTEXT BREAKS */
hr {
  width: 25%;
  margin-left: 37.5%;
  margin-top: 1.4375em;
  margin-bottom: 1.4375em;
  height: 0;
  border: none;
  border-top: 0.125em solid currentColor;
  /* currentColor = color of text (getting around night mode) */
  opacity: 0.5;
  /* Opacity -> grayscale. If opacity is declared for too many elements in a XHTML file, performance of legacy RMSDK takes a hit */
}
@media amzn-mobi {
  hr {
    margin: 1em 0;
  }
}
/* Blank-line context change */
hr.transition {
  width: 100%;
  margin: 0;
  height: 1.5em;
  border: none;
  background: none;
  /* Note: overridden in night mode excepted when using linear-gradient */
}
/* Over-engineered asterism with an SVG background 
which is legacy RMSDK-compliant, reflows with text and 
is night-mode compatible (black asterisk + white border) */
hr.asterism {
  width: auto;
  border: none;
  margin: 1.5em 0;
  height: 1.5em;
  text-indent: 0;
  text-align: center;
  background: transparent url("../Images/asterism.svg") no-repeat center;
  /* Change url if you put asterism in a folder */
  background-size: 2.5em 1.25em;
  /* RMSDK doesn't support -> won't scale but SVG viewport is OK for a wide range of font-sizes */
  overflow: hidden;
  /* Fixes legacy RMSDK bug when contents before hr are invisible */
  opacity: 0.7;
  /* Better border color match in night mode (less disruptive) */
}
/* TABLES */
table {
  display: table;
  table-layout: auto;
  border-collapse: collapse;
  border-spacing: 0;
  max-width: 100%;
  margin: 1.5em auto;
  /* Note: legacy RMSDK sets auto to 0, which is permitted in a footnote of the EPUB2 specs */
  font-feature-settings: "tnum" 1;
  font-variant-numeric: tabular-nums;
}
@media amzn-mobi {
  table {
    margin: 1em 0;
  }
}
caption {
  caption-side: top;
  /* Note: only value legacy RMSDK supports */
  adobe-hyphenate: none;
  /* proprietary for Legacy RMSDK */
  -ms-hyphens: none;
  -moz-hyphens: none;
  -webkit-hyphens: none;
  -epub-hyphens: none;
  hyphens: none;
  text-indent: 0;
  /* Necessary as RS may define text-indent for p */
  text-align: center;
  font-weight: bold;
}
tbody {
  /* Styles */
}
thead {
  /* Styles */
}
tfoot {
  /* Styles */
}
tr {
  /* Styles */
}
th {
  empty-cells: show;
  border-bottom: 0.125em solid currentColor;
  /* Current color = color of text (inverted in night mode) */
  padding: 0.6875em 10px 0.6875em 0;
  text-align: left;
  font-weight: bold;
}
td {
  empty-cells: show;
  border-bottom: 0.0625em solid currentColor;
  /* Current color = color of text (inverted in night mode) */
  padding: 0.75em 10px 0.6875em 0;
  text-align: left;
}
.table-fixed {
  table-layout: fixed;
}
/* CODE */
pre {
  margin: 1.5em 0 1.5em 5%;
  word-wrap: break-word;
  white-space: pre-wrap;
  -ms-tab-size: 2;
  -moz-tab-size: 2;
  -webkit-tab-size: 2;
  tab-size: 2;
}
@media amzn-mobi {
  pre {
    margin: 1em 0;
  }
}
code,
kbd,
samp,
var {
  font-family: monospace;
  /* Embed a font cos Kobo doesn't provide a monospace */
}
/* MEDIAS */
audio {
  /* Styles */
}
video {
  /* Styles */
}
canvas,
iframe,
svg,
video {
  width: auto;
  max-width: 100%;
  height: auto;
}
svg {
  object-fit: contain;
}
/* CONTAINERS — WRAPPERS */
.wrap-100 {
  width: auto;
  /* defaults to 100% but takes borders into account = big win since we can’t rely on box-sizing */
  margin: 1.5em 0;
}
.wrap-90 {
  width: 90%;
  margin: 1.5em 5%;
  /* Since we can’t use auto (0 in legacy RMSDK), we must center using % */
}
.wrap-80 {
  width: 80%;
  /* width: 80% -> KF8 bug, see amzn-kf8 query for fix */
  margin: 1.5em 10%;
}
.wrap-70 {
  width: 70%;
  margin: 1.5em 15%;
}
.wrap-60 {
  width: 60%;
  margin: 1.5em 20%;
}
.wrap-50 {
  width: 50%;
  margin: 1.5em 25%;
}
.wrap-40 {
  width: 40%;
  margin: 1.5em 30%;
}
.wrap-30 {
  width: 30%;
  margin: 1.5em 35%;
}
.wrap-20 {
  width: 20%;
  margin: 1.5em 40%;
}
.wrap-10 {
  width: 10%;
  margin: 1.5em 45%;
}
/* Widths */
.w-100 {
  width: 100%;
}
.w-90 {
  width: 90%;
}
.w-80 {
  width: 80%;
}
@media amzn-kf8 {
  .w-80 {
    width: 78%;
    /* Because Kindle developers… */
    margin: 1.5em 11%;
  }
}
.w-70 {
  width: 70%;
}
.w-60 {
  width: 60%;
}
.w-50 {
  width: 50%;
}
.w-40 {
  width: 40%;
}
.w-30 {
  width: 30%;
}
.w-20 {
  width: 20%;
}
.w-10 {
  width: 10%;
}
/* Heights */
.h-100 {
  height: 99%;
  /* 99.8% cos’ legacy RMSDK applies blank page after if 100% */
}
.h-90 {
  height: 90%;
}
.h-80 {
  height: 80%;
}
.h-70 {
  height: 70%;
}
.h-60 {
  height: 60%;
}
.h-50 {
  height: 50%;
}
.h-40 {
  height: 40%;
}
.h-30 {
  height: 30%;
}
.h-20 {
  height: 20%;
}
.h-10 {
  height: 10%;
}
@supports (height: 100vh) {
  /* Can be used for containers and images 
     but Should be used in combination with .portrait if used for img */
  .h-100 {
    height: 99vh;
    /* Trying to avoid blank page after */
  }
  .h-90 {
    height: 90vh;
  }
  .h-80 {
    height: 80vh;
  }
  .h-70 {
    height: 70vh;
  }
  .h-60 {
    height: 60vh;
  }
  .h-50 {
    height: 50vh;
  }
  .h-40 {
    height: 40vh;
  }
  .h-30 {
    height: 30vh;
  }
  .h-20 {
    height: 20vh;
  }
  .h-10 {
    height: 10vh;
  }
}
/* Floats */
/* If there are too many floating elements in a XHTML file, 
performance of legacy RMSDK takes a huge hit (± 4 seconds to 
render page on eInk devices)                                    */
.float-left {
  float: left;
  margin: 0 1.5% 0 0;
}
@media amzn-mobi {
  .float-left {
    float: none;
    /* Mobi 7 doesn’t support float */
    margin: 1em 0;
    text-align: center;
  }
}
.float-right {
  float: right;
  margin: 0 0 0 1.5%;
}
@media amzn-mobi {
  .float-right {
    float: none;
    margin: 1em 0;
    text-align: center;
  }
}
/* UTILITIES */
/* Display */
.block {
  display: block;
}
.inline-block {
  display: inline-block;
}
.hidden {
  /* Typical usage: EPUB 3 landmarks inline TOC */
}
/* May be useful for EPUB 3.1, warning .absolute in ePubCheck in EPUB 3.0.1
.relative {
  position: relative;   
}

.absolute {
  position: absolute;   
}   
*/
/* Clearings */
.clear {
  /* may be useful if an element is floating in a container */
  clear: both;
}
.clear-left {
  clear: left;
}
.clear-right {
  clear: right;
}
/* Bordered content */
.boxed {
  border: 0.0625em solid currentColor;
  /* Current color = color of text (inverted in night mode) */
  padding: 0.6875em;
}
/* Margins */
.no-margin {
  margin: 0;
}
.no-margin-top {
  margin-top: 0;
}
.no-margin-bottom {
  margin-bottom: 0;
}
.no-margin-left {
  margin-left: 0;
}
.no-margin-right {
  margin-right: 0;
}
.margin-top-s {
  margin-top: 0.75em;
}
@media amzn-mobi {
  .margin-top-s {
    margin-top: 1em;
  }
}
.margin-top-m {
  margin-top: 1.5em;
}
@media amzn-mobi {
  .margin-top-m {
    margin-top: 2em;
  }
}
.margin-top-l {
  margin-top: 3em;
}
@media amzn-mobi {
  .margin-top-l {
    margin-top: 3em;
  }
}
.margin-top-xl {
  margin-top: 4.5em;
}
@media amzn-mobi {
  .margin-top-xl {
    margin-top: 5em;
  }
}
.margin-top-xxl {
  margin-top: 6em;
}
@media amzn-mobi {
  .margin-top-xxl {
    margin-top: 6em;
  }
}
.margin-bottom-s {
  margin-bottom: 0.75em;
}
@media amzn-mobi {
  .margin-bottom-s {
    margin-bottom: 1em;
  }
}
.margin-bottom-m {
  margin-bottom: 1.5em;
}
@media amzn-mobi {
  .margin-bottom-m {
    margin-bottom: 2em;
  }
}
.margin-bottom-l {
  margin-bottom: 3em;
}
@media amzn-mobi {
  .margin-bottom-l {
    margin-bottom: 3em;
  }
}
.margin-bottom-xl {
  margin-bottom: 4.5em;
}
@media amzn-mobi {
  .margin-bottom-xl {
    margin-bottom: 5em;
  }
}
.margin-bottom-xxl {
  margin-bottom: 6em;
}
@media amzn-mobi {
  .margin-bottom-xxl {
    margin-bottom: 6em;
  }
}
.margin-left-s {
  margin-left: 2.5%;
  /* % won't reflow with font-size user setting */
}
.margin-left-m {
  margin-left: 5%;
}
.margin-left-l {
  margin-left: 7.5%;
}
.margin-left-xl {
  margin-left: 10%;
}
.margin-left-xxl {
  margin-left: 15%;
}
.margin-right-s {
  margin-right: 2.5%;
  /* % won't reflow with font-size user setting */
}
.margin-right-m {
  margin-right: 5%;
}
.margin-right-l {
  margin-right: 7.5%;
}
.margin-right-xl {
  margin-right: 10%;
}
.margin-right-xxl {
  margin-right: 15%;
}
/* Font-stacks */
.sans {
  /* Typical usage: headings */
  font-family: sans-serif;
}
.serif {
  font-family: serif;
}
.monospace {
  font-family: monospace;
}
.humanist {
  font-family: "Myriad Pro", Seravek, Ember, "Trebuchet MS", "BN Trebuchet MS", "PT Sans", "Frutiger Neue", Roboto, sans-serif;
}
.geometric {
  font-family: Futura, "Century Gothic", "Apple SD Gothic Neo", AppleGothic, sans-serif;
}
.oldstyle {
  font-family: "Minion Pro", "Iowan Old Style", Palatino, "Palatino Linotype", "Palatino Nova", "BN Amasis", Cambria, FreeSerif, "Times New Roman", serif;
}
.modern {
  font-family: Athelas, Literata, Bookerly, "Merriweather Serif", Malabar, "BN Malabar", Georgia, "Droid Serif", serif;
}
/* Text align */
.justified {
  /* Designed as a class for body — We don't enforce as user setting > author */
  text-align: justify;
  adobe-hyphenate: auto;
  /* proprietary for Legacy RMSDK */
  -ms-hyphens: auto;
  -moz-hyphens: auto;
  -webkit-hyphens: auto;
  -epub-hyphens: auto;
  hyphens: auto;
  /* before and after not in spec but iBooks support all three (-webkit-) */
  -ms-hyphenate-limit-lines: 2;
  -moz-hyphenate-limit-lines: 2;
  -webkit-hyphenate-limit-lines: 2;
  hyphenate-limit-lines: 2;
  /* No support except Trident (Windows) */
  -ms-hyphenate-limit-chars: 6 3 2;
  -moz-hyphenate-limit-chars: 6 3 2;
  -webkit-hyphenate-limit-before: 3;
  -webkit-hyphenate-limit-after: 2;
  hyphenate-limit-chars: 6 3 2;
  /* No support except Trident (Windows) */
  -ms-hyphenate-limit-zone: 10%;
  -moz-hyphenate-limit-zone: 10%;
  -webkit-hyphenate-limit-zone: 10%;
  hyphenate-limit-zone: 10%;
  /* No support */
  -ms-hyphenate-limit-last: always;
  -moz-hyphenate-limit-last: always;
  -webkit-hyphenate-limit-last: always;
  hyphenate-limit-last: always;
}
.align-left {
  text-align: left;
}
.align-center {
  text-indent: 0;
  /* Necessary as RS may define text-indent for p */
  text-align: center;
}
.align-right {
  text-indent: 0;
  /* Necessary as RS may define text-indent for p */
  text-align: right;
}
/* Indents */
.indent {
  text-indent: 1em;
}
.no-indent {
  text-indent: 0;
}
.hanging-indent {
  text-indent: -5%;
  margin-left: 5%;
  /* Since em will reflow with font-size user setting, we’re using % */
}
@media amzn-mobi {
  .hanging-indent {
    margin-left: 0;
    /* hack for negative text-indent */
  }
}
/* Font sizes */
.fs-xxs {
  font-size: 0.8125em;
  line-height: 1.84615385;
}
.fs-xs {
  font-size: 0.875em;
  line-height: 1.71428571;
}
.fs-s {
  font-size: 0.9375em;
  line-height: 1.6;
}
.fs-m {
  font-size: 1em;
  line-height: inherit;
}
.fs-l {
  font-size: 1.125em;
  line-height: 1.33333333;
}
.fs-xl {
  font-size: 1.3125em;
  line-height: 1.14285714;
}
@media amzn-kf8 {
  .fs-xl {
    line-height: 1.2;
    /* Minimum value Kindle supports */
  }
}
.fs-xxl {
  font-size: 1.4375em;
  line-height: 1.04347826;
}
@media amzn-kf8 {
  .fs-xxl {
    line-height: 1.2;
    /* Minimum value Kindle supports */
  }
}
.fs-jumbo {
  font-size: 1.625em;
  line-height: 0.92307692;
}
@media amzn-kf8 {
  .fs-jumbo {
    line-height: 1.2;
    /* Minimum value Kindle supports */
  }
}
/* Font styles */
/* Don't use that with span if b or strong can be used */
.bold {
  font-weight: bold;
}
/* Don't use that with span if i, cite, dfn or em can be used */
.italic {
  font-style: italic;
}
.bold-italic {
  font-weight: bold;
  font-style: italic;
}
.small-caps {
  font-variant: small-caps;
  letter-spacing: 0.0625em;
  /* The two previous props are not supported in legacy RMSDK */
}
.caps-to-small-caps {
  text-transform: lowercase;
  /* Don't rely on this property, text should be typed in uppercase (legacy RMSDK) */
  font-variant: small-caps;
  letter-spacing: 0.0625em;
  /* The two previous props are not supported in legacy RMSDK */
}
@media amzn-mobi {
  .caps-to-small-caps {
    font-size: small;
  }
}
.underline {
  text-decoration: underline;
}
.line-through {
  text-decoration: line-through;
}
/* Misc */
.no-list-type {
  /* Typical usage: hiding numbers in toc */
  list-style-type: none;
}
/* PAGE BREAKS */
.break-before {
  page-break-before: always;
  break-before: always;
  /* Future according to https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before */
}
/* For some reason, after has better support than before (Google, Kobo, etc.) */
.break-after {
  page-break-after: always;
  break-after: always;
}
.break-inside {
  page-break-inside: auto;
  break-inside: auto;
}
/* Let’s pretend it is supported (legacy RMSDK does, others not so much) */
.no-break-before {
  page-break-before: avoid;
  break-before: avoid;
}
/* Let’s pretend it is supported (legacy RMSDK does, others not so much) */
.no-break-after {
  page-break-after: avoid;
  break-after: avoid;
}
/* This one works in iBooks, useful for figure or to keep 2 elements together */
.no-break-inside {
  page-break-inside: avoid;
  break-inside: avoid;
}
@media amzn-kf8 {
  .no-break-inside {
    page-break-inside: auto;
    break-inside: auto;
    /* Fix blank bug because of page-break-inside: avoid… */
  }
}
/* End CSS */



/* Pandoc Styles */
body { margin: 5%; text-align: justify; font-size: medium; }
code { font-family: monospace; }
h1.title { }
h2.author { }
h3.date { }
ol.toc { padding: 0; margin-left: 1em; }
ol.toc li { list-style-type: none; margin: 0; padding: 0; }
a.footnote-ref { vertical-align: super; }
em, em em em, em em em em em { font-style: italic;}
em em, em em em em { font-style: normal; }

      code{white-space: pre-wrap;}
      span.smallcaps{font-variant: small-caps;}
      span.underline{text-decoration: underline;}
      div.column{display: inline-block; vertical-align: top; width: 50%;}

/* Styles from Root Devil */

h1.main {
      font-size: 3rem;
      text-align: center;
      font-style: italic;
      margin-bottom: 2rem;
      margin-top: 2rem;
}

p.author {
      font-size: 2rem;
}

p.author, p.publisher, div.rights {
      text-align: center;
      text-indent: 0;
}

section.level1 h1 + p, hr + p {
      text-indent: 0;
}

section.level1 h1 {
      font-weight: bold;
      text-align: center;
      font-size: 1.75em;
      margin-top: 2rem;
}

p.author {
      margin-bottom: 5rem;
}

Yes Chef! Chef background for Cairn 2e

2024 June 28 | |

Yes Chef! Chef background for Cairn 2e

2024 June 28 |

| Skip to the recipe

Weather Hex Flower App for Dolmenwood

2024 April 28 | |

Weather Hex Flower App for Dolmenwood

2024 April 28 |

| I found this Hex Flower app for generating random weather with sensible, gradual trends. Works great, really liked it, but I wanted it to work...

Hexer Background for Cairn 2e

2024 March 30 | |

Hexer Background for Cairn 2e

2024 March 30 |

| My pal Rio (co-host with me on Tabletopped) said that he really wanted to play a Witcher character. I asked him what he felt the...

EPUBs for TTRPG Adventures

2024 March 22 | |

EPUBs for TTRPG Adventures

2024 March 22 |

| When I started publishing adventures last year, I decided to add EPUB and HTML versions to offer options accessible to screen reader users. I was...

Six-Room Dungeon Diagram Generator, based on BSD by Traverse Fantasy

2024 January 16 | |

Six-Room Dungeon Diagram Generator, based on BSD by Traverse Fantasy

2024 January 16 |

| Based on Bite-Sized Dungeons by Marcia B.