feat: add light/dark theme support with CSS variables (#441)

- Replace hardcoded colors across 35 files with CSS custom properties
- Add theme switcher (dark/light/system) in settings menu
- Define semantic color tokens: bg-*, border-*, text-*, accent-*
- Light theme adapts all UI elements including tracks, header, tags
- Remove zen mode feature (was only hiding a few elements)
- Use color-mix() for derived colors to maintain consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>

authored by zzstoatzz.io Claude and committed by GitHub d9e191fd d4c28ec5

+2 -1
docs/frontend/keyboard-shortcuts.md
··· 10 10 11 11 ### Q - toggle queue 12 12 13 - **location**: `frontend/src/routes/+layout.svelte:27-48` 13 + **location**: `frontend/src/routes/+layout.svelte` 14 14 15 15 toggles the queue sidebar visibility. 16 16 ··· 79 79 - **shift + arrow** - navigate queue 80 80 - **/** - focus search (common pattern) 81 81 - **esc** - close overlays/modals 82 + - **T** - cycle theme (dark/light/system) 82 83 83 84 ## design principles 84 85
+18 -18
frontend/src/lib/components/AlbumSelect.svelte
··· 100 100 .album-input { 101 101 width: 100%; 102 102 padding: 0.75rem; 103 - background: #0a0a0a; 104 - border: 1px solid #333; 103 + background: var(--bg-primary); 104 + border: 1px solid var(--border-default); 105 105 border-radius: 4px; 106 - color: white; 106 + color: var(--text-primary); 107 107 font-size: 1rem; 108 108 font-family: inherit; 109 109 transition: all 0.2s; ··· 111 111 112 112 .album-input:focus { 113 113 outline: none; 114 - border-color: #3a7dff; 114 + border-color: var(--accent); 115 115 } 116 116 117 117 .album-input:disabled { ··· 125 125 width: 100%; 126 126 max-height: 300px; 127 127 overflow-y: auto; 128 - background: #1a1a1a; 129 - border: 1px solid #333; 128 + background: var(--bg-tertiary); 129 + border: 1px solid var(--border-default); 130 130 border-radius: 4px; 131 131 margin-top: 0.25rem; 132 132 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); ··· 138 138 } 139 139 140 140 .album-results::-webkit-scrollbar-track { 141 - background: #0a0a0a; 141 + background: var(--bg-primary); 142 142 border-radius: 4px; 143 143 } 144 144 145 145 .album-results::-webkit-scrollbar-thumb { 146 - background: #333; 146 + background: var(--border-default); 147 147 border-radius: 4px; 148 148 } 149 149 150 150 .album-results::-webkit-scrollbar-thumb:hover { 151 - background: #444; 151 + background: var(--border-emphasis); 152 152 } 153 153 154 154 /* firefox scrollbar */ 155 155 .album-results { 156 156 scrollbar-width: thin; 157 - scrollbar-color: #333 #0a0a0a; 157 + scrollbar-color: var(--border-default) var(--bg-primary); 158 158 } 159 159 160 160 .album-result-item { ··· 165 165 padding: 0.75rem; 166 166 background: transparent; 167 167 border: none; 168 - border-bottom: 1px solid #2a2a2a; 169 - color: white; 168 + border-bottom: 1px solid var(--border-subtle); 169 + color: var(--text-primary); 170 170 text-align: left; 171 171 font-family: inherit; 172 172 cursor: pointer; ··· 179 179 } 180 180 181 181 .album-result-item:hover { 182 - background: #222; 182 + background: var(--bg-hover); 183 183 } 184 184 185 185 .album-result-item.exact-match { 186 - background: rgba(58, 125, 255, 0.1); 187 - border-left: 3px solid #3a7dff; 186 + background: color-mix(in srgb, var(--accent) 10%, transparent); 187 + border-left: 3px solid var(--accent); 188 188 } 189 189 190 190 .album-info { ··· 195 195 196 196 .album-title { 197 197 font-weight: 500; 198 - color: #e8e8e8; 198 + color: var(--text-primary); 199 199 margin-bottom: 0.125rem; 200 200 overflow: hidden; 201 201 text-overflow: ellipsis; ··· 204 204 205 205 .album-stats { 206 206 font-size: 0.85rem; 207 - color: #888; 207 + color: var(--text-tertiary); 208 208 overflow: hidden; 209 209 text-overflow: ellipsis; 210 210 white-space: nowrap; ··· 213 213 .similar-hint { 214 214 margin-top: 0.5rem; 215 215 font-size: 0.85rem; 216 - color: #ff9800; 216 + color: var(--warning); 217 217 font-style: italic; 218 218 margin-bottom: 0; 219 219 }
+24 -24
frontend/src/lib/components/BrokenTracks.svelte
··· 192 192 193 193 .broken-tracks-section { 194 194 margin-bottom: 3rem; 195 - background: rgba(255, 152, 0, 0.05); 196 - border: 1px solid rgba(255, 152, 0, 0.2); 195 + background: color-mix(in srgb, var(--warning) 5%, transparent); 196 + border: 1px solid color-mix(in srgb, var(--warning) 20%, transparent); 197 197 border-radius: 8px; 198 198 padding: 1.5rem; 199 199 } ··· 215 215 .section-header h2 { 216 216 font-size: 1.5rem; 217 217 margin: 0; 218 - color: #ff9800; 218 + color: var(--warning); 219 219 } 220 220 221 221 .restore-all-btn { 222 222 padding: 0.5rem 1rem; 223 - background: rgba(255, 152, 0, 0.2); 224 - border: 1px solid rgba(255, 152, 0, 0.5); 223 + background: color-mix(in srgb, var(--warning) 20%, transparent); 224 + border: 1px solid color-mix(in srgb, var(--warning) 50%, transparent); 225 225 border-radius: 4px; 226 - color: #ff9800; 226 + color: var(--warning); 227 227 font-family: inherit; 228 228 font-size: 0.9rem; 229 229 font-weight: 600; ··· 233 233 } 234 234 235 235 .restore-all-btn:hover:not(:disabled) { 236 - background: rgba(255, 152, 0, 0.3); 237 - border-color: #ff9800; 236 + background: color-mix(in srgb, var(--warning) 30%, transparent); 237 + border-color: var(--warning); 238 238 transform: translateY(-1px); 239 239 } 240 240 ··· 245 245 } 246 246 247 247 .count-badge { 248 - background: rgba(255, 152, 0, 0.2); 249 - color: #ff9800; 248 + background: color-mix(in srgb, var(--warning) 20%, transparent); 249 + color: var(--warning); 250 250 padding: 0.25rem 0.6rem; 251 251 border-radius: 12px; 252 252 font-size: 0.85rem; ··· 261 261 } 262 262 263 263 .broken-track-item { 264 - background: #1a1a1a; 265 - border: 1px solid rgba(255, 152, 0, 0.3); 264 + background: var(--bg-tertiary); 265 + border: 1px solid color-mix(in srgb, var(--warning) 30%, transparent); 266 266 border-radius: 6px; 267 267 padding: 1rem; 268 268 display: flex; ··· 293 293 font-weight: 600; 294 294 font-size: 1rem; 295 295 margin-bottom: 0.25rem; 296 - color: #fff; 296 + color: var(--text-primary); 297 297 } 298 298 299 299 .track-meta { 300 300 font-size: 0.9rem; 301 - color: #aaa; 301 + color: var(--text-secondary); 302 302 margin-bottom: 0.5rem; 303 303 } 304 304 305 305 .issue-description { 306 306 font-size: 0.85rem; 307 - color: #ff9800; 307 + color: var(--warning); 308 308 } 309 309 310 310 .restore-btn { 311 311 padding: 0.5rem 1rem; 312 - background: rgba(255, 152, 0, 0.15); 313 - border: 1px solid rgba(255, 152, 0, 0.4); 312 + background: color-mix(in srgb, var(--warning) 15%, transparent); 313 + border: 1px solid color-mix(in srgb, var(--warning) 40%, transparent); 314 314 border-radius: 4px; 315 - color: #ff9800; 315 + color: var(--warning); 316 316 font-family: inherit; 317 317 font-size: 0.9rem; 318 318 font-weight: 500; ··· 323 323 } 324 324 325 325 .restore-btn:hover:not(:disabled) { 326 - background: rgba(255, 152, 0, 0.25); 327 - border-color: #ff9800; 326 + background: color-mix(in srgb, var(--warning) 25%, transparent); 327 + border-color: var(--warning); 328 328 transform: translateY(-1px); 329 329 } 330 330 ··· 335 335 } 336 336 337 337 .info-box { 338 - background: #0a0a0a; 339 - border: 1px solid #2a2a2a; 338 + background: var(--bg-primary); 339 + border: 1px solid var(--border-subtle); 340 340 border-radius: 6px; 341 341 padding: 1rem; 342 342 font-size: 0.9rem; 343 - color: #aaa; 343 + color: var(--text-secondary); 344 344 } 345 345 346 346 .info-box strong { 347 347 display: block; 348 - color: #ff9800; 348 + color: var(--warning); 349 349 margin-bottom: 0.5rem; 350 350 } 351 351
+19 -19
frontend/src/lib/components/HandleAutocomplete.svelte
··· 126 126 .input-wrapper input { 127 127 width: 100%; 128 128 padding: 0.75rem; 129 - background: #0a0a0a; 130 - border: 1px solid #333; 129 + background: var(--bg-primary); 130 + border: 1px solid var(--border-default); 131 131 border-radius: 4px; 132 - color: white; 132 + color: var(--text-primary); 133 133 font-size: 1rem; 134 134 font-family: inherit; 135 135 transition: border-color 0.2s; ··· 138 138 139 139 .input-wrapper input:focus { 140 140 outline: none; 141 - border-color: var(--accent, #3a7dff); 141 + border-color: var(--accent); 142 142 } 143 143 144 144 .input-wrapper input:disabled { ··· 147 147 } 148 148 149 149 .input-wrapper input::placeholder { 150 - color: #666; 150 + color: var(--text-muted); 151 151 } 152 152 153 153 .spinner { ··· 155 155 right: 0.75rem; 156 156 top: 50%; 157 157 transform: translateY(-50%); 158 - color: #666; 158 + color: var(--text-muted); 159 159 font-size: 0.85rem; 160 160 } 161 161 ··· 165 165 width: 100%; 166 166 max-height: 240px; 167 167 overflow-y: auto; 168 - background: #1a1a1a; 169 - border: 1px solid #333; 168 + background: var(--bg-tertiary); 169 + border: 1px solid var(--border-default); 170 170 border-radius: 4px; 171 171 margin-top: 0.25rem; 172 172 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); 173 173 scrollbar-width: thin; 174 - scrollbar-color: #333 #0a0a0a; 174 + scrollbar-color: var(--border-default) var(--bg-primary); 175 175 } 176 176 177 177 .results::-webkit-scrollbar { ··· 179 179 } 180 180 181 181 .results::-webkit-scrollbar-track { 182 - background: #0a0a0a; 182 + background: var(--bg-primary); 183 183 border-radius: 4px; 184 184 } 185 185 186 186 .results::-webkit-scrollbar-thumb { 187 - background: #333; 187 + background: var(--border-default); 188 188 border-radius: 4px; 189 189 } 190 190 191 191 .results::-webkit-scrollbar-thumb:hover { 192 - background: #444; 192 + background: var(--border-emphasis); 193 193 } 194 194 195 195 .result-item { ··· 200 200 padding: 0.75rem; 201 201 background: transparent; 202 202 border: none; 203 - border-bottom: 1px solid #2a2a2a; 204 - color: white; 203 + border-bottom: 1px solid var(--border-subtle); 204 + color: var(--text-primary); 205 205 text-align: left; 206 206 font-family: inherit; 207 207 cursor: pointer; ··· 213 213 } 214 214 215 215 .result-item:hover { 216 - background: #222; 216 + background: var(--bg-hover); 217 217 } 218 218 219 219 .avatar { ··· 221 221 height: 36px; 222 222 border-radius: 50%; 223 223 object-fit: cover; 224 - border: 2px solid #333; 224 + border: 2px solid var(--border-default); 225 225 flex-shrink: 0; 226 226 } 227 227 ··· 229 229 width: 36px; 230 230 height: 36px; 231 231 border-radius: 50%; 232 - background: #333; 232 + background: var(--border-default); 233 233 flex-shrink: 0; 234 234 } 235 235 ··· 241 241 242 242 .display-name { 243 243 font-weight: 500; 244 - color: #e8e8e8; 244 + color: var(--text-primary); 245 245 margin-bottom: 0.125rem; 246 246 overflow: hidden; 247 247 text-overflow: ellipsis; ··· 250 250 251 251 .handle { 252 252 font-size: 0.85rem; 253 - color: #888; 253 + color: var(--text-tertiary); 254 254 overflow: hidden; 255 255 text-overflow: ellipsis; 256 256 white-space: nowrap;
+27 -27
frontend/src/lib/components/HandleSearch.svelte
··· 172 172 .search-input { 173 173 width: 100%; 174 174 padding: 0.75rem; 175 - background: #0a0a0a; 176 - border: 1px solid #333; 175 + background: var(--bg-primary); 176 + border: 1px solid var(--border-default); 177 177 border-radius: 4px; 178 - color: white; 178 + color: var(--text-primary); 179 179 font-size: 1rem; 180 180 font-family: inherit; 181 181 transition: all 0.2s; ··· 183 183 184 184 .search-input:focus { 185 185 outline: none; 186 - border-color: #3a7dff; 186 + border-color: var(--accent); 187 187 } 188 188 189 189 .search-input:disabled { ··· 197 197 top: 50%; 198 198 transform: translateY(-50%); 199 199 font-size: 0.85rem; 200 - color: #666; 200 + color: var(--text-muted); 201 201 } 202 202 203 203 .search-results { ··· 206 206 width: 100%; 207 207 max-height: 300px; 208 208 overflow-y: auto; 209 - background: #1a1a1a; 210 - border: 1px solid #333; 209 + background: var(--bg-tertiary); 210 + border: 1px solid var(--border-default); 211 211 border-radius: 4px; 212 212 margin-top: 0.25rem; 213 213 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); ··· 219 219 } 220 220 221 221 .search-results::-webkit-scrollbar-track { 222 - background: #0a0a0a; 222 + background: var(--bg-primary); 223 223 border-radius: 4px; 224 224 } 225 225 226 226 .search-results::-webkit-scrollbar-thumb { 227 - background: #333; 227 + background: var(--border-default); 228 228 border-radius: 4px; 229 229 } 230 230 231 231 .search-results::-webkit-scrollbar-thumb:hover { 232 - background: #444; 232 + background: var(--border-emphasis); 233 233 } 234 234 235 235 /* firefox scrollbar */ 236 236 .search-results { 237 237 scrollbar-width: thin; 238 - scrollbar-color: #333 #0a0a0a; 238 + scrollbar-color: var(--border-default) var(--bg-primary); 239 239 } 240 240 241 241 .search-result-item { ··· 246 246 padding: 0.75rem; 247 247 background: transparent; 248 248 border: none; 249 - border-bottom: 1px solid #2a2a2a; 250 - color: white; 249 + border-bottom: 1px solid var(--border-subtle); 250 + color: var(--text-primary); 251 251 text-align: left; 252 252 font-family: inherit; 253 253 cursor: pointer; ··· 260 260 } 261 261 262 262 .search-result-item:hover:not(:disabled) { 263 - background: #222; 263 + background: var(--bg-hover); 264 264 } 265 265 266 266 .search-result-item.selected, ··· 274 274 height: 36px; 275 275 border-radius: 50%; 276 276 object-fit: cover; 277 - border: 2px solid #333; 277 + border: 2px solid var(--border-default); 278 278 flex-shrink: 0; 279 279 } 280 280 ··· 286 286 287 287 .result-name { 288 288 font-weight: 500; 289 - color: #e8e8e8; 289 + color: var(--text-primary); 290 290 margin-bottom: 0.125rem; 291 291 overflow: hidden; 292 292 text-overflow: ellipsis; ··· 295 295 296 296 .result-handle { 297 297 font-size: 0.85rem; 298 - color: #888; 298 + color: var(--text-tertiary); 299 299 overflow: hidden; 300 300 text-overflow: ellipsis; 301 301 white-space: nowrap; ··· 313 313 align-items: center; 314 314 gap: 0.5rem; 315 315 padding: 0.5rem 0.75rem; 316 - background: #1a2330; 317 - border: 1px solid #2a3a4a; 316 + background: color-mix(in srgb, var(--accent) 10%, var(--bg-tertiary)); 317 + border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--border-subtle)); 318 318 border-radius: 20px; 319 - color: #e8e8e8; 319 + color: var(--text-primary); 320 320 font-size: 0.9rem; 321 321 } 322 322 ··· 325 325 height: 24px; 326 326 border-radius: 50%; 327 327 object-fit: cover; 328 - border: 1px solid #333; 328 + border: 1px solid var(--border-default); 329 329 } 330 330 331 331 .chip-name { ··· 335 335 .chip-remove { 336 336 background: transparent; 337 337 border: none; 338 - color: #888; 338 + color: var(--text-tertiary); 339 339 font-size: 1.3rem; 340 340 font-family: inherit; 341 341 line-height: 1; ··· 350 350 } 351 351 352 352 .chip-remove:hover:not(:disabled) { 353 - color: #ff6b6b; 353 + color: var(--error); 354 354 } 355 355 356 356 .chip-remove:disabled { ··· 361 361 .max-features-message { 362 362 margin-top: 0.5rem; 363 363 font-size: 0.85rem; 364 - color: #ff9966; 364 + color: var(--warning); 365 365 } 366 366 367 367 .no-results-message { 368 368 margin-top: 0.5rem; 369 369 padding: 0.75rem; 370 - background: #2a1a1a; 371 - border: 1px solid #4a3030; 370 + background: color-mix(in srgb, var(--warning) 10%, var(--bg-primary)); 371 + border: 1px solid color-mix(in srgb, var(--warning) 20%, var(--border-subtle)); 372 372 border-radius: 4px; 373 - color: #ff9966; 373 + color: var(--warning); 374 374 font-size: 0.9rem; 375 375 text-align: center; 376 376 }
+9 -8
frontend/src/lib/components/Header.svelte
··· 87 87 88 88 <style> 89 89 header { 90 - border-bottom: 1px solid #333; 90 + border-bottom: 1px solid var(--border-default); 91 91 margin-bottom: 2rem; 92 92 position: relative; 93 + z-index: 50; 93 94 } 94 95 95 96 .header-content { ··· 194 195 195 196 .nav-link:hover { 196 197 color: var(--text-primary); 197 - background: #1a1a1a; 198 - border-color: #333; 198 + background: var(--bg-tertiary); 199 + border-color: var(--border-default); 199 200 } 200 201 201 202 .nav-link svg { ··· 208 209 text-decoration: none; 209 210 font-size: 0.9rem; 210 211 padding: 0.4rem 0.75rem; 211 - background: #1a1a1a; 212 + background: var(--bg-tertiary); 212 213 border-radius: 6px; 213 - border: 1px solid #333; 214 + border: 1px solid var(--border-default); 214 215 transition: all 0.2s; 215 216 white-space: nowrap; 216 217 } ··· 218 219 .user-handle:hover { 219 220 border-color: var(--accent); 220 221 color: var(--accent); 221 - background: #222; 222 + background: var(--bg-hover); 222 223 } 223 224 224 225 .btn-primary { ··· 241 242 242 243 .btn-logout { 243 244 background: transparent; 244 - border: 1px solid #444; 245 - color: #b0b0b0; 245 + border: 1px solid var(--border-emphasis); 246 + color: var(--text-secondary); 246 247 padding: 0.5rem 1rem; 247 248 border-radius: 6px; 248 249 font-size: 0.9rem;
+2 -2
frontend/src/lib/components/HiddenTagsFilter.svelte
··· 193 193 } 194 194 195 195 .tag-chip:hover { 196 - border-color: rgba(255, 107, 107, 0.5); 197 - color: #ff6b6b; 196 + border-color: color-mix(in srgb, var(--error) 50%, transparent); 197 + color: var(--error); 198 198 } 199 199 200 200 .tag-chip:active {
+7 -7
frontend/src/lib/components/LikeButton.svelte
··· 84 84 align-items: center; 85 85 justify-content: center; 86 86 background: transparent; 87 - border: 1px solid #333; 87 + border: 1px solid var(--border-default); 88 88 border-radius: 4px; 89 - color: #888; 89 + color: var(--text-tertiary); 90 90 cursor: pointer; 91 91 transition: all 0.2s; 92 92 } 93 93 94 94 .like-button:hover { 95 - background: #1a1a1a; 95 + background: var(--bg-tertiary); 96 96 border-color: var(--accent); 97 97 color: var(--accent); 98 98 } ··· 114 114 115 115 .like-button.disabled-state { 116 116 opacity: 0.4; 117 - border-color: #555; 118 - color: #666; 117 + border-color: var(--text-muted); 118 + color: var(--text-muted); 119 119 } 120 120 121 121 .like-button.disabled-state:hover { 122 122 background: transparent; 123 - border-color: #555; 124 - color: #666; 123 + border-color: var(--text-muted); 124 + color: var(--text-muted); 125 125 } 126 126 127 127 .like-button.loading {
+14 -14
frontend/src/lib/components/LikersTooltip.svelte
··· 110 110 left: 50%; 111 111 transform: translateX(-50%); 112 112 margin-bottom: 0.5rem; 113 - background: #1a1a1a; 114 - border: 1px solid #333; 113 + background: var(--bg-secondary); 114 + border: 1px solid var(--border-default); 115 115 border-radius: 8px; 116 116 padding: 0.75rem; 117 117 min-width: 240px; ··· 124 124 .loading, 125 125 .error, 126 126 .empty { 127 - color: #888; 127 + color: var(--text-tertiary); 128 128 font-size: 0.85rem; 129 129 text-align: center; 130 130 padding: 0.5rem; 131 131 } 132 132 133 133 .error { 134 - color: #e74c3c; 134 + color: var(--error); 135 135 } 136 136 137 137 .likers-list { ··· 151 151 } 152 152 153 153 .liker:hover { 154 - background: #252525; 154 + background: var(--bg-hover); 155 155 } 156 156 157 157 .avatar, ··· 164 164 165 165 .avatar { 166 166 object-fit: cover; 167 - border: 1px solid #333; 167 + border: 1px solid var(--border-default); 168 168 } 169 169 170 170 .avatar-placeholder { 171 - background: #333; 171 + background: var(--border-default); 172 172 display: flex; 173 173 align-items: center; 174 174 justify-content: center; 175 - color: #888; 175 + color: var(--text-tertiary); 176 176 font-weight: 600; 177 177 font-size: 0.9rem; 178 178 } ··· 183 183 } 184 184 185 185 .display-name { 186 - color: #e8e8e8; 186 + color: var(--text-primary); 187 187 font-weight: 500; 188 188 font-size: 0.9rem; 189 189 white-space: nowrap; ··· 192 192 } 193 193 194 194 .handle { 195 - color: #888; 195 + color: var(--text-tertiary); 196 196 font-size: 0.8rem; 197 197 white-space: nowrap; 198 198 overflow: hidden; ··· 200 200 } 201 201 202 202 .liked-time { 203 - color: #666; 203 + color: var(--text-muted); 204 204 font-size: 0.75rem; 205 205 flex-shrink: 0; 206 206 } ··· 211 211 } 212 212 213 213 .likers-list::-webkit-scrollbar-track { 214 - background: #1a1a1a; 214 + background: var(--bg-tertiary); 215 215 } 216 216 217 217 .likers-list::-webkit-scrollbar-thumb { 218 - background: #333; 218 + background: var(--border-default); 219 219 border-radius: 3px; 220 220 } 221 221 222 222 .likers-list::-webkit-scrollbar-thumb:hover { 223 - background: #444; 223 + background: var(--border-emphasis); 224 224 } 225 225 </style>
+2 -2
frontend/src/lib/components/LinksMenu.svelte
··· 120 120 width: 32px; 121 121 height: 32px; 122 122 background: transparent; 123 - border: 1px solid #333; 123 + border: 1px solid var(--border-default); 124 124 border-radius: 6px; 125 125 color: var(--text-secondary); 126 126 cursor: pointer; ··· 128 128 } 129 129 130 130 .menu-button:hover { 131 - background: #1a1a1a; 131 + background: var(--bg-tertiary); 132 132 border-color: var(--accent); 133 133 color: var(--accent); 134 134 }
+18 -18
frontend/src/lib/components/MigrationBanner.svelte
··· 150 150 151 151 <style> 152 152 .migration-banner { 153 - background: var(--background-alt, #1a1a1a); 154 - border: 1px solid var(--border-color, #333); 153 + background: var(--bg-tertiary); 154 + border: 1px solid var(--border-default); 155 155 border-radius: 8px; 156 156 padding: 1rem; 157 157 margin-bottom: 1.5rem; ··· 171 171 172 172 .migration-message strong { 173 173 font-size: 1.1em; 174 - color: var(--text-primary, #fff); 174 + color: var(--text-primary); 175 175 } 176 176 177 177 .migration-message p { 178 178 margin: 0; 179 - color: var(--text-secondary, #aaa); 179 + color: var(--text-secondary); 180 180 font-size: 0.9em; 181 181 } 182 182 183 183 .error { 184 - color: var(--error-color, #ff6b6b); 184 + color: var(--error); 185 185 } 186 186 187 187 .success-message { 188 188 display: flex; 189 189 align-items: center; 190 190 gap: 1rem; 191 - background: rgba(81, 207, 102, 0.1); 192 - border: 1px solid rgba(81, 207, 102, 0.3); 191 + background: color-mix(in srgb, var(--success) 10%, transparent); 192 + border: 1px solid color-mix(in srgb, var(--success) 30%, transparent); 193 193 border-radius: 6px; 194 194 padding: 1rem; 195 195 animation: slideIn 0.3s ease-out; ··· 197 197 198 198 .success-icon { 199 199 font-size: 2rem; 200 - color: var(--success-color, #51cf66); 200 + color: var(--success); 201 201 animation: checkmark 0.5s ease-out; 202 202 } 203 203 204 204 .success-title { 205 205 font-weight: 600; 206 - color: var(--success-color, #51cf66); 206 + color: var(--success); 207 207 margin: 0; 208 208 } 209 209 210 210 .success-detail { 211 - color: var(--text-secondary, #aaa); 211 + color: var(--text-secondary); 212 212 margin: 0.25rem 0 0 0; 213 213 font-size: 0.85em; 214 214 } ··· 237 237 } 238 238 239 239 .collection-name { 240 - background: rgba(255, 255, 255, 0.05); 240 + background: color-mix(in srgb, var(--text-primary) 5%, transparent); 241 241 padding: 0.15em 0.4em; 242 242 border-radius: 3px; 243 243 font-family: monospace; 244 244 font-size: 0.95em; 245 - color: var(--text-primary, #fff); 245 + color: var(--text-primary); 246 246 } 247 247 248 248 .collection-link { 249 - color: var(--accent, #6a9fff); 249 + color: var(--accent); 250 250 text-decoration: none; 251 251 border-bottom: 1px solid transparent; 252 252 transition: border-color 0.2s; 253 253 } 254 254 255 255 .collection-link:hover { 256 - border-bottom-color: var(--accent, #6a9fff); 256 + border-bottom-color: var(--accent); 257 257 } 258 258 259 259 .migration-actions { ··· 274 274 } 275 275 276 276 .migrate-button { 277 - background: var(--primary-color, #007bff); 277 + background: var(--accent); 278 278 color: white; 279 279 } 280 280 ··· 284 284 285 285 .dismiss-button { 286 286 background: transparent; 287 - color: var(--text-secondary, #aaa); 288 - border: 1px solid var(--border-color, #333); 287 + color: var(--text-secondary); 288 + border: 1px solid var(--border-default); 289 289 } 290 290 291 291 .dismiss-button:hover:not(:disabled) { 292 - background: var(--background-hover, #222); 292 + background: var(--bg-hover); 293 293 } 294 294 295 295 button:disabled {
+1 -1
frontend/src/lib/components/PlatformStats.svelte
··· 181 181 display: block; 182 182 width: 100%; 183 183 height: 60px; 184 - background: linear-gradient(90deg, #1a1a1a 0%, #242424 50%, #1a1a1a 100%); 184 + background: linear-gradient(90deg, var(--bg-tertiary) 0%, var(--bg-hover) 50%, var(--bg-tertiary) 100%); 185 185 background-size: 200% 100%; 186 186 animation: shimmer 1.5s ease-in-out infinite; 187 187 border-radius: 6px;
+102 -2
frontend/src/lib/components/SettingsMenu.svelte
··· 1 1 <script lang="ts"> 2 2 import { onMount } from 'svelte'; 3 3 import { queue } from '$lib/queue.svelte'; 4 - import { preferences } from '$lib/preferences.svelte'; 4 + import { preferences, type Theme } from '$lib/preferences.svelte'; 5 5 6 6 let showSettings = $state(false); 7 7 ··· 14 14 { name: 'red', value: '#ef4444' } 15 15 ]; 16 16 17 + const themes: { value: Theme; label: string; icon: string }[] = [ 18 + { value: 'dark', label: 'dark', icon: 'moon' }, 19 + { value: 'light', label: 'light', icon: 'sun' }, 20 + { value: 'system', label: 'system', icon: 'auto' } 21 + ]; 22 + 17 23 // derive from preferences store 18 24 let currentColor = $derived(preferences.accentColor ?? '#6a9fff'); 19 25 let autoAdvance = $derived(preferences.autoAdvance); 26 + let currentTheme = $derived(preferences.theme); 20 27 21 28 // apply color when it changes 22 29 $effect(() => { ··· 73 80 queue.setAutoAdvance(value); 74 81 localStorage.setItem('autoAdvance', value ? '1' : '0'); 75 82 await preferences.update({ auto_advance: value }); 83 + } 84 + 85 + function selectTheme(theme: Theme) { 86 + preferences.setTheme(theme); 76 87 } 77 88 </script> 78 89 ··· 92 103 </div> 93 104 94 105 <section class="settings-section"> 106 + <h3>theme</h3> 107 + <div class="theme-buttons"> 108 + {#each themes as theme} 109 + <button 110 + class="theme-btn" 111 + class:active={currentTheme === theme.value} 112 + onclick={() => selectTheme(theme.value)} 113 + title={theme.label} 114 + > 115 + {#if theme.icon === 'moon'} 116 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 117 + <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /> 118 + </svg> 119 + {:else if theme.icon === 'sun'} 120 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 121 + <circle cx="12" cy="12" r="5" /> 122 + <path d="M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72 1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" /> 123 + </svg> 124 + {:else} 125 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 126 + <circle cx="12" cy="12" r="9" /> 127 + <path d="M12 3v18" /> 128 + <path d="M12 3a9 9 0 0 1 0 18" fill="currentColor" opacity="0.3" /> 129 + </svg> 130 + {/if} 131 + <span>{theme.label}</span> 132 + </button> 133 + {/each} 134 + </div> 135 + </section> 136 + 137 + <section class="settings-section"> 95 138 <h3>accent color</h3> 96 139 <div class="color-picker-row"> 97 140 <input type="color" value={currentColor} oninput={handleColorInput} class="color-input" /> ··· 120 163 </label> 121 164 <p class="toggle-hint">when a track ends, start the next item in your queue</p> 122 165 </section> 166 + 123 167 </div> 124 168 {/if} 125 169 </div> ··· 155 199 border: 1px solid var(--border-default); 156 200 border-radius: 8px; 157 201 padding: 1.25rem; 158 - min-width: 260px; 202 + min-width: 280px; 159 203 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45); 160 204 z-index: 1000; 161 205 display: flex; ··· 204 248 color: var(--text-tertiary); 205 249 } 206 250 251 + .theme-buttons { 252 + display: flex; 253 + gap: 0.5rem; 254 + } 255 + 256 + .theme-btn { 257 + flex: 1; 258 + display: flex; 259 + flex-direction: column; 260 + align-items: center; 261 + gap: 0.35rem; 262 + padding: 0.6rem 0.5rem; 263 + background: var(--bg-tertiary); 264 + border: 1px solid var(--border-default); 265 + border-radius: 6px; 266 + color: var(--text-secondary); 267 + cursor: pointer; 268 + transition: all 0.2s; 269 + } 270 + 271 + .theme-btn:hover { 272 + border-color: var(--accent); 273 + color: var(--accent); 274 + } 275 + 276 + .theme-btn.active { 277 + background: color-mix(in srgb, var(--accent) 15%, transparent); 278 + border-color: var(--accent); 279 + color: var(--accent); 280 + } 281 + 282 + .theme-btn svg { 283 + width: 18px; 284 + height: 18px; 285 + } 286 + 287 + .theme-btn span { 288 + font-size: 0.7rem; 289 + text-transform: uppercase; 290 + letter-spacing: 0.05em; 291 + } 292 + 207 293 .color-picker-row { 208 294 display: flex; 209 295 align-items: center; ··· 278 364 cursor: pointer; 279 365 transition: background 0.2s, border 0.2s; 280 366 border: 1px solid var(--border-default); 367 + flex-shrink: 0; 281 368 } 282 369 283 370 .toggle input::after { ··· 315 402 color: var(--text-tertiary); 316 403 font-size: 0.8rem; 317 404 line-height: 1.3; 405 + } 406 + 407 + @media (max-width: 768px) { 408 + .settings-panel { 409 + position: fixed; 410 + top: auto; 411 + bottom: calc(var(--player-height, 0px) + 1rem); 412 + right: 1rem; 413 + left: 1rem; 414 + min-width: auto; 415 + max-height: 70vh; 416 + overflow-y: auto; 417 + } 318 418 } 319 419 </style>
+3 -3
frontend/src/lib/components/ShareButton.svelte
··· 37 37 <style> 38 38 .share-btn { 39 39 background: transparent; 40 - border: 1px solid #333; 40 + border: 1px solid var(--border-default); 41 41 border-radius: 4px; 42 42 width: 32px; 43 43 height: 32px; 44 44 padding: 0; 45 - color: #888; 45 + color: var(--text-tertiary); 46 46 cursor: pointer; 47 47 display: flex; 48 48 align-items: center; ··· 62 62 top: -2rem; 63 63 left: 50%; 64 64 transform: translateX(-50%); 65 - background: #1a1a1a; 65 + background: var(--bg-tertiary); 66 66 border: 1px solid var(--accent); 67 67 color: var(--accent); 68 68 padding: 0.25rem 0.75rem;
+4 -4
frontend/src/lib/components/TagInput.svelte
··· 192 192 align-items: center; 193 193 gap: 0.35rem; 194 194 padding: 0.35rem 0.6rem; 195 - background: rgba(138, 179, 255, 0.1); 196 - border: 1px solid rgba(138, 179, 255, 0.2); 197 - color: #8ab3ff; 195 + background: color-mix(in srgb, var(--accent) 10%, transparent); 196 + border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent); 197 + color: var(--accent-hover); 198 198 border-radius: 20px; 199 199 font-size: 0.9rem; 200 200 font-weight: 500; ··· 218 218 } 219 219 220 220 .tag-remove:hover { 221 - color: #ff6b6b; 221 + color: var(--error); 222 222 } 223 223 224 224 .tag-remove:disabled {
+3 -3
frontend/src/lib/components/TrackActionsMenu.svelte
··· 153 153 align-items: center; 154 154 justify-content: center; 155 155 background: transparent; 156 - border: 1px solid #333; 156 + border: 1px solid var(--border-default); 157 157 border-radius: 4px; 158 - color: #888; 158 + color: var(--text-tertiary); 159 159 cursor: pointer; 160 160 transition: all 0.2s; 161 161 } 162 162 163 163 .menu-button:hover { 164 - background: #1a1a1a; 164 + background: var(--bg-tertiary); 165 165 border-color: var(--accent); 166 166 color: var(--accent); 167 167 }
+31 -31
frontend/src/lib/components/TrackItem.svelte
··· 270 270 display: flex; 271 271 align-items: center; 272 272 gap: 0.75rem; 273 - background: #141414; 274 - border: 1px solid #282828; 273 + background: var(--bg-secondary); 274 + border: 1px solid var(--border-subtle); 275 275 border-left: 3px solid transparent; 276 276 padding: 1rem; 277 277 transition: all 0.15s ease-in-out; 278 278 } 279 279 280 280 .track-container:hover { 281 - background: #1a1a1a; 281 + background: var(--bg-tertiary); 282 282 border-left-color: var(--accent); 283 - border-color: #333; 283 + border-color: var(--border-default); 284 284 transform: translateX(2px); 285 285 } 286 286 287 287 .track-container.playing { 288 - background: #1a2330; 288 + background: color-mix(in srgb, var(--accent) 10%, var(--bg-tertiary)); 289 289 border-left-color: var(--accent); 290 - border-color: #2a3a4a; 290 + border-color: color-mix(in srgb, var(--accent) 20%, var(--border-subtle)); 291 291 } 292 292 293 293 .track { ··· 314 314 justify-content: center; 315 315 border-radius: 4px; 316 316 overflow: hidden; 317 - background: #1a1a1a; 318 - border: 1px solid #282828; 317 + background: var(--bg-tertiary); 318 + border: 1px solid var(--border-subtle); 319 319 } 320 320 321 321 .track-avatar { ··· 342 342 } 343 343 344 344 .track-image-placeholder { 345 - color: #606060; 345 + color: var(--text-muted); 346 346 } 347 347 348 348 .track-avatar img { 349 349 border-radius: 50%; 350 - border: 2px solid #333; 350 + border: 2px solid var(--border-default); 351 351 transition: border-color 0.2s; 352 352 } 353 353 ··· 384 384 font-family: inherit; 385 385 font-weight: 600; 386 386 font-size: 1.05rem; 387 - color: #e8e8e8; 387 + color: var(--text-primary); 388 388 white-space: nowrap; 389 389 overflow: hidden; 390 390 text-overflow: ellipsis; ··· 395 395 flex-direction: column; 396 396 align-items: flex-start; 397 397 gap: 0.15rem; 398 - color: #b0b0b0; 398 + color: var(--text-secondary); 399 399 font-size: 0.9rem; 400 400 font-family: inherit; 401 401 min-width: 0; ··· 420 420 } 421 421 422 422 .artist-link { 423 - color: #b0b0b0; 423 + color: var(--text-secondary); 424 424 text-decoration: none; 425 425 transition: color 0.2s; 426 426 font-weight: 500; ··· 440 440 display: inline-flex; 441 441 align-items: center; 442 442 gap: 0.25rem; 443 - color: #b5bed1; 443 + color: var(--text-secondary); 444 444 white-space: nowrap; 445 445 } 446 446 447 447 .features-label { 448 - color: #8ab3ff; 448 + color: var(--accent-hover); 449 449 font-weight: 500; 450 450 text-transform: lowercase; 451 451 } 452 452 453 453 .feature-link { 454 - color: #8ab3ff; 454 + color: var(--accent-hover); 455 455 text-decoration: none; 456 456 font-weight: 500; 457 457 transition: color 0.2s; ··· 463 463 } 464 464 465 465 .feature-separator { 466 - color: #8ab3ff; 466 + color: var(--accent-hover); 467 467 } 468 468 469 469 .album { 470 - color: #909090; 470 + color: var(--text-tertiary); 471 471 display: inline-flex; 472 472 align-items: center; 473 473 gap: 0.35rem; ··· 476 476 } 477 477 478 478 .album-link { 479 - color: #909090; 479 + color: var(--text-tertiary); 480 480 text-decoration: none; 481 481 transition: color 0.2s; 482 482 display: inline-block; ··· 508 508 .tag-badge { 509 509 display: inline-block; 510 510 padding: 0.1rem 0.4rem; 511 - background: rgba(138, 179, 255, 0.15); 512 - color: #8ab3ff; 511 + background: color-mix(in srgb, var(--accent) 15%, transparent); 512 + color: var(--accent-hover); 513 513 border-radius: 3px; 514 514 font-size: 0.75rem; 515 515 font-weight: 500; ··· 518 518 } 519 519 520 520 .tag-badge:hover { 521 - background: rgba(138, 179, 255, 0.25); 522 - color: #a8c8ff; 521 + background: color-mix(in srgb, var(--accent) 25%, transparent); 522 + color: var(--accent); 523 523 } 524 524 525 525 .track-meta { 526 526 font-size: 0.8rem; 527 - color: #808080; 527 + color: var(--text-tertiary); 528 528 display: flex; 529 529 align-items: center; 530 530 gap: 0.5rem; 531 531 } 532 532 533 533 .plays { 534 - color: #999; 534 + color: var(--text-tertiary); 535 535 font-family: inherit; 536 536 } 537 537 538 538 .meta-separator { 539 - color: #555; 539 + color: var(--text-muted); 540 540 font-size: 0.7rem; 541 541 } 542 542 543 543 .likes { 544 - color: #999; 544 + color: var(--text-tertiary); 545 545 font-family: inherit; 546 546 position: relative; 547 547 cursor: help; ··· 553 553 } 554 554 555 555 .comments { 556 - color: #999; 556 + color: var(--text-tertiary); 557 557 font-family: inherit; 558 558 display: inline-flex; 559 559 align-items: center; ··· 583 583 align-items: center; 584 584 justify-content: center; 585 585 background: transparent; 586 - border: 1px solid #333; 586 + border: 1px solid var(--border-default); 587 587 border-radius: 4px; 588 - color: #888; 588 + color: var(--text-tertiary); 589 589 cursor: pointer; 590 590 transition: all 0.2s; 591 591 text-decoration: none; 592 592 } 593 593 594 594 .action-button:hover { 595 - background: #1a1a1a; 595 + background: var(--bg-tertiary); 596 596 border-color: var(--accent); 597 597 color: var(--accent); 598 598 }
+6 -6
frontend/src/lib/components/player/PlaybackControls.svelte
··· 254 254 255 255 .control-btn:hover { 256 256 color: var(--accent); 257 - background: rgba(106, 159, 255, 0.1); 257 + background: color-mix(in srgb, var(--accent) 10%, transparent); 258 258 } 259 259 260 260 .control-btn.play-pause:active { ··· 311 311 312 312 .time { 313 313 font-size: 0.85rem; 314 - color: #909090; 314 + color: var(--text-tertiary); 315 315 min-width: 45px; 316 316 font-variant-numeric: tabular-nums; 317 317 } ··· 325 325 display: flex; 326 326 align-items: center; 327 327 gap: 0.5rem; 328 - color: #909090; 328 + color: var(--text-tertiary); 329 329 min-width: 140px; 330 330 position: relative; 331 331 } ··· 337 337 } 338 338 339 339 .volume-icon.muted { 340 - color: #ff6b6b; 340 + color: var(--error); 341 341 animation: shake 0.5s ease-in-out; 342 342 } 343 343 ··· 395 395 } 396 396 397 397 input[type="range"].muted::-webkit-slider-thumb { 398 - background: #ff6b6b; 398 + background: var(--error); 399 399 } 400 400 401 401 input[type="range"].max::-webkit-slider-thumb { ··· 432 432 } 433 433 434 434 input[type="range"].muted::-moz-range-thumb { 435 - background: #ff6b6b; 435 + background: var(--error); 436 436 } 437 437 438 438 input[type="range"].max::-moz-range-thumb {
+2 -2
frontend/src/lib/components/player/Player.svelte
··· 350 350 bottom: 0; 351 351 left: 0; 352 352 right: 0; 353 - background: #1a1a1a; 354 - border-top: 1px solid #333; 353 + background: var(--bg-tertiary); 354 + border-top: 1px solid var(--border-default); 355 355 padding: 0.75rem 2rem; 356 356 padding-bottom: max(0.75rem, env(safe-area-inset-bottom)); 357 357 z-index: 100;
+6 -6
frontend/src/lib/components/player/TrackInfo.svelte
··· 155 155 height: 56px; 156 156 border-radius: 4px; 157 157 overflow: hidden; 158 - background: #1a1a1a; 159 - border: 1px solid #333; 158 + background: var(--bg-tertiary); 159 + border: 1px solid var(--border-default); 160 160 display: block; 161 161 text-decoration: none; 162 162 transition: transform 0.18s ease, border-color 0.2s ease; ··· 180 180 display: flex; 181 181 align-items: center; 182 182 justify-content: center; 183 - color: #666; 183 + color: var(--text-muted); 184 184 } 185 185 186 186 .player-info { ··· 196 196 .player-title-link { 197 197 font-size: 0.95rem; 198 198 font-weight: 600; 199 - color: #e8e8e8; 199 + color: var(--text-primary); 200 200 margin-bottom: 0; 201 201 position: relative; 202 202 overflow: hidden; ··· 253 253 flex-direction: column; 254 254 justify-content: center; 255 255 gap: 0.15rem; 256 - color: #a0a0a0; 256 + color: var(--text-secondary); 257 257 font-size: 0.82rem; 258 258 min-width: 0; 259 259 height: 32px; ··· 303 303 } 304 304 305 305 .features-inline { 306 - color: #b5b5b5; 306 + color: var(--text-secondary); 307 307 display: inline-flex; 308 308 align-items: center; 309 309 gap: 0.25rem;
+37 -2
frontend/src/lib/preferences.svelte.ts
··· 3 3 import { API_URL } from '$lib/config'; 4 4 import { auth } from '$lib/auth.svelte'; 5 5 6 + export type Theme = 'dark' | 'light' | 'system'; 7 + 6 8 export interface Preferences { 7 9 accent_color: string | null; 8 10 auto_advance: boolean; 9 11 allow_comments: boolean; 10 12 hidden_tags: string[]; 13 + theme: Theme; 11 14 } 12 15 13 16 const DEFAULT_PREFERENCES: Preferences = { 14 17 accent_color: null, 15 18 auto_advance: true, 16 19 allow_comments: true, 17 - hidden_tags: ['ai'] 20 + hidden_tags: ['ai'], 21 + theme: 'dark' 18 22 }; 19 23 20 24 class PreferencesManager { ··· 42 46 return this.data?.allow_comments ?? DEFAULT_PREFERENCES.allow_comments; 43 47 } 44 48 49 + get theme(): Theme { 50 + return this.data?.theme ?? DEFAULT_PREFERENCES.theme; 51 + } 52 + 53 + setTheme(theme: Theme): void { 54 + if (browser) { 55 + localStorage.setItem('theme', theme); 56 + this.applyTheme(theme); 57 + } 58 + this.update({ theme }); 59 + } 60 + 61 + applyTheme(theme: Theme): void { 62 + if (!browser) return; 63 + const root = document.documentElement; 64 + root.classList.remove('theme-dark', 'theme-light'); 65 + 66 + let effectiveTheme: 'dark' | 'light'; 67 + if (theme === 'system') { 68 + effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 69 + } else { 70 + effectiveTheme = theme; 71 + } 72 + root.classList.add(`theme-${effectiveTheme}`); 73 + } 74 + 45 75 async initialize(): Promise<void> { 46 76 if (!browser || this.initialized || this.loading) return; 47 77 this.initialized = true; ··· 62 92 accent_color: data.accent_color ?? null, 63 93 auto_advance: data.auto_advance ?? DEFAULT_PREFERENCES.auto_advance, 64 94 allow_comments: data.allow_comments ?? DEFAULT_PREFERENCES.allow_comments, 65 - hidden_tags: data.hidden_tags ?? DEFAULT_PREFERENCES.hidden_tags 95 + hidden_tags: data.hidden_tags ?? DEFAULT_PREFERENCES.hidden_tags, 96 + theme: data.theme ?? DEFAULT_PREFERENCES.theme 66 97 }; 67 98 } else { 68 99 this.data = { ...DEFAULT_PREFERENCES }; 100 + } 101 + // apply theme after fetching 102 + if (browser) { 103 + this.applyTheme(this.data.theme); 69 104 } 70 105 } catch (error) { 71 106 console.error('failed to fetch preferences:', error);
+4 -4
frontend/src/routes/+error.svelte
··· 56 56 57 57 h1 { 58 58 font-size: 4rem; 59 - color: #e8e8e8; 59 + color: var(--text-primary); 60 60 margin: 0 0 1rem 0; 61 61 font-weight: 700; 62 62 } 63 63 64 64 .error-message { 65 65 font-size: 1.25rem; 66 - color: #b0b0b0; 66 + color: var(--text-secondary); 67 67 margin: 0 0 0.5rem 0; 68 68 } 69 69 70 70 .error-detail { 71 71 font-size: 1rem; 72 - color: #808080; 72 + color: var(--text-tertiary); 73 73 margin: 0 0 2rem 0; 74 74 } 75 75 ··· 85 85 86 86 .home-link:hover { 87 87 background: var(--accent); 88 - color: #000; 88 + color: var(--bg-primary); 89 89 } 90 90 91 91 @media (max-width: 768px) {
+89 -3
frontend/src/routes/+layout.svelte
··· 109 109 document.documentElement.style.setProperty('--accent-hover', getHoverColor(savedAccent)); 110 110 } 111 111 112 + // apply saved theme from localStorage 113 + const savedTheme = localStorage.getItem('theme') as 'dark' | 'light' | 'system' | null; 114 + if (savedTheme) { 115 + preferences.applyTheme(savedTheme); 116 + } else { 117 + // default to dark 118 + document.documentElement.classList.add('theme-dark'); 119 + } 120 + 112 121 // restore queue visibility preference 113 122 const savedQueueVisibility = localStorage.getItem('showQueue'); 114 123 if (savedQueueVisibility !== null) { 115 124 showQueue = savedQueueVisibility === 'true'; 116 125 } 117 126 118 - // add keyboard listener for queue toggle 127 + // add keyboard listener for shortcuts 119 128 window.addEventListener('keydown', handleQueueShortcut); 129 + 130 + // listen for system theme changes 131 + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 132 + const handleSystemThemeChange = () => { 133 + const currentTheme = localStorage.getItem('theme'); 134 + if (currentTheme === 'system') { 135 + preferences.applyTheme('system'); 136 + } 137 + }; 138 + mediaQuery.addEventListener('change', handleSystemThemeChange); 139 + 140 + return () => { 141 + mediaQuery.removeEventListener('change', handleSystemThemeChange); 142 + }; 120 143 }); 121 144 122 145 onDestroy(() => { ··· 178 201 // prevent flash by applying saved settings immediately 179 202 if (typeof window !== 'undefined') { 180 203 (function() { 204 + const root = document.documentElement; 205 + 206 + // apply accent color 181 207 const savedAccent = localStorage.getItem('accentColor'); 182 208 if (savedAccent) { 183 - document.documentElement.style.setProperty('--accent', savedAccent); 209 + root.style.setProperty('--accent', savedAccent); 184 210 // simple lightening for hover state 185 211 const r = parseInt(savedAccent.slice(1, 3), 16); 186 212 const g = parseInt(savedAccent.slice(3, 5), 16); 187 213 const b = parseInt(savedAccent.slice(5, 7), 16); 188 214 const hover = `rgb(${Math.min(255, r + 30)}, ${Math.min(255, g + 30)}, ${Math.min(255, b + 30)})`; 189 - document.documentElement.style.setProperty('--accent-hover', hover); 215 + root.style.setProperty('--accent-hover', hover); 216 + } 217 + 218 + // apply theme 219 + const savedTheme = localStorage.getItem('theme') || 'dark'; 220 + let effectiveTheme = savedTheme; 221 + if (savedTheme === 'system') { 222 + effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 190 223 } 224 + root.classList.add('theme-' + effectiveTheme); 191 225 })(); 192 226 } 193 227 </script> ··· 239 273 --accent: #6a9fff; 240 274 --accent-hover: #8ab3ff; 241 275 --accent-muted: #4a7ddd; 276 + --accent-rgb: 106, 159, 255; 242 277 243 278 /* backgrounds */ 244 279 --bg-primary: #0a0a0a; ··· 267 302 --success: #4ade80; 268 303 --warning: #fbbf24; 269 304 --error: #ef4444; 305 + } 306 + 307 + /* light theme overrides */ 308 + :global(:root.theme-light) { 309 + --bg-primary: #fafafa; 310 + --bg-secondary: #ffffff; 311 + --bg-tertiary: #f5f5f5; 312 + --bg-hover: #ebebeb; 313 + 314 + --border-subtle: #e5e5e5; 315 + --border-default: #d4d4d4; 316 + --border-emphasis: #a3a3a3; 317 + 318 + --text-primary: #171717; 319 + --text-secondary: #525252; 320 + --text-tertiary: #737373; 321 + --text-muted: #a3a3a3; 322 + 323 + /* accent colors preserved from user preference */ 324 + /* accent-muted darkened for light bg readability */ 325 + --accent-muted: color-mix(in srgb, var(--accent) 70%, black); 326 + 327 + /* semantic colors adjusted for light bg */ 328 + --success: #16a34a; 329 + --warning: #d97706; 330 + --error: #dc2626; 331 + } 332 + 333 + /* light theme specific overrides for components */ 334 + :global(:root.theme-light) :global(.track-container) { 335 + background: var(--bg-secondary); 336 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); 337 + } 338 + 339 + :global(:root.theme-light) :global(.track-container:hover) { 340 + background: var(--bg-tertiary); 341 + } 342 + 343 + :global(:root.theme-light) :global(.track-container.playing) { 344 + background: color-mix(in srgb, var(--accent) 8%, white); 345 + border-color: color-mix(in srgb, var(--accent) 30%, white); 346 + } 347 + 348 + :global(:root.theme-light) :global(header) { 349 + background: var(--bg-primary); 350 + border-color: var(--border-default); 351 + } 352 + 353 + :global(:root.theme-light) :global(.tag-badge) { 354 + background: color-mix(in srgb, var(--accent) 12%, white); 355 + color: var(--accent-muted); 270 356 } 271 357 272 358 :global(body) {
+4 -2
frontend/src/routes/+layout.ts
··· 14 14 accent_color: null, 15 15 auto_advance: true, 16 16 allow_comments: true, 17 - hidden_tags: ['ai'] 17 + hidden_tags: ['ai'], 18 + theme: 'dark' 18 19 }; 19 20 20 21 export async function load({ fetch }: LoadEvent): Promise<LayoutData> { ··· 46 47 accent_color: data.accent_color ?? null, 47 48 auto_advance: data.auto_advance ?? true, 48 49 allow_comments: data.allow_comments ?? true, 49 - hidden_tags: data.hidden_tags ?? ['ai'] 50 + hidden_tags: data.hidden_tags ?? ['ai'], 51 + theme: data.theme ?? 'dark' 50 52 }; 51 53 } 52 54 } catch (e) {
+1 -1
frontend/src/routes/+page.svelte
··· 157 157 } 158 158 159 159 .empty { 160 - color: #808080; 160 + color: var(--text-tertiary); 161 161 padding: 2rem; 162 162 text-align: center; 163 163 }
+13 -13
frontend/src/routes/embed/track/[id]/+page.svelte
··· 114 114 padding: 0; 115 115 overflow: hidden; 116 116 font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Consolas', monospace; 117 - background: #000; 118 - color: #fff; 117 + background: var(--bg-primary); 118 + color: var(--text-primary); 119 119 } 120 120 121 121 .embed-container { 122 122 display: flex; 123 123 height: 165px; 124 - background: #1a1a1a; 124 + background: var(--bg-tertiary); 125 125 overflow: hidden; 126 126 position: relative; 127 127 } ··· 151 151 .art-placeholder { 152 152 width: 100%; 153 153 height: 100%; 154 - background: #333; 154 + background: var(--border-default); 155 155 display: flex; 156 156 align-items: center; 157 157 justify-content: center; 158 158 font-size: 48px; 159 - color: #555; 159 + color: var(--text-muted); 160 160 } 161 161 162 162 .content { ··· 216 216 overflow: hidden; 217 217 text-overflow: ellipsis; 218 218 text-decoration: none; 219 - color: #fff; 219 + color: var(--text-primary); 220 220 } 221 221 222 222 .title:hover { ··· 226 226 .artist { 227 227 display: block; 228 228 font-size: 14px; 229 - color: #aaa; 229 + color: var(--text-secondary); 230 230 white-space: nowrap; 231 231 overflow: hidden; 232 232 text-overflow: ellipsis; ··· 243 243 right: 16px; 244 244 font-size: 12px; 245 245 font-weight: 700; 246 - color: #444; 246 + color: var(--border-emphasis); 247 247 text-decoration: none; 248 248 text-transform: uppercase; 249 249 letter-spacing: 1px; 250 250 } 251 251 252 252 .logo:hover { 253 - color: #666; 253 + color: var(--text-muted); 254 254 } 255 255 256 256 .player-controls { ··· 262 262 263 263 .time { 264 264 font-size: 12px; 265 - color: #777; 265 + color: var(--text-tertiary); 266 266 font-variant-numeric: tabular-nums; 267 267 width: 35px; 268 268 text-align: center; ··· 280 280 .progress-bg { 281 281 width: 100%; 282 282 height: 4px; 283 - background: #333; 283 + background: var(--border-default); 284 284 border-radius: 2px; 285 285 } 286 286 ··· 289 289 left: 0; 290 290 top: 10px; /* (24 - 4) / 2 */ 291 291 height: 4px; 292 - background: #fff; 292 + background: var(--text-primary); 293 293 border-radius: 2px; 294 294 pointer-events: none; 295 295 } 296 296 297 297 .progress-bar:hover .progress-fill { 298 - background: #3b82f6; /* blue-500 */ 298 + background: var(--accent); 299 299 } 300 300 301 301 /* mobile: background image layout */
+4 -4
frontend/src/routes/liked/+page.svelte
··· 114 114 115 115 .subtitle { 116 116 font-size: 0.95rem; 117 - color: #888; 117 + color: var(--text-tertiary); 118 118 margin: 0; 119 119 } 120 120 ··· 146 146 .empty-state { 147 147 text-align: center; 148 148 padding: 4rem 1rem; 149 - color: #888; 149 + color: var(--text-tertiary); 150 150 } 151 151 152 152 .empty-state svg { 153 153 margin: 0 auto 1.5rem; 154 - color: #555; 154 + color: var(--text-muted); 155 155 } 156 156 157 157 .empty-state h2 { 158 158 font-size: 1.5rem; 159 159 font-weight: 600; 160 - color: #b0b0b0; 160 + color: var(--text-secondary); 161 161 margin: 0 0 0.5rem 0; 162 162 } 163 163
+10 -10
frontend/src/routes/login/+page.svelte
··· 64 64 display: flex; 65 65 align-items: center; 66 66 justify-content: center; 67 - background: #0a0a0a; 67 + background: var(--bg-primary); 68 68 padding: 1rem; 69 69 } 70 70 71 71 .login-card { 72 - background: #1a1a1a; 73 - border: 1px solid #2a2a2a; 72 + background: var(--bg-tertiary); 73 + border: 1px solid var(--border-subtle); 74 74 border-radius: 8px; 75 75 padding: 3rem; 76 76 max-width: 400px; ··· 80 80 h1 { 81 81 font-size: 2.5rem; 82 82 margin: 0 0 0.5rem 0; 83 - color: #fff; 83 + color: var(--text-primary); 84 84 text-align: center; 85 85 } 86 86 87 87 p { 88 - color: #888; 88 + color: var(--text-tertiary); 89 89 text-align: center; 90 90 margin: 0 0 2rem 0; 91 91 font-size: 0.95rem; ··· 103 103 } 104 104 105 105 label { 106 - color: #aaa; 106 + color: var(--text-secondary); 107 107 font-size: 0.9rem; 108 108 } 109 109 ··· 122 122 .input-help { 123 123 margin: 0.5rem 0 0 0; 124 124 font-size: 0.85rem; 125 - color: #888; 125 + color: var(--text-tertiary); 126 126 } 127 127 128 128 .input-help a { ··· 139 139 button { 140 140 width: 100%; 141 141 padding: 0.75rem; 142 - background: #3a7dff; 142 + background: var(--accent); 143 143 color: white; 144 144 border: none; 145 145 border-radius: 4px; ··· 151 151 } 152 152 153 153 button:hover:not(:disabled) { 154 - background: #2868e6; 154 + background: var(--accent-hover); 155 155 transform: translateY(-1px); 156 - box-shadow: 0 4px 12px rgba(58, 125, 255, 0.3); 156 + box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 30%, transparent); 157 157 } 158 158 159 159 button:disabled {
+171 -171
frontend/src/routes/portal/+page.svelte
··· 1395 1395 align-items: center; 1396 1396 justify-content: center; 1397 1397 min-height: 100vh; 1398 - color: #888; 1398 + color: var(--text-tertiary); 1399 1399 gap: 1rem; 1400 1400 } 1401 1401 ··· 1450 1450 text-decoration: none; 1451 1451 font-size: 0.9rem; 1452 1452 padding: 0.4rem 0.75rem; 1453 - background: #1a1a1a; 1453 + background: var(--bg-tertiary); 1454 1454 border-radius: 6px; 1455 - border: 1px solid #333; 1455 + border: 1px solid var(--border-default); 1456 1456 transition: all 0.2s; 1457 1457 white-space: nowrap; 1458 1458 } ··· 1460 1460 .view-profile-link:hover { 1461 1461 border-color: var(--accent); 1462 1462 color: var(--accent); 1463 - background: #222; 1463 + background: var(--bg-hover); 1464 1464 } 1465 1465 1466 1466 form { 1467 - background: #1a1a1a; 1467 + background: var(--bg-tertiary); 1468 1468 padding: 2rem; 1469 1469 border-radius: 8px; 1470 - border: 1px solid #2a2a2a; 1470 + border: 1px solid var(--border-subtle); 1471 1471 } 1472 1472 1473 1473 .form-group { ··· 1476 1476 1477 1477 label { 1478 1478 display: block; 1479 - color: #aaa; 1479 + color: var(--text-secondary); 1480 1480 margin-bottom: 0.5rem; 1481 1481 font-size: 0.9rem; 1482 1482 } ··· 1484 1484 input[type='text'] { 1485 1485 width: 100%; 1486 1486 padding: 0.75rem; 1487 - background: #0a0a0a; 1488 - border: 1px solid #333; 1487 + background: var(--bg-primary); 1488 + border: 1px solid var(--border-default); 1489 1489 border-radius: 4px; 1490 - color: white; 1490 + color: var(--text-primary); 1491 1491 font-size: 1rem; 1492 1492 font-family: inherit; 1493 1493 transition: all 0.2s; ··· 1495 1495 1496 1496 input[type='text']:focus { 1497 1497 outline: none; 1498 - border-color: #3a7dff; 1498 + border-color: var(--accent); 1499 1499 } 1500 1500 1501 1501 input[type='text']:disabled { ··· 1506 1506 textarea { 1507 1507 width: 100%; 1508 1508 padding: 0.75rem; 1509 - background: #0a0a0a; 1510 - border: 1px solid #333; 1509 + background: var(--bg-primary); 1510 + border: 1px solid var(--border-default); 1511 1511 border-radius: 4px; 1512 - color: white; 1512 + color: var(--text-primary); 1513 1513 font-size: 1rem; 1514 1514 font-family: inherit; 1515 1515 transition: all 0.2s; ··· 1519 1519 1520 1520 textarea:focus { 1521 1521 outline: none; 1522 - border-color: #3a7dff; 1522 + border-color: var(--accent); 1523 1523 } 1524 1524 1525 1525 textarea:disabled { ··· 1530 1530 .hint { 1531 1531 margin-top: 0.5rem; 1532 1532 font-size: 0.85rem; 1533 - color: #666; 1533 + color: var(--text-muted); 1534 1534 } 1535 1535 1536 1536 .message { ··· 1540 1540 } 1541 1541 1542 1542 .message.success { 1543 - background: rgba(46, 160, 67, 0.1); 1544 - border: 1px solid rgba(46, 160, 67, 0.3); 1545 - color: #5ce87b; 1543 + background: color-mix(in srgb, var(--success) 10%, transparent); 1544 + border: 1px solid color-mix(in srgb, var(--success) 30%, transparent); 1545 + color: var(--success); 1546 1546 } 1547 1547 1548 1548 .message.error { 1549 - background: rgba(233, 69, 96, 0.1); 1550 - border: 1px solid rgba(233, 69, 96, 0.3); 1551 - color: #ff6b6b; 1549 + background: color-mix(in srgb, var(--error) 10%, transparent); 1550 + border: 1px solid color-mix(in srgb, var(--error) 30%, transparent); 1551 + color: var(--error); 1552 1552 } 1553 1553 1554 1554 .avatar-preview { ··· 1563 1563 height: 64px; 1564 1564 border-radius: 50%; 1565 1565 object-fit: cover; 1566 - border: 2px solid #333; 1566 + border: 2px solid var(--border-default); 1567 1567 } 1568 1568 1569 1569 input[type='file'] { 1570 1570 width: 100%; 1571 1571 padding: 0.75rem; 1572 - background: #0a0a0a; 1573 - border: 1px solid #333; 1572 + background: var(--bg-primary); 1573 + border: 1px solid var(--border-default); 1574 1574 border-radius: 4px; 1575 - color: white; 1575 + color: var(--text-primary); 1576 1576 font-size: 0.9rem; 1577 1577 font-family: inherit; 1578 1578 cursor: pointer; ··· 1586 1586 .format-hint { 1587 1587 margin-top: 0.25rem; 1588 1588 font-size: 0.8rem; 1589 - color: #888; 1589 + color: var(--text-tertiary); 1590 1590 } 1591 1591 1592 1592 .file-info { 1593 1593 margin-top: 0.5rem; 1594 1594 font-size: 0.85rem; 1595 - color: #666; 1595 + color: var(--text-muted); 1596 1596 } 1597 1597 1598 1598 button { 1599 1599 width: 100%; 1600 1600 padding: 0.75rem; 1601 - background: #3a7dff; 1602 - color: white; 1601 + background: var(--accent); 1602 + color: var(--text-primary); 1603 1603 border: none; 1604 1604 border-radius: 4px; 1605 1605 font-size: 1rem; ··· 1610 1610 } 1611 1611 1612 1612 button:hover:not(:disabled) { 1613 - background: #2868e6; 1613 + background: var(--accent-hover); 1614 1614 transform: translateY(-1px); 1615 - box-shadow: 0 4px 12px rgba(58, 125, 255, 0.3); 1615 + box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 30%, transparent); 1616 1616 } 1617 1617 1618 1618 button:disabled { ··· 1635 1635 } 1636 1636 1637 1637 .empty { 1638 - color: #666; 1638 + color: var(--text-muted); 1639 1639 padding: 2rem; 1640 1640 text-align: center; 1641 - background: #1a1a1a; 1641 + background: var(--bg-tertiary); 1642 1642 border-radius: 8px; 1643 - border: 1px solid #2a2a2a; 1643 + border: 1px solid var(--border-subtle); 1644 1644 } 1645 1645 1646 1646 .tracks-list { ··· 1654 1654 align-items: flex-start; 1655 1655 justify-content: space-between; 1656 1656 gap: 1rem; 1657 - background: #1a1a1a; 1658 - border: 1px solid #2a2a2a; 1657 + background: var(--bg-tertiary); 1658 + border: 1px solid var(--border-subtle); 1659 1659 border-radius: 6px; 1660 1660 padding: 1rem; 1661 1661 transition: all 0.2s; ··· 1667 1667 } 1668 1668 1669 1669 .track-item.copyright-flagged { 1670 - background: rgba(233, 165, 69, 0.08); 1671 - border-color: rgba(233, 165, 69, 0.3); 1670 + background: color-mix(in srgb, var(--warning) 8%, transparent); 1671 + border-color: color-mix(in srgb, var(--warning) 30%, transparent); 1672 1672 } 1673 1673 1674 1674 .track-item.copyright-flagged .track-title { 1675 - color: #e9a545; 1675 + color: var(--warning); 1676 1676 } 1677 1677 1678 1678 .track-item.copyright-flagged .track-artwork img, ··· 1686 1686 height: 48px; 1687 1687 border-radius: 4px; 1688 1688 overflow: hidden; 1689 - background: #0f0f0f; 1690 - border: 1px solid #282828; 1689 + background: var(--bg-primary); 1690 + border: 1px solid var(--border-subtle); 1691 1691 } 1692 1692 1693 1693 .track-artwork img { ··· 1702 1702 display: flex; 1703 1703 align-items: center; 1704 1704 justify-content: center; 1705 - color: #555; 1705 + color: var(--text-muted); 1706 1706 } 1707 1707 1708 1708 .track-item:hover { 1709 - background: #222; 1710 - border-color: #333; 1709 + background: var(--bg-hover); 1710 + border-color: var(--border-default); 1711 1711 } 1712 1712 1713 1713 .track-info { ··· 1743 1743 1744 1744 .edit-label { 1745 1745 font-size: 0.85rem; 1746 - color: #aaa; 1746 + color: var(--text-secondary); 1747 1747 } 1748 1748 1749 1749 .track-title { 1750 1750 font-weight: 600; 1751 1751 font-size: 1rem; 1752 1752 margin-bottom: 0.25rem; 1753 - color: #fff; 1753 + color: var(--text-primary); 1754 1754 display: flex; 1755 1755 align-items: center; 1756 1756 gap: 0.5rem; ··· 1759 1759 .copyright-flag { 1760 1760 display: inline-flex; 1761 1761 align-items: center; 1762 - color: #e9a545; 1762 + color: var(--warning); 1763 1763 flex-shrink: 0; 1764 1764 text-decoration: none; 1765 1765 } 1766 1766 1767 1767 .copyright-flag:hover { 1768 - color: #ffb860; 1768 + color: color-mix(in srgb, var(--warning) 80%, white); 1769 1769 } 1770 1770 1771 1771 a.copyright-flag { ··· 1778 1778 1779 1779 .track-meta { 1780 1780 font-size: 0.9rem; 1781 - color: #b0b0b0; 1781 + color: var(--text-secondary); 1782 1782 margin-bottom: 0.25rem; 1783 1783 display: flex; 1784 1784 flex-direction: column; ··· 1799 1799 } 1800 1800 1801 1801 .features-label { 1802 - color: #8ab3ff; 1802 + color: var(--accent-hover); 1803 1803 font-weight: 600; 1804 1804 } 1805 1805 1806 1806 .features-list { 1807 - color: #8ab3ff; 1807 + color: var(--accent-hover); 1808 1808 white-space: nowrap; 1809 1809 overflow: hidden; 1810 1810 text-overflow: ellipsis; 1811 1811 } 1812 1812 1813 1813 .meta-album { 1814 - color: #909090; 1814 + color: var(--text-tertiary); 1815 1815 } 1816 1816 1817 1817 .album-link { 1818 - color: #909090; 1818 + color: var(--text-tertiary); 1819 1819 text-decoration: none; 1820 1820 transition: color 0.2s; 1821 1821 white-space: nowrap; ··· 1843 1843 .meta-tag { 1844 1844 display: inline-block; 1845 1845 padding: 0.1rem 0.4rem; 1846 - background: rgba(138, 179, 255, 0.15); 1847 - color: #8ab3ff; 1846 + background: color-mix(in srgb, var(--accent) 15%, transparent); 1847 + color: var(--accent-hover); 1848 1848 border-radius: 3px; 1849 1849 font-size: 0.8rem; 1850 1850 font-weight: 500; ··· 1853 1853 } 1854 1854 1855 1855 .meta-tag:hover { 1856 - background: rgba(138, 179, 255, 0.25); 1857 - color: #a8c8ff; 1856 + background: color-mix(in srgb, var(--accent) 25%, transparent); 1857 + color: var(--accent-hover); 1858 1858 } 1859 1859 1860 1860 .track-date { 1861 1861 font-size: 0.85rem; 1862 - color: #666; 1862 + color: var(--text-muted); 1863 1863 } 1864 1864 1865 1865 .track-actions { ··· 1875 1875 height: 36px; 1876 1876 padding: 0; 1877 1877 background: transparent; 1878 - border: 1px solid #444; 1878 + border: 1px solid var(--border-emphasis); 1879 1879 border-radius: 4px; 1880 - color: #888; 1880 + color: var(--text-tertiary); 1881 1881 font-size: 1.2rem; 1882 1882 line-height: 1; 1883 1883 cursor: pointer; ··· 1885 1885 } 1886 1886 1887 1887 .edit-btn:hover { 1888 - background: rgba(106, 159, 255, 0.1); 1889 - border-color: rgba(106, 159, 255, 0.5); 1888 + background: color-mix(in srgb, var(--accent) 10%, transparent); 1889 + border-color: color-mix(in srgb, var(--accent) 50%, transparent); 1890 1890 color: var(--accent); 1891 1891 transform: none; 1892 1892 box-shadow: none; ··· 1897 1897 } 1898 1898 1899 1899 .delete-btn:hover { 1900 - background: rgba(233, 69, 96, 0.1); 1901 - border-color: rgba(233, 69, 96, 0.5); 1902 - color: #ff6b6b; 1900 + background: color-mix(in srgb, var(--error) 10%, transparent); 1901 + border-color: color-mix(in srgb, var(--error) 50%, transparent); 1902 + color: var(--error); 1903 1903 transform: none; 1904 1904 box-shadow: none; 1905 1905 } 1906 1906 1907 1907 .save-btn:hover { 1908 - background: rgba(46, 160, 67, 0.1); 1909 - border-color: rgba(46, 160, 67, 0.5); 1910 - color: #5ce87b; 1908 + background: color-mix(in srgb, var(--success) 10%, transparent); 1909 + border-color: color-mix(in srgb, var(--success) 50%, transparent); 1910 + color: var(--success); 1911 1911 transform: none; 1912 1912 box-shadow: none; 1913 1913 } 1914 1914 1915 1915 .cancel-btn:hover { 1916 - background: rgba(136, 136, 136, 0.1); 1917 - border-color: rgba(136, 136, 136, 0.5); 1918 - color: #aaa; 1916 + background: color-mix(in srgb, var(--text-tertiary) 10%, transparent); 1917 + border-color: color-mix(in srgb, var(--text-tertiary) 50%, transparent); 1918 + color: var(--text-secondary); 1919 1919 transform: none; 1920 1920 box-shadow: none; 1921 1921 } ··· 1923 1923 .edit-input { 1924 1924 width: 100%; 1925 1925 padding: 0.5rem; 1926 - background: #0a0a0a; 1927 - border: 1px solid #333; 1926 + background: var(--bg-primary); 1927 + border: 1px solid var(--border-default); 1928 1928 border-radius: 4px; 1929 - color: white; 1929 + color: var(--text-primary); 1930 1930 font-size: 0.9rem; 1931 1931 font-family: inherit; 1932 1932 } ··· 1936 1936 align-items: center; 1937 1937 gap: 0.75rem; 1938 1938 padding: 0.5rem; 1939 - background: #0a0a0a; 1940 - border: 1px solid #333; 1939 + background: var(--bg-primary); 1940 + border: 1px solid var(--border-default); 1941 1941 border-radius: 4px; 1942 1942 margin-bottom: 0.5rem; 1943 1943 } ··· 1950 1950 } 1951 1951 1952 1952 .current-image-label { 1953 - color: #888; 1953 + color: var(--text-tertiary); 1954 1954 font-size: 0.85rem; 1955 1955 } 1956 1956 ··· 1988 1988 } 1989 1989 1990 1990 .album-card { 1991 - background: #1a1a1a; 1992 - border: 1px solid #2a2a2a; 1991 + background: var(--bg-tertiary); 1992 + border: 1px solid var(--border-subtle); 1993 1993 border-radius: 8px; 1994 1994 padding: 1rem; 1995 1995 transition: all 0.2s; ··· 1999 1999 } 2000 2000 2001 2001 .album-card:hover { 2002 - border-color: #3a3a3a; 2002 + border-color: var(--border-emphasis); 2003 2003 transform: translateY(-2px); 2004 2004 } 2005 2005 ··· 2012 2012 aspect-ratio: 1; 2013 2013 border-radius: 6px; 2014 2014 overflow: hidden; 2015 - background: #0f0f0f; 2016 - border: 1px solid #282828; 2015 + background: var(--bg-primary); 2016 + border: 1px solid var(--border-subtle); 2017 2017 } 2018 2018 2019 2019 .album-cover { ··· 2029 2029 flex-direction: column; 2030 2030 align-items: center; 2031 2031 justify-content: center; 2032 - color: #606060; 2032 + color: var(--text-muted); 2033 2033 gap: 0.5rem; 2034 2034 } 2035 2035 2036 2036 .album-cover-placeholder .file-name { 2037 2037 font-size: 0.85rem; 2038 - color: #888; 2038 + color: var(--text-tertiary); 2039 2039 text-align: center; 2040 2040 word-break: break-word; 2041 2041 padding: 0 0.5rem; ··· 2043 2043 2044 2044 .album-cover-placeholder .file-size { 2045 2045 font-size: 0.75rem; 2046 - color: #666; 2046 + color: var(--text-muted); 2047 2047 } 2048 2048 2049 2049 .album-info { ··· 2053 2053 .album-title { 2054 2054 font-size: 1rem; 2055 2055 font-weight: 600; 2056 - color: #e8e8e8; 2056 + color: var(--text-primary); 2057 2057 margin: 0 0 0.25rem 0; 2058 2058 overflow: hidden; 2059 2059 text-overflow: ellipsis; ··· 2062 2062 2063 2063 .album-stats { 2064 2064 font-size: 0.85rem; 2065 - color: #888; 2065 + color: var(--text-tertiary); 2066 2066 margin: 0; 2067 2067 } 2068 2068 ··· 2074 2074 2075 2075 .edit-cover-btn { 2076 2076 padding: 0.5rem; 2077 - background: #2a2a2a; 2078 - border: 1px solid #3a3a3a; 2077 + background: var(--border-subtle); 2078 + border: 1px solid var(--border-emphasis); 2079 2079 border-radius: 4px; 2080 2080 cursor: pointer; 2081 2081 transition: all 0.2s; ··· 2085 2085 } 2086 2086 2087 2087 .edit-cover-btn:hover { 2088 - background: #3a3a3a; 2088 + background: var(--border-emphasis); 2089 2089 border-color: var(--accent); 2090 2090 color: var(--accent); 2091 2091 } ··· 2101 2101 aspect-ratio: 1; 2102 2102 border-radius: 6px; 2103 2103 overflow: hidden; 2104 - background: #0f0f0f; 2105 - border: 1px solid #282828; 2104 + background: var(--bg-primary); 2105 + border: 1px solid var(--border-subtle); 2106 2106 } 2107 2107 2108 2108 .album-edit-actions { ··· 2113 2113 2114 2114 .file-input-label { 2115 2115 font-size: 0.85rem; 2116 - color: #b0b0b0; 2116 + color: var(--text-secondary); 2117 2117 font-weight: 500; 2118 2118 margin-bottom: 0.25rem; 2119 2119 } 2120 2120 2121 2121 .album-edit-actions .file-input { 2122 2122 padding: 0.5rem; 2123 - background: #2a2a2a; 2124 - border: 1px solid #3a3a3a; 2123 + background: var(--border-subtle); 2124 + border: 1px solid var(--border-emphasis); 2125 2125 border-radius: 4px; 2126 - color: #e8e8e8; 2126 + color: var(--text-primary); 2127 2127 font-size: 0.85rem; 2128 2128 cursor: pointer; 2129 2129 } 2130 2130 2131 2131 .album-edit-actions .file-input:hover { 2132 - background: #3a3a3a; 2132 + background: var(--border-emphasis); 2133 2133 border-color: var(--accent); 2134 2134 } 2135 2135 ··· 2151 2151 2152 2152 .data-control { 2153 2153 padding: 1.5rem; 2154 - background: #1a1a1a; 2155 - border: 1px solid #2a2a2a; 2154 + background: var(--bg-tertiary); 2155 + border: 1px solid var(--border-subtle); 2156 2156 border-radius: 8px; 2157 2157 display: flex; 2158 2158 justify-content: space-between; ··· 2169 2169 font-size: 1rem; 2170 2170 font-weight: 600; 2171 2171 margin: 0 0 0.25rem 0; 2172 - color: #e8e8e8; 2172 + color: var(--text-primary); 2173 2173 } 2174 2174 2175 2175 .control-description { 2176 2176 font-size: 0.85rem; 2177 - color: #888; 2177 + color: var(--text-tertiary); 2178 2178 margin: 0; 2179 2179 } 2180 2180 2181 2181 .export-btn { 2182 2182 padding: 0.6rem 1.25rem; 2183 - background: #3a7dff; 2184 - color: white; 2183 + background: var(--accent); 2184 + color: var(--text-primary); 2185 2185 border: none; 2186 2186 border-radius: 6px; 2187 2187 font-size: 0.9rem; ··· 2193 2193 } 2194 2194 2195 2195 .export-btn:hover:not(:disabled) { 2196 - background: #2868e6; 2196 + background: var(--accent-hover); 2197 2197 transform: translateY(-1px); 2198 - box-shadow: 0 4px 12px rgba(58, 125, 255, 0.3); 2198 + box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 30%, transparent); 2199 2199 } 2200 2200 2201 2201 .export-btn:disabled { ··· 2206 2206 2207 2207 /* danger zone / account deletion */ 2208 2208 .danger-zone { 2209 - border-color: #4a2020; 2210 - background: #1a1010; 2209 + border-color: color-mix(in srgb, var(--error) 30%, var(--bg-tertiary)); 2210 + background: color-mix(in srgb, var(--error) 8%, var(--bg-tertiary)); 2211 2211 flex-direction: column; 2212 2212 align-items: stretch; 2213 2213 } 2214 2214 2215 2215 .danger-zone .control-info h3 { 2216 - color: #ff6b6b; 2216 + color: var(--error); 2217 2217 } 2218 2218 2219 2219 .danger-zone .control-description a { 2220 - color: #888; 2220 + color: var(--text-tertiary); 2221 2221 text-decoration: underline; 2222 2222 } 2223 2223 2224 2224 .danger-zone .control-description a:hover { 2225 - color: #aaa; 2225 + color: var(--text-secondary); 2226 2226 } 2227 2227 2228 2228 .delete-account-btn { 2229 2229 padding: 0.6rem 1.25rem; 2230 2230 background: transparent; 2231 - color: #ff6b6b; 2232 - border: 1px solid #ff6b6b; 2231 + color: var(--error); 2232 + border: 1px solid var(--error); 2233 2233 border-radius: 6px; 2234 2234 font-size: 0.9rem; 2235 2235 font-weight: 600; ··· 2239 2239 } 2240 2240 2241 2241 .delete-account-btn:hover { 2242 - background: #ff6b6b; 2243 - color: white; 2242 + background: var(--error); 2243 + color: var(--text-primary); 2244 2244 } 2245 2245 2246 2246 .delete-confirm-panel { 2247 2247 margin-top: 1rem; 2248 2248 padding-top: 1rem; 2249 - border-top: 1px solid #3a2020; 2249 + border-top: 1px solid color-mix(in srgb, var(--error) 25%, var(--bg-tertiary)); 2250 2250 } 2251 2251 2252 2252 .delete-warning { 2253 - color: #ff8888; 2253 + color: color-mix(in srgb, var(--error) 80%, white); 2254 2254 font-size: 0.9rem; 2255 2255 margin: 0 0 1rem 0; 2256 2256 line-height: 1.5; ··· 2265 2265 align-items: center; 2266 2266 gap: 0.5rem; 2267 2267 font-size: 0.9rem; 2268 - color: #aaa; 2268 + color: var(--text-secondary); 2269 2269 cursor: pointer; 2270 2270 } 2271 2271 ··· 2278 2278 .atproto-note { 2279 2279 margin: 0.5rem 0 0 0; 2280 2280 font-size: 0.85rem; 2281 - color: #777; 2281 + color: var(--text-muted); 2282 2282 } 2283 2283 2284 2284 .atproto-note a { 2285 - color: #999; 2285 + color: var(--text-tertiary); 2286 2286 text-decoration: underline; 2287 2287 } 2288 2288 2289 2289 .atproto-note a:hover { 2290 - color: #bbb; 2290 + color: var(--text-secondary); 2291 2291 } 2292 2292 2293 2293 .atproto-warning { 2294 2294 margin: 0.75rem 0 0 0; 2295 2295 padding: 0.75rem; 2296 - background: rgba(255, 107, 107, 0.1); 2297 - border-left: 2px solid #ff6b6b; 2296 + background: color-mix(in srgb, var(--error) 10%, transparent); 2297 + border-left: 2px solid var(--error); 2298 2298 font-size: 0.85rem; 2299 - color: #cc8888; 2299 + color: color-mix(in srgb, var(--error) 70%, var(--text-secondary)); 2300 2300 line-height: 1.5; 2301 2301 } 2302 2302 2303 2303 .confirm-prompt { 2304 2304 font-size: 0.9rem; 2305 - color: #888; 2305 + color: var(--text-tertiary); 2306 2306 margin: 0 0 0.5rem 0; 2307 2307 } 2308 2308 2309 2309 .confirm-prompt strong { 2310 - color: #e8e8e8; 2310 + color: var(--text-primary); 2311 2311 font-family: monospace; 2312 2312 } 2313 2313 2314 2314 .confirm-input { 2315 2315 width: 100%; 2316 2316 padding: 0.75rem; 2317 - background: #0a0505; 2318 - border: 1px solid #3a2020; 2317 + background: color-mix(in srgb, var(--error) 5%, var(--bg-primary)); 2318 + border: 1px solid color-mix(in srgb, var(--error) 25%, var(--bg-tertiary)); 2319 2319 border-radius: 6px; 2320 - color: #e8e8e8; 2320 + color: var(--text-primary); 2321 2321 font-size: 0.9rem; 2322 2322 font-family: monospace; 2323 2323 margin-bottom: 1rem; ··· 2325 2325 2326 2326 .confirm-input:focus { 2327 2327 outline: none; 2328 - border-color: #ff6b6b; 2328 + border-color: var(--error); 2329 2329 } 2330 2330 2331 2331 .confirm-input::placeholder { 2332 - color: #555; 2332 + color: var(--text-muted); 2333 2333 } 2334 2334 2335 2335 .delete-actions { ··· 2341 2341 .cancel-btn { 2342 2342 padding: 0.6rem 1.25rem; 2343 2343 background: transparent; 2344 - color: #888; 2345 - border: 1px solid #444; 2344 + color: var(--text-tertiary); 2345 + border: 1px solid var(--border-emphasis); 2346 2346 border-radius: 6px; 2347 2347 font-size: 0.9rem; 2348 2348 cursor: pointer; ··· 2350 2350 } 2351 2351 2352 2352 .cancel-btn:hover:not(:disabled) { 2353 - border-color: #666; 2354 - color: #aaa; 2353 + border-color: var(--text-muted); 2354 + color: var(--text-secondary); 2355 2355 } 2356 2356 2357 2357 .cancel-btn:disabled { ··· 2361 2361 2362 2362 .confirm-delete-btn { 2363 2363 padding: 0.6rem 1.25rem; 2364 - background: #ff4444; 2365 - color: white; 2364 + background: var(--error); 2365 + color: var(--text-primary); 2366 2366 border: none; 2367 2367 border-radius: 6px; 2368 2368 font-size: 0.9rem; ··· 2372 2372 } 2373 2373 2374 2374 .confirm-delete-btn:hover:not(:disabled) { 2375 - background: #ff2222; 2375 + background: color-mix(in srgb, var(--error) 80%, black); 2376 2376 } 2377 2377 2378 2378 .confirm-delete-btn:disabled { ··· 2395 2395 .toggle-slider { 2396 2396 width: 44px; 2397 2397 height: 24px; 2398 - background: #333; 2398 + background: var(--border-default); 2399 2399 border-radius: 12px; 2400 2400 position: relative; 2401 2401 transition: background 0.2s; ··· 2408 2408 left: 2px; 2409 2409 width: 20px; 2410 2410 height: 20px; 2411 - background: #888; 2411 + background: var(--text-tertiary); 2412 2412 border-radius: 50%; 2413 2413 transition: all 0.2s; 2414 2414 } 2415 2415 2416 2416 .toggle-switch input:checked + .toggle-slider { 2417 - background: var(--accent, #3a7dff); 2417 + background: var(--accent); 2418 2418 } 2419 2419 2420 2420 .toggle-switch input:checked + .toggle-slider::after { 2421 2421 left: 22px; 2422 - background: white; 2422 + background: var(--text-primary); 2423 2423 } 2424 2424 2425 2425 .toggle-label { 2426 2426 font-size: 0.85rem; 2427 - color: #888; 2427 + color: var(--text-tertiary); 2428 2428 min-width: 60px; 2429 2429 } 2430 2430 ··· 2451 2451 align-items: center; 2452 2452 gap: 0.5rem; 2453 2453 font-size: 0.9rem; 2454 - color: #888; 2454 + color: var(--text-tertiary); 2455 2455 } 2456 2456 2457 2457 .expires-select { 2458 2458 padding: 0.5rem 0.75rem; 2459 - background: #0a0a0a; 2460 - border: 1px solid #333; 2459 + background: var(--bg-primary); 2460 + border: 1px solid var(--border-default); 2461 2461 border-radius: 4px; 2462 - color: white; 2462 + color: var(--text-primary); 2463 2463 font-size: 0.9rem; 2464 2464 font-family: inherit; 2465 2465 cursor: pointer; ··· 2499 2499 display: flex; 2500 2500 align-items: center; 2501 2501 gap: 0.5rem; 2502 - background: #0a0a0a; 2503 - border: 1px solid #333; 2502 + background: var(--bg-primary); 2503 + border: 1px solid var(--border-default); 2504 2504 border-radius: 6px; 2505 2505 padding: 0.75rem; 2506 2506 overflow: hidden; ··· 2510 2510 flex: 1; 2511 2511 font-family: monospace; 2512 2512 font-size: 0.85rem; 2513 - color: #5ce87b; 2513 + color: var(--success); 2514 2514 word-break: break-all; 2515 2515 user-select: all; 2516 2516 } ··· 2518 2518 .copy-btn, 2519 2519 .dismiss-btn { 2520 2520 padding: 0.4rem 0.75rem; 2521 - background: #2a2a2a; 2522 - border: 1px solid #3a3a3a; 2521 + background: var(--border-subtle); 2522 + border: 1px solid var(--border-emphasis); 2523 2523 border-radius: 4px; 2524 - color: #888; 2524 + color: var(--text-tertiary); 2525 2525 font-size: 0.85rem; 2526 2526 cursor: pointer; 2527 2527 transition: all 0.2s; ··· 2529 2529 } 2530 2530 2531 2531 .copy-btn:hover { 2532 - background: #3a3a3a; 2532 + background: var(--border-emphasis); 2533 2533 border-color: var(--accent); 2534 2534 color: var(--accent); 2535 2535 } 2536 2536 2537 2537 .dismiss-btn:hover { 2538 - background: #3a3a3a; 2539 - border-color: #666; 2540 - color: #aaa; 2538 + background: var(--border-emphasis); 2539 + border-color: var(--text-muted); 2540 + color: var(--text-secondary); 2541 2541 } 2542 2542 2543 2543 .token-warning { 2544 2544 font-size: 0.85rem; 2545 - color: #e9a545; 2545 + color: var(--warning); 2546 2546 margin: 0; 2547 2547 } 2548 2548 ··· 2555 2555 .tokens-header { 2556 2556 font-size: 0.9rem; 2557 2557 font-weight: 600; 2558 - color: #888; 2558 + color: var(--text-tertiary); 2559 2559 margin: 0 0 0.75rem 0; 2560 2560 } 2561 2561 ··· 2571 2571 align-items: center; 2572 2572 gap: 1rem; 2573 2573 padding: 0.75rem; 2574 - background: #0a0a0a; 2575 - border: 1px solid #2a2a2a; 2574 + background: var(--bg-primary); 2575 + border: 1px solid var(--border-subtle); 2576 2576 border-radius: 6px; 2577 2577 } 2578 2578 ··· 2587 2587 .token-name { 2588 2588 font-family: monospace; 2589 2589 font-size: 0.9rem; 2590 - color: #e8e8e8; 2590 + color: var(--text-primary); 2591 2591 white-space: nowrap; 2592 2592 overflow: hidden; 2593 2593 text-overflow: ellipsis; ··· 2595 2595 2596 2596 .token-meta { 2597 2597 font-size: 0.8rem; 2598 - color: #666; 2598 + color: var(--text-muted); 2599 2599 } 2600 2600 2601 2601 .revoke-btn { 2602 2602 padding: 0.4rem 0.75rem; 2603 2603 background: transparent; 2604 - border: 1px solid #4a2020; 2604 + border: 1px solid color-mix(in srgb, var(--error) 30%, var(--bg-tertiary)); 2605 2605 border-radius: 4px; 2606 - color: #ff6b6b; 2606 + color: var(--error); 2607 2607 font-size: 0.85rem; 2608 2608 cursor: pointer; 2609 2609 transition: all 0.2s; ··· 2612 2612 } 2613 2613 2614 2614 .revoke-btn:hover:not(:disabled) { 2615 - background: rgba(255, 107, 107, 0.1); 2616 - border-color: #ff6b6b; 2615 + background: color-mix(in srgb, var(--error) 10%, transparent); 2616 + border-color: var(--error); 2617 2617 } 2618 2618 2619 2619 .revoke-btn:disabled { ··· 2623 2623 2624 2624 .token-name-input { 2625 2625 padding: 0.5rem 0.75rem; 2626 - background: #0a0a0a; 2627 - border: 1px solid #333; 2626 + background: var(--bg-primary); 2627 + border: 1px solid var(--border-default); 2628 2628 border-radius: 4px; 2629 - color: white; 2629 + color: var(--text-primary); 2630 2630 font-size: 0.9rem; 2631 2631 font-family: inherit; 2632 2632 min-width: 150px; ··· 2644 2644 2645 2645 .loading-tokens { 2646 2646 font-size: 0.9rem; 2647 - color: #666; 2647 + color: var(--text-muted); 2648 2648 margin: 0; 2649 2649 } 2650 2650 </style>
+16 -16
frontend/src/routes/profile/setup/+page.svelte
··· 191 191 align-items: center; 192 192 justify-content: center; 193 193 min-height: 100vh; 194 - color: #888; 194 + color: var(--text-tertiary); 195 195 } 196 196 197 197 main { ··· 213 213 } 214 214 215 215 .subtitle { 216 - color: #aaa; 216 + color: var(--text-secondary); 217 217 margin-bottom: 2rem; 218 218 line-height: 1.5; 219 219 } ··· 222 222 padding: 1rem; 223 223 border-radius: 4px; 224 224 margin-bottom: 1.5rem; 225 - background: rgba(233, 69, 96, 0.1); 226 - border: 1px solid rgba(233, 69, 96, 0.3); 227 - color: #ff6b6b; 225 + background: color-mix(in srgb, var(--error) 10%, transparent); 226 + border: 1px solid color-mix(in srgb, var(--error) 30%, transparent); 227 + color: var(--error); 228 228 } 229 229 230 230 form { 231 - background: #1a1a1a; 231 + background: var(--bg-tertiary); 232 232 padding: 2rem; 233 233 border-radius: 8px; 234 - border: 1px solid #2a2a2a; 234 + border: 1px solid var(--border-subtle); 235 235 } 236 236 237 237 .form-group { ··· 244 244 245 245 label { 246 246 display: block; 247 - color: #aaa; 247 + color: var(--text-secondary); 248 248 margin-bottom: 0.5rem; 249 249 font-size: 0.9rem; 250 250 font-weight: 500; ··· 255 255 textarea { 256 256 width: 100%; 257 257 padding: 0.75rem; 258 - background: #0a0a0a; 259 - border: 1px solid #333; 258 + background: var(--bg-primary); 259 + border: 1px solid var(--border-default); 260 260 border-radius: 4px; 261 - color: white; 261 + color: var(--text-primary); 262 262 font-size: 1rem; 263 263 font-family: inherit; 264 264 transition: all 0.2s; ··· 268 268 input[type='url']:focus, 269 269 textarea:focus { 270 270 outline: none; 271 - border-color: #3a7dff; 271 + border-color: var(--accent); 272 272 } 273 273 274 274 input[type='text']:disabled, ··· 286 286 .hint { 287 287 margin-top: 0.5rem; 288 288 font-size: 0.85rem; 289 - color: #666; 289 + color: var(--text-muted); 290 290 } 291 291 292 292 button { 293 293 width: 100%; 294 294 padding: 0.75rem; 295 - background: #3a7dff; 295 + background: var(--accent); 296 296 color: white; 297 297 border: none; 298 298 border-radius: 4px; ··· 303 303 } 304 304 305 305 button:hover:not(:disabled) { 306 - background: #2868e6; 306 + background: var(--accent-hover); 307 307 transform: translateY(-1px); 308 - box-shadow: 0 4px 12px rgba(58, 125, 255, 0.3); 308 + box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 30%, transparent); 309 309 } 310 310 311 311 button:disabled {
+1 -1
frontend/src/routes/tag/[name]/+page.svelte
··· 124 124 gap: 0.4rem; 125 125 font-size: var(--text-page-heading); 126 126 font-weight: 700; 127 - color: #8ab3ff; 127 + color: var(--accent-hover); 128 128 margin: 0 0 0.5rem 0; 129 129 } 130 130
+59 -59
frontend/src/routes/track/[id]/+page.svelte
··· 637 637 display: flex; 638 638 align-items: center; 639 639 justify-content: center; 640 - background: #1a1a1a; 641 - border: 1px solid #282828; 642 - color: #606060; 640 + background: var(--bg-tertiary); 641 + border: 1px solid var(--border-subtle); 642 + color: var(--text-muted); 643 643 } 644 644 645 645 .track-info-wrapper { ··· 672 672 .track-title { 673 673 font-size: 2rem; 674 674 font-weight: 700; 675 - color: #e8e8e8; 675 + color: var(--text-primary); 676 676 margin: 0; 677 677 line-height: 1.2; 678 678 text-align: center; ··· 684 684 justify-content: center; 685 685 gap: 0.75rem; 686 686 flex-wrap: wrap; 687 - color: #b0b0b0; 687 + color: var(--text-secondary); 688 688 font-size: 1.1rem; 689 689 } 690 690 691 691 .separator { 692 - color: #555; 692 + color: var(--text-muted); 693 693 font-size: 0.8rem; 694 694 } 695 695 696 696 .artist-link { 697 - color: #b0b0b0; 697 + color: var(--text-secondary); 698 698 text-decoration: none; 699 699 font-weight: 500; 700 700 transition: color 0.2s; ··· 712 712 } 713 713 714 714 .features-label { 715 - color: #8ab3ff; 715 + color: var(--accent-hover); 716 716 font-weight: 500; 717 717 } 718 718 719 719 .feature-link { 720 - color: #8ab3ff; 720 + color: var(--accent-hover); 721 721 text-decoration: none; 722 722 font-weight: 500; 723 723 transition: color 0.2s; ··· 729 729 } 730 730 731 731 .feature-separator { 732 - color: #8ab3ff; 732 + color: var(--accent-hover); 733 733 } 734 734 735 735 .album { 736 - color: #909090; 736 + color: var(--text-tertiary); 737 737 display: flex; 738 738 align-items: center; 739 739 gap: 0.5rem; ··· 743 743 744 744 .album-link { 745 745 text-decoration: none; 746 - color: #909090; 746 + color: var(--text-tertiary); 747 747 transition: color 0.2s; 748 748 display: flex; 749 749 align-items: center; ··· 770 770 } 771 771 772 772 .track-stats { 773 - color: #808080; 773 + color: var(--text-tertiary); 774 774 font-size: 0.95rem; 775 775 display: flex; 776 776 align-items: center; ··· 792 792 .tag-badge { 793 793 display: inline-block; 794 794 padding: 0.25rem 0.6rem; 795 - background: rgba(138, 179, 255, 0.15); 796 - color: #8ab3ff; 795 + background: color-mix(in srgb, var(--accent) 15%, transparent); 796 + color: var(--accent-hover); 797 797 border-radius: 4px; 798 798 font-size: 0.85rem; 799 799 font-weight: 500; ··· 802 802 } 803 803 804 804 .tag-badge:hover { 805 - background: rgba(138, 179, 255, 0.25); 806 - color: #a8c8ff; 805 + background: color-mix(in srgb, var(--accent) 25%, transparent); 806 + color: var(--accent-hover); 807 807 } 808 808 809 809 .mobile-side-buttons { ··· 828 828 gap: 0.5rem; 829 829 padding: 0.75rem 1.5rem; 830 830 background: var(--accent); 831 - color: #000; 831 + color: var(--bg-primary); 832 832 border: none; 833 833 border-radius: 24px; 834 834 font-size: 0.95rem; ··· 853 853 gap: 0.5rem; 854 854 padding: 0.75rem 1.5rem; 855 855 background: transparent; 856 - color: #e8e8e8; 857 - border: 1px solid #404040; 856 + color: var(--text-primary); 857 + border: 1px solid var(--border-emphasis); 858 858 border-radius: 24px; 859 859 font-size: 0.95rem; 860 860 font-weight: 500; ··· 866 866 .btn-queue:hover { 867 867 border-color: var(--accent); 868 868 color: var(--accent); 869 - background: rgba(138, 179, 255, 0.1); 869 + background: color-mix(in srgb, var(--accent) 10%, transparent); 870 870 } 871 871 872 872 @media (max-width: 768px) { ··· 963 963 max-width: 500px; 964 964 margin: 1.5rem auto 0; 965 965 padding-top: 1.5rem; 966 - border-top: 1px solid #2a2a2a; 966 + border-top: 1px solid var(--border-subtle); 967 967 } 968 968 969 969 .comments-title { 970 970 font-size: 1rem; 971 971 font-weight: 600; 972 - color: #e8e8e8; 972 + color: var(--text-primary); 973 973 margin: 0 0 0.75rem 0; 974 974 display: flex; 975 975 align-items: center; ··· 977 977 } 978 978 979 979 .comment-count { 980 - color: #888; 980 + color: var(--text-tertiary); 981 981 font-weight: 400; 982 982 } 983 983 ··· 990 990 .comment-input { 991 991 flex: 1; 992 992 padding: 0.6rem 0.8rem; 993 - background: #1a1a1a; 994 - border: 1px solid #333; 993 + background: var(--bg-tertiary); 994 + border: 1px solid var(--border-default); 995 995 border-radius: 6px; 996 - color: #e8e8e8; 996 + color: var(--text-primary); 997 997 font-size: 0.9rem; 998 998 font-family: inherit; 999 999 } ··· 1004 1004 } 1005 1005 1006 1006 .comment-input::placeholder { 1007 - color: #666; 1007 + color: var(--text-muted); 1008 1008 } 1009 1009 1010 1010 .comment-submit { 1011 1011 padding: 0.6rem 1rem; 1012 1012 background: var(--accent); 1013 - color: #000; 1013 + color: var(--bg-primary); 1014 1014 border: none; 1015 1015 border-radius: 6px; 1016 1016 font-size: 0.9rem; ··· 1030 1030 } 1031 1031 1032 1032 .login-prompt { 1033 - color: #888; 1033 + color: var(--text-tertiary); 1034 1034 font-size: 0.9rem; 1035 1035 margin-bottom: 1rem; 1036 1036 } ··· 1045 1045 } 1046 1046 1047 1047 .no-comments { 1048 - color: #666; 1048 + color: var(--text-muted); 1049 1049 font-size: 0.9rem; 1050 1050 text-align: center; 1051 1051 padding: 1rem; ··· 1058 1058 max-height: 300px; 1059 1059 overflow-y: auto; 1060 1060 scrollbar-width: thin; 1061 - scrollbar-color: #333 #0a0a0a; 1061 + scrollbar-color: var(--border-default) var(--bg-primary); 1062 1062 } 1063 1063 1064 1064 .comments-list::-webkit-scrollbar { ··· 1066 1066 } 1067 1067 1068 1068 .comments-list::-webkit-scrollbar-track { 1069 - background: #0a0a0a; 1069 + background: var(--bg-primary); 1070 1070 border-radius: 4px; 1071 1071 } 1072 1072 1073 1073 .comments-list::-webkit-scrollbar-thumb { 1074 - background: #333; 1074 + background: var(--border-default); 1075 1075 border-radius: 4px; 1076 1076 } 1077 1077 1078 1078 .comments-list::-webkit-scrollbar-thumb:hover { 1079 - background: #444; 1079 + background: var(--border-emphasis); 1080 1080 } 1081 1081 1082 1082 .comment { ··· 1084 1084 align-items: flex-start; 1085 1085 gap: 0.6rem; 1086 1086 padding: 0.5rem 0.6rem; 1087 - background: #1a1a1a; 1087 + background: var(--bg-tertiary); 1088 1088 border-radius: 6px; 1089 1089 transition: background 0.15s; 1090 1090 } 1091 1091 1092 1092 .comment:hover { 1093 - background: #222; 1093 + background: var(--bg-hover); 1094 1094 } 1095 1095 1096 1096 .comment-timestamp { 1097 1097 font-size: 0.8rem; 1098 1098 font-weight: 600; 1099 1099 color: var(--accent); 1100 - background: rgba(138, 179, 255, 0.1); 1100 + background: color-mix(in srgb, var(--accent) 10%, transparent); 1101 1101 padding: 0.2rem 0.5rem; 1102 1102 border-radius: 4px; 1103 1103 white-space: nowrap; ··· 1109 1109 } 1110 1110 1111 1111 .comment-timestamp:hover { 1112 - background: rgba(138, 179, 255, 0.25); 1112 + background: color-mix(in srgb, var(--accent) 25%, transparent); 1113 1113 transform: scale(1.05); 1114 1114 } 1115 1115 ··· 1127 1127 } 1128 1128 1129 1129 .comment-separator { 1130 - color: #444; 1130 + color: var(--border-emphasis); 1131 1131 font-size: 0.6rem; 1132 1132 } 1133 1133 1134 1134 .comment-time { 1135 1135 font-size: 0.75rem; 1136 - color: #666; 1136 + color: var(--text-muted); 1137 1137 } 1138 1138 1139 1139 .comment-avatar { ··· 1147 1147 width: 20px; 1148 1148 height: 20px; 1149 1149 border-radius: 50%; 1150 - background: #333; 1150 + background: var(--border-default); 1151 1151 } 1152 1152 1153 1153 .comment-author { 1154 1154 font-size: 0.85rem; 1155 1155 font-weight: 500; 1156 - color: #b0b0b0; 1156 + color: var(--text-secondary); 1157 1157 text-decoration: none; 1158 1158 } 1159 1159 ··· 1163 1163 1164 1164 .comment-text { 1165 1165 font-size: 0.9rem; 1166 - color: #e8e8e8; 1166 + color: var(--text-primary); 1167 1167 margin: 0; 1168 1168 line-height: 1.4; 1169 1169 word-break: break-word; ··· 1180 1180 } 1181 1181 1182 1182 .edited-indicator { 1183 - color: #555; 1183 + color: var(--text-muted); 1184 1184 font-style: italic; 1185 1185 } 1186 1186 ··· 1201 1201 background: none; 1202 1202 border: none; 1203 1203 padding: 0; 1204 - color: #666; 1204 + color: var(--text-muted); 1205 1205 font-size: 0.8rem; 1206 1206 cursor: pointer; 1207 1207 transition: color 0.15s; ··· 1213 1213 } 1214 1214 1215 1215 .comment-action-btn.delete:hover { 1216 - color: #ff6b6b; 1216 + color: var(--error); 1217 1217 } 1218 1218 1219 1219 /* mobile: always show actions */ ··· 1233 1233 .comment-edit-input { 1234 1234 width: 100%; 1235 1235 padding: 0.5rem; 1236 - background: #0a0a0a; 1237 - border: 1px solid #333; 1236 + background: var(--bg-primary); 1237 + border: 1px solid var(--border-default); 1238 1238 border-radius: 4px; 1239 - color: #e8e8e8; 1239 + color: var(--text-primary); 1240 1240 font-size: 0.9rem; 1241 1241 font-family: inherit; 1242 1242 } ··· 1263 1263 1264 1264 .edit-form-btn.save { 1265 1265 background: var(--accent); 1266 - color: #000; 1266 + color: var(--bg-primary); 1267 1267 border: none; 1268 1268 font-weight: 500; 1269 1269 } ··· 1274 1274 1275 1275 .edit-form-btn.cancel { 1276 1276 background: transparent; 1277 - color: #888; 1278 - border: 1px solid #444; 1277 + color: var(--text-tertiary); 1278 + border: 1px solid var(--border-emphasis); 1279 1279 } 1280 1280 1281 1281 .edit-form-btn.cancel:hover { 1282 - border-color: #666; 1283 - color: #aaa; 1282 + border-color: var(--text-muted); 1283 + color: var(--text-secondary); 1284 1284 } 1285 1285 1286 1286 /* comments container prevents layout shift during transition */ ··· 1294 1294 } 1295 1295 1296 1296 .comment.skeleton:hover { 1297 - background: #1a1a1a; 1297 + background: var(--bg-tertiary); 1298 1298 } 1299 1299 1300 1300 .skeleton-bar { 1301 1301 background: linear-gradient( 1302 1302 90deg, 1303 - #1a1a1a 0%, 1304 - #242424 50%, 1305 - #1a1a1a 100% 1303 + var(--bg-tertiary) 0%, 1304 + var(--bg-hover) 50%, 1305 + var(--bg-tertiary) 100% 1306 1306 ); 1307 1307 background-size: 200% 100%; 1308 1308 animation: shimmer 1.5s ease-in-out infinite;
+7 -7
frontend/src/routes/u/[handle]/+error.svelte
··· 92 92 93 93 h1 { 94 94 font-size: 4rem; 95 - color: #e8e8e8; 95 + color: var(--text-primary); 96 96 margin: 0 0 1rem 0; 97 97 font-weight: 700; 98 98 } 99 99 100 100 .error-message { 101 101 font-size: 1.25rem; 102 - color: #b0b0b0; 102 + color: var(--text-secondary); 103 103 margin: 0 0 0.5rem 0; 104 104 } 105 105 106 106 .error-detail { 107 107 font-size: 1rem; 108 - color: #808080; 108 + color: var(--text-tertiary); 109 109 margin: 0 0 2rem 0; 110 110 } 111 111 ··· 130 130 131 131 .bsky-link { 132 132 background: var(--accent); 133 - color: #000; 133 + color: var(--bg-primary); 134 134 } 135 135 136 136 .bsky-link:hover { 137 - background: #6a9fff; 138 - border-color: #6a9fff; 137 + background: var(--accent-hover); 138 + border-color: var(--accent-hover); 139 139 } 140 140 141 141 .home-link:hover { 142 142 background: var(--accent); 143 - color: #000; 143 + color: var(--bg-primary); 144 144 } 145 145 146 146 @media (max-width: 768px) {
+32 -32
frontend/src/routes/u/[handle]/+page.svelte
··· 356 356 gap: 2rem; 357 357 margin-bottom: 3rem; 358 358 padding: 2rem; 359 - background: #141414; 360 - border: 1px solid #282828; 359 + background: var(--bg-secondary); 360 + border: 1px solid var(--border-subtle); 361 361 border-radius: 8px; 362 362 } 363 363 ··· 390 390 height: 120px; 391 391 border-radius: 50%; 392 392 object-fit: cover; 393 - border: 3px solid #333; 393 + border: 3px solid var(--border-default); 394 394 } 395 395 396 396 .artist-info h1 { 397 397 font-size: 2.5rem; 398 398 margin: 0 0 0.5rem 0; 399 - color: #e8e8e8; 399 + color: var(--text-primary); 400 400 word-wrap: break-word; 401 401 overflow-wrap: break-word; 402 402 hyphens: auto; 403 403 } 404 404 405 405 .handle { 406 - color: #909090; 406 + color: var(--text-tertiary); 407 407 font-size: 1.1rem; 408 408 margin: 0 0 1rem 0; 409 409 text-decoration: none; ··· 416 416 } 417 417 418 418 .bio { 419 - color: #b0b0b0; 419 + color: var(--text-secondary); 420 420 line-height: 1.6; 421 421 margin: 0; 422 422 } ··· 427 427 428 428 .analytics h2 { 429 429 margin-bottom: 1.5rem; 430 - color: #e8e8e8; 430 + color: var(--text-primary); 431 431 font-size: 1.8rem; 432 432 } 433 433 ··· 448 448 } 449 449 450 450 .section-header span { 451 - color: #808080; 451 + color: var(--text-tertiary); 452 452 font-size: 0.9rem; 453 453 text-transform: uppercase; 454 454 letter-spacing: 0.1em; ··· 465 465 gap: 1rem; 466 466 align-items: center; 467 467 padding: 1rem; 468 - background: #141414; 469 - border: 1px solid #282828; 468 + background: var(--bg-secondary); 469 + border: 1px solid var(--border-subtle); 470 470 border-radius: 10px; 471 471 color: inherit; 472 472 text-decoration: none; ··· 484 484 border-radius: 6px; 485 485 overflow: hidden; 486 486 flex-shrink: 0; 487 - background: #1a1a1a; 488 - border: 1px solid #333; 487 + background: var(--bg-tertiary); 488 + border: 1px solid var(--border-default); 489 489 display: flex; 490 490 align-items: center; 491 491 justify-content: center; ··· 499 499 } 500 500 501 501 .album-cover-placeholder { 502 - color: #666; 502 + color: var(--text-muted); 503 503 display: flex; 504 504 align-items: center; 505 505 justify-content: center; ··· 510 510 .album-card-meta h3 { 511 511 margin: 0 0 0.35rem 0; 512 512 font-size: 1.05rem; 513 - color: #fafafa; 513 + color: var(--text-primary); 514 514 text-transform: lowercase; 515 515 white-space: nowrap; 516 516 overflow: hidden; ··· 529 529 530 530 .album-card-meta p { 531 531 margin: 0; 532 - color: #9a9a9a; 532 + color: var(--text-tertiary); 533 533 font-size: 0.9rem; 534 534 display: flex; 535 535 align-items: center; ··· 538 538 539 539 .album-card-meta .dot { 540 540 font-size: 0.65rem; 541 - color: #555; 541 + color: var(--text-muted); 542 542 } 543 543 544 544 .stat-card { 545 - background: #141414; 546 - border: 1px solid #282828; 545 + background: var(--bg-secondary); 546 + border: 1px solid var(--border-subtle); 547 547 border-radius: 8px; 548 548 padding: 1.5rem; 549 549 transition: border-color 0.2s; 550 550 } 551 551 552 552 .stat-card:hover { 553 - border-color: #404040; 553 + border-color: var(--border-emphasis); 554 554 } 555 555 556 556 .stat-value { ··· 562 562 } 563 563 564 564 .stat-label { 565 - color: #909090; 565 + color: var(--text-tertiary); 566 566 font-size: 0.9rem; 567 567 text-transform: lowercase; 568 568 line-height: 1; ··· 578 578 .stat-card.top-item:hover { 579 579 border-color: var(--accent); 580 580 transform: translateY(-2px); 581 - box-shadow: 0 4px 12px rgba(138, 179, 255, 0.2); 581 + box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 20%, transparent); 582 582 } 583 583 584 584 .top-item-title { 585 585 font-size: 1.2rem; 586 - color: #e8e8e8; 586 + color: var(--text-primary); 587 587 margin: 0.5rem 0; 588 588 font-weight: 500; 589 589 line-height: 1; ··· 605 605 .skeleton-bar { 606 606 background: linear-gradient( 607 607 90deg, 608 - #1a1a1a 0%, 609 - #242424 50%, 610 - #1a1a1a 100% 608 + var(--bg-tertiary) 0%, 609 + var(--bg-hover) 50%, 610 + var(--bg-tertiary) 100% 611 611 ); 612 612 background-size: 200% 100%; 613 613 animation: shimmer 1.5s ease-in-out infinite; ··· 652 652 653 653 .tracks h2 { 654 654 margin-bottom: 1.5rem; 655 - color: #e8e8e8; 655 + color: var(--text-primary); 656 656 font-size: 1.8rem; 657 657 } 658 658 659 659 .tracks-loading { 660 660 margin-left: 0.75rem; 661 661 font-size: 0.95rem; 662 - color: #9ea5b4; 662 + color: var(--text-secondary); 663 663 font-weight: 400; 664 664 text-transform: lowercase; 665 665 } ··· 673 673 .empty-state { 674 674 text-align: center; 675 675 padding: 3rem; 676 - background: #141414; 677 - border: 1px solid #282828; 676 + background: var(--bg-secondary); 677 + border: 1px solid var(--border-subtle); 678 678 border-radius: 8px; 679 679 } 680 680 681 681 .empty-message { 682 - color: #b0b0b0; 682 + color: var(--text-secondary); 683 683 font-size: 1.25rem; 684 684 margin: 0 0 0.5rem 0; 685 685 } 686 686 687 687 .empty-detail { 688 - color: #808080; 688 + color: var(--text-tertiary); 689 689 margin: 0 0 1.5rem 0; 690 690 } 691 691 ··· 702 702 703 703 .bsky-link:hover { 704 704 background: var(--accent); 705 - color: #000; 705 + color: var(--bg-primary); 706 706 } 707 707 708 708 /* respect reduced motion preference */
+12 -12
frontend/src/routes/u/[handle]/album/[slug]/+page.svelte
··· 178 178 width: 200px; 179 179 height: 200px; 180 180 border-radius: 8px; 181 - background: #1a1a1a; 182 - border: 1px solid #282828; 181 + background: var(--bg-tertiary); 182 + border: 1px solid var(--border-subtle); 183 183 display: flex; 184 184 align-items: center; 185 185 justify-content: center; 186 - color: #606060; 186 + color: var(--text-muted); 187 187 } 188 188 189 189 .album-info-wrapper { ··· 217 217 font-size: 0.75rem; 218 218 font-weight: 600; 219 219 letter-spacing: 0.1em; 220 - color: #808080; 220 + color: var(--text-tertiary); 221 221 margin: 0; 222 222 } 223 223 ··· 225 225 font-size: 3rem; 226 226 font-weight: 700; 227 227 margin: 0; 228 - color: #ffffff; 228 + color: var(--text-primary); 229 229 line-height: 1.1; 230 230 word-wrap: break-word; 231 231 overflow-wrap: break-word; ··· 237 237 align-items: center; 238 238 gap: 0.75rem; 239 239 font-size: 0.95rem; 240 - color: #b0b0b0; 240 + color: var(--text-secondary); 241 241 } 242 242 243 243 .artist-link { 244 - color: #b0b0b0; 244 + color: var(--text-secondary); 245 245 text-decoration: none; 246 246 font-weight: 600; 247 247 transition: color 0.2s; ··· 252 252 } 253 253 254 254 .meta-separator { 255 - color: #555; 255 + color: var(--text-muted); 256 256 font-size: 0.7rem; 257 257 } 258 258 ··· 279 279 280 280 .play-button { 281 281 background: var(--accent); 282 - color: #000; 282 + color: var(--bg-primary); 283 283 } 284 284 285 285 .play-button:hover { ··· 288 288 289 289 .queue-button { 290 290 background: transparent; 291 - color: #e8e8e8; 292 - border: 1px solid #333; 291 + color: var(--text-primary); 292 + border: 1px solid var(--border-default); 293 293 } 294 294 295 295 .queue-button:hover { ··· 305 305 .section-heading { 306 306 font-size: 1.25rem; 307 307 font-weight: 600; 308 - color: #e8e8e8; 308 + color: var(--text-primary); 309 309 margin-bottom: 1rem; 310 310 text-transform: lowercase; 311 311 }