@abreen/tada 1.15.1 → 1.15.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.15.1",
3
+ "version": "1.15.2",
4
4
  "type": "module",
5
5
  "description": "A static site generator",
6
6
  "license": "MIT",
@@ -115,7 +115,7 @@
115
115
  .js .question-a-body[data-revealed] {
116
116
  cursor: unset;
117
117
 
118
- @include user-select(text);
118
+ @include user-select(unset);
119
119
  }
120
120
 
121
121
  .question-multiple-choice {
@@ -185,7 +185,7 @@
185
185
  .question-multiple-choice-option {
186
186
  cursor: unset;
187
187
 
188
- @include user-select(text);
188
+ @include user-select(unset);
189
189
 
190
190
  &:focus,
191
191
  &:focus-visible {
@@ -9,7 +9,37 @@ interface SlidesPresentDetail {
9
9
  slideIndex?: number;
10
10
  }
11
11
 
12
+ interface AnnotationPoint {
13
+ x: number;
14
+ y: number;
15
+ }
16
+
17
+ interface AnnotationStroke {
18
+ type: 'stroke';
19
+ points: AnnotationPoint[];
20
+ }
21
+
22
+ interface AnnotationErase {
23
+ type: 'erase';
24
+ points: AnnotationPoint[];
25
+ radius: number;
26
+ }
27
+
28
+ type AnnotationOperation = AnnotationStroke | AnnotationErase;
29
+
30
+ interface SlideAnnotationState {
31
+ canvas: HTMLCanvasElement;
32
+ operations: AnnotationOperation[];
33
+ width: number;
34
+ height: number;
35
+ dpr: number;
36
+ }
37
+
12
38
  const FULLSCREEN_STORAGE_KEY = 'slidesFullscreen';
39
+ const ANNOTATION_CANVAS_SELECTOR = '[data-slides-annotations]';
40
+ const ANNOTATION_COLOR = 'blueviolet';
41
+ const ANNOTATION_ERASER_RADIUS = 18;
42
+ const ANNOTATION_ERASER_DIAMETER = ANNOTATION_ERASER_RADIUS * 2;
13
43
 
14
44
  function isInteractive(
15
45
  view: Window & typeof globalThis,
@@ -111,15 +141,39 @@ export default function mountSlides(window: Window): void | (() => void) {
111
141
  overlay.append(closeButton);
112
142
  document.body.appendChild(overlay);
113
143
 
144
+ const eraserPreview = document.createElement('div');
145
+ eraserPreview.hidden = true;
146
+ eraserPreview.dataset.slidesEraserPreview = '';
147
+ eraserPreview.setAttribute('aria-hidden', 'true');
148
+ eraserPreview.style.setProperty(
149
+ '--slides-eraser-preview-size',
150
+ `${ANNOTATION_ERASER_DIAMETER}px`,
151
+ );
152
+ document.body.appendChild(eraserPreview);
153
+
114
154
  let isPresenting = false;
115
155
  let activeIndex = -1;
116
156
  let presentationMode: PresentationMode | null = null;
117
157
  let isToolbarPinnedVisible = false;
158
+ let isAnnotating = false;
159
+ let isShiftPressed = false;
160
+ let isErasing = false;
161
+ let activeAnnotation: {
162
+ slide: HTMLElement;
163
+ stroke: AnnotationStroke;
164
+ } | null = null;
165
+ let activeEraser: { slide: HTMLElement; erase: AnnotationErase } | null =
166
+ null;
118
167
 
119
168
  const toolbarStates = new WeakMap<HTMLElement, TraceToolbarState>();
120
169
  const slideTabIndexes = new WeakMap<HTMLElement, string | null>();
170
+ const annotationStates = new WeakMap<HTMLElement, SlideAnnotationState>();
121
171
 
122
172
  function hidePresentationCursor(): void {
173
+ if (isAnnotating) {
174
+ return;
175
+ }
176
+
123
177
  document.body.classList.add('is-presentation-cursor-hidden');
124
178
  }
125
179
 
@@ -194,8 +248,12 @@ export default function mountSlides(window: Window): void | (() => void) {
194
248
  }
195
249
 
196
250
  function setActiveSlide(index: number): void {
251
+ activeAnnotation = null;
252
+ activeEraser = null;
253
+ hideEraserPreview();
197
254
  activeIndex = Math.max(0, Math.min(index, slides.length - 1));
198
255
  updateActiveSlide(slides, activeIndex, isPresenting);
256
+ resizeActiveAnnotationCanvas();
199
257
  }
200
258
 
201
259
  function focusSlide(slide: HTMLElement | undefined): void {
@@ -247,6 +305,9 @@ export default function mountSlides(window: Window): void | (() => void) {
247
305
  isPresenting = false;
248
306
  activeIndex = -1;
249
307
  presentationMode = null;
308
+ isShiftPressed = false;
309
+ setAnnotationMode(false);
310
+ clearAnnotationCanvases();
250
311
  hidePresentationControls();
251
312
  showPresentationCursor();
252
313
  document.body.classList.remove('is-presenting');
@@ -289,6 +350,7 @@ export default function mountSlides(window: Window): void | (() => void) {
289
350
  function enterPresentation(mode: PresentationMode, slideIndex = 0): void {
290
351
  isPresenting = true;
291
352
  presentationMode = mode;
353
+ setAnnotationMode(false);
292
354
  hidePresentationControls();
293
355
  showPresentationCursor();
294
356
  document.body.classList.add('is-presenting');
@@ -428,6 +490,12 @@ export default function mountSlides(window: Window): void | (() => void) {
428
490
  return;
429
491
  }
430
492
 
493
+ if (event.key === 'Shift') {
494
+ isShiftPressed = true;
495
+ setEraserMode(isAnnotating);
496
+ return;
497
+ }
498
+
431
499
  if (
432
500
  (event.key === 'ArrowRight' ||
433
501
  event.key === 'ArrowLeft' ||
@@ -465,6 +533,405 @@ export default function mountSlides(window: Window): void | (() => void) {
465
533
  move(1);
466
534
  }
467
535
 
536
+ function getActiveSlide(): HTMLElement | null {
537
+ return activeIndex >= 0 ? (slides[activeIndex] ?? null) : null;
538
+ }
539
+
540
+ function setAnnotationMode(enabled: boolean): void {
541
+ isAnnotating = enabled;
542
+ activeAnnotation = null;
543
+ activeEraser = null;
544
+ hideEraserPreview();
545
+ document.body.classList.toggle('is-slides-annotating', enabled);
546
+ setEraserMode(enabled && isShiftPressed);
547
+
548
+ if (enabled) {
549
+ showPresentationCursor();
550
+ }
551
+ }
552
+
553
+ function setEraserMode(enabled: boolean): void {
554
+ isErasing = enabled && isPresenting && isAnnotating;
555
+ activeEraser = null;
556
+
557
+ if (isErasing) {
558
+ activeAnnotation = null;
559
+ } else {
560
+ hideEraserPreview();
561
+ }
562
+
563
+ document.body.classList.toggle('is-slides-erasing', isErasing);
564
+ }
565
+
566
+ function hideEraserPreview(): void {
567
+ eraserPreview.hidden = true;
568
+ }
569
+
570
+ function showEraserPreview(event: PointerEvent): void {
571
+ eraserPreview.hidden = false;
572
+ eraserPreview.style.left = `${event.clientX}px`;
573
+ eraserPreview.style.top = `${event.clientY}px`;
574
+ }
575
+
576
+ function toggleAnnotationMode(): void {
577
+ setAnnotationMode(!isAnnotating);
578
+ }
579
+
580
+ function getSlidePoint(
581
+ slide: HTMLElement,
582
+ event: Pick<PointerEvent, 'clientX' | 'clientY'>,
583
+ ): AnnotationPoint | null {
584
+ const rect = slide.getBoundingClientRect();
585
+ if (rect.width <= 0 || rect.height <= 0) {
586
+ return null;
587
+ }
588
+
589
+ const x = Math.min(Math.max(event.clientX - rect.left, 0), rect.width);
590
+ const y = Math.min(Math.max(event.clientY - rect.top, 0), rect.height);
591
+
592
+ return { x: x / rect.width, y: y / rect.height };
593
+ }
594
+
595
+ function eventIsInsideSlide(
596
+ slide: HTMLElement,
597
+ event: Pick<PointerEvent, 'clientX' | 'clientY'>,
598
+ ): boolean {
599
+ const rect = slide.getBoundingClientRect();
600
+
601
+ return (
602
+ event.clientX >= rect.left &&
603
+ event.clientX <= rect.right &&
604
+ event.clientY >= rect.top &&
605
+ event.clientY <= rect.bottom
606
+ );
607
+ }
608
+
609
+ function getCanvasContext(
610
+ canvas: HTMLCanvasElement,
611
+ ): CanvasRenderingContext2D | null {
612
+ try {
613
+ return canvas.getContext('2d');
614
+ } catch {
615
+ return null;
616
+ }
617
+ }
618
+
619
+ function getAnnotationState(slide: HTMLElement): SlideAnnotationState {
620
+ const existing = annotationStates.get(slide);
621
+ if (existing) {
622
+ return existing;
623
+ }
624
+
625
+ const canvas = document.createElement('canvas');
626
+ canvas.dataset.slidesAnnotations = '';
627
+ canvas.setAttribute('aria-hidden', 'true');
628
+ slide.append(canvas);
629
+
630
+ const state: SlideAnnotationState = {
631
+ canvas,
632
+ operations: [],
633
+ width: 0,
634
+ height: 0,
635
+ dpr: 1,
636
+ };
637
+ annotationStates.set(slide, state);
638
+ resizeAnnotationCanvas(slide, state);
639
+
640
+ return state;
641
+ }
642
+
643
+ function resizeAnnotationCanvas(
644
+ slide: HTMLElement,
645
+ state: SlideAnnotationState,
646
+ ): void {
647
+ const rect = slide.getBoundingClientRect();
648
+ const width = Math.max(1, rect.width);
649
+ const height = Math.max(1, rect.height);
650
+ const dpr = Math.max(1, window.devicePixelRatio || 1);
651
+
652
+ if (state.width === width && state.height === height && state.dpr === dpr) {
653
+ return;
654
+ }
655
+
656
+ state.width = width;
657
+ state.height = height;
658
+ state.dpr = dpr;
659
+ state.canvas.width = Math.round(width * dpr);
660
+ state.canvas.height = Math.round(height * dpr);
661
+ redrawAnnotationCanvas(state);
662
+ }
663
+
664
+ function resizeActiveAnnotationCanvas(): void {
665
+ const slide = getActiveSlide();
666
+ if (!slide) {
667
+ return;
668
+ }
669
+
670
+ const state = annotationStates.get(slide);
671
+ if (state) {
672
+ resizeAnnotationCanvas(slide, state);
673
+ }
674
+ }
675
+
676
+ function redrawAnnotationCanvas(state: SlideAnnotationState): void {
677
+ const ctx = getCanvasContext(state.canvas);
678
+ if (!ctx) {
679
+ return;
680
+ }
681
+
682
+ ctx.clearRect(0, 0, state.canvas.width, state.canvas.height);
683
+
684
+ for (const operation of state.operations) {
685
+ if (operation.type === 'stroke') {
686
+ drawAnnotationStroke(ctx, operation, state);
687
+ } else {
688
+ drawAnnotationErase(ctx, operation, state);
689
+ }
690
+ }
691
+ }
692
+
693
+ function drawAnnotationStroke(
694
+ ctx: CanvasRenderingContext2D,
695
+ stroke: AnnotationStroke,
696
+ state: SlideAnnotationState,
697
+ ): void {
698
+ if (stroke.points.length === 0) {
699
+ return;
700
+ }
701
+
702
+ ctx.globalCompositeOperation = 'source-over';
703
+ ctx.strokeStyle = ANNOTATION_COLOR;
704
+ ctx.lineWidth = 3 * state.dpr;
705
+ ctx.lineCap = 'round';
706
+ ctx.lineJoin = 'round';
707
+ ctx.beginPath();
708
+ const [first] = stroke.points;
709
+ ctx.moveTo(first.x * state.canvas.width, first.y * state.canvas.height);
710
+
711
+ for (const point of stroke.points.slice(1)) {
712
+ ctx.lineTo(point.x * state.canvas.width, point.y * state.canvas.height);
713
+ }
714
+
715
+ ctx.stroke();
716
+ }
717
+
718
+ function drawAnnotationErase(
719
+ ctx: CanvasRenderingContext2D,
720
+ erase: AnnotationErase,
721
+ state: SlideAnnotationState,
722
+ ): void {
723
+ if (erase.points.length === 0) {
724
+ return;
725
+ }
726
+
727
+ const radius = erase.radius * state.dpr;
728
+ ctx.globalCompositeOperation = 'destination-out';
729
+ ctx.lineWidth = radius * 2;
730
+ ctx.lineCap = 'round';
731
+ ctx.lineJoin = 'round';
732
+
733
+ const [first] = erase.points;
734
+ if (erase.points.length === 1) {
735
+ ctx.beginPath();
736
+ ctx.arc(
737
+ first.x * state.canvas.width,
738
+ first.y * state.canvas.height,
739
+ radius,
740
+ 0,
741
+ Math.PI * 2,
742
+ );
743
+ ctx.fill();
744
+ ctx.globalCompositeOperation = 'source-over';
745
+ return;
746
+ }
747
+
748
+ ctx.beginPath();
749
+ ctx.moveTo(first.x * state.canvas.width, first.y * state.canvas.height);
750
+
751
+ for (const point of erase.points.slice(1)) {
752
+ ctx.lineTo(point.x * state.canvas.width, point.y * state.canvas.height);
753
+ }
754
+
755
+ ctx.stroke();
756
+ ctx.globalCompositeOperation = 'source-over';
757
+ }
758
+
759
+ function drawCurrentAnnotation(): void {
760
+ if (!activeAnnotation) {
761
+ return;
762
+ }
763
+
764
+ const state = getAnnotationState(activeAnnotation.slide);
765
+ resizeAnnotationCanvas(activeAnnotation.slide, state);
766
+ redrawAnnotationCanvas(state);
767
+ }
768
+
769
+ function drawCurrentEraser(): void {
770
+ if (!activeEraser) {
771
+ return;
772
+ }
773
+
774
+ const state = annotationStates.get(activeEraser.slide);
775
+ if (!state) {
776
+ return;
777
+ }
778
+
779
+ resizeAnnotationCanvas(activeEraser.slide, state);
780
+ redrawAnnotationCanvas(state);
781
+ }
782
+
783
+ function clearAnnotationCanvases(): void {
784
+ for (const slide of slides) {
785
+ slide.querySelector(ANNOTATION_CANVAS_SELECTOR)?.remove();
786
+ annotationStates.delete(slide);
787
+ }
788
+ activeAnnotation = null;
789
+ activeEraser = null;
790
+ }
791
+
792
+ function handleAnnotationContextMenu(event: MouseEvent): void {
793
+ if (!isPresenting) {
794
+ return;
795
+ }
796
+
797
+ event.preventDefault();
798
+ event.stopPropagation();
799
+ toggleAnnotationMode();
800
+ }
801
+
802
+ function handleAnnotationPointerDown(event: PointerEvent): void {
803
+ if (!isPresenting || !isAnnotating || event.button !== 0) {
804
+ return;
805
+ }
806
+
807
+ event.preventDefault();
808
+ event.stopPropagation();
809
+
810
+ if (isErasing) {
811
+ return;
812
+ }
813
+
814
+ const slide = getActiveSlide();
815
+ if (!slide || !eventIsInsideSlide(slide, event)) {
816
+ activeAnnotation = null;
817
+ return;
818
+ }
819
+
820
+ const point = getSlidePoint(slide, event);
821
+ if (!point) {
822
+ return;
823
+ }
824
+
825
+ const state = getAnnotationState(slide);
826
+ const stroke: AnnotationStroke = { type: 'stroke', points: [point] };
827
+ state.operations.push(stroke);
828
+ activeAnnotation = { slide, stroke };
829
+ drawCurrentAnnotation();
830
+
831
+ const target =
832
+ event.target instanceof domView.Element ? event.target : slide;
833
+ target.setPointerCapture?.(event.pointerId);
834
+ }
835
+
836
+ function handleAnnotationPointerMove(event: PointerEvent): void {
837
+ if (!isPresenting || !isAnnotating) {
838
+ return;
839
+ }
840
+
841
+ if (isErasing) {
842
+ handleAnnotationEraseMove(event);
843
+ return;
844
+ }
845
+
846
+ if (!activeAnnotation) {
847
+ return;
848
+ }
849
+
850
+ event.preventDefault();
851
+ event.stopPropagation();
852
+
853
+ const point = getSlidePoint(activeAnnotation.slide, event);
854
+ if (!point) {
855
+ return;
856
+ }
857
+
858
+ activeAnnotation.stroke.points.push(point);
859
+ drawCurrentAnnotation();
860
+ }
861
+
862
+ function handleAnnotationEraseMove(event: PointerEvent): void {
863
+ const slide = getActiveSlide();
864
+ if (!slide || !eventIsInsideSlide(slide, event)) {
865
+ activeEraser = null;
866
+ hideEraserPreview();
867
+ return;
868
+ }
869
+
870
+ const state = annotationStates.get(slide);
871
+ if (!state) {
872
+ activeEraser = null;
873
+ hideEraserPreview();
874
+ return;
875
+ }
876
+
877
+ const point = getSlidePoint(slide, event);
878
+ if (!point) {
879
+ activeEraser = null;
880
+ hideEraserPreview();
881
+ return;
882
+ }
883
+
884
+ event.preventDefault();
885
+ event.stopPropagation();
886
+ showEraserPreview(event);
887
+
888
+ if (!activeEraser || activeEraser.slide !== slide) {
889
+ const erase: AnnotationErase = {
890
+ type: 'erase',
891
+ points: [],
892
+ radius: ANNOTATION_ERASER_RADIUS,
893
+ };
894
+ state.operations.push(erase);
895
+ activeEraser = { slide, erase };
896
+ }
897
+
898
+ activeEraser.erase.points.push(point);
899
+ drawCurrentEraser();
900
+ }
901
+
902
+ function handleAnnotationPointerUp(event: PointerEvent): void {
903
+ if (!isPresenting || !isAnnotating || !activeAnnotation) {
904
+ return;
905
+ }
906
+
907
+ event.preventDefault();
908
+ event.stopPropagation();
909
+ activeAnnotation = null;
910
+ }
911
+
912
+ function handleKeyup(event: KeyboardEvent): void {
913
+ if (event.key !== 'Shift') {
914
+ return;
915
+ }
916
+
917
+ isShiftPressed = false;
918
+ setEraserMode(false);
919
+ }
920
+
921
+ function handleWindowBlur(): void {
922
+ isShiftPressed = false;
923
+ setEraserMode(false);
924
+ }
925
+
926
+ function handleAnnotationClick(event: MouseEvent): void {
927
+ if (!isPresenting || !isAnnotating) {
928
+ return;
929
+ }
930
+
931
+ event.preventDefault();
932
+ event.stopPropagation();
933
+ }
934
+
468
935
  function handleMouseMove(event: MouseEvent): void {
469
936
  if (!isPresenting) {
470
937
  return;
@@ -507,8 +974,17 @@ export default function mountSlides(window: Window): void | (() => void) {
507
974
  closeButton.addEventListener('click', handleCloseClick);
508
975
  document.addEventListener('fullscreenchange', handleFullscreenChange);
509
976
  window.addEventListener('keydown', handleKeydown);
977
+ window.addEventListener('keyup', handleKeyup);
978
+ window.addEventListener('blur', handleWindowBlur);
510
979
  window.addEventListener('mousemove', handleMouseMove);
980
+ window.addEventListener('resize', resizeActiveAnnotationCanvas);
981
+ window.addEventListener('pointermove', handleAnnotationPointerMove);
982
+ window.addEventListener('pointerup', handleAnnotationPointerUp);
983
+ window.addEventListener('pointercancel', handleAnnotationPointerUp);
984
+ slidesRoot.addEventListener('click', handleAnnotationClick, true);
511
985
  slidesRoot.addEventListener('click', handleSlideClick);
986
+ slidesRoot.addEventListener('contextmenu', handleAnnotationContextMenu);
987
+ slidesRoot.addEventListener('pointerdown', handleAnnotationPointerDown);
512
988
  slidesRoot.addEventListener('tada:slides-present', handleSlidesPresent);
513
989
 
514
990
  return () => {
@@ -521,9 +997,19 @@ export default function mountSlides(window: Window): void | (() => void) {
521
997
  closeButton.removeEventListener('click', handleCloseClick);
522
998
  document.removeEventListener('fullscreenchange', handleFullscreenChange);
523
999
  window.removeEventListener('keydown', handleKeydown);
1000
+ window.removeEventListener('keyup', handleKeyup);
1001
+ window.removeEventListener('blur', handleWindowBlur);
524
1002
  window.removeEventListener('mousemove', handleMouseMove);
1003
+ window.removeEventListener('resize', resizeActiveAnnotationCanvas);
1004
+ window.removeEventListener('pointermove', handleAnnotationPointerMove);
1005
+ window.removeEventListener('pointerup', handleAnnotationPointerUp);
1006
+ window.removeEventListener('pointercancel', handleAnnotationPointerUp);
1007
+ slidesRoot.removeEventListener('click', handleAnnotationClick, true);
525
1008
  slidesRoot.removeEventListener('click', handleSlideClick);
1009
+ slidesRoot.removeEventListener('contextmenu', handleAnnotationContextMenu);
1010
+ slidesRoot.removeEventListener('pointerdown', handleAnnotationPointerDown);
526
1011
  slidesRoot.removeEventListener('tada:slides-present', handleSlidesPresent);
527
1012
  overlay.remove();
1013
+ eraserPreview.remove();
528
1014
  };
529
1015
  }
@@ -6,16 +6,54 @@
6
6
  }
7
7
 
8
8
  body.is-presenting {
9
- --slides-scale: clamp(150%, 2.6vmin, 200%);
9
+ --slides-annotation-cursor:
10
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg transform='rotate(45 12 12)'%3E%3Crect x='10' y='4.8' width='4' height='13.2' rx='2' fill='white' stroke='black' stroke-width='1.5'/%3E%3Cline x1='12' y1='18' x2='12' y2='20.8' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E")
11
+ 6 18,
12
+ crosshair;
13
+ --slides-eraser-cursor:
14
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg transform='rotate(-45 12 12)'%3E%3Cpath d='M6 10 Q6 8.5 7.5 8.5 H16.5 Q18 8.5 18 10 V14 Q18 15.5 16.5 15.5 H7.5 Q6 15.5 6 14 Z' fill='white' stroke='black' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cline x1='10' y1='8.5' x2='10' y2='15.5' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E")
15
+ 12 12,
16
+ cell;
17
+ --slides-scale: max(
18
+ 100%,
19
+ min(
20
+ clamp(150%, 2.8vmin, 200%),
21
+ calc((100vw - var(--gap) - var(--gap)) / 35),
22
+ calc(100vh / 26.25)
23
+ )
24
+ );
10
25
 
11
26
  overflow: hidden;
12
27
  }
13
28
 
14
- body.is-presenting.is-presentation-cursor-hidden,
15
- body.is-presenting.is-presentation-cursor-hidden * {
29
+ body.is-presenting.is-presentation-cursor-hidden:not(.is-slides-annotating),
30
+ body.is-presenting.is-presentation-cursor-hidden:not(.is-slides-annotating) * {
16
31
  cursor: none !important;
17
32
  }
18
33
 
34
+ body.is-presenting.is-slides-annotating .slide-deck,
35
+ body.is-presenting.is-slides-annotating .slide-deck * {
36
+ cursor: var(--slides-annotation-cursor) !important;
37
+ }
38
+
39
+ body.is-presenting.is-slides-annotating.is-slides-erasing .slide-deck,
40
+ body.is-presenting.is-slides-annotating.is-slides-erasing .slide-deck * {
41
+ cursor: var(--slides-eraser-cursor) !important;
42
+ }
43
+
44
+ body.is-presenting [data-slides-eraser-preview] {
45
+ position: fixed;
46
+ z-index: 1002;
47
+ box-sizing: border-box;
48
+ width: var(--slides-eraser-preview-size, 36px);
49
+ height: var(--slides-eraser-preview-size, 36px);
50
+ pointer-events: none;
51
+ background: transparent;
52
+ border: 2px solid var(--bg2-color);
53
+ border-radius: 50%;
54
+ transform: translate(-50%, -50%);
55
+ }
56
+
19
57
  body.is-presenting header,
20
58
  body.is-presenting .title-and-info,
21
59
  body.is-presenting nav.toc,
@@ -70,6 +108,16 @@ body.is-presenting .slide {
70
108
  background: var(--bg-color);
71
109
  transform: translateX(-50%);
72
110
 
111
+ > [data-slides-annotations] {
112
+ position: absolute;
113
+ inset: 0;
114
+ z-index: 1;
115
+ display: block;
116
+ width: 100%;
117
+ height: 100%;
118
+ pointer-events: none;
119
+ }
120
+
73
121
  > :first-child {
74
122
  margin-top: 0;
75
123
  }