powerpointproto
slides.waow.tech
slides
1<script lang="ts">
2 import {
3 editorState,
4 getCurrentSlide,
5 nextSlide,
6 prevSlide,
7 stopPresenting,
8 } from "$lib/state.svelte";
9
10 let elapsed = $state("00:00");
11 let interval: ReturnType<typeof setInterval>;
12 let isDragging = $state(false);
13 let dragStartY = 0;
14 let dragStartHeight = 0;
15
16 const MIN_HEIGHT = 60;
17 const MAX_HEIGHT = 400;
18
19 $effect(() => {
20 if (editorState.isPresenting && editorState.presentationStartTime) {
21 interval = setInterval(() => {
22 const diff = Date.now() - editorState.presentationStartTime!;
23 const mins = Math.floor(diff / 60000);
24 const secs = Math.floor((diff % 60000) / 1000);
25 elapsed = `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
26 }, 1000);
27
28 return () => clearInterval(interval);
29 }
30 });
31
32 const handleKeydown = (e: KeyboardEvent) => {
33 if (!editorState.isPresenting) return;
34
35 switch (e.key) {
36 case "ArrowRight":
37 case " ":
38 case "Enter":
39 nextSlide();
40 break;
41 case "ArrowLeft":
42 prevSlide();
43 break;
44 case "Escape":
45 stopPresenting();
46 break;
47 }
48 };
49
50 const handleDragStart = (e: MouseEvent | TouchEvent) => {
51 isDragging = true;
52 dragStartY = "touches" in e ? e.touches[0].clientY : e.clientY;
53 dragStartHeight = editorState.presenterPanelHeight;
54 e.preventDefault();
55 };
56
57 const handleDragMove = (e: MouseEvent | TouchEvent) => {
58 if (!isDragging) return;
59 const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
60 const delta = dragStartY - clientY;
61 const newHeight = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, dragStartHeight + delta));
62 editorState.presenterPanelHeight = newHeight;
63 };
64
65 const handleDragEnd = () => {
66 isDragging = false;
67 };
68</script>
69
70<svelte:window
71 onkeydown={handleKeydown}
72 onmousemove={handleDragMove}
73 onmouseup={handleDragEnd}
74 ontouchmove={handleDragMove}
75 ontouchend={handleDragEnd}
76/>
77
78{#if editorState.isPresenting}
79 <div
80 class="presenter-overlay"
81 class:dragging={isDragging}
82 style="height: {editorState.presenterPanelHeight}px;"
83 >
84 <!-- svelte-ignore a11y_no_static_element_interactions -->
85 <div
86 class="drag-handle"
87 onmousedown={handleDragStart}
88 ontouchstart={handleDragStart}
89 >
90 <div class="drag-indicator"></div>
91 </div>
92
93 <div class="controls">
94 <button onclick={prevSlide}>←</button>
95 <span class="slide-counter">
96 {editorState.currentSlideIndex + 1} / {editorState.deck?.slides.length}
97 </span>
98 <button onclick={nextSlide}>→</button>
99 <span class="timer">{elapsed}</span>
100 <button onclick={stopPresenting}>exit</button>
101 </div>
102
103 {#if editorState.showNotes}
104 <div class="notes">
105 {#if getCurrentSlide()?.notes}
106 <p>{getCurrentSlide()?.notes}</p>
107 {:else}
108 <p class="no-notes">no notes for this slide</p>
109 {/if}
110 </div>
111 {/if}
112
113 <button
114 class="notes-toggle"
115 onclick={() => (editorState.showNotes = !editorState.showNotes)}
116 >
117 {editorState.showNotes ? "hide notes" : "show notes"}
118 </button>
119 </div>
120{/if}
121
122<style>
123 .presenter-overlay {
124 position: fixed;
125 bottom: 0;
126 left: 0;
127 right: 0;
128 background: rgba(0, 0, 0, 0.95);
129 z-index: 1000;
130 display: flex;
131 flex-direction: column;
132 min-height: 60px;
133 }
134
135 .presenter-overlay.dragging {
136 user-select: none;
137 }
138
139 .drag-handle {
140 position: absolute;
141 top: 0;
142 left: 0;
143 right: 0;
144 height: 12px;
145 cursor: ns-resize;
146 display: flex;
147 align-items: center;
148 justify-content: center;
149 }
150
151 .drag-handle:hover .drag-indicator,
152 .dragging .drag-indicator {
153 background: #666;
154 }
155
156 .drag-indicator {
157 width: 40px;
158 height: 4px;
159 background: #444;
160 border-radius: 2px;
161 transition: background 0.15s ease;
162 }
163
164 .controls {
165 display: flex;
166 align-items: center;
167 justify-content: center;
168 gap: 16px;
169 padding: 16px 20px 8px;
170 }
171
172 .controls button {
173 padding: 8px 16px;
174 background: #333;
175 border: 1px solid #555;
176 color: #fff;
177 cursor: pointer;
178 border-radius: 4px;
179 }
180
181 .controls button:hover {
182 background: #444;
183 }
184
185 .slide-counter {
186 font-size: 18px;
187 font-weight: bold;
188 color: #fff;
189 min-width: 60px;
190 text-align: center;
191 }
192
193 .timer {
194 font-family: monospace;
195 font-size: 18px;
196 color: #3b82f6;
197 }
198
199 .notes {
200 flex: 1;
201 margin: 8px 20px 12px;
202 padding: 12px;
203 background: #1a1a1a;
204 border-radius: 4px;
205 color: #ccc;
206 overflow-y: auto;
207 min-height: 0;
208 }
209
210 .notes p {
211 margin: 0;
212 white-space: pre-wrap;
213 line-height: 1.5;
214 }
215
216 .notes .no-notes {
217 color: #666;
218 font-style: italic;
219 }
220
221 .notes-toggle {
222 position: absolute;
223 right: 20px;
224 top: 16px;
225 padding: 6px 12px;
226 background: transparent;
227 border: 1px solid #555;
228 color: #888;
229 cursor: pointer;
230 font-size: 12px;
231 border-radius: 4px;
232 }
233
234 .notes-toggle:hover {
235 border-color: #777;
236 color: #ccc;
237 }
238</style>