fb-slides 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/runtime/annotate.js +166 -30
- package/runtime/deck.js +4 -2
- package/runtime/pointer.js +249 -0
- package/runtime/theme.base.css +143 -4
package/README.md
CHANGED
|
@@ -129,6 +129,8 @@ Note: a bad description is the most common reason a tool never gets used.
|
|
|
129
129
|
| --- | --- |
|
|
130
130
|
| `→` / `Space` | next step (fragments included) |
|
|
131
131
|
| `P` | the pen — draw on the slide, the ink fades on its own |
|
|
132
|
+
| `A` | the arrow — click the tail, then the tip; same colours as the pen |
|
|
133
|
+
| `C` | the pointer — replaces the cursor: a glowing halo, a dart aimed at the centre, or the normal mouse |
|
|
132
134
|
| `Shift` (tap) | the spotlight — drag a rectangle, the rest of the slide dims and blurs |
|
|
133
135
|
| `S` | speaker view — notes, timer, next slide |
|
|
134
136
|
| `Esc` / `O` | overview — the slides as a grid |
|
package/package.json
CHANGED
package/runtime/annotate.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
// whole feature — its chrome, its canvas and its key bindings — and index.html
|
|
6
6
|
// stays a page of slides. Register it in the `plugins:` array like any other.
|
|
7
7
|
//
|
|
8
|
+
// Two tools share everything here: the pen, and the arrow — click where it
|
|
9
|
+
// starts, click (or release a drag) where it points. Same canvas, same
|
|
10
|
+
// palette, same hold-then-fade; an arrow is just one more kind of stroke.
|
|
11
|
+
//
|
|
8
12
|
// The ink goes on one full-viewport canvas in screen pixels, which keeps it
|
|
9
13
|
// clear of the CSS transform reveal scales a slide with: the pen follows the
|
|
10
14
|
// pointer, not the slide's coordinate space.
|
|
@@ -46,6 +50,14 @@ const TENSION = 1 / 6;
|
|
|
46
50
|
|
|
47
51
|
const WIDTH = 3.4; // px, the bright core of the line
|
|
48
52
|
|
|
53
|
+
// The arrow's head: how far back along the shaft the wings reach, and how far
|
|
54
|
+
// they open off it. A short arrow shrinks its head rather than being all head.
|
|
55
|
+
const HEAD_LEN = 56;
|
|
56
|
+
const HEAD_SPREAD = 0.55; // radians
|
|
57
|
+
// Under this, a press-and-release is a click placing the tail, not a whole
|
|
58
|
+
// arrow: the tip then follows the pointer until the second click.
|
|
59
|
+
const MIN_ARROW = 12;
|
|
60
|
+
|
|
49
61
|
// Two layers, each the same path stroked at a few widths: a soft halo, then the
|
|
50
62
|
// core on top of it. Within a layer the passes stack additively, hence the low
|
|
51
63
|
// alphas. Cheaper than a shadowBlur per segment.
|
|
@@ -64,8 +76,10 @@ const QUANTA = 24;
|
|
|
64
76
|
// `P` is reveal's own previous-slide key, and a plugin binding wins over it —
|
|
65
77
|
// which is the point: on stage the pen is reached for far more often than a key
|
|
66
78
|
// ← and Shift+Space already do. Reveal's own row in the help overlay still
|
|
67
|
-
// lists it, though, so it has to be corrected.
|
|
68
|
-
|
|
79
|
+
// lists it, though, so it has to be corrected. `A` is reveal's auto-slide
|
|
80
|
+
// toggle, idle in a deck that does not auto-advance — the arrow takes it.
|
|
81
|
+
const PEN_KEY_CODE = 80;
|
|
82
|
+
const ARROW_KEY_CODE = 65;
|
|
69
83
|
const HELP_TAKEN_FROM = { key: 'P', from: /previous/i };
|
|
70
84
|
|
|
71
85
|
const PEN_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
|
@@ -73,6 +87,11 @@ const PEN_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" str
|
|
|
73
87
|
<path d="M15.2 4.6l4.2 4.2" /><path d="M4 20l4.7-1L19.8 7.9a2 2 0 0 0-2.8-2.8L5.9 16.3 4 20z" />
|
|
74
88
|
</svg>`;
|
|
75
89
|
|
|
90
|
+
const ARROW_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
|
91
|
+
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
92
|
+
<path d="M5 19L19 5" /><path d="M9.5 5H19v9.5" />
|
|
93
|
+
</svg>`;
|
|
94
|
+
|
|
76
95
|
// Black text on a light chip, light text on a dark one — the armed pen button
|
|
77
96
|
// wears the ink's own colour, and two of the six are the extremes.
|
|
78
97
|
const readable = (hex) => {
|
|
@@ -126,7 +145,9 @@ const RevealAnnotate = () => ({
|
|
|
126
145
|
toolbar.innerHTML = `
|
|
127
146
|
<button id="tool-pen" class="deck-tool" type="button" aria-pressed="false"
|
|
128
147
|
title="Pen — draw on the slide (P)" aria-label="Pen">${PEN_ICON}</button>
|
|
129
|
-
<
|
|
148
|
+
<button id="tool-arrow" class="deck-tool" type="button" aria-pressed="false"
|
|
149
|
+
title="Arrow — click the tail, then the tip (A)" aria-label="Arrow">${ARROW_ICON}</button>
|
|
150
|
+
<div id="deck-swatches" role="radiogroup" aria-label="Ink colour" hidden>
|
|
130
151
|
${COLOURS.map(
|
|
131
152
|
(colour, i) => `<button class="deck-swatch" type="button" role="radio"
|
|
132
153
|
aria-checked="${i === 0}" data-colour="${i}" style="--ink:${colour.glow}"
|
|
@@ -137,6 +158,7 @@ const RevealAnnotate = () => ({
|
|
|
137
158
|
|
|
138
159
|
document.body.append(canvas, toolbar);
|
|
139
160
|
const penButton = toolbar.querySelector('#tool-pen');
|
|
161
|
+
const arrowButton = toolbar.querySelector('#tool-arrow');
|
|
140
162
|
const swatches = toolbar.querySelector('#deck-swatches');
|
|
141
163
|
const ctx = canvas.getContext('2d');
|
|
142
164
|
|
|
@@ -208,9 +230,12 @@ const RevealAnnotate = () => ({
|
|
|
208
230
|
// ---- strokes --------------------------------------------------------
|
|
209
231
|
// A stroke is its points, the colour it was drawn in, and the moment the
|
|
210
232
|
// pointer left the slide: `releasedAt: null` is one still under the pen.
|
|
233
|
+
// An arrow rides the same list with `kind: 'arrow'` and two points, `from`
|
|
234
|
+
// and `to` — it holds and fades exactly like a line of ink.
|
|
211
235
|
|
|
212
236
|
const strokes = [];
|
|
213
|
-
let drawing = null;
|
|
237
|
+
let drawing = null; // the pen's live stroke, or null
|
|
238
|
+
let placing = null; // an arrow whose tip still follows the pointer, or null
|
|
214
239
|
let frame = 0;
|
|
215
240
|
let holdTimer = 0;
|
|
216
241
|
|
|
@@ -229,8 +254,9 @@ const RevealAnnotate = () => ({
|
|
|
229
254
|
|
|
230
255
|
// The slab of canvas a stroke can reach, clamped to the viewport. Padded by
|
|
231
256
|
// half the widest pass, plus a little for the curve's overshoot between
|
|
232
|
-
// points
|
|
233
|
-
|
|
257
|
+
// points; `extra` widens it further for shapes whose paint reaches past
|
|
258
|
+
// the points themselves.
|
|
259
|
+
const boundsOf = (points, extra = 0) => {
|
|
234
260
|
let minX = Infinity;
|
|
235
261
|
let minY = Infinity;
|
|
236
262
|
let maxX = -Infinity;
|
|
@@ -241,7 +267,7 @@ const RevealAnnotate = () => ({
|
|
|
241
267
|
if (x > maxX) maxX = x;
|
|
242
268
|
if (y > maxY) maxY = y;
|
|
243
269
|
}
|
|
244
|
-
const pad = MAX_WIDTH / 2 + 3;
|
|
270
|
+
const pad = MAX_WIDTH / 2 + 3 + extra;
|
|
245
271
|
const x = Math.max(0, Math.floor(minX - pad));
|
|
246
272
|
const y = Math.max(0, Math.floor(minY - pad));
|
|
247
273
|
return {
|
|
@@ -285,6 +311,58 @@ const RevealAnnotate = () => ({
|
|
|
285
311
|
return runs;
|
|
286
312
|
};
|
|
287
313
|
|
|
314
|
+
// An arrow has no oldest end worth erasing first: it fades out whole, the
|
|
315
|
+
// shaft and its head together. The shaft ends where the head begins, so
|
|
316
|
+
// the glow's rounded cap never pokes past the tip.
|
|
317
|
+
const paintArrow = (stroke, progress) => {
|
|
318
|
+
const { from, to } = stroke;
|
|
319
|
+
const length = Math.hypot(to.x - from.x, to.y - from.y);
|
|
320
|
+
if (length < 1) return true;
|
|
321
|
+
|
|
322
|
+
const fade = progress === null ? 1 : 1 - progress;
|
|
323
|
+
const angle = Math.atan2(to.y - from.y, to.x - from.x);
|
|
324
|
+
const head = Math.min(HEAD_LEN, length * 0.6);
|
|
325
|
+
const wings = [HEAD_SPREAD, -HEAD_SPREAD].map((spread) => ({
|
|
326
|
+
x: to.x - head * Math.cos(angle + spread),
|
|
327
|
+
y: to.y - head * Math.sin(angle + spread),
|
|
328
|
+
}));
|
|
329
|
+
const base = {
|
|
330
|
+
x: to.x - head * 0.75 * Math.cos(angle),
|
|
331
|
+
y: to.y - head * 0.75 * Math.sin(angle),
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// The wings go into the bounds along with the line's own ends: an
|
|
335
|
+
// axis-aligned arrow has a razor-thin from-to box, and the head reaches
|
|
336
|
+
// further out of it sideways than the stroke padding covers. The extra
|
|
337
|
+
// head-width of pad keeps the glow well clear of the clip on every side.
|
|
338
|
+
const box = boundsOf([from, to, ...wings], head);
|
|
339
|
+
if (box.w <= 0 || box.h <= 0) return true;
|
|
340
|
+
|
|
341
|
+
for (const { ink, passes } of LAYERS) {
|
|
342
|
+
onLayer(box, stroke.ink[ink], (c) => {
|
|
343
|
+
c.lineCap = 'round';
|
|
344
|
+
for (const pass of passes) {
|
|
345
|
+
c.lineWidth = WIDTH * pass.width;
|
|
346
|
+
c.strokeStyle = c.fillStyle = `rgba(255, 255, 255, ${fade * pass.alpha})`;
|
|
347
|
+
c.beginPath();
|
|
348
|
+
c.moveTo(from.x, from.y);
|
|
349
|
+
c.lineTo(base.x, base.y);
|
|
350
|
+
c.stroke();
|
|
351
|
+
// The head is filled for its body and stroked for its glow: the
|
|
352
|
+
// stroke fattens it by the same width the shaft wears.
|
|
353
|
+
c.beginPath();
|
|
354
|
+
c.moveTo(to.x, to.y);
|
|
355
|
+
c.lineTo(wings[0].x, wings[0].y);
|
|
356
|
+
c.lineTo(wings[1].x, wings[1].y);
|
|
357
|
+
c.closePath();
|
|
358
|
+
c.fill();
|
|
359
|
+
c.stroke();
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return true;
|
|
364
|
+
};
|
|
365
|
+
|
|
288
366
|
// Returns false once the stroke has faded out and can be dropped.
|
|
289
367
|
const paint = (stroke, now) => {
|
|
290
368
|
// Clamped at 0 through the hold: a stroke waiting its turn paints exactly
|
|
@@ -295,6 +373,8 @@ const RevealAnnotate = () => ({
|
|
|
295
373
|
: Math.max(0, (now - stroke.releasedAt - HOLD_MS) / FADE_MS);
|
|
296
374
|
if (progress !== null && progress >= 1) return false;
|
|
297
375
|
|
|
376
|
+
if (stroke.kind === 'arrow') return paintArrow(stroke, progress);
|
|
377
|
+
|
|
298
378
|
const box = boundsOf(stroke.points);
|
|
299
379
|
if (box.w <= 0 || box.h <= 0) return true;
|
|
300
380
|
|
|
@@ -360,16 +440,18 @@ const RevealAnnotate = () => ({
|
|
|
360
440
|
|
|
361
441
|
const clear = () => {
|
|
362
442
|
drawing = null;
|
|
443
|
+
placing = null;
|
|
363
444
|
strokes.length = 0;
|
|
364
445
|
clearTimeout(holdTimer);
|
|
365
446
|
holdTimer = 0;
|
|
366
447
|
ctx.clearRect(0, 0, width, height);
|
|
367
448
|
};
|
|
368
449
|
|
|
369
|
-
// ---- the
|
|
450
|
+
// ---- the tools ------------------------------------------------------
|
|
370
451
|
|
|
371
|
-
let
|
|
452
|
+
let tool = null; // null, 'pen' or 'arrow'
|
|
372
453
|
let ink = COLOURS[0];
|
|
454
|
+
const toolButtons = { pen: penButton, arrow: arrowButton };
|
|
373
455
|
|
|
374
456
|
const setInk = (colour) => {
|
|
375
457
|
ink = colour;
|
|
@@ -381,29 +463,60 @@ const RevealAnnotate = () => ({
|
|
|
381
463
|
}
|
|
382
464
|
};
|
|
383
465
|
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
466
|
+
const setTool = (next) => {
|
|
467
|
+
if (next === tool) return;
|
|
468
|
+
// Whatever was mid-gesture under the old tool does not survive the
|
|
469
|
+
// switch: a half-drawn line is released, a half-aimed arrow discarded.
|
|
470
|
+
if (drawing) {
|
|
471
|
+
drawing.releasedAt = performance.now();
|
|
472
|
+
drawing = null;
|
|
473
|
+
}
|
|
474
|
+
if (placing) {
|
|
475
|
+
strokes.splice(strokes.indexOf(placing), 1);
|
|
476
|
+
placing = null;
|
|
477
|
+
schedule();
|
|
478
|
+
}
|
|
479
|
+
tool = next;
|
|
480
|
+
for (const [name, button] of Object.entries(toolButtons)) {
|
|
481
|
+
button.setAttribute('aria-pressed', String(name === tool));
|
|
482
|
+
}
|
|
483
|
+
toolbar.classList.toggle('is-armed', tool !== null);
|
|
484
|
+
// The colours are only worth showing while there is a tool to apply them
|
|
485
|
+
// to, which makes arming one the gesture that opens them.
|
|
486
|
+
swatches.hidden = tool === null;
|
|
391
487
|
// Only an armed canvas takes the pointer; the rest of the time clicks
|
|
392
488
|
// fall through to the slide underneath.
|
|
393
|
-
canvas.classList.toggle('is-live',
|
|
394
|
-
if (
|
|
489
|
+
canvas.classList.toggle('is-live', tool !== null);
|
|
490
|
+
if (tool) document.dispatchEvent(new CustomEvent('deck:tool-armed', { detail: tool }));
|
|
395
491
|
else clear();
|
|
396
492
|
};
|
|
493
|
+
const toggleTool = (name) => setTool(tool === name ? null : name);
|
|
397
494
|
|
|
398
|
-
//
|
|
495
|
+
// Several tools, one pointer: arming any disarms the rest. The handshake
|
|
399
496
|
// is a DOM event rather than an import either way round, so each plugin
|
|
400
|
-
// works alone.
|
|
497
|
+
// works alone. Pen and arrow are both this plugin's, so only a foreign
|
|
498
|
+
// detail stands it down.
|
|
401
499
|
document.addEventListener('deck:tool-armed', (event) => {
|
|
402
|
-
if (event.detail !== 'pen' &&
|
|
500
|
+
if (event.detail !== 'pen' && event.detail !== 'arrow' && tool) setTool(null);
|
|
403
501
|
});
|
|
404
502
|
|
|
405
503
|
canvas.addEventListener('pointerdown', (event) => {
|
|
406
|
-
if (!
|
|
504
|
+
if (!tool || event.button !== 0) return;
|
|
505
|
+
if (tool === 'arrow') {
|
|
506
|
+
// Second click: the tip lands here. First click: the tail does, and
|
|
507
|
+
// the tip follows the pointer until the next one.
|
|
508
|
+
if (placing) {
|
|
509
|
+
placing.to = { x: event.clientX, y: event.clientY };
|
|
510
|
+
placing.releasedAt = performance.now();
|
|
511
|
+
placing = null;
|
|
512
|
+
} else {
|
|
513
|
+
const at = { x: event.clientX, y: event.clientY };
|
|
514
|
+
placing = { kind: 'arrow', from: at, to: { ...at }, releasedAt: null, ink };
|
|
515
|
+
strokes.push(placing);
|
|
516
|
+
}
|
|
517
|
+
schedule();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
407
520
|
canvas.setPointerCapture(event.pointerId);
|
|
408
521
|
// The colour is caught at the down-stroke, so a line already fading keeps
|
|
409
522
|
// the one it was drawn in when you pick another.
|
|
@@ -414,6 +527,12 @@ const RevealAnnotate = () => ({
|
|
|
414
527
|
});
|
|
415
528
|
|
|
416
529
|
canvas.addEventListener('pointermove', (event) => {
|
|
530
|
+
// An arrow being aimed follows the bare pointer — no button held.
|
|
531
|
+
if (placing) {
|
|
532
|
+
placing.to = { x: event.clientX, y: event.clientY };
|
|
533
|
+
schedule();
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
417
536
|
if (!drawing || event.pointerId !== drawing.id) return;
|
|
418
537
|
// A trackpad reports faster than the display refreshes, and the samples
|
|
419
538
|
// in between are what keeps a quick curve smooth instead of faceted.
|
|
@@ -425,6 +544,19 @@ const RevealAnnotate = () => ({
|
|
|
425
544
|
});
|
|
426
545
|
|
|
427
546
|
const release = (event) => {
|
|
547
|
+
// A drag long enough to be deliberate is the whole gesture: down at the
|
|
548
|
+
// tail, up at the tip. Anything shorter was a click, and the arrow stays
|
|
549
|
+
// under the pointer waiting for the second one.
|
|
550
|
+
if (placing) {
|
|
551
|
+
const span = Math.hypot(event.clientX - placing.from.x, event.clientY - placing.from.y);
|
|
552
|
+
if (span >= MIN_ARROW) {
|
|
553
|
+
placing.to = { x: event.clientX, y: event.clientY };
|
|
554
|
+
placing.releasedAt = performance.now();
|
|
555
|
+
placing = null;
|
|
556
|
+
schedule();
|
|
557
|
+
}
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
428
560
|
if (!drawing || event.pointerId !== drawing.id) return;
|
|
429
561
|
drawing.releasedAt = performance.now();
|
|
430
562
|
drawing = null;
|
|
@@ -433,7 +565,8 @@ const RevealAnnotate = () => ({
|
|
|
433
565
|
canvas.addEventListener('pointerup', release);
|
|
434
566
|
canvas.addEventListener('pointercancel', release);
|
|
435
567
|
|
|
436
|
-
penButton.addEventListener('click', () =>
|
|
568
|
+
penButton.addEventListener('click', () => toggleTool('pen'));
|
|
569
|
+
arrowButton.addEventListener('click', () => toggleTool('arrow'));
|
|
437
570
|
swatches.addEventListener('click', (event) => {
|
|
438
571
|
const button = event.target.closest('.deck-swatch');
|
|
439
572
|
if (button) setInk(COLOURS[button.dataset.colour]);
|
|
@@ -441,19 +574,22 @@ const RevealAnnotate = () => ({
|
|
|
441
574
|
|
|
442
575
|
// `addKeyBinding` with a descriptor binds the key and lists it in reveal's
|
|
443
576
|
// own help overlay, which `registerKeyboardShortcut` alone would not do.
|
|
444
|
-
deck.addKeyBinding({ keyCode:
|
|
445
|
-
|
|
577
|
+
deck.addKeyBinding({ keyCode: PEN_KEY_CODE, key: 'P', description: 'Toggle the pen' }, () =>
|
|
578
|
+
toggleTool('pen'),
|
|
579
|
+
);
|
|
580
|
+
deck.addKeyBinding({ keyCode: ARROW_KEY_CODE, key: 'A', description: 'Toggle the arrow' }, () =>
|
|
581
|
+
toggleTool('arrow'),
|
|
446
582
|
);
|
|
447
|
-
// Keys the
|
|
583
|
+
// Keys the tools borrow only while one is armed, taken in capture phase so
|
|
448
584
|
// reveal's own bindings never see them: Escape (otherwise the overview
|
|
449
|
-
// toggle) disarms it, and the number row picks
|
|
585
|
+
// toggle) disarms it, and the number row picks the ink — 1 to 6, in the
|
|
450
586
|
// order the swatches sit in the toolbar. The rest of the time both fall
|
|
451
587
|
// through untouched.
|
|
452
588
|
document.addEventListener(
|
|
453
589
|
'keydown',
|
|
454
590
|
(event) => {
|
|
455
|
-
if (!
|
|
456
|
-
if (event.key === 'Escape')
|
|
591
|
+
if (!tool || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
592
|
+
if (event.key === 'Escape') setTool(null);
|
|
457
593
|
else {
|
|
458
594
|
const index = '123456'.indexOf(event.key);
|
|
459
595
|
if (index === -1 || index >= COLOURS.length) return;
|
|
@@ -471,7 +607,7 @@ const RevealAnnotate = () => ({
|
|
|
471
607
|
// A new slide is a clean sheet — including a line still under the pointer.
|
|
472
608
|
deck.on('slidechanged', clear);
|
|
473
609
|
// The overview is a different surface; drawing over it would annotate nothing.
|
|
474
|
-
deck.on('overviewshown', () =>
|
|
610
|
+
deck.on('overviewshown', () => setTool(null));
|
|
475
611
|
|
|
476
612
|
window.addEventListener('resize', resize);
|
|
477
613
|
setInk(ink);
|
package/runtime/deck.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import RevealAnnotate from './annotate.js';
|
|
2
2
|
import RevealSpotlight from './spotlight.js';
|
|
3
|
+
import RevealPointer from './pointer.js';
|
|
3
4
|
import RevealOutline from './outline.js';
|
|
4
5
|
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -161,8 +162,9 @@ await Reveal.initialize({
|
|
|
161
162
|
pdfSeparateFragments: false,
|
|
162
163
|
// Anything the project wants to change, from `reveal:` in slides.config.js.
|
|
163
164
|
...(CFG.reveal ?? {}),
|
|
164
|
-
// Spotlight after Annotate:
|
|
165
|
-
|
|
165
|
+
// Spotlight and Pointer after Annotate: they hang their buttons on the
|
|
166
|
+
// toolbar the pen builds.
|
|
167
|
+
plugins: [RevealMarkdown, RevealHighlight, RevealNotes, RevealAnnotate(), RevealSpotlight(), RevealPointer(), RevealOutline()],
|
|
166
168
|
});
|
|
167
169
|
|
|
168
170
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// RevealPointer — swap the mouse cursor for a stage pointer.
|
|
3
|
+
//
|
|
4
|
+
// A reveal.js plugin, built like spotlight.js: `init()` gets the deck, the file
|
|
5
|
+
// owns its chrome and key bindings, and index.html stays a page of slides.
|
|
6
|
+
//
|
|
7
|
+
// Armed, it hides the real cursor everywhere and hangs its own on the pointer
|
|
8
|
+
// instead. Three kinds, picked from a popover like the pen's swatches:
|
|
9
|
+
//
|
|
10
|
+
// halo — a bright dot inside a glowing ring; the ring trails the dot on a
|
|
11
|
+
// short elastic, which is what makes it read as alive rather than
|
|
12
|
+
// as a cursor theme.
|
|
13
|
+
// compass — a dart that always aims at the centre of the slide, so wherever
|
|
14
|
+
// the hand wanders the audience is pointed back at the deck.
|
|
15
|
+
// native — the system cursor again, without standing the tool down: a way
|
|
16
|
+
// back to a normal mouse that keeps the popover one keystroke away.
|
|
17
|
+
//
|
|
18
|
+
// Unlike the pen and the spotlight it survives a slide change: it is the
|
|
19
|
+
// cursor now, not a mark on one slide.
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
const KEY_CODE = 67; // C
|
|
23
|
+
|
|
24
|
+
const KINDS = [
|
|
25
|
+
{
|
|
26
|
+
id: 'halo',
|
|
27
|
+
label: 'Halo pointer',
|
|
28
|
+
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
|
29
|
+
aria-hidden="true"><circle cx="12" cy="12" r="7.5" /><circle cx="12" cy="12" r="1.6"
|
|
30
|
+
fill="currentColor" stroke="none" /></svg>`,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'compass',
|
|
34
|
+
label: 'Compass pointer — aims at the centre',
|
|
35
|
+
icon: `<svg viewBox="0 0 24 24" fill="currentColor" stroke="none" aria-hidden="true">
|
|
36
|
+
<path d="M21 12L4.5 19.5 8.6 12 4.5 4.5z" /></svg>`,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'native',
|
|
40
|
+
label: 'Normal cursor',
|
|
41
|
+
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
|
42
|
+
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
43
|
+
<path d="M4 4l7.07 17 2.51-7.39L21 11.07z" /></svg>`,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// How hard the halo's ring is pulled towards the dot each frame: 1 would pin
|
|
48
|
+
// it, lower trails further behind. The loop stops once it has caught up.
|
|
49
|
+
const CHASE = 0.28;
|
|
50
|
+
const SETTLED = 0.4; // px of remaining lag that counts as caught up
|
|
51
|
+
|
|
52
|
+
const POINTER_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
|
53
|
+
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
54
|
+
<circle cx="12" cy="12" r="7" /><path d="M12 2v3M12 19v3M2 12h3M19 12h3" />
|
|
55
|
+
</svg>`;
|
|
56
|
+
|
|
57
|
+
const RevealPointer = () => ({
|
|
58
|
+
id: 'pointer',
|
|
59
|
+
|
|
60
|
+
init(deck) {
|
|
61
|
+
// ---- chrome ---------------------------------------------------------
|
|
62
|
+
// Fixed overlays on <body>, outside `.reveal` and its transforms. The
|
|
63
|
+
// pointer rides above every other overlay — it stands in for the cursor,
|
|
64
|
+
// which is always on top of what it points at.
|
|
65
|
+
const layer = document.createElement('div');
|
|
66
|
+
layer.id = 'deck-pointer';
|
|
67
|
+
layer.setAttribute('aria-hidden', 'true');
|
|
68
|
+
layer.dataset.kind = KINDS[0].id;
|
|
69
|
+
layer.innerHTML = `
|
|
70
|
+
<div class="deck-pointer-halo"></div>
|
|
71
|
+
<div class="deck-pointer-dot"></div>
|
|
72
|
+
<svg class="deck-pointer-compass" viewBox="0 0 24 24" aria-hidden="true">
|
|
73
|
+
<path d="M21 12L4.5 19.5 8.6 12 4.5 4.5z" />
|
|
74
|
+
</svg>
|
|
75
|
+
`;
|
|
76
|
+
layer.hidden = true;
|
|
77
|
+
document.body.append(layer);
|
|
78
|
+
const halo = layer.querySelector('.deck-pointer-halo');
|
|
79
|
+
const dot = layer.querySelector('.deck-pointer-dot');
|
|
80
|
+
const compass = layer.querySelector('.deck-pointer-compass');
|
|
81
|
+
|
|
82
|
+
// The toolbar is annotate's; this plugin registers after it in deck.js and
|
|
83
|
+
// hangs its button there. A deck running without the pen still gets one.
|
|
84
|
+
let toolbar = document.querySelector('#deck-tools');
|
|
85
|
+
if (!toolbar) {
|
|
86
|
+
toolbar = document.createElement('div');
|
|
87
|
+
toolbar.id = 'deck-tools';
|
|
88
|
+
toolbar.setAttribute('role', 'toolbar');
|
|
89
|
+
toolbar.setAttribute('aria-label', 'Slide tools');
|
|
90
|
+
document.body.append(toolbar);
|
|
91
|
+
}
|
|
92
|
+
const button = document.createElement('button');
|
|
93
|
+
button.id = 'tool-pointer';
|
|
94
|
+
button.className = 'deck-tool';
|
|
95
|
+
button.type = 'button';
|
|
96
|
+
button.setAttribute('aria-pressed', 'false');
|
|
97
|
+
button.title = 'Pointer — replace the cursor (C)';
|
|
98
|
+
button.setAttribute('aria-label', 'Pointer');
|
|
99
|
+
button.innerHTML = POINTER_ICON;
|
|
100
|
+
toolbar.append(button);
|
|
101
|
+
|
|
102
|
+
// The kind picker, hanging under the toolbar exactly like the pen's
|
|
103
|
+
// swatches — the two are never open at once, since arming one tool
|
|
104
|
+
// disarms the other.
|
|
105
|
+
const picker = document.createElement('div');
|
|
106
|
+
picker.id = 'deck-pointer-kinds';
|
|
107
|
+
picker.setAttribute('role', 'radiogroup');
|
|
108
|
+
picker.setAttribute('aria-label', 'Pointer kind');
|
|
109
|
+
picker.hidden = true;
|
|
110
|
+
picker.innerHTML = KINDS.map(
|
|
111
|
+
(kind, i) => `<button class="deck-pointer-kind" type="button" role="radio"
|
|
112
|
+
aria-checked="${i === 0}" data-kind="${kind.id}"
|
|
113
|
+
title="${kind.label}" aria-label="${kind.label}">${kind.icon}</button>`,
|
|
114
|
+
).join('');
|
|
115
|
+
toolbar.append(picker);
|
|
116
|
+
|
|
117
|
+
// ---- following the pointer ------------------------------------------
|
|
118
|
+
|
|
119
|
+
let armed = false;
|
|
120
|
+
let kind = KINDS[0].id;
|
|
121
|
+
let x = -100; // the pointer, in screen px; parked off-screen until a move
|
|
122
|
+
let y = -100;
|
|
123
|
+
let hx = -100; // where the halo's ring has got to on its way there
|
|
124
|
+
let hy = -100;
|
|
125
|
+
let frame = 0;
|
|
126
|
+
|
|
127
|
+
const aim = () => {
|
|
128
|
+
// The dart pivots on its own centre to face the middle of the viewport;
|
|
129
|
+
// right at the centre it just keeps its last heading.
|
|
130
|
+
const dx = window.innerWidth / 2 - x;
|
|
131
|
+
const dy = window.innerHeight / 2 - y;
|
|
132
|
+
if (dx || dy) compass.style.rotate = `${Math.atan2(dy, dx)}rad`;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const render = () => {
|
|
136
|
+
frame = 0;
|
|
137
|
+
hx += (x - hx) * CHASE;
|
|
138
|
+
hy += (y - hy) * CHASE;
|
|
139
|
+
// `translate` the individual property, not `transform`: the press effect
|
|
140
|
+
// scales through `transform` in CSS, and the two must not fight.
|
|
141
|
+
dot.style.translate = `${x}px ${y}px`;
|
|
142
|
+
compass.style.translate = `${x}px ${y}px`;
|
|
143
|
+
halo.style.translate = `${hx}px ${hy}px`;
|
|
144
|
+
if (Math.hypot(x - hx, y - hy) > SETTLED) schedule();
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const schedule = () => {
|
|
148
|
+
frame ||= requestAnimationFrame(render);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const move = (event) => {
|
|
152
|
+
x = event.clientX;
|
|
153
|
+
y = event.clientY;
|
|
154
|
+
if (!armed || kind === 'native') return;
|
|
155
|
+
layer.hidden = false;
|
|
156
|
+
aim();
|
|
157
|
+
schedule();
|
|
158
|
+
};
|
|
159
|
+
document.addEventListener('pointermove', move);
|
|
160
|
+
// Falling off the window edge — or the window losing focus — leaves no
|
|
161
|
+
// cursor to stand in for; a stale pointer would sit there pointing at
|
|
162
|
+
// nothing until the mouse came back.
|
|
163
|
+
document.addEventListener('mouseleave', () => { layer.hidden = true; });
|
|
164
|
+
window.addEventListener('blur', () => { layer.hidden = true; });
|
|
165
|
+
|
|
166
|
+
// A click lands somewhere: the pointer acknowledges it — the dot swells,
|
|
167
|
+
// the ring snaps in, the dart lunges. Pure CSS off this class.
|
|
168
|
+
document.addEventListener('pointerdown', (event) => {
|
|
169
|
+
if (armed && event.button === 0) layer.classList.add('is-pressed');
|
|
170
|
+
});
|
|
171
|
+
document.addEventListener('pointerup', () => layer.classList.remove('is-pressed'));
|
|
172
|
+
|
|
173
|
+
// ---- arming ---------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
// The real cursor stands down only while a stand-in is up: armed on the
|
|
176
|
+
// native kind, the mouse is its ordinary self and the overlay stays away.
|
|
177
|
+
const applyCursor = () => {
|
|
178
|
+
const standIn = armed && kind !== 'native';
|
|
179
|
+
document.documentElement.classList.toggle('deck-pointer-on', standIn);
|
|
180
|
+
layer.hidden = true; // shown again by the first move, in the right place
|
|
181
|
+
if (standIn) {
|
|
182
|
+
// Start the ring already caught up, not sprinting in from off-screen.
|
|
183
|
+
hx = x;
|
|
184
|
+
hy = y;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const setKind = (next) => {
|
|
189
|
+
kind = next;
|
|
190
|
+
layer.dataset.kind = kind;
|
|
191
|
+
for (const chip of picker.children) {
|
|
192
|
+
chip.setAttribute('aria-checked', String(chip.dataset.kind === kind));
|
|
193
|
+
}
|
|
194
|
+
applyCursor();
|
|
195
|
+
aim();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const setPointer = (on) => {
|
|
199
|
+
armed = on;
|
|
200
|
+
button.setAttribute('aria-pressed', String(on));
|
|
201
|
+
toolbar.classList.toggle('is-armed', on);
|
|
202
|
+
picker.hidden = !on;
|
|
203
|
+
applyCursor();
|
|
204
|
+
if (on) document.dispatchEvent(new CustomEvent('deck:tool-armed', { detail: 'pointer' }));
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Several tools, one pointer: arming any disarms the rest — same DOM-event
|
|
208
|
+
// handshake as the pen and the spotlight, so each plugin works alone.
|
|
209
|
+
document.addEventListener('deck:tool-armed', (event) => {
|
|
210
|
+
if (event.detail !== 'pointer' && armed) setPointer(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
button.addEventListener('click', () => setPointer(!armed));
|
|
214
|
+
picker.addEventListener('click', (event) => {
|
|
215
|
+
const chip = event.target.closest('.deck-pointer-kind');
|
|
216
|
+
if (chip) setKind(chip.dataset.kind);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// `addKeyBinding` with a descriptor binds the key and lists it in reveal's
|
|
220
|
+
// own help overlay. `C` is unclaimed by reveal.
|
|
221
|
+
deck.addKeyBinding({ keyCode: KEY_CODE, key: 'C', description: 'Toggle the pointer' }, () =>
|
|
222
|
+
setPointer(!armed),
|
|
223
|
+
);
|
|
224
|
+
// While armed: Escape stands it down, the number row picks the kind — the
|
|
225
|
+
// same capture-phase borrowing the pen does with its colours.
|
|
226
|
+
document.addEventListener(
|
|
227
|
+
'keydown',
|
|
228
|
+
(event) => {
|
|
229
|
+
if (!armed || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
230
|
+
if (event.key === 'Escape') setPointer(false);
|
|
231
|
+
else {
|
|
232
|
+
const index = '123'.indexOf(event.key);
|
|
233
|
+
if (index === -1 || index >= KINDS.length) return;
|
|
234
|
+
setKind(KINDS[index].id);
|
|
235
|
+
}
|
|
236
|
+
event.stopPropagation();
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
},
|
|
239
|
+
true,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// No `slidechanged` hook on purpose: the pointer is the cursor, and the
|
|
243
|
+
// cursor does not vanish because the slide moved on. The overview is fair
|
|
244
|
+
// ground for it too.
|
|
245
|
+
window.addEventListener('resize', aim);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
export default RevealPointer;
|
package/runtime/theme.base.css
CHANGED
|
@@ -449,10 +449,11 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
/* Faded back until it is wanted: on stage the slide is the point, and the
|
|
452
|
-
toolbar
|
|
452
|
+
toolbar keeps to the quiet strip along the bottom, between the arrows and
|
|
453
|
+
the signature. */
|
|
453
454
|
#deck-tools {
|
|
454
455
|
position: fixed;
|
|
455
|
-
|
|
456
|
+
bottom: 14px;
|
|
456
457
|
left: 50%;
|
|
457
458
|
transform: translateX(-50%);
|
|
458
459
|
z-index: 40;
|
|
@@ -505,10 +506,11 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
505
506
|
0 0 16px color-mix(in srgb, var(--pen, #ff8c1a) 55%, transparent);
|
|
506
507
|
}
|
|
507
508
|
|
|
508
|
-
/* The colours,
|
|
509
|
+
/* The colours, floating above the toolbar while a tool is armed — the
|
|
510
|
+
toolbar sits on the bottom edge, so up is where the room is. */
|
|
509
511
|
#deck-swatches {
|
|
510
512
|
position: absolute;
|
|
511
|
-
|
|
513
|
+
bottom: calc(100% + 9px);
|
|
512
514
|
left: 50%;
|
|
513
515
|
transform: translateX(-50%);
|
|
514
516
|
display: flex;
|
|
@@ -821,6 +823,142 @@ body.outline-open #deck-outline { transform: none; }
|
|
|
821
823
|
background: color-mix(in srgb, var(--text) 5%, transparent);
|
|
822
824
|
}
|
|
823
825
|
|
|
826
|
+
/* --- the pointer (pointer.js) -------------------------------------------- */
|
|
827
|
+
|
|
828
|
+
/* The real cursor goes away everywhere while the pointer is armed — the
|
|
829
|
+
glowing stand-in below is the cursor now, toolbar included. */
|
|
830
|
+
html.deck-pointer-on,
|
|
831
|
+
html.deck-pointer-on * { cursor: none !important; }
|
|
832
|
+
|
|
833
|
+
/* Above everything, spotlight and ink included: it stands in for the cursor,
|
|
834
|
+
which always rides on top of what it points at. Never touchable. */
|
|
835
|
+
#deck-pointer {
|
|
836
|
+
position: fixed;
|
|
837
|
+
inset: 0;
|
|
838
|
+
z-index: 60;
|
|
839
|
+
pointer-events: none;
|
|
840
|
+
}
|
|
841
|
+
#deck-pointer[hidden] { display: none; }
|
|
842
|
+
|
|
843
|
+
/* Each piece sits at 0,0 and is carried by the `translate` property pointer.js
|
|
844
|
+
writes per frame; `transform` is left free for the press effect, so the two
|
|
845
|
+
compose instead of overwriting each other. */
|
|
846
|
+
/* The halo wears the pen's own orange (annotate.js's palette), not the
|
|
847
|
+
theme accent: on stage the pointer has to burn, not blend in. */
|
|
848
|
+
#deck-pointer { --pointer-hot: #ff8c1a; }
|
|
849
|
+
|
|
850
|
+
.deck-pointer-dot {
|
|
851
|
+
position: absolute;
|
|
852
|
+
left: -3.5px;
|
|
853
|
+
top: -3.5px;
|
|
854
|
+
width: 7px;
|
|
855
|
+
height: 7px;
|
|
856
|
+
border-radius: 999px;
|
|
857
|
+
background: #fff;
|
|
858
|
+
box-shadow:
|
|
859
|
+
0 0 6px 1px rgb(255 255 255 / 0.9),
|
|
860
|
+
0 0 14px 4px color-mix(in srgb, var(--pointer-hot) 70%, transparent);
|
|
861
|
+
transition: transform 0.12s ease;
|
|
862
|
+
}
|
|
863
|
+
/* The ring trails the dot on a short elastic (pointer.js lerps it), lit from
|
|
864
|
+
inside and haloed outside — the trailing is what makes it read as alive. */
|
|
865
|
+
.deck-pointer-halo {
|
|
866
|
+
position: absolute;
|
|
867
|
+
left: -19px;
|
|
868
|
+
top: -19px;
|
|
869
|
+
width: 38px;
|
|
870
|
+
height: 38px;
|
|
871
|
+
border-radius: 999px;
|
|
872
|
+
border: 3px solid color-mix(in srgb, var(--pointer-hot) 90%, #fff);
|
|
873
|
+
background: radial-gradient(circle, color-mix(in srgb, var(--pointer-hot) 30%, transparent) 0%, transparent 68%);
|
|
874
|
+
box-shadow:
|
|
875
|
+
0 0 26px color-mix(in srgb, var(--pointer-hot) 75%, transparent),
|
|
876
|
+
inset 0 0 14px color-mix(in srgb, var(--pointer-hot) 55%, transparent);
|
|
877
|
+
transition: transform 0.16s ease, border-color 0.16s ease;
|
|
878
|
+
}
|
|
879
|
+
/* A click: the dot swells, the ring snaps in around it. */
|
|
880
|
+
#deck-pointer.is-pressed .deck-pointer-dot { transform: scale(1.7); }
|
|
881
|
+
#deck-pointer.is-pressed .deck-pointer-halo {
|
|
882
|
+
transform: scale(0.7);
|
|
883
|
+
border-color: #fff;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/* The compass dart. pointer.js sets `rotate` so it always aims at the centre
|
|
887
|
+
of the viewport; the pivot is its own middle, under the cursor's hotspot. */
|
|
888
|
+
.deck-pointer-compass {
|
|
889
|
+
position: absolute;
|
|
890
|
+
left: -21px;
|
|
891
|
+
top: -21px;
|
|
892
|
+
width: 42px;
|
|
893
|
+
height: 42px;
|
|
894
|
+
fill: var(--pointer-hot);
|
|
895
|
+
filter:
|
|
896
|
+
drop-shadow(0 0 3px rgb(255 255 255 / 0.5))
|
|
897
|
+
drop-shadow(0 0 12px color-mix(in srgb, var(--pointer-hot) 75%, transparent));
|
|
898
|
+
transition: transform 0.12s ease;
|
|
899
|
+
}
|
|
900
|
+
#deck-pointer.is-pressed .deck-pointer-compass { transform: scale(1.22); }
|
|
901
|
+
|
|
902
|
+
/* One kind on screen at a time; the native kind is the system cursor itself,
|
|
903
|
+
so it paints nothing at all. */
|
|
904
|
+
#deck-pointer[data-kind='halo'] .deck-pointer-compass { display: none; }
|
|
905
|
+
#deck-pointer[data-kind='compass'] .deck-pointer-dot,
|
|
906
|
+
#deck-pointer[data-kind='compass'] .deck-pointer-halo { display: none; }
|
|
907
|
+
#deck-pointer[data-kind='native'] > * { display: none; }
|
|
908
|
+
|
|
909
|
+
/* The armed pointer button wears the accent, not the pen's ink — it draws in
|
|
910
|
+
nothing, it glows. The id outweighs the generic armed-tool rule above. */
|
|
911
|
+
#tool-pointer[aria-pressed='true'] {
|
|
912
|
+
background: var(--accent);
|
|
913
|
+
color: #0d1626;
|
|
914
|
+
box-shadow:
|
|
915
|
+
0 0 0 1px color-mix(in srgb, var(--text) 30%, transparent),
|
|
916
|
+
0 0 16px color-mix(in srgb, var(--accent) 55%, transparent);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/* The kind picker, floating above the toolbar like the pen's swatches. */
|
|
920
|
+
#deck-pointer-kinds {
|
|
921
|
+
position: absolute;
|
|
922
|
+
bottom: calc(100% + 9px);
|
|
923
|
+
left: 50%;
|
|
924
|
+
transform: translateX(-50%);
|
|
925
|
+
display: flex;
|
|
926
|
+
gap: 6px;
|
|
927
|
+
padding: 5px;
|
|
928
|
+
background: color-mix(in srgb, var(--card) 82%, transparent);
|
|
929
|
+
border: 1px solid var(--line);
|
|
930
|
+
border-radius: 999px;
|
|
931
|
+
backdrop-filter: blur(6px);
|
|
932
|
+
}
|
|
933
|
+
#deck-pointer-kinds[hidden] { display: none; }
|
|
934
|
+
|
|
935
|
+
.deck-pointer-kind {
|
|
936
|
+
display: grid;
|
|
937
|
+
place-items: center;
|
|
938
|
+
width: 26px;
|
|
939
|
+
height: 26px;
|
|
940
|
+
padding: 0;
|
|
941
|
+
border: 0;
|
|
942
|
+
border-radius: 999px;
|
|
943
|
+
background: transparent;
|
|
944
|
+
color: var(--muted);
|
|
945
|
+
cursor: pointer;
|
|
946
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
947
|
+
}
|
|
948
|
+
.deck-pointer-kind svg {
|
|
949
|
+
display: block;
|
|
950
|
+
width: 15px;
|
|
951
|
+
height: 15px;
|
|
952
|
+
}
|
|
953
|
+
.deck-pointer-kind:hover {
|
|
954
|
+
background: color-mix(in srgb, var(--text) 10%, transparent);
|
|
955
|
+
color: var(--text);
|
|
956
|
+
}
|
|
957
|
+
.deck-pointer-kind[aria-checked='true'] {
|
|
958
|
+
background: var(--accent);
|
|
959
|
+
color: #0d1626;
|
|
960
|
+
}
|
|
961
|
+
|
|
824
962
|
/* --- the spotlight (spotlight.js) ---------------------------------------- */
|
|
825
963
|
|
|
826
964
|
/* One veil over the slides, under the ink: spotlight.js punches the lit
|
|
@@ -878,6 +1016,7 @@ body.outline-open #deck-outline { transform: none; }
|
|
|
878
1016
|
#deck-ink,
|
|
879
1017
|
#deck-spot,
|
|
880
1018
|
#deck-spot-ring,
|
|
1019
|
+
#deck-pointer,
|
|
881
1020
|
#deck-tools,
|
|
882
1021
|
#deck-menu,
|
|
883
1022
|
#deck-outline { display: none; }
|