oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1{{/* present — GET /p/{session}, the fullscreen surface, and the
2 audience's follow view (GET /s/{token}?follow=1), which is the
3 same page with nothing to press.
4
5 One template for both because they ARE the same screen: a slide
6 filling the viewport, where in the deck it is, and a way to move —
7 forms for whoever holds the remote, links for whoever is only
8 watching, nothing at all for an audience following along.
9
10 Nothing here needs the script. With it, static/present.js swaps
11 the slide when the session moves; without it, the follow view has
12 a meta refresh and the presenter has next and previous. */}}
13{{define "page-head"}}<link rel="stylesheet" href="{{asset "static/slides.css"}}">
14{{if .Refresh}}{{/* The scriptless heartbeat: an audience with no
15 JavaScript still moves with the presenter, five seconds behind.
16 Only on the follow view — a refresh on the presenter's own screen
17 would blink the projector mid-sentence. */}}
18<meta http-equiv="refresh" content="5">{{end}}
19<script defer src="{{asset "static/present.js"}}"></script>
20<style>
21 /* The projector is a bounded black stage, never a document that can
22 push its remote controls underneath the fold. The controls live on
23 the stage so the slide can use every pixel behind them. */
24 [slides-surface] {
25 background: #000;
26 block-size: 100dvh;
27 box-sizing: border-box;
28 display: flex;
29 inline-size: 100%;
30 overflow: hidden;
31 position: relative;
32 }
33 /* The projector is its own full-screen stage, not a document inside
34 the ordinary constrained rst-page wrapper. Its capability is what
35 makes it safe to omit the app bar and account menu. */
36 [slides-present-stage] {
37 box-sizing: border-box;
38 inline-size: 100%;
39 max-inline-size: none;
40 min-block-size: 100dvh;
41 padding: 0;
42 }
43 [slides-stage] {
44 align-items: center;
45 display: flex;
46 flex: 1 1 auto;
47 inline-size: 100%;
48 justify-content: center;
49 margin: 0;
50 min-block-size: 0;
51 min-inline-size: 0;
52 }
53 [slides-stage] > [slides-slide] {
54 block-size: min(100dvh, calc(100vw * 9 / 16));
55 inline-size: min(100vw, calc(100dvh * 16 / 9));
56 max-block-size: 100%;
57 max-inline-size: 100%;
58 }
59 [slides-bar] {
60 align-items: center;
61 background: color-mix(in srgb, #000 72%, transparent);
62 bottom: 0;
63 display: flex;
64 flex-wrap: wrap;
65 gap: var(--rst-sp-3);
66 inset-inline: 0;
67 justify-content: center;
68 padding: var(--rst-sp-3);
69 position: absolute;
70 }
71 [slides-bar] form {
72 display: inline;
73 margin: 0;
74 }
75 [slides-counter] {
76 color: var(--rst-text-muted);
77 margin: 0;
78 }
79 [slides-progress] {
80 block-size: var(--rst-sp-1);
81 inline-size: min(100%, 24rem);
82 }
83 /* Blacking out hides the slide and says so in words. Never colour
84 alone: the sentence below the stage is what tells somebody using a
85 screen reader — or looking at a projector that is simply off —
86 that this is deliberate. */
87 [slides-surface][data-blackout="true"] [slides-stage] {
88 visibility: hidden;
89 }
90 [slides-blackout] {
91 align-self: center;
92 color: #fff;
93 font-weight: 600;
94 margin: 0;
95 position: absolute;
96 text-align: center;
97 z-index: 1;
98 }
99</style>{{end}}
100{{define "main"}}<main id="main" slides-present-stage>{{template "content" .}}</main>{{end}}
101{{define "content"}}
102{{/* The two URLs the script reads come DOWN rather than being built up
103 there: which capability this surface reads under is the handler's
104 decision (presentation.go's surfaceOptions), and the audience's
105 follow view deliberately carries no session sqid at all. */}}
106<div slides-surface
107 {{- if .Session}} data-session="{{.Session}}"{{end}}
108 {{- if .StreamHref}} data-stream="{{.StreamHref}}"{{end}}
109 {{- if .FragmentHref}} data-fragment="{{.FragmentHref}}"{{end}}
110 data-slide="{{.Now}}"
111 data-blackout="{{if .Blackout}}true{{else}}false{{end}}"
112 {{- if .Token}} data-remote-token="{{.Token}}"{{end}}
113 {{- if .NotesHref}} data-notes="{{.NotesHref}}"{{end}}>
114{{if .HasSlide}}
115 <div slides-stage>{{.Slide}}</div>
116 <p slides-blackout{{if not .Blackout}} hidden{{end}}>{{.BlackoutLabel}}</p>
117 <div slides-bar>
118 {{if .Controls}}{{template "present-controls" .Controls}}{{end}}
119 {{if .PrevHref}}<a rst-btn href="{{.PrevHref}}" rel="prev">{{.PrevLabel}}</a>{{end}}
120 {{if .NextHref}}<a rst-btn href="{{.NextHref}}" rel="next">{{.NextLabel}}</a>{{end}}
121 {{/* "3 / 12" alone is terse read aloud, so the counter carries what
122 the number means in a line only a screen reader gets. The bar
123 beside it is the same fact as a picture, and is hidden from
124 assistive tech rather than announced twice. */}}
125 <p slides-counter><span class="rst-sr-only">{{.ProgressLabel}}</span> {{.Counter}}</p>
126 <progress slides-progress value="{{.Now}}" max="{{.Total}}" aria-hidden="true"></progress>
127 {{if .NotesHref}}<a rst-btn href="{{.NotesHref}}">{{.NotesLabel}}</a>{{end}}
128 {{if .BackHref}}<a rst-btn href="{{.BackHref}}">{{.BackLabel}}</a>{{end}}
129 </div>
130{{else}}
131 <div rst-box>
132 {{template "empty-state" dict "Title" .EmptyTitle "Body" .EmptyBody}}
133 </div>
134{{end}}
135</div>
136{{end}}
137