at main 5.9 kB view raw
1<script lang="ts"> 2 import { onMount } from 'svelte'; 3 import { preferences } from '$lib/preferences.svelte'; 4 5 let showSettings = $state(false); 6 7 const presetColors = [ 8 { name: 'blue', value: '#6a9fff' }, 9 { name: 'purple', value: '#a78bfa' }, 10 { name: 'pink', value: '#f472b6' }, 11 { name: 'green', value: '#4ade80' }, 12 { name: 'orange', value: '#fb923c' }, 13 { name: 'red', value: '#ef4444' } 14 ]; 15 16 // derive from preferences store 17 let currentColor = $derived(preferences.accentColor ?? '#6a9fff'); 18 19 // apply color when it changes 20 $effect(() => { 21 if (currentColor) { 22 applyColorLocally(currentColor); 23 } 24 }); 25 26 onMount(() => { 27 // apply initial color from localStorage while waiting for preferences 28 const saved = localStorage.getItem('accentColor'); 29 if (saved) { 30 applyColorLocally(saved); 31 } 32 }); 33 34 function applyColorLocally(color: string) { 35 document.documentElement.style.setProperty('--accent', color); 36 37 // calculate hover color 38 const r = parseInt(color.slice(1, 3), 16); 39 const g = parseInt(color.slice(3, 5), 16); 40 const b = parseInt(color.slice(5, 7), 16); 41 const hover = `rgb(${Math.min(255, r + 30)}, ${Math.min(255, g + 30)}, ${Math.min(255, b + 30)})`; 42 document.documentElement.style.setProperty('--accent-hover', hover); 43 } 44 45 async function applyColor(color: string) { 46 applyColorLocally(color); 47 localStorage.setItem('accentColor', color); 48 await preferences.update({ accent_color: color }); 49 } 50 51 function handleColorInput(e: Event) { 52 const input = e.target as HTMLInputElement; 53 applyColor(input.value); 54 } 55 56 function selectPreset(color: string) { 57 applyColor(color); 58 } 59 60 function toggleSettings() { 61 showSettings = !showSettings; 62 } 63</script> 64 65<div class="color-settings"> 66 <button class="settings-toggle" onclick={toggleSettings} title="customize accent color"> 67 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 68 <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path> 69 <circle cx="12" cy="12" r="3"></circle> 70 </svg> 71 </button> 72 73 {#if showSettings} 74 <div class="settings-panel"> 75 <div class="panel-header"> 76 <span>accent color</span> 77 <button class="close-btn" onclick={toggleSettings}>×</button> 78 </div> 79 80 <div class="color-picker-row"> 81 <input 82 type="color" 83 value={currentColor} 84 oninput={handleColorInput} 85 class="color-input" 86 /> 87 <span class="color-value">{currentColor}</span> 88 </div> 89 90 <div class="presets"> 91 <span class="presets-label">presets</span> 92 <div class="preset-grid"> 93 {#each presetColors as preset} 94 <button 95 class="preset-btn" 96 class:active={currentColor.toLowerCase() === preset.value.toLowerCase()} 97 style="background: {preset.value}" 98 onclick={() => selectPreset(preset.value)} 99 title={preset.name} 100 ></button> 101 {/each} 102 </div> 103 </div> 104 </div> 105 {/if} 106</div> 107 108<style> 109 .color-settings { 110 position: relative; 111 } 112 113 .settings-toggle { 114 background: transparent; 115 border: 1px solid var(--border-default); 116 color: var(--text-secondary); 117 padding: 0.5rem; 118 border-radius: var(--radius-sm); 119 cursor: pointer; 120 transition: all 0.2s; 121 display: flex; 122 align-items: center; 123 justify-content: center; 124 } 125 126 .settings-toggle:hover { 127 color: var(--accent); 128 border-color: var(--accent); 129 } 130 131 .settings-panel { 132 position: absolute; 133 top: calc(100% + 0.5rem); 134 right: 0; 135 background: var(--bg-secondary); 136 border: 1px solid var(--border-default); 137 border-radius: var(--radius-base); 138 padding: 1rem; 139 min-width: 240px; 140 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); 141 z-index: 1000; 142 } 143 144 .panel-header { 145 display: flex; 146 justify-content: space-between; 147 align-items: center; 148 margin-bottom: 1rem; 149 color: var(--text-primary); 150 font-size: var(--text-base); 151 } 152 153 .close-btn { 154 background: transparent; 155 border: none; 156 color: var(--text-secondary); 157 font-size: var(--text-3xl); 158 cursor: pointer; 159 padding: 0; 160 width: 24px; 161 height: 24px; 162 display: flex; 163 align-items: center; 164 justify-content: center; 165 transition: color 0.2s; 166 } 167 168 .close-btn:hover { 169 color: var(--text-primary); 170 } 171 172 .color-picker-row { 173 display: flex; 174 align-items: center; 175 gap: 0.75rem; 176 margin-bottom: 1rem; 177 padding-bottom: 1rem; 178 border-bottom: 1px solid var(--border-subtle); 179 } 180 181 .color-input { 182 width: 48px; 183 height: 32px; 184 border: 1px solid var(--border-default); 185 border-radius: var(--radius-sm); 186 cursor: pointer; 187 background: transparent; 188 } 189 190 .color-input::-webkit-color-swatch-wrapper { 191 padding: 2px; 192 } 193 194 .color-input::-webkit-color-swatch { 195 border: none; 196 border-radius: 2px; 197 } 198 199 .color-value { 200 font-family: monospace; 201 font-size: var(--text-sm); 202 color: var(--text-secondary); 203 } 204 205 .presets { 206 display: flex; 207 flex-direction: column; 208 gap: 0.5rem; 209 } 210 211 .presets-label { 212 font-size: var(--text-sm); 213 color: var(--text-tertiary); 214 } 215 216 .preset-grid { 217 display: grid; 218 grid-template-columns: repeat(6, 1fr); 219 gap: 0.5rem; 220 } 221 222 .preset-btn { 223 width: 32px; 224 height: 32px; 225 border-radius: var(--radius-sm); 226 border: 2px solid transparent; 227 cursor: pointer; 228 transition: all 0.2s; 229 padding: 0; 230 } 231 232 .preset-btn:hover { 233 transform: scale(1.1); 234 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); 235 } 236 237 .preset-btn.active { 238 border-color: var(--text-primary); 239 box-shadow: 0 0 0 1px var(--bg-secondary), 0 2px 8px rgba(0, 0, 0, 0.3); 240 } 241</style>