slides: reveal.js deck template for the BGP lecture

Structured like Matt Godbolt's newer talks (hrt-theme.css in
what-happens-beneath-accu-2026): reveal.js with no stock theme but a custom
dark one, every slide on a rounded panel, IBM Plex Sans and Mono, monokai
code with glowing stepped lines, 16:9 with fade transitions, and the
content in one Markdown file per section.

Made our own: the palette is a handful of variables (with ?accent= presets
for comparing), and his ring-and-thread corner motif became a chain of
nodes and links, an AS path, which also opens the title slide. A wordmark
sits where his decks carry the employer logo.

Also: a small `bird` syntax highlighter for router config on slides, a
bytes component in the four groups of the /statusz bytes view, columns,
boxes, and a `bare` slide class. reveal.js 6 loads from a vendored copy
when slides/vendor-reveal.sh has been run (works offline, ignored by git)
and from the CDN otherwise. Section files carry a starter outline for the
lab that doubles as a tour of the slide types.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-09-20 21:30:41 +02:00
1 parent e35507d082
commit 106edd1df0
13 files changed
+1051

No files matched your search

+1
View File
@@ -1,2 +1,3 @@
/target
__pycache__/
slides/reveal.js/
+81
View File
@@ -0,0 +1,81 @@
# Slides
A reveal.js deck structured like Matt Godbolt's newer talks
([what-happens-beneath-accu-2026](https://github.com/mattgodbolt/what-happens-beneath-accu-2026)):
no stock theme but a custom dark one, every slide on a rounded panel, IBM Plex
Sans and Mono, monokai code with glowing stepped lines, 16:9 with fade
transitions, and the content in one Markdown file per section.
What is ours: the palette, and the corner motif. His ring-and-thread became a
chain of nodes and links, an AS path, which also opens the title slide.
| File | Holds |
|---|---|
| `theme.css` | the look: palette, type, panels, motif, title slide |
| `style.css` | components used by slides: columns, boxes, bytes, notes |
| `index.html` | section list, reveal.js loader and options, `bird` highlighter |
| `sections/*.md` | the slides |
## Make it yours
Everything brand-like is a variable at the top of `theme.css`. To compare
accents without editing, open `?accent=green`, `?accent=amber` or
`?accent=magenta` (blue is the default); copy the one you like into `:root`.
The wordmark bottom-left is the `<div class="wordmark">` in `index.html`.
For a full-bleed title image, empty the title `<section>` and give it
`data-background="images/title.png"`.
## Show it
The sections are loaded over HTTP, so open the deck through a server, not as a
file:
python3 -m http.server 8000 --directory slides
Then <http://localhost:8000>. Keys: arrows to move, `S` speaker view with
notes and a timer, `O` overview, `F` fullscreen, `B` blank the screen.
## Offline
slides/vendor-reveal.sh
copies reveal.js into `slides/reveal.js/` (ignored by git). The deck uses that
copy when present and the CDN otherwise. Do this before presenting anywhere
with uncertain Wi-Fi. IBM Plex still comes from Google Fonts; without a
network the deck falls back to Helvetica/Arial and the system monospace, so
install IBM Plex on the presenting machine if that matters.
## Write slides
Edit the files in `sections/`; add a section by adding a file and a
`<section data-markdown=...>` line in `index.html`.
----- next slide
--- next vertical slide (detail you can skip)
Notes: speaker notes from here to the end of the slide
Apply a class to the element above with `<!-- .element: class="..." -->`.
| Class | Effect |
|---|---|
| `fragment` | appears on the next click |
| `fragment pop` | appears, grows and takes the accent colour |
| `big` | one large statement on the slide |
| `note` | quiet italic aside |
| `attribution` | small credit under an image or quote |
| `borders` | table with visible borders |
| `r-stretch` | image or iframe fills the remaining height |
| `blur-edges` | soft edge for photos |
Blocks: `<div class="cols">` two columns, `<div class="boxes">` labelled cards
(`us`, `ok`, `bad`), `<pre class="bytes">` BGP bytes in the colours of the lab
page. Code fences take a language and optional line steps, e.g.
` ```bird [1-3|5-9] `; `bird` is a small highlighter defined in `index.html`.
`<!-- .slide: class="bare" -->` drops the panel, the motif and the wordmark
for one slide, e.g. a full-bleed image. The title slide does so by itself.
## PDF
Open <http://localhost:8000/?print-pdf> in Chrome and print to PDF with
background graphics on.
+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="6" fill="#11131f"/>
<g fill="none" stroke="#5eb1ff" stroke-width="3">
<line x1="9" y1="16" x2="23" y2="16"/>
<circle cx="7" cy="16" r="4" fill="#5eb1ff"/>
<circle cx="25" cy="16" r="4" fill="#11131f"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 326 B

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 480" font-family="sans-serif">
<rect width="960" height="480" rx="12" fill="#11131f" stroke="#4f526b" stroke-dasharray="10 8"/>
<text x="480" y="230" text-anchor="middle" font-size="40" fill="#eceff9">your picture here</text>
<text x="480" y="280" text-anchor="middle" font-size="22" fill="#7d7f99">slides/images/</text>
</svg>

After

Width:  |  Height:  |  Size: 391 B

+143
View File
@@ -0,0 +1,143 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BGP hands-on: announce your own prefix</title>
<!--
Deck structure and look follow Matt Godbolt's newer talks (e.g.
github.com/mattgodbolt/what-happens-beneath-accu-2026): reveal.js with
no stock theme, a custom dark theme (theme.css), monokai code blocks,
16:9, fade transitions, and one Markdown file per section.
reveal.js is loaded from ./reveal.js/dist if present (run
./vendor-reveal.sh once; works offline), otherwise from the CDN. Its
stylesheets are inserted BEFORE ours by the loader below, so theme.css
and style.css always have the last word.
-->
<link rel="stylesheet" href="theme.css" id="deck-style" />
<link rel="stylesheet" href="style.css" />
<link rel="icon" href="images/favicon.svg" type="image/svg+xml" />
<link rel="shortcut icon" href="images/favicon.svg" />
</head>
<body>
<div class="reveal">
<div class="slides">
<!-- Title slide. For a full-bleed image instead, empty it and add
data-background="images/title.png" like Matt does. -->
<section class="title-slide">
<svg class="as-path" viewBox="0 0 1000 90" preserveAspectRatio="xMinYMid meet" aria-hidden="true">
<line x1="20" y1="30" x2="980" y2="30" />
<g class="you"><circle cx="20" cy="30" r="11" /><text x="4" y="72">you</text></g>
<g><circle cx="250" cy="30" r="11" /><text x="196" y="72">AS4200000001</text></g>
<g><circle cx="500" cy="30" r="11" /><text x="463" y="72">AS210036</text></g>
<g><circle cx="750" cy="30" r="11" /><text x="722" y="72">AS42476</text></g>
<g><circle cx="980" cy="30" r="11" /><text x="890" y="72">the Internet</text></g>
</svg>
<h1>Announce your own prefix</h1>
<h3>A hands-on introduction to BGP</h3>
<p class="byline">Rayhaan Jaufeerally &middot; Network Security &middot; ETH Z&uuml;rich, 2026</p>
</section>
<!--
One Markdown file per section. Inside a file:
----- next horizontal slide
--- next vertical slide (goes down, for detail you may skip)
Notes: everything after this is speaker notes (press S)
-->
<section data-markdown="sections/01-intro.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/02-bgp.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/03-lab.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/04-communities.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/05-wrap-up.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
</div>
</div>
<!-- Sits where Matt's decks carry the employer logo. -->
<div class="wordmark"><b>ferrix</b>_bgp &middot; netsec 2026</div>
<script type="module">
const VERSION = "6.0.2";
const LOCAL = "./reveal.js/dist";
const CDN = `https://cdn.jsdelivr.net/npm/reveal.js@${VERSION}/dist`;
// Prefer the vendored copy so the deck works without a network.
async function pickBase() {
try {
const r = await fetch(`${LOCAL}/reveal.css`, { method: "HEAD" });
if (r.ok) return LOCAL;
} catch (_) {}
return CDN;
}
const base = await pickBase();
const deckStyle = document.getElementById("deck-style");
await Promise.all(
["reset.css", "reveal.css", "plugin/highlight/monokai.css"].map(
(href) =>
new Promise((resolve) => {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = `${base}/${href}`;
link.onload = link.onerror = resolve;
deckStyle.before(link);
}),
),
);
const [{ default: Reveal }, { default: Markdown }, { default: Highlight }, { default: Notes }] =
await Promise.all(
["reveal.mjs", "plugin/markdown.mjs", "plugin/highlight.mjs", "plugin/notes.mjs"].map(
(m) => import(/* @vite-ignore */ `${base}/${m}`),
),
);
// BIRD configuration, so router config on slides is highlighted. Same
// trick Matt uses for the assembly dialects in his talks.
function bird() {
return {
name: "bird",
keywords: {
keyword:
"protocol template filter function define table if then else case return accept reject " +
"import export where none all limit action block neighbor local as direct multihop passive " +
"description ipv4 ipv6 bgp kernel device static pipe include router id",
built_in:
"net proto source dest bgp_path bgp_community bgp_large_community bgp_ext_community " +
"bgp_med bgp_local_pref bgp_next_hop",
literal: "true false on off RTD_UNREACHABLE RTS_BGP",
},
contains: [
{ className: "comment", begin: "#", end: "$" },
{ className: "string", begin: '"', end: '"' },
{ className: "number", begin: "\\b[0-9a-fA-F:]*::?[0-9a-fA-F:]*(/[0-9]+)?(\\{[0-9]+,[0-9]+\\})?\\b" },
{ className: "number", begin: "\\b[0-9]+(\\.[0-9]+){3}(/[0-9]+)?\\b" },
{ className: "number", begin: "\\b[0-9]+\\b" },
],
};
}
// ?accent=green|amber|magenta previews another accent; blue is the default.
const accent = new URLSearchParams(location.search).get("accent");
if (accent) document.documentElement.dataset.accent = accent;
// 16:9. Nudge scaleFactor to fit the projector, as Matt does.
const scaleFactor = 0.65;
const deck = new Reveal({
hash: true,
transition: "fade",
slideNumber: false,
margin: 0.15,
width: 1920 * scaleFactor,
height: 1080 * scaleFactor,
markdown: { smartypants: true },
highlight: {
beforeHighlight: (hljs) => hljs.registerLanguage("bird", bird),
},
plugins: [Markdown, Highlight, Notes],
});
await deck.initialize();
window.deck = deck; // handy in the console: deck.slide(3), deck.getTotalSlides()
</script>
</body>
</html>
+31
View File
@@ -0,0 +1,31 @@
## Today
* What BGP is, in ten minutes <!-- .element: class="fragment" -->
* You announce a real prefix to the real Internet <!-- .element: class="fragment" -->
* You steer where it goes with communities <!-- .element: class="fragment" -->
* We look at what could go wrong <!-- .element: class="fragment" -->
Notes:
Speaker notes go after a line that starts with "Notes:". Press S to open the
speaker view; it shows these, a timer, and the next slide.
-----
The Internet is about
<span class="fragment pop">75,000</span>
networks that agreed to talk to each other.
<!-- .element: class="big" -->
Notes:
One idea per slide, large. The class="big" comment applies to the paragraph
above it. "pop" is a fragment that grows when it appears.
A slide with class "bare" drops the panel and the path motif: <!-- .slide: class="bare" -->
-----
<img class="r-stretch blur-edges" src="images/placeholder.svg" alt="Replace with a picture">
Replace `images/placeholder.svg`. `r-stretch` makes an image fill the slide.
<!-- .element: class="attribution" -->
+53
View File
@@ -0,0 +1,53 @@
## An autonomous system
<div class="boxes">
<div class="us">AS210036<br>our upstream</div>
<div>AS559<br>SWITCH</div>
<div>AS6939<br>Hurricane Electric</div>
<div>AS13335<br>Cloudflare</div>
</div>
A network under one administration, with a number.
Notes:
"boxes" lays out small labelled cards. Classes: us, ok, bad.
-----
## A route is a prefix plus a promise
```text
2a0d:d741:1::/48 via AS210036 AS4200000001
^ nearest ^ origin
```
"Send me traffic for this prefix. It crossed these networks to reach you."
<!-- .element: class="note" -->
-----
## What actually goes on the wire
<div class="legend"><span class="hh">header</span><span class="hl">lengths</span><span class="ha">path attributes</span><span class="hn">NLRI</span></div>
<pre class="bytes"><span class="hh">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh">00 50</span> <span class="hh">02</span> <span class="hl">00 00</span> <span class="hl">00 35</span> <span class="ha">40 01 01 00</span> <span class="ha2">40 02 0a 02 02 00 00 fd e8 00 00 fd e8</span> <span class="ha">40 03 04 7f 00 00 01</span> <span class="ha2">80 04 04 00 00 00 32</span> <span class="ha">c0 08 04 fd e8 00 64</span> <span class="hn">18 0a 64 01</span></pre>
Same colours as the bytes view on the lab page, so you can point from one to the other.
<!-- .element: class="note" -->
---
## Going deeper (a vertical slide)
Press <kbd>↓</kbd>. Put detail here that you can skip when short of time.
| Attribute | Type | Meaning |
|---|---|---|
| ORIGIN | 1 | how the route entered BGP |
| AS_PATH | 2 | networks it crossed |
| COMMUNITIES | 8 | 32-bit tags, meaning by agreement |
| LARGE_COMMUNITIES | 32 | three 32-bit numbers |
<!-- .element: class="borders" -->
+61
View File
@@ -0,0 +1,61 @@
## The lab
<div class="cols">
<div>
* Open the status page
* Your prefix is `2a0d:d741:<n>::/48`
* `n` is your number, **in hex**
* Use your name as owner
</div>
<div>
```text
you ─▶ ferrix ─▶ AS210036 ─▶ SwissIX ─▶ …
AS42000… (BIRD)
```
</div>
</div>
Notes:
"cols" gives two equal columns. Leave a blank line after the opening div and
before the closing one, or the Markdown inside is not parsed.
-----
## The router's side
```bird [1-3|5-9|11]
filter ferrix_import {
if !(net ~ [ 2a0d:d741::/40{48,48} ]) then reject;
if bgp_path.first != 4200000001 then reject;
bgp_community.filter([ (65535, 65281), (65535, 65282) ]);
bgp_large_community.filter([ (42476, *, *), (210036, 0..3, *) ]);
bgp_ext_community.empty;
dest = RTD_UNREACHABLE;
accept;
}
```
`[1-3|5-9|11]` steps through the highlighted lines on each click.
<!-- .element: class="note" -->
-----
## Live
<iframe class="live" data-src="about:blank" data-preload width="1100" height="440"></iframe>
Point `data-src` at the status page to have it live on the slide.
<!-- .element: class="attribution" -->
Notes:
A live page on the slide is very much the Godbolt move. It needs the network,
and a page behind a password will prompt inside the frame. Have a screenshot
on a vertical slide below as the fallback.
+25
View File
@@ -0,0 +1,25 @@
## Communities
Tags on a route. They mean whatever the receiving network says they mean.
<div class="boxes">
<div class="ok">NO_EXPORT<br><code>65535:65281</code></div>
<div class="bad">BLACKHOLE<br><code>65535:666</code></div>
<div class="us">210036:0:0<br>announce nowhere</div>
<div>42476:1:559<br>SwissIX: only SWITCH</div>
</div>
-----
## Two hops, one idea
| You set | Who acts | Effect |
|---|---|---|
| `210036:0:0` `210036:1:42476` | our router | only the SwissIX route servers |
| `42476:0:0` `42476:1:559` | SwissIX | only SWITCH |
<!-- .element: class="borders" -->
Now find your prefix on a looking glass. Who can see it?
<!-- .element: class="fragment note" -->
+21
View File
@@ -0,0 +1,21 @@
## What could go wrong?
* Anyone can announce anything <!-- .element: class="fragment" -->
* Filters are the only thing between a typo and an outage <!-- .element: class="fragment" -->
* IRR objects and RPKI say who *may* announce a prefix <!-- .element: class="fragment" -->
-----
> "The Internet works because a lot of people are careful, most of the time."
Quotes use a plain Markdown blockquote.
<!-- .element: class="attribution" -->
-----
## Thank you
Questions?
<p class="note">Slides: <code>slides/</code> in the ferrix_bgp repository</p>
+177
View File
@@ -0,0 +1,177 @@
/*
* Components and helpers used by the slides. The look (palette, type, panels,
* motif) is in theme.css; nothing here sets a brand colour directly.
*/
.hidden {
display: none;
}
/* ---- text -------------------------------------------------------------------- */
/* A quiet aside under the main point. */
.reveal .note {
font-style: italic;
font-size: 75%;
color: var(--muted);
}
/* Image and quote credits: <!-- .element: class="attribution" --> */
.reveal .attribution {
font-size: 45%;
font-style: italic;
color: var(--muted);
}
/* One big statement on an otherwise empty slide. */
.reveal .big {
font-size: 1.9em;
font-weight: 300;
line-height: 1.25;
color: var(--ink-strong);
}
/* A fragment that grows when it appears, for the one thing to look at. It
grows by font size, not transform, so the text around it makes room. */
.reveal .pop {
transition: font-size 0.4s ease, color 0.4s ease;
}
.reveal .pop.visible {
font-size: 1.5em;
font-weight: 700;
color: var(--accent);
}
/* ---- layout ------------------------------------------------------------------ */
.container {
display: flex;
justify-content: center;
align-items: center;
gap: 1em;
}
.flex-around {
display: flex;
justify-content: space-around;
align-items: flex-start;
gap: 1em;
}
/* Two equal columns: <div class="cols"><div>…</div><div>…</div></div> */
.cols {
display: flex;
gap: 1.2em;
align-items: center;
text-align: left;
}
.cols > * {
flex: 1 1 0;
min-width: 0;
}
.float-left {
float: left;
}
.float-right {
float: right;
}
.no-border {
background: none !important;
border: none !important;
box-shadow: none !important;
}
.reveal img.blur-edges {
border-radius: 12px;
box-shadow: 0 0 25px 8px rgba(0, 0, 0, 0.5);
}
.reveal table.borders td,
.reveal table.borders th {
border: 1px solid var(--rule);
}
/* ---- boxes: small labelled cards, e.g. one per AS or per community ----------- */
.boxes {
display: flex;
flex-wrap: wrap;
justify-content: center;
font-size: 70%;
margin: 0.6em 0;
}
.boxes > div {
padding: 0.35em 0.8em;
margin: 0.3em 0.5em;
border: 1px solid var(--rule);
border-radius: 10px;
background: var(--ground);
line-height: 1.35;
}
.boxes > div.us {
border-color: var(--accent);
box-shadow: 0 0 18px rgba(var(--accent-rgb), 0.35);
color: var(--ink-strong);
}
.boxes > div.ok {
border-color: #4fd98a;
}
.boxes > div.bad {
border-color: #ff6b6b;
}
/* ---- BGP bytes, matching the four groups of the /statusz bytes view ---------- */
.reveal pre.bytes {
font-size: 0.5em;
line-height: 1.9;
white-space: pre-wrap;
word-break: break-all;
background: rgba(8, 9, 20, 0.65);
border-radius: 6px;
padding: 15px 20px;
box-sizing: border-box;
color: var(--ink);
}
.bytes span,
.legend span {
border-radius: 3px;
padding: 1px 3px;
}
.bytes .hh,
.legend .hh { background: #34406e; }
.bytes .hl,
.legend .hl { background: #6e3440; }
.bytes .ha,
.legend .ha { background: #245c3a; }
.bytes .ha2 { background: #2f7a4b; }
.bytes .hn,
.legend .hn { background: #6b5a1f; }
.legend {
font-size: 50%;
font-family: var(--r-code-font);
}
.legend span {
padding: 1px 0.6em;
margin: 0 0.2em;
}
/* A framed live page on a slide. */
.reveal iframe.live {
border: 1px solid var(--rule);
border-radius: 8px;
background: #fff;
}
+429
View File
@@ -0,0 +1,429 @@
/*
* The look of the deck. Structure borrowed from Matt Godbolt's newer talks
* (hrt-theme.css in github.com/mattgodbolt/what-happens-beneath-accu-2026):
* near-black navy ground, every slide on a rounded panel, IBM Plex Sans with
* thin white h1 and bold accent h2, IBM Plex Mono code, accent-coloured list
* markers, links and progress bar, glowing highlighted code lines.
*
* Made our own: the palette is ours (change it in :root below), and his
* "thread" motif becomes a chain of nodes and links, an AS path.
*
* Try accents without editing: index.html?accent=green (blue, green,
* amber, magenta). Make one permanent by copying its values into :root.
*/
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&family=IBM+Plex+Mono:ital,wght@0,400;0,500;1,400&display=swap");
:root {
--ground: #11131f; /* behind the panels */
--panel: #1b1e33; /* the slide itself */
--panel-edge: rgba(94, 177, 255, 0.14);
--ink: #eceff9; /* body text */
--ink-strong: #ffffff; /* h1, h3 */
--muted: #7d7f99; /* h4, captions */
--rule: #4f526b; /* table rules, borders */
--accent: #5eb1ff; /* h2, markers, links, motif */
--accent-soft: #b7dcff; /* inline code, emphasis */
--accent-rgb: 94, 177, 255;
--r-code-font: "IBM Plex Mono", "SF Mono", Menlo, Consolas, monospace;
--r-heading-text-shadow: none;
}
:root[data-accent="green"] {
--accent: #4fd98a;
--accent-soft: #b5f0cf;
--accent-rgb: 79, 217, 138;
--panel-edge: rgba(79, 217, 138, 0.14);
}
:root[data-accent="amber"] {
--accent: #ffb020;
--accent-soft: #ffdc99;
--accent-rgb: 255, 176, 32;
--panel-edge: rgba(255, 176, 32, 0.14);
}
:root[data-accent="magenta"] {
--accent: #ff6ac1;
--accent-soft: #ffc2e6;
--accent-rgb: 255, 106, 193;
--panel-edge: rgba(255, 106, 193, 0.14);
}
/* ---- ground and panels ---------------------------------------------------- */
.reveal-viewport {
background: var(--ground);
}
.reveal {
font-family: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif;
font-size: 40px;
font-weight: 400;
color: var(--ink);
}
.reveal .slides section:not(.stack):not(.title-slide):not(.bare) {
background-color: var(--panel);
border-radius: 12px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
border: 1px solid var(--panel-edge);
padding: 1em 0.8em;
box-sizing: border-box;
}
/* ---- type ------------------------------------------------------------------ */
.reveal h1,
.reveal h2,
.reveal h3,
.reveal h4 {
font-family: inherit;
text-transform: none;
text-shadow: none;
margin: 0 0 20px 0;
line-height: 1.25;
}
.reveal h1 {
font-weight: 300;
color: var(--ink-strong);
font-size: 2.2em;
line-height: 1.15;
}
.reveal h2 {
font-weight: 700;
color: var(--accent);
font-size: 1.5em;
}
.reveal h3 {
font-weight: 500;
color: var(--ink-strong);
font-size: 1.2em;
}
.reveal h4 {
font-weight: 500;
color: var(--muted);
font-size: 1em;
}
.reveal p {
margin: 20px 0;
line-height: 1.5;
}
.reveal strong,
.reveal b {
font-weight: 700;
color: var(--ink-strong);
}
.reveal em {
font-style: italic;
color: var(--accent-soft);
}
.reveal a {
color: var(--accent);
text-decoration: none;
transition: color 0.15s ease;
}
.reveal a:hover {
color: var(--accent-soft);
}
.reveal ul,
.reveal ol {
display: inline-block;
text-align: left;
margin: 0 0 0 1em;
}
.reveal ul {
list-style-type: square;
}
.reveal ul li,
.reveal ol li {
margin-bottom: 0.4em;
}
.reveal li::marker {
color: var(--accent);
}
.reveal blockquote {
background: var(--ground);
border-left: 4px solid var(--accent);
padding: 20px 30px;
font-style: italic;
width: 85%;
margin: 20px auto;
text-align: left;
}
.reveal img,
.reveal video,
.reveal iframe {
max-width: 95%;
max-height: 95%;
}
.reveal kbd {
font-family: var(--r-code-font);
font-size: 80%;
border: 1px solid var(--rule);
border-bottom-width: 3px;
border-radius: 5px;
padding: 0 0.35em;
}
/* ---- code ------------------------------------------------------------------ */
.reveal pre {
width: 100%;
margin: 20px auto;
box-shadow: none;
text-align: left;
position: relative;
font-size: 0.55em;
line-height: 1.4;
}
.reveal code {
font-family: var(--r-code-font);
color: var(--accent-soft);
}
.reveal :not(pre) > code {
white-space: nowrap;
font-size: 92%;
}
.reveal pre code {
display: block;
color: var(--ink);
background: rgba(8, 9, 20, 0.65);
border-radius: 6px;
padding: 15px 20px;
max-height: 560px;
overflow: auto;
}
/* The lines being talked about glow; the rest dim (reveal handles the dim). */
.reveal .hljs.has-highlights tr.highlight-line {
text-shadow:
0 0 6px currentColor,
0 0 12px currentColor;
transition: text-shadow 0.4s ease;
}
/* ---- tables ---------------------------------------------------------------- */
.reveal table {
border-collapse: collapse;
border-spacing: 0;
margin: 20px auto;
font-size: 75%;
}
.reveal table th {
font-weight: 700;
color: var(--ink-strong);
}
.reveal table th,
.reveal table td {
text-align: left;
border-bottom: 1px solid var(--rule);
padding: 0.25em 0.6em;
}
.reveal table tr:last-child td {
border-bottom: none;
}
/* ---- reveal chrome ---------------------------------------------------------- */
.reveal .controls {
color: var(--accent);
bottom: 40px;
}
.reveal .progress {
background: var(--panel);
height: 4px;
}
.reveal .progress span {
background: var(--accent);
transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
}
.reveal .slide-number {
color: var(--ground);
font-family: var(--r-code-font);
font-size: 18px;
font-weight: 500;
background: rgba(var(--accent-rgb), 0.9);
padding: 3px 9px;
border-radius: 4px;
right: 16px;
bottom: 16px;
}
::selection {
color: var(--ground);
background: var(--accent);
}
/* ---- the path motif ---------------------------------------------------------
* Matt's decks carry a ring with a line running off the edge. Ours is a short
* AS path: nodes joined by links, heading off the top-right corner, mirrored
* running up from the bottom-left. Drawn in the accent colour via a mask, so
* it follows --accent. Hidden on the title slide and on "bare" slides.
*/
.reveal::before,
.reveal-viewport::before {
content: "";
position: fixed;
background: var(--accent);
z-index: 0;
pointer-events: none;
opacity: 0.9;
}
.reveal::before {
top: 26px;
right: 0;
width: 38vw;
height: 22px;
-webkit-mask:
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 190 22'><g fill='none' stroke='black' stroke-width='3'><circle cx='11' cy='11' r='8'/><line x1='19' y1='11' x2='67' y2='11'/><circle cx='75' cy='11' r='8'/><line x1='83' y1='11' x2='131' y2='11'/><circle cx='139' cy='11' r='8'/><line x1='147' y1='11' x2='190' y2='11'/></g></svg>")
left center / 190px 22px no-repeat,
linear-gradient(black, black) 189px center / calc(100% - 189px) 3px no-repeat;
mask:
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 190 22'><g fill='none' stroke='black' stroke-width='3'><circle cx='11' cy='11' r='8'/><line x1='19' y1='11' x2='67' y2='11'/><circle cx='75' cy='11' r='8'/><line x1='83' y1='11' x2='131' y2='11'/><circle cx='139' cy='11' r='8'/><line x1='147' y1='11' x2='190' y2='11'/></g></svg>")
left center / 190px 22px no-repeat,
linear-gradient(black, black) 189px center / calc(100% - 189px) 3px no-repeat;
}
.reveal-viewport::before {
bottom: 64px;
left: 40px;
width: 22px;
height: 36vh;
-webkit-mask:
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 126'><g fill='none' stroke='black' stroke-width='3'><circle cx='11' cy='115' r='8'/><line x1='11' y1='107' x2='11' y2='59'/><circle cx='11' cy='51' r='8'/><line x1='11' y1='43' x2='11' y2='0'/></g></svg>")
center bottom / 22px 126px no-repeat,
linear-gradient(black, black) center top / 3px calc(100% - 125px) no-repeat;
mask:
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 126'><g fill='none' stroke='black' stroke-width='3'><circle cx='11' cy='115' r='8'/><line x1='11' y1='107' x2='11' y2='59'/><circle cx='11' cy='51' r='8'/><line x1='11' y1='43' x2='11' y2='0'/></g></svg>")
center bottom / 22px 126px no-repeat,
linear-gradient(black, black) center top / 3px calc(100% - 125px) no-repeat;
}
.reveal:has(> .slides > section.title-slide.present)::before,
.reveal:has(> .slides section.bare.present)::before {
display: none;
}
.reveal-viewport:has(.reveal > .slides > section.title-slide.present)::before,
.reveal-viewport:has(.reveal > .slides section.bare.present)::before {
display: none;
}
.reveal:has(section.title-slide.present) .controls {
display: none !important;
}
/* ---- wordmark, where his logo sits ------------------------------------------ */
.wordmark {
position: fixed;
left: 22px;
bottom: 18px;
z-index: 10;
font-family: var(--r-code-font);
font-size: 15px;
letter-spacing: 0.04em;
color: var(--muted);
pointer-events: none;
}
.wordmark b {
color: var(--accent);
font-weight: 500;
}
.reveal:has(section.title-slide.present) ~ .wordmark,
.reveal:has(section.bare.present) ~ .wordmark {
display: none;
}
/* ---- title slide -------------------------------------------------------------- */
.reveal section.title-slide {
text-align: left;
padding: 0 1.5em;
}
.reveal section.title-slide h1 {
font-size: 2.6em;
margin-bottom: 0.15em;
}
.reveal section.title-slide h3 {
font-weight: 400;
color: var(--accent);
margin-bottom: 1.6em;
}
.reveal section.title-slide .byline {
font-size: 60%;
color: var(--muted);
margin: 0;
}
.reveal section.title-slide .as-path {
display: block;
width: 100%;
max-width: none;
height: 90px;
margin: 0 0 1em;
}
.as-path circle {
fill: var(--ground);
stroke: var(--accent);
stroke-width: 3;
}
.as-path line {
stroke: var(--accent);
stroke-width: 3;
}
.as-path text {
font-family: "IBM Plex Mono", monospace;
font-size: 15px;
fill: var(--muted);
}
.as-path .you circle {
fill: var(--accent);
}
.as-path .you text {
fill: var(--ink-strong);
}
/* ---- print ------------------------------------------------------------------ */
html.print-pdf .wordmark,
html.reveal-print .wordmark {
display: none;
}
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
# Copies reveal.js into ./reveal.js so the deck works with no network.
# index.html uses this copy when it exists and the CDN otherwise. Keep VERSION
# in step with the one in index.html.
set -eu
VERSION=6.0.2
cd "$(dirname "$0")"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
npm pack "reveal.js@$VERSION" --pack-destination "$tmp" --silent >/dev/null 2>&1
tar -xzf "$tmp/reveal.js-$VERSION.tgz" -C "$tmp"
rm -rf reveal.js
mkdir -p reveal.js
cp -R "$tmp/package/dist" reveal.js/dist
cp "$tmp/package/LICENSE" reveal.js/LICENSE
echo "reveal.js $VERSION vendored into $(pwd)/reveal.js ($(du -sh reveal.js | cut -f1))"