music on atproto
plyr.fm
1/* plyr.fm design tokens */
2:root {
3 --accent: #6a9fff;
4 --accent-hover: #8ab3ff;
5 --accent-muted: #4a7ddd;
6
7 --bg-primary: #0a0a0a;
8 --bg-secondary: #141414;
9 --bg-tertiary: #1a1a1a;
10 --bg-hover: #1f1f1f;
11
12 --border-subtle: #282828;
13 --border-default: #333333;
14 --border-emphasis: #444444;
15
16 --text-primary: #e8e8e8;
17 --text-secondary: #b0b0b0;
18 --text-tertiary: #808080;
19 --text-muted: #666666;
20
21 --success: #4ade80;
22 --warning: #fbbf24;
23 --error: #ef4444;
24}
25
26* { box-sizing: border-box; }
27
28body {
29 font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Consolas', monospace;
30 background: var(--bg-primary);
31 color: var(--text-primary);
32 max-width: 900px;
33 margin: 0 auto;
34 padding: 24px;
35 line-height: 1.6;
36 -webkit-font-smoothing: antialiased;
37}
38
39h1 {
40 color: var(--text-primary);
41 font-size: 1.5rem;
42 font-weight: 600;
43 margin: 0 0 4px 0;
44}
45
46.subtitle {
47 color: var(--text-tertiary);
48 margin: 0 0 32px 0;
49 font-size: 0.9rem;
50}
51
52/* auth form */
53.auth-section {
54 background: var(--bg-secondary);
55 border: 1px solid var(--border-subtle);
56 border-radius: 8px;
57 padding: 20px;
58 margin-bottom: 24px;
59}
60
61.auth-section input[type="password"] {
62 font-family: inherit;
63 background: var(--bg-tertiary);
64 border: 1px solid var(--border-default);
65 color: var(--text-primary);
66 padding: 10px 14px;
67 border-radius: 6px;
68 width: 280px;
69 font-size: 0.9rem;
70}
71
72.auth-section input:focus {
73 outline: none;
74 border-color: var(--accent);
75}
76
77/* buttons */
78.btn {
79 font-family: inherit;
80 font-size: 0.85rem;
81 font-weight: 500;
82 padding: 10px 16px;
83 border-radius: 6px;
84 border: none;
85 cursor: pointer;
86 transition: all 0.15s ease;
87}
88
89.btn-primary {
90 background: var(--accent);
91 color: var(--bg-primary);
92}
93.btn-primary:hover { background: var(--accent-hover); }
94
95.btn-secondary {
96 background: var(--bg-tertiary);
97 color: var(--text-secondary);
98 border: 1px solid var(--border-default);
99}
100.btn-secondary:hover {
101 background: var(--bg-hover);
102 border-color: var(--border-emphasis);
103}
104
105.btn-warning {
106 background: var(--warning);
107 color: var(--bg-primary);
108}
109.btn-warning:hover {
110 background: #d97706;
111}
112
113.btn:disabled {
114 background: var(--bg-tertiary);
115 color: var(--text-muted);
116 cursor: not-allowed;
117 border: 1px solid var(--border-subtle);
118}
119
120/* header row */
121.header-row {
122 display: flex;
123 align-items: center;
124 justify-content: space-between;
125 margin-bottom: 20px;
126}
127
128/* filter controls */
129.filter-row {
130 display: flex;
131 align-items: center;
132 gap: 8px;
133 margin-bottom: 16px;
134 padding-bottom: 16px;
135 border-bottom: 1px solid var(--border-subtle);
136}
137
138.filter-label {
139 color: var(--text-tertiary);
140 font-size: 0.85rem;
141 margin-right: 4px;
142}
143
144.filter-btn {
145 font-family: inherit;
146 font-size: 0.8rem;
147 padding: 6px 12px;
148 border-radius: 4px;
149 border: 1px solid var(--border-default);
150 background: var(--bg-tertiary);
151 color: var(--text-secondary);
152 cursor: pointer;
153 transition: all 0.15s ease;
154}
155
156.filter-btn:hover {
157 background: var(--bg-hover);
158 border-color: var(--border-emphasis);
159 color: var(--text-primary);
160}
161
162.filter-btn.active {
163 background: var(--accent);
164 color: var(--bg-primary);
165 border-color: var(--accent);
166}
167
168.filter-count {
169 margin-left: auto;
170 color: var(--text-tertiary);
171 font-size: 0.85rem;
172}
173
174.header-row h2 {
175 margin: 0;
176 font-size: 1.1rem;
177 font-weight: 500;
178 color: var(--text-primary);
179}
180
181/* flags list */
182.flags-list {
183 display: flex;
184 flex-direction: column;
185 gap: 16px;
186}
187
188.flag-card {
189 background: var(--bg-secondary);
190 border: 1px solid var(--border-subtle);
191 border-radius: 8px;
192 padding: 20px;
193}
194
195.flag-card.resolved {
196 opacity: 0.5;
197}
198
199.flag-header {
200 display: flex;
201 justify-content: space-between;
202 align-items: flex-start;
203 gap: 16px;
204}
205
206.track-info {
207 flex: 1;
208 min-width: 0;
209}
210
211.track-info h3 {
212 margin: 0 0 4px 0;
213 font-size: 1rem;
214 font-weight: 500;
215 color: var(--text-primary);
216}
217
218.track-info h3 a {
219 color: var(--text-primary);
220 text-decoration: none;
221}
222
223.track-info h3 a:hover {
224 color: var(--accent);
225 text-decoration: underline;
226}
227
228.track-info .artist {
229 color: var(--text-secondary);
230 font-size: 0.9rem;
231}
232
233.track-info .artist a {
234 color: var(--accent);
235 text-decoration: none;
236}
237
238.track-info .artist a:hover {
239 text-decoration: underline;
240}
241
242.track-info .uri {
243 font-size: 0.75rem;
244 color: var(--text-muted);
245 word-break: break-all;
246 margin-top: 8px;
247}
248
249.flag-badges {
250 display: flex;
251 flex-direction: column;
252 align-items: flex-end;
253 gap: 6px;
254 flex-shrink: 0;
255}
256
257.badge {
258 display: inline-block;
259 padding: 4px 10px;
260 border-radius: 4px;
261 font-size: 0.75rem;
262 font-weight: 500;
263}
264
265.badge.pending {
266 background: rgba(251, 191, 36, 0.15);
267 color: var(--warning);
268}
269
270.badge.resolved {
271 background: rgba(74, 222, 128, 0.15);
272 color: var(--success);
273}
274
275.badge.matches {
276 background: rgba(106, 159, 255, 0.15);
277 color: var(--accent);
278}
279
280.badge.env {
281 background: rgba(139, 92, 246, 0.15);
282 color: #a78bfa;
283 text-transform: uppercase;
284 letter-spacing: 0.5px;
285}
286
287/* matches section */
288.matches {
289 background: var(--bg-primary);
290 border-radius: 6px;
291 padding: 14px;
292 margin-top: 16px;
293}
294
295.matches h4 {
296 margin: 0 0 10px 0;
297 color: var(--text-tertiary);
298 font-size: 0.8rem;
299 font-weight: 500;
300 text-transform: lowercase;
301}
302
303.match-item {
304 display: flex;
305 justify-content: space-between;
306 align-items: center;
307 padding: 8px 0;
308 border-bottom: 1px solid var(--border-subtle);
309 font-size: 0.85rem;
310}
311
312.match-item:last-child { border-bottom: none; }
313
314.match-item .title {
315 color: var(--text-primary);
316}
317
318.match-item .artist {
319 color: var(--text-tertiary);
320}
321
322/* actions */
323.flag-actions {
324 display: flex;
325 justify-content: flex-end;
326 gap: 10px;
327 margin-top: 16px;
328 padding-top: 16px;
329 border-top: 1px solid var(--border-subtle);
330}
331
332/* resolution info display */
333.resolution-info {
334 display: flex;
335 flex-direction: column;
336 gap: 8px;
337 width: 100%;
338}
339
340.resolution-reason {
341 color: var(--success);
342 font-size: 0.85rem;
343 font-weight: 500;
344 text-align: right;
345}
346
347.resolution-notes {
348 background: var(--bg-primary);
349 border: 1px solid var(--border-subtle);
350 border-radius: 6px;
351 padding: 12px 14px;
352 font-size: 0.8rem;
353 color: var(--text-secondary);
354 line-height: 1.5;
355 width: 100%;
356 text-align: left;
357}
358
359/* multi-step resolve flow */
360.resolve-flow {
361 display: flex;
362 gap: 8px;
363 align-items: center;
364}
365
366/* reason selection (step 2) */
367.reason-select {
368 display: flex;
369 gap: 6px;
370 flex-wrap: wrap;
371 align-items: center;
372}
373
374.reason-btn {
375 font-family: inherit;
376 font-size: 0.8rem;
377 padding: 6px 12px;
378 border-radius: 4px;
379 border: 1px solid var(--border-default);
380 background: var(--bg-tertiary);
381 color: var(--text-secondary);
382 cursor: pointer;
383 transition: all 0.15s ease;
384}
385
386.reason-btn:hover {
387 background: var(--bg-hover);
388 border-color: var(--border-emphasis);
389 color: var(--text-primary);
390}
391
392.reason-btn.selected {
393 background: var(--warning);
394 color: var(--bg-primary);
395 border-color: var(--warning);
396}
397
398.reason-btn.cancel {
399 background: transparent;
400 border-color: var(--border-subtle);
401 color: var(--text-muted);
402}
403
404.reason-btn.cancel:hover {
405 border-color: var(--error);
406 color: var(--error);
407}
408
409/* confirm step (step 3) */
410.confirm-step {
411 display: flex;
412 gap: 8px;
413 align-items: center;
414}
415
416.confirm-text {
417 color: var(--text-secondary);
418 font-size: 0.85rem;
419}
420
421.confirm-text strong {
422 color: var(--warning);
423}
424
425.btn-confirm {
426 background: var(--error);
427 color: white;
428}
429
430.btn-confirm:hover {
431 background: #dc2626;
432}
433
434/* states */
435.empty, .loading {
436 color: var(--text-muted);
437 text-align: center;
438 padding: 48px 24px;
439}
440
441.no-context {
442 color: var(--text-muted);
443 font-style: italic;
444}
445
446/* toast */
447.toast {
448 position: fixed;
449 bottom: 24px;
450 left: 24px;
451 padding: 12px 20px;
452 border-radius: 6px;
453 font-size: 0.85rem;
454 animation: fadeInUp 0.2s ease, fadeOut 0.3s ease 2.7s forwards;
455}
456
457.toast.success {
458 background: rgba(74, 222, 128, 0.15);
459 color: var(--success);
460 border: 1px solid rgba(74, 222, 128, 0.3);
461}
462
463.toast.error {
464 background: rgba(239, 68, 68, 0.15);
465 color: var(--error);
466 border: 1px solid rgba(239, 68, 68, 0.3);
467}
468
469@keyframes fadeInUp {
470 from { opacity: 0; transform: translateY(10px); }
471 to { opacity: 1; transform: translateY(0); }
472}
473
474@keyframes fadeOut {
475 to { opacity: 0; }
476}
477
478/* htmx indicator */
479.htmx-indicator {
480 opacity: 0;
481 transition: opacity 0.2s ease;
482}
483.htmx-request .htmx-indicator {
484 opacity: 1;
485}
486.htmx-request.htmx-indicator {
487 opacity: 1;
488}
489
490/* refresh button with inline indicator */
491.btn-secondary .htmx-indicator {
492 display: none;
493 margin-right: 6px;
494}
495.btn-secondary.htmx-request .htmx-indicator {
496 display: inline;
497}
498
499/* mobile */
500@media (max-width: 640px) {
501 body { padding: 16px; }
502 .auth-section input[type="password"] { width: 100%; margin-bottom: 12px; }
503 .flag-header { flex-direction: column; }
504 .flag-badges { margin-top: 12px; }
505}