Lexicon-driven ATProto AppView in Gleam — port of HappyView
1:root {
2 color-scheme: dark light;
3 --bg: #0a0f0e;
4 --bg-elev: #121a18;
5 --bg-card: #15201d;
6 --border: #243330;
7 --border-strong: #35524a;
8 --text: #e8f0ed;
9 --muted: #8aa399;
10 --accent: #5eead4;
11 --accent-dim: #2dd4bf33;
12 --accent-text: #042f2e;
13 --warn: #fbbf24;
14 --danger: #f87171;
15 --ok: #4ade80;
16 --shadow: 0 18px 50px #00000066;
17 --radius: 14px;
18 --font: "Segoe UI", ui-sans-serif, system-ui, -apple-system, sans-serif;
19 --mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, Consolas, monospace;
20 --max: 52rem;
21}
22
23@media (prefers-color-scheme: light) {
24 :root {
25 --bg: #f4f7f6;
26 --bg-elev: #ffffff;
27 --bg-card: #ffffff;
28 --border: #d5e0dc;
29 --border-strong: #a8c0b8;
30 --text: #10211c;
31 --muted: #5b726a;
32 --accent: #0f766e;
33 --accent-dim: #14b8a633;
34 --accent-text: #ecfdf5;
35 --shadow: 0 12px 40px #0f172a14;
36 }
37}
38
39* {
40 box-sizing: border-box;
41}
42
43html,
44body {
45 margin: 0;
46 min-height: 100%;
47 background: var(--bg);
48 color: var(--text);
49 font-family: var(--font);
50 line-height: 1.5;
51}
52
53body {
54 position: relative;
55 overflow-x: hidden;
56}
57
58a {
59 color: var(--accent);
60 text-decoration: none;
61}
62
63a:hover {
64 text-decoration: underline;
65}
66
67code,
68pre,
69.mono {
70 font-family: var(--mono);
71}
72
73code {
74 font-size: 0.88em;
75 background: var(--accent-dim);
76 color: var(--accent);
77 padding: 0.1em 0.35em;
78 border-radius: 6px;
79}
80
81.bg-glow {
82 pointer-events: none;
83 position: fixed;
84 inset: -20% -10% auto;
85 height: 55vh;
86 background:
87 radial-gradient(ellipse 50% 60% at 20% 20%, #14b8a622, transparent 60%),
88 radial-gradient(ellipse 40% 50% at 80% 10%, #fbbf2414, transparent 55%);
89 z-index: 0;
90}
91
92.shell {
93 position: relative;
94 z-index: 1;
95 max-width: var(--max);
96 margin: 0 auto;
97 padding: 1.5rem 1.15rem 3rem;
98}
99
100.top {
101 display: flex;
102 align-items: flex-start;
103 justify-content: space-between;
104 gap: 1rem;
105 margin-bottom: 1.25rem;
106}
107
108.brand {
109 display: flex;
110 gap: 0.85rem;
111 align-items: center;
112}
113
114.logo {
115 width: 2.6rem;
116 height: 2.6rem;
117 border-radius: 0.85rem;
118 background:
119 linear-gradient(145deg, #134e4a, #0c1210 55%),
120 linear-gradient(145deg, transparent 40%, #5eead455);
121 border: 1px solid var(--border-strong);
122 box-shadow: inset 0 1px 0 #ffffff18, var(--shadow);
123 position: relative;
124}
125
126.logo::after {
127 content: "";
128 position: absolute;
129 inset: 28% 30% 24%;
130 border: 2px solid var(--accent);
131 border-radius: 2px 2px 40% 40%;
132 border-bottom: none;
133 transform: rotate(180deg);
134}
135
136.brand h1 {
137 margin: 0;
138 font-size: 1.45rem;
139 letter-spacing: -0.02em;
140 font-weight: 700;
141}
142
143.tagline {
144 margin: 0.1rem 0 0;
145 color: var(--muted);
146 font-size: 0.9rem;
147}
148
149.top-meta {
150 display: flex;
151 align-items: center;
152 gap: 0.5rem;
153}
154
155.pill {
156 display: inline-flex;
157 align-items: center;
158 gap: 0.35rem;
159 font-size: 0.78rem;
160 font-weight: 600;
161 padding: 0.28rem 0.65rem;
162 border-radius: 999px;
163 border: 1px solid var(--border);
164 background: var(--bg-elev);
165 color: var(--muted);
166 white-space: nowrap;
167}
168
169.pill.sm {
170 font-size: 0.72rem;
171 padding: 0.12rem 0.45rem;
172 vertical-align: middle;
173}
174
175.pill.ok {
176 color: var(--ok);
177 border-color: #4ade8044;
178 background: #4ade8014;
179}
180
181.pill.warn {
182 color: var(--warn);
183 border-color: #fbbf2444;
184 background: #fbbf2414;
185}
186
187.pill.err {
188 color: var(--danger);
189 border-color: #f8717144;
190 background: #f8717114;
191}
192
193.tabs {
194 display: flex;
195 gap: 0.35rem;
196 overflow-x: auto;
197 padding: 0.25rem;
198 margin-bottom: 1.25rem;
199 border: 1px solid var(--border);
200 border-radius: 999px;
201 background: color-mix(in srgb, var(--bg-elev) 90%, transparent);
202 backdrop-filter: blur(10px);
203}
204
205.tab {
206 appearance: none;
207 border: 0;
208 background: transparent;
209 color: var(--muted);
210 font: inherit;
211 font-size: 0.92rem;
212 font-weight: 600;
213 padding: 0.55rem 0.95rem;
214 border-radius: 999px;
215 cursor: pointer;
216 white-space: nowrap;
217 transition: 0.15s ease;
218}
219
220.tab:hover {
221 color: var(--text);
222 background: #ffffff08;
223}
224
225.tab.active {
226 color: var(--accent-text);
227 background: var(--accent);
228}
229
230.main {
231 min-height: 22rem;
232}
233
234.panel {
235 animation: fade 0.18s ease;
236}
237
238.panel[hidden] {
239 display: none !important;
240}
241
242@keyframes fade {
243 from {
244 opacity: 0;
245 transform: translateY(4px);
246 }
247 to {
248 opacity: 1;
249 transform: none;
250 }
251}
252
253.panel-head {
254 display: flex;
255 align-items: flex-start;
256 justify-content: space-between;
257 gap: 1rem;
258 margin-bottom: 1rem;
259}
260
261.panel-head h2 {
262 margin: 0;
263 font-size: 1.2rem;
264 letter-spacing: -0.02em;
265}
266
267.panel-head p {
268 margin: 0.2rem 0 0;
269}
270
271.muted {
272 color: var(--muted);
273}
274
275h3 {
276 margin: 1.5rem 0 0.65rem;
277 font-size: 0.95rem;
278 text-transform: uppercase;
279 letter-spacing: 0.06em;
280 color: var(--muted);
281 font-weight: 700;
282}
283
284.stack {
285 display: flex;
286 flex-direction: column;
287 gap: 0.9rem;
288}
289
290label {
291 display: flex;
292 flex-direction: column;
293 gap: 0.35rem;
294 font-size: 0.9rem;
295 font-weight: 600;
296}
297
298label.check {
299 flex-direction: row;
300 align-items: center;
301 gap: 0.55rem;
302 font-weight: 500;
303 color: var(--muted);
304}
305
306input[type="text"],
307input[type="password"],
308textarea {
309 width: 100%;
310 font: inherit;
311 color: var(--text);
312 background: var(--bg);
313 border: 1px solid var(--border);
314 border-radius: 10px;
315 padding: 0.7rem 0.85rem;
316 outline: none;
317 transition: border-color 0.15s ease, box-shadow 0.15s ease;
318}
319
320textarea {
321 resize: vertical;
322 min-height: 6rem;
323 line-height: 1.45;
324}
325
326input:focus,
327textarea:focus {
328 border-color: var(--accent);
329 box-shadow: 0 0 0 3px var(--accent-dim);
330}
331
332.row {
333 display: flex;
334 flex-wrap: wrap;
335 align-items: center;
336 gap: 0.65rem;
337}
338
339.btn {
340 appearance: none;
341 border: 1px solid var(--border-strong);
342 background: var(--bg-elev);
343 color: var(--text);
344 font: inherit;
345 font-weight: 650;
346 font-size: 0.92rem;
347 padding: 0.55rem 0.95rem;
348 border-radius: 10px;
349 cursor: pointer;
350 transition: 0.15s ease;
351}
352
353.btn:hover {
354 border-color: var(--accent);
355 color: var(--accent);
356}
357
358.btn:active {
359 transform: translateY(1px);
360}
361
362.btn.primary {
363 background: var(--accent);
364 border-color: transparent;
365 color: var(--accent-text);
366}
367
368.btn.primary:hover {
369 filter: brightness(1.05);
370 color: var(--accent-text);
371}
372
373.btn.ghost {
374 background: transparent;
375}
376
377.btn.danger:hover {
378 border-color: var(--danger);
379 color: var(--danger);
380}
381
382.btn:disabled {
383 opacity: 0.5;
384 cursor: not-allowed;
385}
386
387.inline-form {
388 display: flex;
389 flex-wrap: wrap;
390 gap: 0.55rem;
391 margin-bottom: 0.75rem;
392}
393
394.inline-form input {
395 flex: 1 1 12rem;
396}
397
398.card-list {
399 display: flex;
400 flex-direction: column;
401 gap: 0.7rem;
402}
403
404.card {
405 background: var(--bg-card);
406 border: 1px solid var(--border);
407 border-radius: var(--radius);
408 padding: 0.95rem 1.05rem;
409 box-shadow: var(--shadow);
410}
411
412.card.post .meta {
413 display: flex;
414 flex-wrap: wrap;
415 gap: 0.5rem 0.85rem;
416 margin-bottom: 0.55rem;
417 font-size: 0.82rem;
418 color: var(--muted);
419 font-family: var(--mono);
420}
421
422.card.post .body {
423 white-space: pre-wrap;
424 word-break: break-word;
425 font-size: 1.02rem;
426}
427
428.card .title {
429 font-weight: 700;
430 margin-bottom: 0.25rem;
431}
432
433.card .sub {
434 color: var(--muted);
435 font-size: 0.88rem;
436 font-family: var(--mono);
437 word-break: break-all;
438}
439
440.empty {
441 border: 1px dashed var(--border-strong);
442 border-radius: var(--radius);
443 padding: 2rem 1.25rem;
444 text-align: center;
445 color: var(--muted);
446 background: color-mix(in srgb, var(--bg-elev) 70%, transparent);
447}
448
449.stats {
450 display: grid;
451 grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
452 gap: 0.75rem;
453 margin-bottom: 0.5rem;
454}
455
456.stat {
457 background: var(--bg-card);
458 border: 1px solid var(--border);
459 border-radius: var(--radius);
460 padding: 1rem 1.1rem;
461 display: flex;
462 flex-direction: column;
463 gap: 0.15rem;
464}
465
466.stat-n {
467 font-size: 1.75rem;
468 font-weight: 750;
469 letter-spacing: -0.03em;
470 color: var(--accent);
471 font-variant-numeric: tabular-nums;
472}
473
474.stat-l {
475 color: var(--muted);
476 font-size: 0.85rem;
477 text-transform: uppercase;
478 letter-spacing: 0.05em;
479 font-weight: 650;
480}
481
482.code-block {
483 margin: 0;
484 padding: 0.9rem 1rem;
485 background: #00000055;
486 border: 1px solid var(--border);
487 border-radius: 12px;
488 overflow: auto;
489 font-size: 0.82rem;
490 line-height: 1.45;
491 color: var(--muted);
492 white-space: pre-wrap;
493 word-break: break-word;
494}
495
496.callout {
497 margin: 0.5rem 0 0.85rem;
498 padding: 0.85rem 1rem;
499 border-radius: 12px;
500 border: 1px solid #fbbf2455;
501 background: #fbbf2414;
502 color: var(--text);
503 font-size: 0.9rem;
504}
505
506.callout strong {
507 color: var(--warn);
508}
509
510.callout code {
511 user-select: all;
512}
513
514.hint {
515 margin-top: 1.25rem;
516 font-size: 0.9rem;
517}
518
519.muted-card {
520 margin-top: 1.5rem;
521}
522
523.muted-card h3 {
524 margin-top: 0;
525}
526
527.foot {
528 margin-top: 2.5rem;
529 padding-top: 1rem;
530 border-top: 1px solid var(--border);
531 color: var(--muted);
532 font-size: 0.85rem;
533 display: flex;
534 flex-wrap: wrap;
535 gap: 0.35rem 0.5rem;
536 align-items: center;
537}
538
539.foot .sep {
540 opacity: 0.5;
541}
542
543.toast {
544 position: fixed;
545 left: 50%;
546 bottom: 1.25rem;
547 transform: translateX(-50%);
548 z-index: 50;
549 max-width: min(32rem, calc(100vw - 2rem));
550 padding: 0.75rem 1rem;
551 border-radius: 12px;
552 background: var(--bg-elev);
553 border: 1px solid var(--border-strong);
554 box-shadow: var(--shadow);
555 font-size: 0.92rem;
556 animation: fade 0.15s ease;
557}
558
559.toast.err {
560 border-color: #f8717166;
561}
562
563.toast.ok {
564 border-color: #4ade8066;
565}
566
567@media (max-width: 560px) {
568 .shell {
569 padding: 1rem 0.85rem 2.5rem;
570 }
571
572 .top {
573 flex-direction: column;
574 }
575
576 .panel-head {
577 flex-direction: column;
578 }
579
580 .tabs {
581 border-radius: 16px;
582 }
583}