powerpointproto
slides.waow.tech
slides
1<script lang="ts">
2 import { prefs, setPref, syncPrefsToRemote } from "$lib/prefs.svelte";
3 import { auth } from "$lib/auth.svelte";
4
5 let { open = $bindable(false) } = $props();
6
7 const ACCENT_PRESETS = [
8 { name: "indigo", color: "#6366f1" },
9 { name: "emerald", color: "#10b981" },
10 { name: "amber", color: "#f59e0b" },
11 { name: "rose", color: "#f43f5e" },
12 { name: "cyan", color: "#06b6d4" },
13 { name: "violet", color: "#8b5cf6" },
14 ];
15
16 const FONT_PRESETS = [
17 { name: "system", value: "system-ui" },
18 { name: "mono", value: "ui-monospace, monospace" },
19 { name: "serif", value: "ui-serif, serif" },
20 { name: "sans", value: "ui-sans-serif, sans-serif" },
21 ];
22
23 const handleAccentChange = (color: string) => {
24 setPref("accentColor", color);
25 };
26
27 const handleFontChange = (font: string) => {
28 setPref("font", font);
29 };
30
31 let syncStatus = $state<"idle" | "saving" | "saved" | "error">("idle");
32
33 const handleSync = async () => {
34 syncStatus = "saving";
35 try {
36 await syncPrefsToRemote();
37 syncStatus = "saved";
38 setTimeout(() => syncStatus = "idle", 2000);
39 } catch (e) {
40 console.error("sync failed:", e);
41 syncStatus = "error";
42 setTimeout(() => syncStatus = "idle", 2000);
43 }
44 };
45
46 const handleClose = () => {
47 open = false;
48 };
49
50 const handleKeydown = (e: KeyboardEvent) => {
51 if (!open) return;
52 if (e.key === "Escape") {
53 e.preventDefault();
54 handleClose();
55 }
56 };
57</script>
58
59<svelte:window onkeydown={handleKeydown} />
60
61{#if open}
62 <div
63 class="overlay"
64 onclick={handleClose}
65 onkeydown={handleKeydown}
66 role="dialog"
67 aria-modal="true"
68 tabindex="-1"
69 >
70 <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
71 <div class="panel" onclick={(e) => e.stopPropagation()} role="document">
72 <div class="header">
73 <h3>settings</h3>
74 <button class="close-btn" onclick={handleClose}>×</button>
75 </div>
76
77 <div class="section">
78 <span class="label">accent color</span>
79 <div class="swatches">
80 {#each ACCENT_PRESETS as preset (preset.color)}
81 <button
82 class="swatch"
83 class:active={prefs.accentColor === preset.color}
84 style="background: {preset.color}"
85 onclick={() => handleAccentChange(preset.color)}
86 title={preset.name}
87 ></button>
88 {/each}
89 <input
90 type="color"
91 class="color-picker"
92 value={prefs.accentColor}
93 oninput={(e) => handleAccentChange((e.target as HTMLInputElement).value)}
94 />
95 </div>
96 </div>
97
98 <div class="section">
99 <span class="label">font</span>
100 <div class="font-options">
101 {#each FONT_PRESETS as preset (preset.value)}
102 <button
103 class="font-btn"
104 class:active={prefs.font === preset.value}
105 style="font-family: {preset.value}"
106 onclick={() => handleFontChange(preset.value)}
107 >
108 {preset.name}
109 </button>
110 {/each}
111 </div>
112 </div>
113
114 {#if auth.loggedIn}
115 <div class="section">
116 <button class="sync-btn" onclick={handleSync} disabled={syncStatus === "saving"}>
117 {#if syncStatus === "saving"}
118 saving...
119 {:else if syncStatus === "saved"}
120 saved!
121 {:else if syncStatus === "error"}
122 failed
123 {:else}
124 save preferences
125 {/if}
126 </button>
127 <p class="hint">synced across your devices</p>
128 </div>
129 {/if}
130 </div>
131 </div>
132{/if}
133
134<style>
135 .overlay {
136 position: fixed;
137 inset: 0;
138 background: rgba(0, 0, 0, 0.7);
139 display: flex;
140 align-items: center;
141 justify-content: center;
142 z-index: 1000;
143 }
144
145 .panel {
146 background: #141414;
147 border: 1px solid #333;
148 border-radius: 12px;
149 padding: 20px;
150 min-width: 280px;
151 max-width: 360px;
152 }
153
154 .header {
155 display: flex;
156 justify-content: space-between;
157 align-items: center;
158 margin-bottom: 20px;
159 }
160
161 h3 {
162 margin: 0;
163 font-size: 20px;
164 font-weight: 500;
165 color: #fff;
166 }
167
168 .close-btn {
169 background: transparent;
170 border: none;
171 color: #666;
172 font-size: 24px;
173 cursor: pointer;
174 padding: 0;
175 line-height: 1;
176 }
177
178 .close-btn:hover {
179 color: #fff;
180 }
181
182 .section {
183 margin-bottom: 20px;
184 }
185
186 .section:last-child {
187 margin-bottom: 0;
188 }
189
190 .label {
191 display: block;
192 font-size: 12px;
193 color: #888;
194 margin-bottom: 8px;
195 text-transform: lowercase;
196 }
197
198 .swatches {
199 display: flex;
200 gap: 8px;
201 flex-wrap: wrap;
202 align-items: center;
203 }
204
205 .swatch {
206 width: 28px;
207 height: 28px;
208 border-radius: 6px;
209 border: 2px solid transparent;
210 cursor: pointer;
211 transition: all 0.15s ease;
212 }
213
214 .swatch:hover {
215 transform: scale(1.1);
216 }
217
218 .swatch.active {
219 border-color: #fff;
220 box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
221 }
222
223 .color-picker {
224 width: 28px;
225 height: 28px;
226 border: none;
227 border-radius: 6px;
228 cursor: pointer;
229 background: transparent;
230 padding: 0;
231 }
232
233 .color-picker::-webkit-color-swatch-wrapper {
234 padding: 0;
235 }
236
237 .color-picker::-webkit-color-swatch {
238 border: 2px dashed #444;
239 border-radius: 6px;
240 }
241
242 .font-options {
243 display: flex;
244 gap: 8px;
245 flex-wrap: wrap;
246 }
247
248 .font-btn {
249 padding: 6px 12px;
250 background: #1a1a1a;
251 border: 1px solid #333;
252 border-radius: 6px;
253 color: #ccc;
254 font-size: 13px;
255 cursor: pointer;
256 transition: all 0.15s ease;
257 }
258
259 .font-btn:hover {
260 background: #252525;
261 border-color: #444;
262 }
263
264 .font-btn.active {
265 border-color: var(--accent, #6366f1);
266 color: var(--accent, #6366f1);
267 }
268
269 .sync-btn {
270 width: 100%;
271 padding: 10px;
272 background: var(--accent, #6366f1);
273 border: none;
274 border-radius: 6px;
275 color: #fff;
276 font-size: 13px;
277 cursor: pointer;
278 transition: opacity 0.15s ease;
279 }
280
281 .sync-btn:hover {
282 opacity: 0.9;
283 }
284
285 .hint {
286 margin: 8px 0 0;
287 font-size: 11px;
288 color: #666;
289 text-align: center;
290 }
291</style>