powerpointproto slides.waow.tech
slides
at main 291 lines 6.3 kB view raw
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>