atproto pds in zig pds.zat.dev
pds atproto
24

Configure Feed

Select the types of activity you want to include in your feed.

landing: make repo signal interactive

zzstoatzz (May 27, 2026, 8:00 PM -0500) d23134ec 29006057

+64 -15
+29 -5
src/http/landing/assets.zig
··· 81 81 \\h1 { margin: 0; color: var(--text); font-size: clamp(36px, 11vw, 74px); line-height: 0.95; letter-spacing: 0; } 82 82 \\.lede { margin: 14px 0 0; max-width: 47ch; color: var(--muted); font-size: 15px; } 83 83 \\.lede a, .lede code { color: var(--green); } 84 - \\.entropy { display: flex; align-items: end; gap: 4px; width: min(100%, 240px); height: 30px; margin-top: 20px; transform: rotate(var(--signal-tilt, 0deg)); transform-origin: left center; opacity: .82; } 85 - \\.entropy i { flex: 1 1 0; height: var(--h); min-height: 7px; border-radius: 999px 999px 2px 2px; background: linear-gradient(180deg, var(--signal-a, var(--green)), var(--signal-b, var(--accent)), var(--signal-c, var(--pink))); animation: breathe 7s ease-in-out var(--d) infinite; } 86 - \\@keyframes breathe { 0%, 100% { transform: scaleY(.72); opacity: .58; } 45% { transform: scaleY(1); opacity: .95; } } 87 - \\@media (prefers-reduced-motion: reduce) { .entropy i { animation: none; } } 84 + \\.entropy { display: flex; align-items: end; gap: 5px; width: min(100%, 260px); min-height: 54px; margin-top: 18px; padding-top: 18px; } 85 + \\.entropy a { 86 + \\ position: relative; flex: 1 1 0; min-width: 10px; height: var(--h); min-height: 12px; 87 + \\ border-radius: 999px 999px 3px 3px; text-decoration: none; background: 88 + \\ linear-gradient(180deg, var(--signal-a, var(--green)), var(--signal-b, var(--accent)) 58%, var(--signal-c, var(--pink))); 89 + \\ box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 5px 16px color-mix(in srgb, var(--signal-a, var(--green)) 18%, transparent); 90 + \\ transform: translateY(var(--y)); opacity: .84; 91 + \\ transition: height .16s ease, opacity .16s ease, transform .16s ease, filter .16s ease; 92 + \\} 93 + \\.entropy a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -5px; height: 2px; border-radius: 999px; background: color-mix(in srgb, var(--line) 80%, transparent); } 94 + \\.entropy a span { 95 + \\ position: absolute; left: 50%; bottom: calc(100% + 8px); z-index: 2; max-width: min(68vw, 260px); 96 + \\ padding: 5px 7px; border: 1px solid var(--line); border-radius: 8px; background: color-mix(in srgb, var(--panel) 94%, transparent); 97 + \\ color: var(--text); font-size: 10px; line-height: 1.2; white-space: nowrap; transform: translate(-50%, 4px); 98 + \\ opacity: 0; pointer-events: none; box-shadow: 0 12px 28px rgba(0,0,0,.22); 99 + \\ transition: opacity .12s ease, transform .12s ease; 100 + \\} 101 + \\.entropy a em { 102 + \\ position: absolute; left: 50%; top: calc(100% + 9px); color: var(--dim); font-size: 9px; font-style: normal; 103 + \\ transform: translateX(-50%); opacity: 0; transition: opacity .12s ease; 104 + \\} 105 + \\.entropy a:hover, .entropy a:focus-visible { opacity: 1; transform: translateY(-3px); filter: saturate(1.12); } 106 + \\.entropy a:hover span, .entropy a:focus-visible span, .entropy a:hover em, .entropy a:focus-visible em { opacity: 1; transform: translate(-50%, 0); } 107 + \\.entropy a:hover em, .entropy a:focus-visible em { transform: translateX(-50%); } 108 + \\.signal-empty { color: var(--dim); font-size: 11px; } 109 + \\@media (prefers-reduced-motion: reduce) { .entropy a, .entropy a span, .entropy a em { transition: none; } } 88 110 \\.pulse { 89 111 \\ margin: 0 0 30px; padding: 14px; 90 112 \\ border: 1px solid var(--line); border-radius: 8px; background: ··· 191 213 \\ .icon-link, .settings { width: 32px; height: 32px; border-radius: 10px; } 192 214 \\ .logo-link::after { inset: 5px; border-radius: 8px; } 193 215 \\ .icon-link img { width: 20px; height: 20px; } 194 - \\ .entropy { width: min(100%, 190px); } 216 + \\ .entropy { width: min(100%, 220px); gap: 4px; min-height: 48px; } 217 + \\ .entropy a span { display: none; } 218 + \\ .entropy a em { top: calc(100% + 7px); } 195 219 \\} 196 220 ; 197 221
+35 -10
src/http/landing/entropy.zig
··· 27 27 const hue_a: u16 = @intCast(seed % 360); 28 28 const hue_b: u16 = @intCast((seed >> 11) % 360); 29 29 const hue_c: u16 = @intCast((seed >> 23) % 360); 30 - const tilt: i16 = @intCast(@as(i64, @intCast((seed >> 35) % 21)) - 10); 31 30 const style = try std.fmt.allocPrint( 32 31 allocator, 33 - "--signal-a:hsl({d} 76% 62%);--signal-b:hsl({d} 84% 66%);--signal-c:hsl({d} 78% 65%);--signal-tilt:{d}deg;", 34 - .{ hue_a, hue_b, hue_c, tilt }, 32 + "--signal-a:hsl({d} 76% 62%);--signal-b:hsl({d} 84% 66%);--signal-c:hsl({d} 78% 65%);", 33 + .{ hue_a, hue_b, hue_c }, 35 34 ); 36 35 37 36 var html: std.Io.Writer.Allocating = .init(allocator); 38 37 defer html.deinit(); 39 - try html.writer.writeAll("<div class=\"entropy\" aria-hidden=\"true\">"); 40 - var i: usize = 0; 41 - while (i < 14) : (i += 1) { 42 - const shift: u6 = @intCast((i * 5) % 61); 43 - const height = 18 + ((seed >> shift) & 0x2f); 44 - const delay: i64 = @intCast((seed >> @intCast((i * 3) % 57)) & 0x7); 45 - try html.writer.print("<i style=\"--h:{d}%;--d:-{d}s\"></i>", .{ height, delay }); 38 + 39 + try html.writer.writeAll("<div class=\"entropy\" aria-label=\"record collection signal\">"); 40 + if (collections.len == 0) { 41 + try html.writer.writeAll("<span class=\"signal-empty\">waiting for records</span>"); 42 + } else { 43 + const max_count = collections[0].count; 44 + const limit = @min(collections.len, 14); 45 + var i: usize = 0; 46 + while (i < limit) : (i += 1) { 47 + const collection = collections[i]; 48 + const height = if (max_count == 0) 12 else 12 + (collection.count * 34 / max_count); 49 + const offset = i % 3; 50 + const nsid = try escapeHtml(allocator, collection.collection); 51 + try html.writer.print( 52 + \\<a href="https://ufos.microcosm.blue/collection/?nsid={s}" style="--h:{d}px;--y:{d}px" aria-label="{s}: {d} records"> 53 + \\ <span>{s}</span><em>{d}</em> 54 + \\</a> 55 + , .{ nsid, height, offset, nsid, collection.count, nsid, collection.count }); 56 + } 46 57 } 47 58 try html.writer.writeAll("</div>"); 48 59 return .{ .style = style, .html = try html.toOwnedSlice() }; 49 60 } 61 + 62 + fn escapeHtml(allocator: std.mem.Allocator, text: []const u8) ![]const u8 { 63 + var out: std.Io.Writer.Allocating = .init(allocator); 64 + defer out.deinit(); 65 + for (text) |c| switch (c) { 66 + '&' => try out.writer.writeAll("&amp;"), 67 + '<' => try out.writer.writeAll("&lt;"), 68 + '>' => try out.writer.writeAll("&gt;"), 69 + '"' => try out.writer.writeAll("&quot;"), 70 + '\'' => try out.writer.writeAll("&#39;"), 71 + else => try out.writer.writeByte(c), 72 + }; 73 + return out.toOwnedSlice(); 74 + }