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:
1 parent
e35507d082
commit
106edd1df0
13 files changed
+1051
No files matched your search
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
__pycache__/
|
||||
slides/reveal.js/
|
||||
@@ -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.
|
||||
@@ -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 |
@@ -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 |
@@ -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 · Network Security · ETH Zü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 · 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>
|
||||
@@ -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" -->
|
||||
@@ -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" -->
|
||||
@@ -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.
|
||||
@@ -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" -->
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Executable
+16
@@ -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))"
|
||||
Reference in new issue
Block a user