atproto-embed 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/post.js ADDED
@@ -0,0 +1,1980 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ var API_BASE = "https://public.api.bsky.app/xrpc/";
5
+ var HANDLE_CACHE = new Map();
6
+ var PROFILE_CACHE = new Map();
7
+ var INFLIGHT = new Map();
8
+ var CONTAINER_ABORTS = new WeakMap();
9
+ var SIGNAL_IDS = new WeakMap();
10
+ var NEXT_SIGNAL_ID = 1;
11
+ var TEXT_ENCODER = new TextEncoder();
12
+ var TEXT_DECODER = new TextDecoder();
13
+ var DATE_FMT = new Intl.DateTimeFormat("en-US", {
14
+ year: "numeric",
15
+ month: "short",
16
+ day: "numeric",
17
+ });
18
+ var TIME_FMT = new Intl.DateTimeFormat("en-US", {
19
+ hour: "numeric",
20
+ minute: "2-digit",
21
+ hour12: true,
22
+ });
23
+
24
+ function getSignalId(signal) {
25
+ if (!signal) return 0;
26
+ var id = SIGNAL_IDS.get(signal);
27
+ if (!id) {
28
+ id = NEXT_SIGNAL_ID++;
29
+ SIGNAL_IDS.set(signal, id);
30
+ }
31
+ return id;
32
+ }
33
+
34
+ function fetchJsonOrThrow(url, errMsg, signal) {
35
+ return fetch(url, signal ? { signal: signal } : undefined).then(function (res) {
36
+ if (!res.ok) throw new Error(errMsg);
37
+ return res.json();
38
+ });
39
+ }
40
+
41
+ function fetchJsonDedup(key, url, errMsg, signal) {
42
+ var sigId = getSignalId(signal);
43
+ var inflightKey = key + "|s" + sigId;
44
+ if (INFLIGHT.has(inflightKey)) return INFLIGHT.get(inflightKey);
45
+ var p = fetchJsonOrThrow(url, errMsg, signal)
46
+ .finally(function () {
47
+ INFLIGHT.delete(inflightKey);
48
+ });
49
+ INFLIGHT.set(inflightKey, p);
50
+ return p;
51
+ }
52
+
53
+ /* ───── URI resolution ───── */
54
+
55
+ function parseInput(raw) {
56
+ raw = raw.trim();
57
+ if (raw.startsWith("at://")) return { atUri: raw, sourceDomain: null };
58
+ const m = raw.match(
59
+ /https?:\/\/([^/]+)\/profile\/([^/]+)\/post\/([a-zA-Z0-9]+)/
60
+ );
61
+ if (m)
62
+ return {
63
+ handle: m[2],
64
+ rkey: m[3],
65
+ sourceDomain: m[1],
66
+ sourceUrl: raw,
67
+ };
68
+ return null;
69
+ }
70
+
71
+ async function resolveHandle(handle) {
72
+ if (handle.startsWith("did:")) return handle;
73
+ if (HANDLE_CACHE.has(handle)) return HANDLE_CACHE.get(handle);
74
+ const url =
75
+ API_BASE +
76
+ "com.atproto.identity.resolveHandle?handle=" +
77
+ encodeURIComponent(handle);
78
+ var p = fetchJsonDedup(
79
+ "resolveHandle:" + handle,
80
+ url,
81
+ "Handle resolution failed",
82
+ null
83
+ )
84
+ .then(function (data) {
85
+ return data.did;
86
+ })
87
+ .catch(function (err) {
88
+ HANDLE_CACHE.delete(handle);
89
+ throw err;
90
+ });
91
+ HANDLE_CACHE.set(handle, p);
92
+ return p;
93
+ }
94
+
95
+ async function resolveUri(raw) {
96
+ const parsed = parseInput(raw);
97
+ if (!parsed) throw new Error("Invalid URI: " + raw);
98
+ if (parsed.atUri)
99
+ return { atUri: parsed.atUri, sourceDomain: null, sourceUrl: null };
100
+ const did = await resolveHandle(parsed.handle);
101
+ return {
102
+ atUri: "at://" + did + "/app.bsky.feed.post/" + parsed.rkey,
103
+ sourceDomain: parsed.sourceDomain,
104
+ sourceUrl: parsed.sourceUrl,
105
+ };
106
+ }
107
+
108
+ /* ───── API ───── */
109
+
110
+ async function fetchPost(atUri, signal) {
111
+ const url =
112
+ API_BASE +
113
+ "app.bsky.feed.getPostThread?uri=" +
114
+ encodeURIComponent(atUri) +
115
+ "&depth=0&parentHeight=10";
116
+ const data = await fetchJsonDedup(url, url, "Failed to fetch post", signal);
117
+ return data.thread;
118
+ }
119
+
120
+ async function fetchThread(atUri, signal) {
121
+ const url =
122
+ API_BASE +
123
+ "app.bsky.feed.getPostThread?uri=" +
124
+ encodeURIComponent(atUri) +
125
+ "&depth=6&parentHeight=10";
126
+ const data = await fetchJsonDedup(url, url, "Failed to fetch thread", signal);
127
+ return data.thread;
128
+ }
129
+
130
+ async function fetchQuotes(atUri, cursor, signal) {
131
+ let url =
132
+ API_BASE +
133
+ "app.bsky.feed.getQuotes?uri=" +
134
+ encodeURIComponent(atUri) +
135
+ "&limit=25";
136
+ if (cursor) url += "&cursor=" + encodeURIComponent(cursor);
137
+ return fetchJsonDedup(url, url, "Failed to fetch quotes", signal);
138
+ }
139
+
140
+ async function fetchLikes(atUri, signal) {
141
+ const url =
142
+ API_BASE +
143
+ "app.bsky.feed.getLikes?uri=" +
144
+ encodeURIComponent(atUri) +
145
+ "&limit=60";
146
+ return fetchJsonDedup(url, url, "Failed to fetch likes", signal);
147
+ }
148
+
149
+ async function fetchProfile(did) {
150
+ try {
151
+ if (PROFILE_CACHE.has(did)) return PROFILE_CACHE.get(did);
152
+ const url =
153
+ API_BASE +
154
+ "app.bsky.actor.getProfile?actor=" +
155
+ encodeURIComponent(did);
156
+ var p = fetchJsonDedup(url, url, "Failed to fetch profile", null)
157
+ .then(function (data) {
158
+ return data;
159
+ })
160
+ .catch(function (_) {
161
+ PROFILE_CACHE.delete(did);
162
+ return null;
163
+ });
164
+ PROFILE_CACHE.set(did, p);
165
+ var resData = await p;
166
+ if (!resData) PROFILE_CACHE.delete(did);
167
+ return resData;
168
+ } catch (_) {
169
+ return null;
170
+ }
171
+ }
172
+
173
+ function extractDid(atUri) {
174
+ const m = atUri.match(/^at:\/\/(did:[^/]+)/);
175
+ return m ? m[1] : null;
176
+ }
177
+
178
+ /* ───── Helpers ───── */
179
+
180
+ function el(tag, className, attrs) {
181
+ const e = document.createElement(tag);
182
+ if (className) e.className = className;
183
+ if (attrs) {
184
+ for (const k in attrs) {
185
+ if (k === "textContent") e.textContent = attrs[k];
186
+ else if (k === "innerHTML") e.innerHTML = attrs[k];
187
+ else e.setAttribute(k, attrs[k]);
188
+ }
189
+ }
190
+ return e;
191
+ }
192
+
193
+ function appendInChunks(items, chunkSize, renderFn, container, beforeNode) {
194
+ if (!items || items.length === 0) return;
195
+ var i = 0;
196
+ var raf =
197
+ typeof requestAnimationFrame === "function"
198
+ ? requestAnimationFrame
199
+ : function (cb) {
200
+ setTimeout(cb, 16);
201
+ };
202
+ function step() {
203
+ var frag = document.createDocumentFragment();
204
+ var end = Math.min(i + chunkSize, items.length);
205
+ for (; i < end; i++) {
206
+ var node = renderFn(items[i], i);
207
+ if (node) frag.appendChild(node);
208
+ }
209
+ if (beforeNode) container.insertBefore(frag, beforeNode);
210
+ else container.appendChild(frag);
211
+ if (i < items.length) {
212
+ raf(step);
213
+ }
214
+ }
215
+ step();
216
+ }
217
+
218
+ function formatTimestamp(iso) {
219
+ var d = new Date(iso);
220
+ return DATE_FMT.format(d) + " at " + TIME_FMT.format(d);
221
+ }
222
+
223
+ function formatRelativeTime(iso) {
224
+ var now = Date.now();
225
+ var then = new Date(iso).getTime();
226
+ if (!isFinite(then)) return "";
227
+ var diff = Math.max(0, now - then);
228
+ var sec = Math.floor(diff / 1000);
229
+ if (sec < 60) return sec + "s";
230
+ var min = Math.floor(sec / 60);
231
+ if (min < 60) return min + "m";
232
+ var hr = Math.floor(min / 60);
233
+ if (hr < 24) return hr + "h";
234
+ var day = Math.floor(hr / 24);
235
+ if (day < 7) return day + "d";
236
+ var week = Math.floor(day / 7);
237
+ if (day < 30) return week + "w";
238
+ var month = Math.floor(day / 30);
239
+ if (day < 365) return month + "mo";
240
+ var year = Math.floor(day / 365);
241
+ return year + "y";
242
+ }
243
+ function formatCount(n) {
244
+ if (n >= 1000000)
245
+ return (n / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
246
+ if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, "") + "K";
247
+ return String(n);
248
+ }
249
+ function formatCountLabel(n, singular, plural) {
250
+ if (n === 1) return "1 " + singular;
251
+ return String(n) + " " + (plural || singular + "s");
252
+ }
253
+ function formatReplyLabel(n) {
254
+ if (n === 1) return "1 reply";
255
+ return String(n) + " replies";
256
+ }
257
+
258
+ function getRkeyFromUri(uri) {
259
+ var idx = uri.lastIndexOf("/");
260
+ return idx === -1 ? uri : uri.slice(idx + 1);
261
+ }
262
+
263
+ function bskyPostUrl(post) {
264
+ var rkey = getRkeyFromUri(post.uri);
265
+ return "https://bsky.app/profile/" + post.author.handle + "/post/" + rkey;
266
+ }
267
+
268
+ function sortPosts(posts, criteria) {
269
+ var arr = posts.slice();
270
+ arr.sort(function (a, b) {
271
+ var postA = a.post || a;
272
+ var postB = b.post || b;
273
+ var recA = postA.record || postA.value || {};
274
+ var recB = postB.record || postB.value || {};
275
+
276
+ var valA, valB;
277
+ switch (criteria) {
278
+ case "oldest":
279
+ valA = new Date(recA.createdAt || postA.indexedAt).getTime();
280
+ valB = new Date(recB.createdAt || postB.indexedAt).getTime();
281
+ return valA - valB;
282
+ case "likes":
283
+ return (postB.likeCount || 0) - (postA.likeCount || 0);
284
+ case "reposts":
285
+ return (postB.repostCount || 0) - (postA.repostCount || 0);
286
+ case "quotes":
287
+ return (postB.quoteCount || 0) - (postA.quoteCount || 0);
288
+ case "bookmarks":
289
+ return (postB.bookmarkCount || 0) - (postA.bookmarkCount || 0);
290
+ case "replies":
291
+ return (postB.replyCount || 0) - (postA.replyCount || 0);
292
+ case "newest":
293
+ default:
294
+ valA = new Date(recA.createdAt || postA.indexedAt).getTime();
295
+ valB = new Date(recB.createdAt || postB.indexedAt).getTime();
296
+ return valB - valA;
297
+ }
298
+ });
299
+ return arr;
300
+ }
301
+
302
+ function getViaInfo(sourceInfo, post) {
303
+ var bskyUrl = bskyPostUrl(post);
304
+ if (!sourceInfo || !sourceInfo.sourceDomain) {
305
+ return { label: "AT Proto", url: bskyUrl };
306
+ }
307
+ var domain = sourceInfo.sourceDomain;
308
+ if (domain === "bsky.app" || domain === "staging.bsky.app") {
309
+ return { label: "Bluesky", url: sourceInfo.sourceUrl || bskyUrl };
310
+ }
311
+ return { label: domain, url: sourceInfo.sourceUrl || bskyUrl };
312
+ }
313
+
314
+ function getPostLink(sourceInfo, post) {
315
+ var rkey = getRkeyFromUri(post.uri);
316
+ var domain =
317
+ sourceInfo && sourceInfo.sourceDomain ? sourceInfo.sourceDomain : "bsky.app";
318
+ return "https://" + domain + "/profile/" + post.author.handle + "/post/" + rkey;
319
+ }
320
+
321
+ var ICONS = {
322
+ "avatar-fallback": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2248%22%20height%3D%2248%22%20fill%3D%22none%22%20viewBox%3D%220%200%2048%2048%22%3E%3Crect%20width%3D%2248%22%20height%3D%2248%22%20fill%3D%22%23e0e0e0%22%20rx%3D%2224%22%2F%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M32%2020a8%208%200%201%201-16%200%208%208%200%200%201%2016%200%22%20opacity%3D%22.2%22%2F%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M36.865%2034.5c-1.904-3.291-4.838-5.651-8.261-6.77a9%209%200%201%200-9.208%200c-3.424%201.117-6.357%203.477-8.261%206.77a.997.997%200%200%200%20.352%201.389%201%201%200%200%200%201.38-.389C15.22%2031.43%2019.383%2029%2024%2029s8.779%202.43%2011.134%206.5a1.001%201.001%200%201%200%201.73-1M17%2020a7%207%200%201%201%207%207%207.007%207.007%200%200%201-7-7%22%2F%3E%3C%2Fsvg%3E",
323
+ "bookmark-fill": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M23%204H9a2%202%200%200%200-2%202v22a1%201%200%200%200%201.53.848l7.47-4.67%207.471%204.67A.999.999%200%200%200%2025%2028V6a2%202%200%200%200-2-2%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%2216%22%20x2%3D%2216%22%20y1%3D%224%22%20y2%3D%2229%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23a855f7%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%238300ff%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
324
+ "caret-down": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20d%3D%22m213.66%20101.66-80%2080a8%208%200%200%201-11.32%200l-80-80a8%208%200%200%201%2011.32-11.32L128%20164.69l74.34-74.35a8%208%200%200%201%2011.32%2011.32%22%2F%3E%3C%2Fsvg%3E",
325
+ "caret-up": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20d%3D%22M213.66%20165.66a8%208%200%200%201-11.32%200L128%2091.31l-74.34%2074.35a8%208%200%200%201-11.32-11.32l80-80a8%208%200%200%201%2011.32%200l80%2080a8%208%200%200%201%200%2011.32%22%2F%3E%3C%2Fsvg%3E",
326
+ "check-circle": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M16%203a13%2013%200%201%200%2013%2013A13.013%2013.013%200%200%200%2016%203m5.708%2010.708-7%207a1%201%200%200%201-1.415%200l-3-3a1%201%200%200%201%201.415-1.415L14%2018.586l6.293-6.293a1%201%200%200%201%201.415%201.415%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%223%22%20x2%3D%2229%22%20y1%3D%223%22%20y2%3D%2229%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23006aff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23004099%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
327
+ "label": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M30.414%2017%2018%204.586A1.98%201.98%200%200%200%2016.586%204H5a1%201%200%200%200-1%201v11.586A1.98%201.98%200%200%200%204.586%2018L17%2030.414a2%202%200%200%200%202.829%200l10.585-10.585a2%202%200%200%200%200-2.829M10.5%2012a1.5%201.5%200%201%201%200-3%201.5%201.5%200%200%201%200%203%22%2F%3E%3C%2Fsvg%3E",
328
+ "like-fill": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M30%2012.75c0%208.75-12.974%2015.833-13.526%2016.125a1%201%200%200%201-.948%200C14.974%2028.582%202%2021.5%202%2012.75A7.76%207.76%200%200%201%209.75%205c2.581%200%204.841%201.11%206.25%202.986C17.409%206.11%2019.669%205%2022.25%205A7.76%207.76%200%200%201%2030%2012.75%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%2216%22%20x2%3D%2216%22%20y1%3D%225%22%20y2%3D%2228.994%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23ec4899%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ff3a3d%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
329
+ "original-seal": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M28.233%2012.853c-.472-.493-.96-1-1.143-1.447-.17-.409-.18-1.086-.19-1.742-.019-1.22-.039-2.603-1-3.564s-2.344-.981-3.564-1c-.656-.01-1.333-.02-1.742-.19-.445-.184-.954-.671-1.447-1.142C18.286%202.938%2017.306%202%2016%202s-2.284.939-3.148%201.768c-.492.47-1%20.958-1.446%201.142-.406.17-1.086.18-1.742.19-1.22.019-2.603.039-3.564%201s-.975%202.344-1%203.564c-.01.656-.02%201.334-.19%201.742-.184.445-.671.954-1.142%201.446C2.938%2013.715%202%2014.696%202%2016s.939%202.284%201.768%203.148c.47.492.958%201%201.142%201.446.17.409.18%201.086.19%201.742.019%201.22.039%202.603%201%203.564s2.344.981%203.564%201c.656.01%201.334.02%201.742.19.445.184.954.671%201.446%201.143C13.715%2029.06%2014.696%2030%2016%2030s2.284-.939%203.148-1.767c.492-.472%201-.96%201.446-1.143.409-.17%201.086-.18%201.742-.19%201.22-.019%202.603-.039%203.564-1s.981-2.344%201-3.564c.01-.656.02-1.333.19-1.742.184-.445.671-.954%201.143-1.447C29.06%2018.286%2030%2017.306%2030%2016s-.939-2.284-1.767-3.148m-6.526.854-7%207a1%201%200%200%201-1.415%200l-3-3a1%201%200%200%201%201.415-1.415L14%2018.587l6.293-6.293a1%201%200%200%201%201.415%201.415%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%222%22%20x2%3D%2230%22%20y1%3D%222%22%20y2%3D%2230%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23ff6200%22%2F%3E%3Cstop%20offset%3D%22.615%22%20stop-color%3D%22%23f80%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ff5900%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
330
+ "private": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M26%2010h-4V7a6%206%200%201%200-12%200v3H6a2%202%200%200%200-2%202v14a2%202%200%200%200%202%202h20a2%202%200%200%200%202-2V12a2%202%200%200%200-2-2M16%2020.5a1.5%201.5%200%201%201%200-3%201.5%201.5%200%200%201%200%203M20%2010h-8V7a4%204%200%201%201%208%200z%22%2F%3E%3C%2Fsvg%3E",
331
+ "quote-fill": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M14.5%209v11a6.006%206.006%200%200%201-6%206%201%201%200%200%201%200-2%204%204%200%200%200%204-4v-1H5a2%202%200%200%201-2-2V9a2%202%200%200%201%202-2h7.5a2%202%200%200%201%202%202M27%207h-7.5a2%202%200%200%200-2%202v8a2%202%200%200%200%202%202H27v1a4%204%200%200%201-4%204%201%201%200%200%200%200%202%206.006%206.006%200%200%200%206-6V9a2%202%200%200%200-2-2%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%2216%22%20x2%3D%2216%22%20y1%3D%227%22%20y2%3D%2226%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%233b82f6%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%230061ff%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
332
+ "reply-fill": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M29%206v16a2%202%200%200%201-2%202H10.375L6.3%2027.52l-.011.009a1.99%201.99%200%200%201-2.138.281A1.98%201.98%200%200%201%203%2026V6a2%202%200%200%201%202-2h22a2%202%200%200%201%202%202%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%224%22%20x2%3D%2228%22%20y1%3D%224%22%20y2%3D%2228%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23ffa309%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23da4100%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
333
+ "reply": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22%234f575e%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20d%3D%22M229.66%2C157.66l-48%2C48A8%2C8%2C0%2C0%2C1%2C168%2C200V160H128A104.11%2C104.11%2C0%2C0%2C1%2C24%2C56a8%2C8%2C0%2C0%2C1%2C16%2C0%2C88.1%2C88.1%2C0%2C0%2C0%2C88%2C88h40V104a8%2C8%2C0%2C0%2C1%2C13.66-5.66l48%2C48A8%2C8%2C0%2C0%2C1%2C229.66%2C157.66Z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E",
334
+ "repost-fill": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%2322c55e%22%20d%3D%22M2.5%2016A9.51%209.51%200%200%201%2012%206.5h12.375l-.44-.439a1.503%201.503%200%200%201%202.125-2.125l3%203a1.5%201.5%200%200%201%200%202.125l-3%203a1.503%201.503%200%200%201-2.125-2.125l.44-.436H12A6.51%206.51%200%200%200%205.5%2016a1.5%201.5%200%201%201-3%200M28%2014.5a1.5%201.5%200%200%200-1.5%201.5%206.507%206.507%200%200%201-6.5%206.5H7.625l.44-.439a1.502%201.502%200%201%200-2.125-2.125l-3%203a1.5%201.5%200%200%200%200%202.125l3%203a1.503%201.503%200%200%200%202.125-2.125l-.44-.436H20a9.51%209.51%200%200%200%209.5-9.5%201.5%201.5%200%200%200-1.5-1.5%22%2F%3E%3Cpath%20fill%3D%22%2322c55e%22%20d%3D%22M28%207.75v8a8%208%200%200%201-8%208H4v-8a8%208%200%200%201%208-8z%22%20opacity%3D%22.2%22%2F%3E%3C%2Fsvg%3E",
335
+ "spinner": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M17%204v4a1%201%200%200%201-2%200V4a1%201%200%200%201%202%200m4.656%207.344a1%201%200%200%200%20.708-.294l2.828-2.828a1%201%200%200%200-1.415-1.414L20.95%209.636a1%201%200%200%200%20.706%201.708M28%2015h-4a1%201%200%200%200%200%202h4a1%201%200%200%200%200-2m-5.636%205.95a1%201%200%200%200-1.414%201.414l2.828%202.828a1%201%200%201%200%201.415-1.415zM16%2023a1%201%200%200%200-1%201v4a1%201%200%200%200%202%200v-4a1%201%200%200%200-1-1m-6.364-2.05-2.828%202.828a1%201%200%201%200%201.415%201.415l2.827-2.83a1%201%200%200%200-1.414-1.413M9%2016a1%201%200%200%200-1-1H4a1%201%200%200%200%200%202h4a1%201%200%200%200%201-1m-.777-9.192a1%201%200%201%200-1.416%201.415l2.83%202.827a1%201%200%200%200%201.413-1.414z%22%2F%3E%3C%2Fsvg%3E",
336
+ "trusted-seal": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M28.233%2012.853c-.472-.493-.96-1-1.143-1.447-.17-.409-.18-1.086-.19-1.742-.019-1.22-.039-2.603-1-3.564s-2.344-.981-3.564-1c-.656-.01-1.333-.02-1.742-.19-.445-.184-.954-.671-1.447-1.142C18.286%202.938%2017.306%202%2016%202s-2.284.939-3.148%201.768c-.492.47-1%20.958-1.446%201.142-.406.17-1.086.18-1.742.19-1.22.019-2.603.039-3.564%201s-.975%202.344-1%203.564c-.01.656-.02%201.334-.19%201.742-.184.445-.671.954-1.142%201.446C2.938%2013.715%202%2014.696%202%2016s.939%202.284%201.768%203.148c.47.492.958%201%201.142%201.446.17.409.18%201.086.19%201.742.019%201.22.039%202.603%201%203.564s2.344.981%203.564%201c.656.01%201.334.02%201.742.19.445.184.954.671%201.446%201.143C13.715%2029.06%2014.696%2030%2016%2030s2.284-.939%203.148-1.767c.492-.472%201-.96%201.446-1.143.409-.17%201.086-.18%201.742-.19%201.22-.019%202.603-.039%203.564-1s.981-2.344%201-3.564c.01-.656.02-1.333.19-1.742.184-.445.671-.954%201.143-1.447C29.06%2018.286%2030%2017.306%2030%2016s-.939-2.284-1.767-3.148m-6.526.854-7%207a1%201%200%200%201-1.415%200l-3-3a1%201%200%200%201%201.415-1.415L14%2018.587l6.293-6.293a1%201%200%200%201%201.415%201.415%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%222%22%20x2%3D%2230%22%20y1%3D%222%22%20y2%3D%2230%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%2329de6b%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%230f9f44%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
337
+ "warn": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M28.233%2012.853c-.472-.493-.96-1-1.143-1.447-.17-.409-.18-1.086-.19-1.742-.019-1.22-.039-2.603-1-3.564s-2.344-.981-3.564-1c-.656-.01-1.333-.02-1.742-.19-.445-.184-.954-.671-1.447-1.142C18.286%202.938%2017.306%202%2016%202s-2.284.939-3.148%201.768c-.492.47-1%20.958-1.446%201.142-.406.17-1.086.18-1.742.19-1.22.019-2.603.039-3.564%201s-.975%202.344-1%203.564c-.01.656-.02%201.334-.19%201.742-.184.445-.671.954-1.142%201.446C2.938%2013.715%202%2014.696%202%2016s.939%202.284%201.768%203.148c.47.492.958%201%201.142%201.446.17.409.18%201.086.19%201.742.019%201.22.039%202.603%201%203.564s2.344.981%203.564%201c.656.01%201.334.02%201.742.19.445.184.954.671%201.446%201.143C13.715%2029.06%2014.696%2030%2016%2030s2.284-.939%203.148-1.767c.492-.472%201-.96%201.446-1.143.409-.17%201.086-.18%201.742-.19%201.22-.019%202.603-.039%203.564-1s.981-2.344%201-3.564c.01-.656.02-1.333.19-1.742.184-.445.671-.954%201.143-1.447C29.06%2018.286%2030%2017.306%2030%2016s-.939-2.284-1.767-3.148M15%2010a1%201%200%200%201%202%200v7a1%201%200%200%201-2%200zm1%2013a1.5%201.5%200%201%201%200-3%201.5%201.5%200%200%201%200%203%22%2F%3E%3C%2Fsvg%3E"
338
+ };
339
+
340
+ function getIconUrl(name) {
341
+ return ICONS[name] || "";
342
+ }
343
+
344
+ var METRIC_ICONS = {
345
+ post: {
346
+ like: "like-fill",
347
+ repost: "repost-fill",
348
+ reply: "reply-fill",
349
+ quote: "quote-fill",
350
+ bookmark: "bookmark-fill",
351
+ },
352
+ discussion: {
353
+ like: "like-fill",
354
+ repost: "repost-fill",
355
+ reply: "reply-fill",
356
+ quote: "quote-fill",
357
+ bookmark: "bookmark-fill",
358
+ },
359
+ };
360
+
361
+ function metricIcon(name, styleMode) {
362
+ var set = METRIC_ICONS[styleMode] || METRIC_ICONS.discussion;
363
+ return set[name] || name;
364
+ }
365
+
366
+ var BADGE_ICONS = {
367
+ crown: "original-seal",
368
+ seal: "trusted-seal",
369
+ check: "check-circle",
370
+ lock: "private",
371
+ tag: "label",
372
+ warning: "warn",
373
+ };
374
+
375
+ var PRELOAD_ICON_NAMES = [
376
+ "like-fill",
377
+ "repost-fill",
378
+ "reply-fill",
379
+ "quote-fill",
380
+ "bookmark-fill",
381
+ "original-seal",
382
+ "trusted-seal",
383
+ "check-circle",
384
+ "private",
385
+ "label",
386
+ "warn",
387
+ "reply",
388
+ "spinner",
389
+ "avatar-fallback",
390
+ "caret-down",
391
+ "caret-up",
392
+ ];
393
+
394
+ function maybePreloadIcons() {
395
+ if (typeof document === "undefined") return;
396
+ var current = document.currentScript;
397
+ if (!current || current.getAttribute("data-preload-icons") !== "true") return;
398
+ if (typeof fetch !== "function") return;
399
+ var sample = getIconUrl("like-fill");
400
+ if (sample && sample.indexOf("data:") === 0) return;
401
+ PRELOAD_ICON_NAMES.forEach(function (name) {
402
+ var url = getIconUrl(name);
403
+ if (!url || url.indexOf("data:") === 0) return;
404
+ fetch(url, { cache: "force-cache" }).catch(function () { });
405
+ });
406
+ }
407
+
408
+ maybePreloadIcons();
409
+
410
+ /* ───── Badge detection ───── */
411
+
412
+ function detectBadges(author) {
413
+ var badges = [];
414
+ var v = author.verification || {};
415
+ var labels = author.labels || [];
416
+ var associated = author.associated || {};
417
+ if (v.trustedVerifierStatus === "valid") {
418
+ badges.push(author.handle === "bsky.app" ? "crown" : "seal");
419
+ } else if (v.verifiedStatus === "valid") {
420
+ badges.push("check");
421
+ }
422
+ var hasNoUnauth = labels.some(function (l) {
423
+ return l.val === "!no-unauthenticated";
424
+ });
425
+ if (hasNoUnauth) badges.push("lock");
426
+ var warningVals = ["porn", "spam", "nudity", "sexual", "gore"];
427
+ var hasWarning = labels.some(function (l) {
428
+ if (l.val === "!no-unauthenticated") return false;
429
+ return warningVals.indexOf(l.val) !== -1 || l.val.charAt(0) === "!";
430
+ });
431
+ if (hasWarning) badges.push("warning");
432
+ if (associated.labeler === true) badges.push("tag");
433
+ return badges;
434
+ }
435
+
436
+ function renderBadges(author) {
437
+ var badges = detectBadges(author);
438
+ var frag = document.createDocumentFragment();
439
+ badges.forEach(function (b) {
440
+ if (BADGE_ICONS[b]) {
441
+ var span = el("span", "atproto-badge-wrap");
442
+ span.appendChild(
443
+ el("img", "atproto-badge atproto-badge--" + b, {
444
+ src: getIconUrl(BADGE_ICONS[b]),
445
+ alt: b,
446
+ width: "16",
447
+ height: "16",
448
+ })
449
+ );
450
+ frag.appendChild(span);
451
+ }
452
+ });
453
+ return frag;
454
+ }
455
+
456
+ function countReplyDescendants(thread) {
457
+ if (!thread || !thread.replies || !thread.replies.length) return 0;
458
+ var total = thread.replies.length;
459
+ thread.replies.forEach(function (child) {
460
+ total += countReplyDescendants(child);
461
+ });
462
+ return total;
463
+ }
464
+
465
+ /* ───── Configuration ───── */
466
+
467
+ function parseConfig(container) {
468
+ var mode = (container.getAttribute("data-mode") || "post").toLowerCase();
469
+ if (mode !== "post" && mode !== "discussion") {
470
+ mode = "post";
471
+ }
472
+
473
+ var config = {
474
+ mode: mode,
475
+ // Post card options
476
+ showLikes: true,
477
+ showReposts: true,
478
+ showReplies: true,
479
+ showQuotes: true,
480
+ showBookmarks: true,
481
+ showMetrics: true,
482
+ showTimestamp: true,
483
+ showActions: true,
484
+ showReplyContext: true,
485
+ showEmbeds: true,
486
+ showBadges: true,
487
+ showLabels: true,
488
+ showImages: true,
489
+ showVideo: true,
490
+ showExternal: true,
491
+ showQuotePosts: true,
492
+ externalLayout: "vertical",
493
+ // Discussion-specific options
494
+ showMainPost: true,
495
+ showLikedBy: true,
496
+ showRepliesTab: true,
497
+ showQuotesTab: true,
498
+ showTabs: true,
499
+ showSort: true,
500
+ showJoinButton: true,
501
+ repliesSort: "oldest",
502
+ // Sizing
503
+ width: null,
504
+ maxWidth: null,
505
+ // Discussion-only: labels for replies/quotes
506
+ showReplyQuoteLabels: true,
507
+ };
508
+
509
+ function parseAttr(name, defaultVal) {
510
+ var val = container.getAttribute("data-" + name);
511
+ if (val === null) return defaultVal;
512
+ return val === "true" || val === "1" || val === "";
513
+ }
514
+
515
+ function parseStringAttr(name, defaultVal) {
516
+ var val = container.getAttribute("data-" + name);
517
+ return val !== null ? val : defaultVal;
518
+ }
519
+
520
+ // Post card
521
+ config.showLikes = parseAttr("show-likes", config.showLikes);
522
+ config.showReposts = parseAttr("show-reposts", config.showReposts);
523
+ config.showReplies = parseAttr("show-replies", config.showReplies);
524
+ config.showQuotes = parseAttr("show-quotes", config.showQuotes);
525
+ config.showBookmarks = parseAttr("show-bookmarks", config.showBookmarks);
526
+ config.showMetrics = parseAttr("show-metrics", config.showMetrics);
527
+ config.showTimestamp = parseAttr("show-timestamp", config.showTimestamp);
528
+ config.showActions = parseAttr("show-actions", config.showActions);
529
+ config.showReplyContext = parseAttr(
530
+ "show-reply-context",
531
+ config.showReplyContext
532
+ );
533
+ config.showEmbeds = parseAttr("show-embeds", config.showEmbeds);
534
+ config.showBadges = parseAttr("show-badges", config.showBadges);
535
+ config.showLabels = parseAttr("show-labels", config.showLabels);
536
+ config.showImages = parseAttr("show-images", config.showImages);
537
+ config.showVideo = parseAttr("show-video", config.showVideo);
538
+ config.showExternal = parseAttr("show-external", config.showExternal);
539
+ config.showQuotePosts = parseAttr("show-quote-posts", config.showQuotePosts);
540
+ config.externalLayout = parseStringAttr(
541
+ "external-layout",
542
+ config.externalLayout
543
+ );
544
+ if (config.externalLayout !== "horizontal") {
545
+ config.externalLayout = "vertical";
546
+ }
547
+
548
+ // Discussion
549
+ config.showMainPost = parseAttr("show-main-post", config.showMainPost);
550
+ config.showLikedBy = parseAttr("show-liked-by", config.showLikedBy);
551
+ config.showRepliesTab = parseAttr("show-replies-tab", config.showRepliesTab);
552
+ config.showQuotesTab = parseAttr("show-quotes-tab", config.showQuotesTab);
553
+ config.showTabs = parseAttr("show-tabs", config.showTabs);
554
+ config.showSort = parseAttr("show-sort", config.showSort);
555
+ config.showJoinButton = parseAttr("show-join-button", config.showJoinButton);
556
+ config.repliesSort = parseStringAttr("replies-sort", config.repliesSort);
557
+
558
+ config.width = parseStringAttr("width", config.width);
559
+ config.maxWidth = parseStringAttr("max-width", config.maxWidth);
560
+
561
+ if (container.hasAttribute("data-show-reply-quote-labels")) {
562
+ config.showReplyQuoteLabels = parseAttr(
563
+ "show-reply-quote-labels",
564
+ config.showReplyQuoteLabels
565
+ );
566
+ } else {
567
+ // Back-compat: previous name
568
+ config.showReplyQuoteLabels = parseAttr(
569
+ "show-reply-labels",
570
+ config.showReplyQuoteLabels
571
+ );
572
+ }
573
+
574
+ return config;
575
+ }
576
+
577
+ function applySizeConfig(container, config) {
578
+ if (!config) return;
579
+ if (config.width) {
580
+ container.style.setProperty("--atproto-width", config.width);
581
+ }
582
+ if (config.maxWidth) {
583
+ container.style.setProperty("--atproto-max-width", config.maxWidth);
584
+ }
585
+ }
586
+
587
+ function clientBase(sourceInfo) {
588
+ if (sourceInfo && sourceInfo.sourceDomain)
589
+ return "https://" + sourceInfo.sourceDomain;
590
+ return "https://bsky.app";
591
+ }
592
+
593
+ function renderFacets(record, sourceInfo) {
594
+ var text = record.text || "";
595
+ var facets = record.facets;
596
+ if (!facets || !facets.length) return document.createTextNode(text);
597
+
598
+ var base = clientBase(sourceInfo);
599
+ var bytes = TEXT_ENCODER.encode(text);
600
+
601
+ var sorted = facets.slice().sort(function (a, b) {
602
+ return a.index.byteStart - b.index.byteStart;
603
+ });
604
+
605
+ var frag = document.createDocumentFragment();
606
+ var cursor = 0;
607
+
608
+ sorted.forEach(function (facet) {
609
+ var start = facet.index.byteStart;
610
+ var end = facet.index.byteEnd;
611
+
612
+ if (start > cursor) {
613
+ frag.appendChild(
614
+ document.createTextNode(TEXT_DECODER.decode(bytes.slice(cursor, start)))
615
+ );
616
+ }
617
+
618
+ var slice = TEXT_DECODER.decode(bytes.slice(start, end));
619
+ var feat = facet.features[0];
620
+
621
+ if (feat.$type === "app.bsky.richtext.facet#mention") {
622
+ frag.appendChild(
623
+ el("a", "atproto-post__mention", {
624
+ textContent: slice,
625
+ href: base + "/profile/" + feat.did,
626
+ target: "_blank",
627
+ rel: "noopener noreferrer",
628
+ })
629
+ );
630
+ } else if (feat.$type === "app.bsky.richtext.facet#tag") {
631
+ var tag = feat.tag || slice.replace(/^#/, "");
632
+ frag.appendChild(
633
+ el("a", "atproto-post__hashtag", {
634
+ textContent: slice,
635
+ href: base + "/hashtag/" + encodeURIComponent(tag),
636
+ target: "_blank",
637
+ rel: "noopener noreferrer",
638
+ })
639
+ );
640
+ } else if (feat.$type === "app.bsky.richtext.facet#link") {
641
+ frag.appendChild(
642
+ el("a", "atproto-post__link", {
643
+ textContent: slice,
644
+ href: feat.uri,
645
+ target: "_blank",
646
+ rel: "noopener noreferrer",
647
+ })
648
+ );
649
+ } else {
650
+ frag.appendChild(document.createTextNode(slice));
651
+ }
652
+
653
+ cursor = end;
654
+ });
655
+
656
+ if (cursor < bytes.length) {
657
+ frag.appendChild(
658
+ document.createTextNode(TEXT_DECODER.decode(bytes.slice(cursor)))
659
+ );
660
+ }
661
+
662
+ return frag;
663
+ }
664
+
665
+ function renderText(record, sourceInfo, opts) {
666
+ var nodesFrag = renderFacets(record, sourceInfo);
667
+ var finalFrag = document.createDocumentFragment();
668
+ var currentPara = el("div", "atproto-post__paragraph");
669
+ var splitOnSingleNewline = !!(opts && opts.splitOnSingleNewline);
670
+
671
+ function pushPara() {
672
+ if (currentPara.childNodes.length > 0) {
673
+ finalFrag.appendChild(currentPara);
674
+ currentPara = el("div", "atproto-post__paragraph");
675
+ }
676
+ }
677
+
678
+ var nodes = [];
679
+ if (nodesFrag.nodeType === 11) {
680
+ nodes = Array.prototype.slice.call(nodesFrag.childNodes);
681
+ } else {
682
+ nodes = [nodesFrag];
683
+ }
684
+
685
+ nodes.forEach(function (node) {
686
+ if (node.nodeType === 3) {
687
+ var text = node.textContent;
688
+ var splitter = "\n\n";
689
+ if (
690
+ splitOnSingleNewline &&
691
+ text.indexOf("\n\n") === -1 &&
692
+ text.indexOf("\n") !== -1
693
+ ) {
694
+ splitter = "\n";
695
+ }
696
+ var parts = text.split(splitter);
697
+ parts.forEach(function (part, i) {
698
+ if (i > 0) pushPara();
699
+ if (part) currentPara.appendChild(document.createTextNode(part));
700
+ });
701
+ } else {
702
+ currentPara.appendChild(node);
703
+ }
704
+ });
705
+
706
+ pushPara();
707
+ return finalFrag;
708
+ }
709
+
710
+ /* ───── Embed renderers ───── */
711
+
712
+ function renderImages(embed, config) {
713
+ if (config && config.showImages === false) return null;
714
+ var images = embed.images;
715
+ var count = images.length;
716
+ var container = el(
717
+ "div",
718
+ "atproto-embed__images atproto-embed__images--" + count
719
+ );
720
+ images.forEach(function (img) {
721
+ var link = el("a", "atproto-embed__image-link", {
722
+ href: img.fullsize,
723
+ target: "_blank",
724
+ rel: "noopener noreferrer",
725
+ });
726
+ link.appendChild(
727
+ el("img", null, { src: img.thumb, alt: img.alt || "", loading: "lazy" })
728
+ );
729
+ container.appendChild(link);
730
+ });
731
+ return container;
732
+ }
733
+
734
+ function renderVideo(embed, config) {
735
+ if (config && config.showVideo === false) return null;
736
+ var container = el("div", "atproto-embed__video");
737
+ if (embed.playlist) {
738
+ var video = el("video", null, {
739
+ controls: "true",
740
+ preload: "metadata",
741
+ playsinline: "true",
742
+ });
743
+ if (embed.thumbnail) {
744
+ video.setAttribute("poster", embed.thumbnail);
745
+ }
746
+ if (embed.aspectRatio) {
747
+ video.style.aspectRatio = embed.aspectRatio.width + "/" + embed.aspectRatio.height;
748
+ }
749
+ if (video.canPlayType("application/vnd.apple.mpegurl")) {
750
+ video.src = embed.playlist;
751
+ } else {
752
+ var script = document.createElement("script");
753
+ script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js";
754
+ script.onload = function () {
755
+ if (window.Hls && window.Hls.isSupported()) {
756
+ var hls = new window.Hls();
757
+ hls.loadSource(embed.playlist);
758
+ hls.attachMedia(video);
759
+ }
760
+ };
761
+ document.head.appendChild(script);
762
+ }
763
+ container.appendChild(video);
764
+ }
765
+ return container;
766
+ }
767
+
768
+ function isGifUrl(uri) {
769
+ if (!uri) return false;
770
+ if (/\.gif(\?|$)/i.test(uri)) return true;
771
+ if (uri.indexOf("tenor.com") !== -1 || uri.indexOf("giphy.com") !== -1)
772
+ return true;
773
+ return false;
774
+ }
775
+
776
+ function renderExternal(embed, config) {
777
+ if (config && config.showExternal === false) return null;
778
+ var ext = embed.external;
779
+ var layout =
780
+ config && config.externalLayout === "horizontal" ? "horizontal" : "vertical";
781
+
782
+ if (isGifUrl(ext.uri)) {
783
+ var gifWrap = el("a", "atproto-embed__gif", {
784
+ href: ext.uri,
785
+ target: "_blank",
786
+ rel: "noopener noreferrer",
787
+ });
788
+ gifWrap.appendChild(
789
+ el("img", "atproto-embed__gif-img", {
790
+ src: ext.uri,
791
+ alt: ext.description || ext.title || "",
792
+ loading: "lazy",
793
+ })
794
+ );
795
+ return gifWrap;
796
+ }
797
+
798
+ var link = el(
799
+ "a",
800
+ "atproto-embed__external atproto-embed__external--" + layout,
801
+ {
802
+ href: ext.uri,
803
+ target: "_blank",
804
+ rel: "noopener noreferrer",
805
+ }
806
+ );
807
+ if (ext.thumb) {
808
+ link.appendChild(
809
+ el("img", "atproto-embed__external-thumb", {
810
+ src: ext.thumb,
811
+ alt: "",
812
+ loading: "lazy",
813
+ })
814
+ );
815
+ }
816
+ var content = el("div", "atproto-embed__external-content");
817
+ if (ext.title)
818
+ content.appendChild(
819
+ el("div", "atproto-embed__external-title", { textContent: ext.title })
820
+ );
821
+ if (ext.description)
822
+ content.appendChild(
823
+ el("div", "atproto-embed__external-desc", {
824
+ textContent: ext.description,
825
+ })
826
+ );
827
+ var domain = ext.uri;
828
+ try {
829
+ domain = new URL(ext.uri).hostname;
830
+ } catch (_) { }
831
+ content.appendChild(
832
+ el("div", "atproto-embed__external-domain", { textContent: domain })
833
+ );
834
+ link.appendChild(content);
835
+ return link;
836
+ }
837
+
838
+ function renderQuoteEmbed(embed, config, styleMode) {
839
+ if (config && config.showQuotePosts === false) return null;
840
+ var rec = embed.record;
841
+ if (!rec || rec.$type === "app.bsky.embed.record#viewNotFound") return null;
842
+ if (rec.$type === "app.bsky.embed.record#viewBlocked") {
843
+ var blocked = el("div", "atproto-embed__quote");
844
+ blocked.appendChild(
845
+ el("div", null, {
846
+ textContent: "This post is from a blocked account.",
847
+ style: "padding:12px;color:#687684;font-size:14px;",
848
+ })
849
+ );
850
+ return blocked;
851
+ }
852
+ var wrapper = el("div", "atproto-embed__quote");
853
+ wrapper.appendChild(
854
+ renderPostCard(
855
+ {
856
+ post: {
857
+ author: rec.author,
858
+ uri: rec.uri,
859
+ record: rec.value,
860
+ embed: rec.embeds && rec.embeds[0] ? rec.embeds[0] : null,
861
+ indexedAt: rec.indexedAt,
862
+ },
863
+ },
864
+ true,
865
+ null,
866
+ false,
867
+ config,
868
+ styleMode,
869
+ null,
870
+ null
871
+ )
872
+ );
873
+ return wrapper;
874
+ }
875
+
876
+ function renderRecordWithMedia(embed, skipRecord, config, styleMode) {
877
+ var frag = document.createDocumentFragment();
878
+ var media = embed.media;
879
+ if (media) {
880
+ var mt = media.$type;
881
+ if (mt === "app.bsky.embed.images#view")
882
+ frag.appendChild(renderImages(media, config));
883
+ else if (mt === "app.bsky.embed.video#view")
884
+ frag.appendChild(renderVideo(media, config));
885
+ else if (mt === "app.bsky.embed.external#view")
886
+ frag.appendChild(renderExternal(media, config));
887
+ }
888
+ if (!skipRecord && embed.record) {
889
+ var q = renderQuoteEmbed(embed.record, config, styleMode);
890
+ if (q) frag.appendChild(q);
891
+ }
892
+ return frag;
893
+ }
894
+
895
+ function renderEmbed(post, skipQuoteOfRoot, config, styleMode) {
896
+ var embed = post.embed;
897
+ if (!embed) return null;
898
+ var t = embed.$type;
899
+ if (t === "app.bsky.embed.images#view") return renderImages(embed, config);
900
+ if (t === "app.bsky.embed.video#view") return renderVideo(embed, config);
901
+ if (t === "app.bsky.embed.external#view") return renderExternal(embed, config);
902
+ if (t === "app.bsky.embed.record#view") {
903
+ if (skipQuoteOfRoot) return null;
904
+ return renderQuoteEmbed(embed, config, styleMode);
905
+ }
906
+ if (t === "app.bsky.embed.recordWithMedia#view")
907
+ return renderRecordWithMedia(embed, skipQuoteOfRoot, config, styleMode);
908
+ return null;
909
+ }
910
+
911
+ /* ───── Post card ───── */
912
+
913
+ function renderPostCard(
914
+ thread,
915
+ isQuote,
916
+ sourceInfo,
917
+ skipQuoteEmbed,
918
+ config,
919
+ styleMode,
920
+ contextType,
921
+ iconMode
922
+ ) {
923
+ if (!config) config = {};
924
+ if (!thread || !thread.post) return null;
925
+
926
+ var post = thread.post;
927
+ if (!post.author) {
928
+ post.author = {
929
+ handle: "unknown",
930
+ displayName: "Unknown User",
931
+ avatar: getIconUrl("avatar-fallback"),
932
+ };
933
+ }
934
+
935
+ var record = post.record || post.value;
936
+ var card = el("div", "atproto-post");
937
+ var frag = document.createDocumentFragment();
938
+
939
+ var profileUrl =
940
+ post.author.handle === "unknown"
941
+ ? "#"
942
+ : "https://bsky.app/profile/" + post.author.handle;
943
+ var header = el("div", "atproto-post__header");
944
+ var headerMain = el("div", "atproto-post__header-main");
945
+ headerMain.appendChild(
946
+ el("img", "atproto-post__avatar", {
947
+ src: post.author.avatar || getIconUrl("avatar-fallback"),
948
+ alt: post.author.displayName || post.author.handle,
949
+ loading: "lazy",
950
+ })
951
+ );
952
+ var isDiscussionReplyOrQuote =
953
+ styleMode === "discussion" &&
954
+ (contextType === "reply" || contextType === "quote");
955
+ if (isDiscussionReplyOrQuote) {
956
+ card.classList.add("atproto-post--compact");
957
+ var authorInline = el("div", "atproto-post__author-inline");
958
+ var nameWrapInline = el("span", "atproto-post__display-name-wrap");
959
+ nameWrapInline.appendChild(
960
+ el("span", "atproto-post__display-name", {
961
+ textContent: post.author.displayName || post.author.handle,
962
+ })
963
+ );
964
+ if (config.showBadges !== false) {
965
+ var badgesInline = el("span", "atproto-post__badges-inline");
966
+ badgesInline.appendChild(renderBadges(post.author));
967
+ nameWrapInline.appendChild(badgesInline);
968
+ }
969
+ authorInline.appendChild(nameWrapInline);
970
+ authorInline.appendChild(
971
+ el("span", "atproto-post__handle", {
972
+ textContent: "@" + post.author.handle,
973
+ })
974
+ );
975
+ headerMain.appendChild(authorInline);
976
+ } else {
977
+ var author = el("div", "atproto-post__author");
978
+ var nameWrap = el("span", "atproto-post__display-name-wrap");
979
+ nameWrap.appendChild(
980
+ el("span", "atproto-post__display-name", {
981
+ textContent: post.author.displayName || post.author.handle,
982
+ })
983
+ );
984
+ if (config.showBadges !== false) {
985
+ nameWrap.appendChild(renderBadges(post.author));
986
+ }
987
+ author.appendChild(nameWrap);
988
+ author.appendChild(
989
+ el("span", "atproto-post__handle", {
990
+ textContent: "@" + post.author.handle,
991
+ })
992
+ );
993
+ headerMain.appendChild(author);
994
+ }
995
+ var headerLink = el("a", "atproto-post__header-main-link", {
996
+ href: profileUrl,
997
+ target: "_blank",
998
+ rel: "noopener noreferrer",
999
+ });
1000
+ headerLink.appendChild(headerMain);
1001
+ header.appendChild(headerLink);
1002
+
1003
+ if (styleMode === "discussion" && contextType === "root") {
1004
+ card.classList.add("atproto-post--main");
1005
+ }
1006
+ if (isDiscussionReplyOrQuote && config.showTimestamp !== false) {
1007
+ var postLink = getPostLink(sourceInfo, post);
1008
+ var relTime = formatRelativeTime(record.createdAt || post.indexedAt);
1009
+ if (relTime) {
1010
+ header.appendChild(
1011
+ el("a", "atproto-post__header-right", {
1012
+ href: postLink,
1013
+ target: "_blank",
1014
+ rel: "noopener noreferrer",
1015
+ textContent: relTime,
1016
+ })
1017
+ );
1018
+ }
1019
+ }
1020
+ frag.appendChild(header);
1021
+
1022
+ if (
1023
+ !isQuote &&
1024
+ record &&
1025
+ record.reply &&
1026
+ thread.parent &&
1027
+ config.showReplyContext !== false
1028
+ ) {
1029
+ var parent = thread.parent;
1030
+ if (parent.post) {
1031
+ var ctx = el("div", "atproto-post__reply-context");
1032
+ ctx.appendChild(
1033
+ el("img", null, { src: getIconUrl("reply"), width: "14", height: "14" })
1034
+ );
1035
+ ctx.appendChild(
1036
+ document.createTextNode(
1037
+ "Replying to " +
1038
+ (parent.post.author.displayName || "@" + parent.post.author.handle)
1039
+ )
1040
+ );
1041
+ frag.appendChild(ctx);
1042
+ }
1043
+ }
1044
+
1045
+ var hasText = false;
1046
+ if (record && record.text) {
1047
+ var textDiv = el("div", "atproto-post__text");
1048
+ textDiv.appendChild(
1049
+ renderText(record, sourceInfo, {
1050
+ splitOnSingleNewline: contextType === "reply",
1051
+ })
1052
+ );
1053
+ frag.appendChild(textDiv);
1054
+ hasText = true;
1055
+ }
1056
+
1057
+ var hasEmbed = false;
1058
+ if (config.showEmbeds !== false && (!isQuote || post.embed)) {
1059
+ var embedEl = renderEmbed(post, isQuote || !!skipQuoteEmbed, config, styleMode);
1060
+ if (embedEl) {
1061
+ var embedWrap = el("div", "atproto-post__embed");
1062
+ embedWrap.appendChild(embedEl);
1063
+ frag.appendChild(embedWrap);
1064
+ hasEmbed = true;
1065
+ }
1066
+ }
1067
+
1068
+ if (isDiscussionReplyOrQuote && hasText && !hasEmbed) {
1069
+ card.classList.add("atproto-post--no-embed");
1070
+ }
1071
+
1072
+ var footerAdded = false;
1073
+ if (!isQuote) {
1074
+ var hasAnyMetrics =
1075
+ config.showMetrics !== false &&
1076
+ (config.showLikes !== false ||
1077
+ config.showReposts !== false ||
1078
+ config.showReplies !== false ||
1079
+ config.showQuotes !== false ||
1080
+ config.showBookmarks !== false);
1081
+
1082
+ var footer = null;
1083
+ var metricsAdded = false;
1084
+ var postLink = getPostLink(sourceInfo, post);
1085
+
1086
+ if (hasAnyMetrics) {
1087
+ var metricsRow = el("div", "atproto-post__metrics-row");
1088
+ var metricsLeft = el("div", "atproto-post__metrics-left");
1089
+ var metricsRight = el("div", "atproto-post__metrics-right");
1090
+ var iconStyle = iconMode || styleMode;
1091
+ var iconSize = iconStyle === "post" ? 20 : 16;
1092
+ var isSimpleMetrics = isDiscussionReplyOrQuote;
1093
+
1094
+ if (isSimpleMetrics) {
1095
+ metricsRow.classList.add("atproto-post__metrics-row--simple");
1096
+
1097
+ if (config.showLikes !== false && (post.likeCount || 0) > 0) {
1098
+ metricsLeft.appendChild(
1099
+ el("span", "atproto-post__stat-text", {
1100
+ textContent: formatCountLabel(post.likeCount || 0, "like"),
1101
+ })
1102
+ );
1103
+ }
1104
+
1105
+ if (config.showReposts !== false && (post.repostCount || 0) > 0) {
1106
+ metricsLeft.appendChild(
1107
+ el("span", "atproto-post__stat-text", {
1108
+ textContent: formatCountLabel(post.repostCount || 0, "repost"),
1109
+ })
1110
+ );
1111
+ }
1112
+
1113
+ if (config.showQuotes !== false && (post.quoteCount || 0) > 0) {
1114
+ metricsRight.appendChild(
1115
+ el("a", "atproto-post__stat-link", {
1116
+ href: postLink + "/quotes",
1117
+ target: "_blank",
1118
+ rel: "noopener noreferrer",
1119
+ textContent: formatCountLabel(
1120
+ post.quoteCount || 0,
1121
+ "talking",
1122
+ "talking"
1123
+ ),
1124
+ })
1125
+ );
1126
+ }
1127
+ } else {
1128
+ if (config.showLikes !== false) {
1129
+ var likes = el("span", "atproto-post__stat--likes");
1130
+ likes.appendChild(
1131
+ el("img", null, {
1132
+ src: getIconUrl(metricIcon("like", iconStyle)),
1133
+ width: String(iconSize),
1134
+ height: String(iconSize),
1135
+ })
1136
+ );
1137
+ likes.appendChild(
1138
+ document.createTextNode(" " + formatCount(post.likeCount || 0))
1139
+ );
1140
+ metricsLeft.appendChild(likes);
1141
+ }
1142
+
1143
+ if (config.showReposts !== false) {
1144
+ var reposts = el("span", "atproto-post__stat--reposts");
1145
+ reposts.appendChild(
1146
+ el("img", null, {
1147
+ src: getIconUrl(metricIcon("repost", iconStyle)),
1148
+ width: String(iconSize),
1149
+ height: String(iconSize),
1150
+ })
1151
+ );
1152
+ reposts.appendChild(
1153
+ document.createTextNode(" " + formatCount(post.repostCount || 0))
1154
+ );
1155
+ metricsLeft.appendChild(reposts);
1156
+ }
1157
+
1158
+ if (config.showReplies !== false) {
1159
+ var replies = el("span", "atproto-post__stat--replies");
1160
+ replies.appendChild(
1161
+ el("img", null, {
1162
+ src: getIconUrl(metricIcon("reply", iconStyle)),
1163
+ width: String(iconSize),
1164
+ height: String(iconSize),
1165
+ })
1166
+ );
1167
+ replies.appendChild(
1168
+ document.createTextNode(" " + formatCount(post.replyCount || 0))
1169
+ );
1170
+ metricsLeft.appendChild(replies);
1171
+ }
1172
+
1173
+ if (config.showQuotes !== false) {
1174
+ var quotes = el("a", "atproto-post__stat--quotes", {
1175
+ href: postLink + "/quotes",
1176
+ target: "_blank",
1177
+ rel: "noopener noreferrer",
1178
+ });
1179
+ quotes.appendChild(
1180
+ el("img", null, {
1181
+ src: getIconUrl(metricIcon("quote", iconStyle)),
1182
+ width: String(iconSize),
1183
+ height: String(iconSize),
1184
+ })
1185
+ );
1186
+ quotes.appendChild(
1187
+ document.createTextNode(" " + formatCount(post.quoteCount || 0))
1188
+ );
1189
+ metricsRight.appendChild(quotes);
1190
+ }
1191
+
1192
+ if (config.showBookmarks !== false) {
1193
+ var bookmarks = el("span", "atproto-post__stat--bookmarks");
1194
+ bookmarks.appendChild(
1195
+ el("img", null, {
1196
+ src: getIconUrl(metricIcon("bookmark", iconStyle)),
1197
+ width: String(iconSize),
1198
+ height: String(iconSize),
1199
+ })
1200
+ );
1201
+ bookmarks.appendChild(
1202
+ document.createTextNode(" " + formatCount(post.bookmarkCount || 0))
1203
+ );
1204
+ metricsRight.appendChild(bookmarks);
1205
+ }
1206
+ }
1207
+
1208
+ var hasLeft = metricsLeft.childNodes.length > 0;
1209
+ var hasRight = metricsRight.childNodes.length > 0;
1210
+ if (hasLeft) metricsRow.appendChild(metricsLeft);
1211
+ if (hasRight) metricsRow.appendChild(metricsRight);
1212
+ if (hasLeft || hasRight) {
1213
+ footer = footer || el("div", "atproto-post__footer");
1214
+ footer.appendChild(metricsRow);
1215
+ metricsAdded = true;
1216
+ }
1217
+ }
1218
+
1219
+ if (config.showTimestamp !== false && !isDiscussionReplyOrQuote) {
1220
+ var infoRow = el("div", "atproto-post__info-row");
1221
+
1222
+ var timestamp = el("a", "atproto-post__timestamp", {
1223
+ href: postLink,
1224
+ target: "_blank",
1225
+ rel: "noopener noreferrer",
1226
+ textContent:
1227
+ formatTimestamp(record.createdAt || post.indexedAt),
1228
+ });
1229
+ infoRow.appendChild(timestamp);
1230
+
1231
+ var viaInfo = getViaInfo(sourceInfo, post);
1232
+ var via = el("span", "atproto-post__via");
1233
+ via.appendChild(document.createTextNode("via "));
1234
+ via.appendChild(
1235
+ el("a", null, {
1236
+ href: viaInfo.url,
1237
+ target: "_blank",
1238
+ rel: "noopener noreferrer",
1239
+ textContent: viaInfo.label,
1240
+ })
1241
+ );
1242
+ infoRow.appendChild(via);
1243
+
1244
+ footer = footer || el("div", "atproto-post__footer");
1245
+ footer.appendChild(infoRow);
1246
+ }
1247
+
1248
+ if (footer) {
1249
+ frag.appendChild(footer);
1250
+ footerAdded = true;
1251
+ }
1252
+
1253
+ if (styleMode === "post" && config.showActions !== false) {
1254
+ var actions = el("div", "atproto-post__actions");
1255
+ actions.appendChild(
1256
+ el("a", "atproto-post__action atproto-post__action--primary", {
1257
+ href: postLink,
1258
+ target: "_blank",
1259
+ rel: "noopener noreferrer",
1260
+ textContent: "Write your reply",
1261
+ })
1262
+ );
1263
+ actions.appendChild(
1264
+ el("a", "atproto-post__action atproto-post__action--secondary", {
1265
+ href: postLink + "/quotes",
1266
+ target: "_blank",
1267
+ rel: "noopener noreferrer",
1268
+ textContent: "View quotes",
1269
+ })
1270
+ );
1271
+ frag.appendChild(actions);
1272
+ }
1273
+ }
1274
+
1275
+ var isReplyOrQuote = contextType === "reply" || contextType === "quote";
1276
+ if (
1277
+ isReplyOrQuote &&
1278
+ post.labels &&
1279
+ post.labels.length &&
1280
+ config.showReplyQuoteLabels === false
1281
+ ) {
1282
+ return null;
1283
+ }
1284
+
1285
+ if (post.labels && post.labels.length && config.showLabels !== false) {
1286
+ frag.appendChild(
1287
+ el("div", "atproto-post__label", {
1288
+ textContent:
1289
+ "Content warning: " +
1290
+ post.labels
1291
+ .map(function (l) {
1292
+ return l.val;
1293
+ })
1294
+ .join(", "),
1295
+ })
1296
+ );
1297
+ }
1298
+
1299
+ if (!isQuote && !footerAdded) {
1300
+ card.classList.add("atproto-post--no-footer");
1301
+ }
1302
+
1303
+ if (frag.childNodes.length > 0) {
1304
+ card.appendChild(frag);
1305
+ }
1306
+
1307
+ return card;
1308
+ }
1309
+
1310
+ /* ───── Liked by ───── */
1311
+
1312
+ function renderLikedBy(likesData, totalLikes, config) {
1313
+ if (config && config.showLikedBy === false) return null;
1314
+ if (!totalLikes || totalLikes === 0) return null;
1315
+
1316
+ var section = el("div", "atproto-discussion__liked-by");
1317
+ section.appendChild(
1318
+ el("div", "atproto-liked-by__label", { textContent: "Liked by" })
1319
+ );
1320
+
1321
+ var avatarsContainer = el("div", "atproto-liked-by__avatars");
1322
+ var likers = likesData.likes || [];
1323
+ var displayCount;
1324
+ var rows = 1;
1325
+ if (totalLikes > 12) rows = 3;
1326
+ else if (totalLikes > 6) rows = 2;
1327
+ var maxWidth = 600;
1328
+ if (config && config.maxWidth && typeof config.maxWidth === "string") {
1329
+ var px = parseInt(config.maxWidth, 10);
1330
+ if (isFinite(px)) maxWidth = px;
1331
+ }
1332
+ var avatarSize = 32;
1333
+ var gap = 6;
1334
+ var perRow = Math.max(3, Math.floor(maxWidth / (avatarSize + gap)));
1335
+ displayCount = Math.min(likers.length, rows * perRow);
1336
+ if (displayCount >= totalLikes) {
1337
+ displayCount = likers.length;
1338
+ }
1339
+
1340
+ for (var i = 0; i < displayCount; i++) {
1341
+ var liker = likers[i];
1342
+ if (!liker || !liker.actor) continue;
1343
+ var avatarLink = el("a", null, {
1344
+ href: "https://bsky.app/profile/" + liker.actor.handle,
1345
+ target: "_blank",
1346
+ rel: "noopener noreferrer",
1347
+ title: liker.actor.displayName || liker.actor.handle,
1348
+ });
1349
+ avatarLink.appendChild(
1350
+ el("img", null, {
1351
+ src: liker.actor.avatar || getIconUrl("avatar-fallback"),
1352
+ alt: liker.actor.displayName || liker.actor.handle,
1353
+ loading: "lazy",
1354
+ })
1355
+ );
1356
+ avatarsContainer.appendChild(avatarLink);
1357
+ }
1358
+
1359
+ var remaining = totalLikes - displayCount;
1360
+ if (remaining > 0) {
1361
+ var overflow = el("span", "atproto-liked-by__overflow", {
1362
+ textContent: "+" + formatCount(remaining),
1363
+ });
1364
+ if (String(remaining).length >= 3) {
1365
+ overflow.classList.add("atproto-liked-by__overflow--pill");
1366
+ }
1367
+ avatarsContainer.appendChild(overflow);
1368
+ }
1369
+
1370
+ section.appendChild(avatarsContainer);
1371
+
1372
+ return section;
1373
+ }
1374
+
1375
+ /* ───── Threaded replies ───── */
1376
+
1377
+ function renderReplyThread(replyThread, depth, sourceInfo, config) {
1378
+ if (!replyThread || !replyThread.post) return document.createDocumentFragment();
1379
+
1380
+ var wrapper = el("div", "atproto-reply-thread");
1381
+ wrapper.setAttribute("data-depth", depth);
1382
+
1383
+ var card = renderPostCard(
1384
+ { post: replyThread.post },
1385
+ false,
1386
+ sourceInfo,
1387
+ false,
1388
+ config,
1389
+ "discussion",
1390
+ "reply",
1391
+ null
1392
+ );
1393
+ if (!card) return document.createDocumentFragment();
1394
+
1395
+ wrapper.appendChild(card);
1396
+
1397
+ if (replyThread.replies && replyThread.replies.length) {
1398
+ var children = el("div", "atproto-reply-children");
1399
+ replyThread.replies.forEach(function (child) {
1400
+ var childEl = renderReplyThread(child, depth + 1, sourceInfo, config);
1401
+ if (childEl) children.appendChild(childEl);
1402
+ });
1403
+
1404
+ if (depth === 0) {
1405
+ var childCount = countReplyDescendants(replyThread);
1406
+ if (childCount > 0) {
1407
+ var toggleWrap = el("div", "atproto-replies-toggle");
1408
+ var toggleBtn = el("button", "atproto-replies-toggle__btn", {
1409
+ type: "button",
1410
+ });
1411
+ var toggleLabel = formatReplyLabel(childCount);
1412
+ var toggleText = el("span", "atproto-replies-toggle__label", {
1413
+ textContent: toggleLabel,
1414
+ });
1415
+ var toggleIcon = el("img", "atproto-replies-toggle__icon", {
1416
+ src: getIconUrl("caret-down"),
1417
+ alt: "",
1418
+ width: "12",
1419
+ height: "12",
1420
+ });
1421
+ toggleBtn.appendChild(toggleText);
1422
+ toggleBtn.appendChild(toggleIcon);
1423
+ toggleWrap.appendChild(toggleBtn);
1424
+
1425
+ var isOpen = false;
1426
+ children.style.display = "none";
1427
+ toggleBtn.addEventListener("click", function () {
1428
+ isOpen = !isOpen;
1429
+ children.style.display = isOpen ? "" : "none";
1430
+ toggleText.textContent = isOpen ? "Hide replies" : toggleLabel;
1431
+ toggleIcon.setAttribute(
1432
+ "src",
1433
+ getIconUrl(isOpen ? "caret-up" : "caret-down")
1434
+ );
1435
+ });
1436
+
1437
+ card.appendChild(toggleWrap);
1438
+ }
1439
+ }
1440
+
1441
+ wrapper.appendChild(children);
1442
+ }
1443
+
1444
+ return wrapper;
1445
+ }
1446
+
1447
+ /* ───── Tabs ───── */
1448
+
1449
+ function renderTabs(repliesData, quotesState, atUri, sourceInfo, config, mainPost, hostContainer) {
1450
+ var currentSort = config.repliesSort || "oldest";
1451
+ var tabs = el("div", "atproto-discussion__tabs");
1452
+
1453
+ var rc = mainPost ? mainPost.replyCount || repliesData.length : repliesData.length;
1454
+ var qc = mainPost ? mainPost.quoteCount || quotesState.posts.length : quotesState.posts.length;
1455
+
1456
+ var tabBar = el("div", "atproto-discussion__tab-bar");
1457
+ var repliesBtn = el("button", null, {
1458
+ textContent: "Replies (" + formatCount(rc) + ")",
1459
+ "aria-selected": "true",
1460
+ });
1461
+ var quotesBtn = el("button", null, {
1462
+ textContent: "Quotes (" + formatCount(qc) + ")",
1463
+ "aria-selected": "false",
1464
+ });
1465
+ if (
1466
+ config.showTabs !== false &&
1467
+ (config.showRepliesTab !== false || config.showQuotesTab !== false)
1468
+ ) {
1469
+ tabBar.appendChild(repliesBtn);
1470
+ tabBar.appendChild(quotesBtn);
1471
+ tabs.appendChild(tabBar);
1472
+ }
1473
+
1474
+ var sortBar = el("div", "atproto-discussion__sort-bar");
1475
+ var sortSelect = el("select", "atproto-discussion__sort-select");
1476
+ [
1477
+ { val: "newest", text: "Newest" },
1478
+ { val: "oldest", text: "Oldest" },
1479
+ { val: "likes", text: "Most Liked" },
1480
+ { val: "reposts", text: "Most Reposted" },
1481
+ { val: "quotes", text: "Most Quoted" },
1482
+ { val: "bookmarks", text: "Most Bookmarked" },
1483
+ { val: "replies", text: "Most Replies" },
1484
+ ].forEach(function (opt) {
1485
+ sortSelect.appendChild(
1486
+ el("option", null, { value: opt.val, textContent: opt.text })
1487
+ );
1488
+ });
1489
+ sortSelect.value = currentSort;
1490
+ if (config.showSort !== false) {
1491
+ sortBar.appendChild(sortSelect);
1492
+ tabs.appendChild(sortBar);
1493
+ }
1494
+
1495
+ var tabContent = el("div", "atproto-discussion__tab-content");
1496
+ tabs.appendChild(tabContent);
1497
+
1498
+ function refresh() {
1499
+ var isReplies = repliesBtn.getAttribute("aria-selected") === "true";
1500
+ var frag = document.createDocumentFragment();
1501
+ if (config.showTabs === false || isReplies) {
1502
+ var sortedReplies = sortPosts(repliesData, currentSort);
1503
+ frag.appendChild(renderRepliesPanel(sortedReplies, sourceInfo, config));
1504
+ } else {
1505
+ var sortedQuotes = sortPosts(quotesState.posts, currentSort);
1506
+ frag.appendChild(
1507
+ renderQuotesPanel(
1508
+ { posts: sortedQuotes, cursor: quotesState.cursor },
1509
+ atUri,
1510
+ sourceInfo,
1511
+ config
1512
+ )
1513
+ );
1514
+ }
1515
+ tabContent.replaceChildren(frag);
1516
+ }
1517
+
1518
+ repliesBtn.addEventListener("click", function () {
1519
+ repliesBtn.setAttribute("aria-selected", "true");
1520
+ quotesBtn.setAttribute("aria-selected", "false");
1521
+ refresh();
1522
+ });
1523
+
1524
+ quotesBtn.addEventListener("click", function () {
1525
+ quotesBtn.setAttribute("aria-selected", "true");
1526
+ repliesBtn.setAttribute("aria-selected", "false");
1527
+ refresh();
1528
+ });
1529
+
1530
+ sortSelect.addEventListener("change", function () {
1531
+ currentSort = sortSelect.value;
1532
+ if (hostContainer) {
1533
+ hostContainer.setAttribute("data-replies-sort", currentSort);
1534
+ }
1535
+ refresh();
1536
+ });
1537
+
1538
+ refresh();
1539
+ return tabs;
1540
+ }
1541
+
1542
+ /* ───── Replies panel ───── */
1543
+
1544
+ function renderRepliesPanel(repliesData, sourceInfo, config) {
1545
+ if (config.showRepliesTab === false) return el("div");
1546
+ var panel = el("div", "atproto-replies");
1547
+ var replies = repliesData || [];
1548
+
1549
+ if (!replies.length) {
1550
+ panel.appendChild(
1551
+ el("div", "atproto-discussion__empty", {
1552
+ textContent: "No replies yet",
1553
+ })
1554
+ );
1555
+ return panel;
1556
+ }
1557
+
1558
+ var pageSize = 20;
1559
+ var displayedCount = 0;
1560
+
1561
+ function renderMore() {
1562
+ var next = replies.slice(displayedCount, displayedCount + pageSize);
1563
+ appendInChunks(
1564
+ next,
1565
+ 10,
1566
+ function (reply) {
1567
+ return renderReplyThread(reply, 0, sourceInfo, config);
1568
+ },
1569
+ panel,
1570
+ loadMoreWrap
1571
+ );
1572
+ displayedCount += next.length;
1573
+
1574
+ if (displayedCount >= replies.length) {
1575
+ loadMoreWrap.style.display = "none";
1576
+ }
1577
+ }
1578
+
1579
+ var loadMoreWrap = el("div", "atproto-load-more");
1580
+ var loadMoreBtn = el("button", null, {
1581
+ textContent: "Load more replies",
1582
+ });
1583
+ loadMoreBtn.addEventListener("click", renderMore);
1584
+ loadMoreWrap.appendChild(loadMoreBtn);
1585
+ panel.appendChild(loadMoreWrap);
1586
+
1587
+ renderMore();
1588
+
1589
+ return panel;
1590
+ }
1591
+
1592
+ /* ───── Quotes panel ───── */
1593
+
1594
+ function renderQuoteCard(quotePost, sourceInfo, config) {
1595
+ return renderPostCard(
1596
+ {
1597
+ post: {
1598
+ author: quotePost.author,
1599
+ uri: quotePost.uri,
1600
+ record: quotePost.record || quotePost.value,
1601
+ embed: quotePost.embed || (quotePost.embeds && quotePost.embeds[0]) || null,
1602
+ indexedAt: quotePost.indexedAt,
1603
+ replyCount: quotePost.replyCount || 0,
1604
+ repostCount: quotePost.repostCount || 0,
1605
+ likeCount: quotePost.likeCount || 0,
1606
+ labels: quotePost.labels,
1607
+ },
1608
+ },
1609
+ false,
1610
+ sourceInfo,
1611
+ true,
1612
+ config,
1613
+ "discussion",
1614
+ "quote",
1615
+ null
1616
+ );
1617
+ }
1618
+
1619
+ function renderQuotesPanel(quotesState, atUri, sourceInfo, config) {
1620
+ if (config.showQuotesTab === false) return el("div");
1621
+ var panel = el("div", "atproto-quotes");
1622
+
1623
+ var posts = quotesState.posts || [];
1624
+ if (!posts.length && !quotesState.cursor) {
1625
+ panel.appendChild(
1626
+ el("div", "atproto-discussion__empty", {
1627
+ textContent: "No quotes yet",
1628
+ })
1629
+ );
1630
+ return panel;
1631
+ }
1632
+
1633
+ appendInChunks(
1634
+ posts,
1635
+ 12,
1636
+ function (qp) {
1637
+ if (!qp) return null;
1638
+ return renderQuoteCard(qp, sourceInfo, config);
1639
+ },
1640
+ panel
1641
+ );
1642
+
1643
+ if (quotesState.cursor) {
1644
+ var loadMoreWrap = el("div", "atproto-load-more");
1645
+ var loadMoreBtn = el("button", null, {
1646
+ textContent: "Load more quotes",
1647
+ });
1648
+ loadMoreBtn.addEventListener("click", async function () {
1649
+ loadMoreBtn.innerHTML =
1650
+ "<img src='" +
1651
+ getIconUrl("spinner") +
1652
+ "' class='atproto-spinner' style='width:16px;height:16px;margin-right:6px;vertical-align:middle;'> Loading…";
1653
+ loadMoreBtn.disabled = true;
1654
+ try {
1655
+ var data = await fetchQuotes(atUri, quotesState.cursor);
1656
+ var newPosts = data.posts || [];
1657
+ quotesState.cursor = data.cursor || null;
1658
+
1659
+ appendInChunks(
1660
+ newPosts,
1661
+ 12,
1662
+ function (qp) {
1663
+ if (!qp) return null;
1664
+ return renderQuoteCard(qp, sourceInfo, config);
1665
+ },
1666
+ panel,
1667
+ loadMoreWrap
1668
+ );
1669
+
1670
+ if (!quotesState.cursor) {
1671
+ loadMoreWrap.remove();
1672
+ } else {
1673
+ loadMoreBtn.textContent = "Load more quotes";
1674
+ loadMoreBtn.disabled = false;
1675
+ }
1676
+ } catch (err) {
1677
+ loadMoreBtn.textContent = "Failed — retry";
1678
+ loadMoreBtn.disabled = false;
1679
+ console.error("[atproto-embed]", err);
1680
+ }
1681
+ });
1682
+ loadMoreWrap.appendChild(loadMoreBtn);
1683
+ panel.appendChild(loadMoreWrap);
1684
+ }
1685
+
1686
+ return panel;
1687
+ }
1688
+
1689
+ /* ───── Discussion render ───── */
1690
+
1691
+ function buildDiscussion(atUri, sourceInfo, config, data, hostContainer) {
1692
+ var thread = data.thread;
1693
+ var quotesData = data.quotesData;
1694
+ var likesData = data.likesData;
1695
+
1696
+ var discussion = el("div", "atproto-discussion");
1697
+
1698
+ if (config.showMainPost !== false) {
1699
+ var rootCard = renderPostCard(
1700
+ thread,
1701
+ false,
1702
+ sourceInfo,
1703
+ false,
1704
+ config,
1705
+ "discussion",
1706
+ "root",
1707
+ "post"
1708
+ );
1709
+ if (!rootCard) throw new Error("Root post unavailable");
1710
+ discussion.appendChild(rootCard);
1711
+ }
1712
+
1713
+ var headerBar = el("div", "atproto-discussion__comments-bar");
1714
+ headerBar.appendChild(
1715
+ el("div", "atproto-discussion__comments-title", {
1716
+ textContent: "Comments",
1717
+ })
1718
+ );
1719
+ if (config.showJoinButton !== false) {
1720
+ var joinLink = getPostLink(sourceInfo, thread.post);
1721
+ headerBar.appendChild(
1722
+ el("a", "atproto-post__action atproto-post__action--primary", {
1723
+ href: joinLink,
1724
+ target: "_blank",
1725
+ rel: "noopener noreferrer",
1726
+ textContent: "Write your reply",
1727
+ })
1728
+ );
1729
+ }
1730
+ discussion.appendChild(headerBar);
1731
+
1732
+ var totalLikes = thread.post.likeCount || 0;
1733
+ var likedByEl = renderLikedBy(likesData, totalLikes, config);
1734
+ if (likedByEl) discussion.appendChild(likedByEl);
1735
+
1736
+ var repliesData = thread.replies || [];
1737
+ var quotesState = {
1738
+ posts: quotesData.posts || [],
1739
+ cursor: quotesData.cursor || null,
1740
+ };
1741
+
1742
+ discussion.appendChild(
1743
+ renderTabs(repliesData, quotesState, atUri, sourceInfo, config, thread.post, hostContainer)
1744
+ );
1745
+
1746
+ return discussion;
1747
+ }
1748
+
1749
+ async function fetchDiscussionData(atUri, signal) {
1750
+ var did = extractDid(atUri);
1751
+ var results = await Promise.all([
1752
+ fetchThread(atUri, signal),
1753
+ fetchQuotes(atUri, null, signal),
1754
+ fetchLikes(atUri, signal),
1755
+ fetchProfile(did),
1756
+ ]);
1757
+
1758
+ var thread = results[0];
1759
+ var quotesData = results[1] || { posts: [] };
1760
+ var likesData = results[2] || { likes: [] };
1761
+ var profile = results[3];
1762
+
1763
+ if (profile && thread && thread.post && thread.post.author) {
1764
+ if (profile.verification) thread.post.author.verification = profile.verification;
1765
+ if (profile.associated) thread.post.author.associated = profile.associated;
1766
+ }
1767
+
1768
+ return {
1769
+ thread: thread,
1770
+ quotesData: quotesData,
1771
+ likesData: likesData,
1772
+ };
1773
+ }
1774
+
1775
+ async function renderPostEmbed(container, resolved, config, signal) {
1776
+ container.innerHTML = "";
1777
+ var loadingDiv = el("div", "atproto-embed--loading");
1778
+ loadingDiv.appendChild(
1779
+ el("img", "atproto-spinner", { src: getIconUrl("spinner") })
1780
+ );
1781
+ loadingDiv.appendChild(document.createTextNode("Loading post…"));
1782
+ container.appendChild(loadingDiv);
1783
+
1784
+ try {
1785
+ var did = extractDid(resolved.atUri);
1786
+ var results = await Promise.all([
1787
+ fetchPost(resolved.atUri, signal),
1788
+ fetchProfile(did),
1789
+ ]);
1790
+ var thread = results[0];
1791
+ var profile = results[1];
1792
+
1793
+ if (thread && thread.post && thread.post.author && profile) {
1794
+ if (profile.verification) thread.post.author.verification = profile.verification;
1795
+ if (profile.associated) thread.post.author.associated = profile.associated;
1796
+ }
1797
+ var card = renderPostCard(
1798
+ thread,
1799
+ false,
1800
+ {
1801
+ sourceDomain: resolved.sourceDomain,
1802
+ sourceUrl: resolved.sourceUrl,
1803
+ },
1804
+ false,
1805
+ config,
1806
+ "post",
1807
+ "root",
1808
+ null
1809
+ );
1810
+ if (!card) throw new Error("Post not found or unavailable");
1811
+ container.innerHTML = "";
1812
+ container.appendChild(card);
1813
+ } catch (err) {
1814
+ if (err && err.name === "AbortError") return;
1815
+ container.innerHTML = "";
1816
+ container.appendChild(
1817
+ el("div", "atproto-embed--error", {
1818
+ textContent: "Failed to load post",
1819
+ })
1820
+ );
1821
+ console.error("[atproto-embed]", err);
1822
+ }
1823
+ }
1824
+
1825
+ async function renderDiscussionEmbed(container, resolved, config, hostContainer, signal) {
1826
+ container.innerHTML = "";
1827
+ var loadingDiv = el("div", "atproto-embed--loading");
1828
+ loadingDiv.appendChild(
1829
+ el("img", "atproto-spinner", { src: getIconUrl("spinner") })
1830
+ );
1831
+ loadingDiv.appendChild(document.createTextNode("Loading discussion…"));
1832
+ container.appendChild(loadingDiv);
1833
+
1834
+ try {
1835
+ var data = await fetchDiscussionData(resolved.atUri, signal);
1836
+ container.innerHTML = "";
1837
+ container.appendChild(
1838
+ buildDiscussion(
1839
+ resolved.atUri,
1840
+ {
1841
+ sourceDomain: resolved.sourceDomain,
1842
+ sourceUrl: resolved.sourceUrl,
1843
+ },
1844
+ config,
1845
+ data,
1846
+ hostContainer
1847
+ )
1848
+ );
1849
+ } catch (err) {
1850
+ if (err && err.name === "AbortError") return;
1851
+ container.innerHTML = "";
1852
+ container.appendChild(
1853
+ el("div", "atproto-embed--error", {
1854
+ textContent: "Failed to load discussion",
1855
+ })
1856
+ );
1857
+ console.error("[atproto-embed]", err);
1858
+ }
1859
+ }
1860
+
1861
+
1862
+
1863
+ /* ───── CSS injection ───── */
1864
+
1865
+ function injectStyles(root) {
1866
+ var style = document.createElement("style");
1867
+ style.textContent = "@import url('https://fonts.googleapis.com/css2?family=Google+Sans+Flex:opsz,wght@6..144,1..1000&display=swap');:root,:host{--neutral-0:#ffffff;--neutral-1:#f8f9fa;--neutral-2:#f1f3f5;--neutral-3:#e9ecef;--neutral-4:#dee2e6;--neutral-5:#ced4da;--neutral-6:#adb5bd;--neutral-7:#6a7178;--neutral-8:#4f575e;--neutral-9:#272b30;--neutral-10:#101213;--neutral-11:#000000;--primary-light:#f8f9ff;--primary-base:#0a66f4;--primary-hover:#20439b;--primary-dark:#1c2855;--font-displayLarge:45px;--font-displaymedium:40px;--font-displaySmall:36px;--font-heading1:32px;--font-heading2:28px;--font-heading3:25px;--font-heading4:22px;--font-heading5:20px;--font-heading6:18px;--font-subtitle:16px;--font-body:14px;--font-caption:12px;--font-label:11px;--font-tagline:10px;--font-sans:\"Google Sans Flex\",-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;--transition:all 0.32s ease-in-out;--atproto-font-family:var(--font-sans);--atproto-display-name-color:var(--neutral-10);--atproto-display-name-size:var(--font-body);--atproto-display-name-weight:700;--atproto-handle-color:var(--neutral-7);--atproto-handle-size:var(--font-body);--atproto-text-color:var(--neutral-11);--atproto-text-size:var(--font-subtitle);--atproto-text-line-height:1.5;--atproto-timestamp-color:var(--neutral-7);--atproto-timestamp-size:var(--font-caption);--atproto-mention-color:var(--primary-base);--atproto-hashtag-color:var(--primary-base);--atproto-link-color:var(--primary-base);--atproto-link-decoration:none;--atproto-bg:var(--neutral-0);--atproto-border-color:var(--neutral-1);--atproto-border-radius:12px;--atproto-border-width:1px;--atproto-max-width:600px;--atproto-image-radius:8px;--atproto-image-gap:4px;--atproto-video-radius:8px;--atproto-card-bg:var(--neutral-1);--atproto-card-border-color:var(--neutral-2);--atproto-card-title-color:var(--neutral-10);--atproto-card-title-size:var(--font-body);--atproto-card-desc-color:var(--neutral-7);--atproto-card-desc-size:var(--font-caption);--atproto-card-domain-color:var(--neutral-9);--atproto-card-domain-size:var(--font-label);--atproto-external-thumb-ratio:1.91 / 1;--atproto-quote-bg:var(--neutral-0);--atproto-quote-border-color:var(--neutral-1);--atproto-stat-color:var(--neutral-10);--atproto-stat-size:var(--font-body);--atproto-stat-icon-color:var(--neutral-7);--atproto-via-color:var(--primary-base);--atproto-via-size:var(--font-caption);--atproto-label-bg:#fff3cd;--atproto-label-color:#856404;--atproto-label-border-color:#ffc107;--atproto-action-size:var(--font-body);--atproto-avatar-size:42px;--atproto-avatar-radius:50%;--atproto-thread-line-color:var(--neutral-2);--atproto-thread-line-width:2px;--atproto-reply-indent:24px;--atproto-load-more-color:var(--primary-base);--atproto-load-more-bg:transparent;--atproto-load-more-border-color:var(--neutral-2);--atproto-liked-by-label-color:var(--neutral-7);--atproto-liked-by-avatar-size:36px;--atproto-liked-by-avatar-gap:6px;--atproto-liked-by-overflow-color:var(--neutral-7);--atproto-liked-by-overflow-bg:var(--neutral-2)}:host(.atproto-embed-host){display:block;width:var(--atproto-width,100%);max-width:var(--atproto-max-width);min-width:0;box-sizing:border-box;margin:32px auto}:host(.atproto-embed-host--post){margin:32px auto}:is(.atproto-embed--post,.atproto-embed--discussion){width:var(--atproto-width,100%);max-width:var(--atproto-max-width);font-family:var(--atproto-font-family);box-sizing:border-box}:is(.atproto-embed--post,.atproto-embed--discussion) *,:is(.atproto-embed--post,.atproto-embed--discussion) *::before,:is(.atproto-embed--post,.atproto-embed--discussion) *::after{box-sizing:border-box;margin:0;padding:0;scrollbar-width:thin;scrollbar-color:var(--neutral-7) transparent}a,label,select,input,textarea,button{font-family:inherit}:is(.atproto-embed--post,.atproto-embed--discussion) .atproto-post{background:var(--atproto-bg);border:1px solid var(--neutral-3);border-radius:20px;box-shadow:0 0 0 3px var(--neutral-1);padding:16px;color:var(--atproto-text-color)}.atproto-embed--post .atproto-post__header,.atproto-embed--discussion .atproto-post__header{display:flex;align-items:center;gap:8px;margin-bottom:8px;text-decoration:none;color:inherit}.atproto-embed--post .atproto-post__header-main-link,.atproto-embed--discussion .atproto-post__header-main-link{display:inline-flex;align-items:center;gap:8px;text-decoration:none;color:inherit;min-width:0}.atproto-embed--post .atproto-post__header-main,.atproto-embed--discussion .atproto-post__header-main{display:flex;align-items:center;gap:8px;min-width:0}.atproto-embed--post .atproto-post__author,.atproto-embed--discussion .atproto-post__author{display:flex;flex-direction:column;min-width:0;gap:2px}.atproto-embed--post .atproto-post__display-name,.atproto-embed--discussion .atproto-post__display-name{color:var(--atproto-display-name-color);font-size:var(--atproto-display-name-size);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:-0.01em;line-height:1.2}.atproto-embed--post .atproto-post__display-name-wrap,.atproto-embed--discussion .atproto-post__display-name-wrap{display:flex;align-items:center;gap:4px;min-width:0}.atproto-embed--post .atproto-badge-wrap,.atproto-embed--discussion .atproto-badge-wrap{display:inline-flex;align-items:center;flex-shrink:0}.atproto-embed--post .atproto-badge,.atproto-embed--discussion .atproto-badge{display:block}.atproto-embed--post .atproto-badge-wrap img,.atproto-embed--discussion .atproto-badge-wrap img{width:16px;height:16px}.atproto-embed--post .atproto-post__handle,.atproto-embed--discussion .atproto-post__handle{color:var(--atproto-handle-color);font-size:var(--atproto-handle-size);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2}.atproto-embed--post .atproto-post__reply-context,.atproto-embed--discussion .atproto-post__reply-context{background:var(--neutral-1);color:var(--neutral-8);border-radius:8px;padding:4px 8px;font-size:var(--font-caption);margin-bottom:8px;display:flex;align-items:center;gap:6px;width:fit-content}.atproto-embed--post .atproto-post__reply-context img,.atproto-embed--discussion .atproto-post__reply-context img{width:14px;height:14px;flex-shrink:0}.atproto-embed--post .atproto-post__text,.atproto-embed--discussion .atproto-post__text{color:var(--atproto-text-color);font-size:var(--atproto-text-size);line-height:var(--atproto-text-line-height);margin-bottom:12px;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word}.atproto-embed--post .atproto-post__paragraph:not(:last-child),.atproto-embed--discussion .atproto-post__paragraph:not(:last-child){margin-bottom:8px}.atproto-embed--post .atproto-post__mention,.atproto-embed--discussion .atproto-post__mention{color:var(--atproto-mention-color);font-weight:600;text-decoration:none}.atproto-embed--post .atproto-post__hashtag,.atproto-embed--discussion .atproto-post__hashtag{color:var(--atproto-hashtag-color);font-weight:600;text-decoration:none}.atproto-embed--post .atproto-post__link,.atproto-embed--discussion .atproto-post__link{color:var(--atproto-link-color);font-weight:600;text-decoration:var(--atproto-link-decoration)}@media (hover:hover){.atproto-embed--post .atproto-post__mention:hover,.atproto-embed--discussion .atproto-post__mention:hover,.atproto-embed--post .atproto-post__hashtag:hover,.atproto-embed--discussion .atproto-post__hashtag:hover,.atproto-embed--post .atproto-post__link:hover,.atproto-embed--discussion .atproto-post__link:hover{text-decoration:underline}.atproto-embed--post .atproto-post__timestamp:hover,.atproto-embed--discussion .atproto-post__timestamp:hover{text-decoration:underline}.atproto-embed--post .atproto-post__via a:hover,.atproto-embed--discussion .atproto-post__via a:hover{text-decoration:underline}}.atproto-embed--post .atproto-post__embed,.atproto-embed--discussion .atproto-post__embed{margin-top:12px}.atproto-embed--post .atproto-post__embed>*:not(:first-child),.atproto-embed--discussion .atproto-post__embed>*:not(:first-child){margin-top:12px}.atproto-embed--post .atproto-embed__images,.atproto-embed--discussion .atproto-embed__images{display:grid;gap:var(--atproto-image-gap);border-radius:var(--atproto-image-radius);overflow:hidden;max-height:530px}.atproto-embed--post .atproto-embed__images--1,.atproto-embed--discussion .atproto-embed__images--1{grid-template-columns:1fr}.atproto-embed--post .atproto-embed__images--2,.atproto-embed--discussion .atproto-embed__images--2{grid-template-columns:1fr 1fr;height:320px}.atproto-embed--post .atproto-embed__images--3,.atproto-embed--discussion .atproto-embed__images--3{grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;height:320px}.atproto-embed--post .atproto-embed__images--3 .atproto-embed__image-link:first-child,.atproto-embed--discussion .atproto-embed__images--3 .atproto-embed__image-link:first-child{grid-row:1 / -1}.atproto-embed--post .atproto-embed__images--4,.atproto-embed--discussion .atproto-embed__images--4{grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;height:400px}.atproto-embed--post .atproto-embed__image-link,.atproto-embed--discussion .atproto-embed__image-link{display:block;width:100%;height:100%;overflow:hidden}.atproto-embed--post .atproto-embed__images img,.atproto-embed--discussion .atproto-embed__images img{width:100%;height:100%;display:block;object-fit:cover;transition:opacity 0.2s,transform 0.3s ease-out}.atproto-embed--post .atproto-embed__image-link:hover img,.atproto-embed--discussion .atproto-embed__image-link:hover img{opacity:0.9;transform:scale(1.02)}.atproto-embed--post .atproto-embed__images--1 img,.atproto-embed--discussion .atproto-embed__images--1 img{max-height:530px;object-fit:contain;object-position:center;background:var(--neutral-1);border:2px solid var(--neutral-2);border-radius:16px}.atproto-embed--post .atproto-embed__images--2 img,.atproto-embed--post .atproto-embed__images--3 img,.atproto-embed--post .atproto-embed__images--4 img,.atproto-embed--discussion .atproto-embed__images--2 img,.atproto-embed--discussion .atproto-embed__images--3 img,.atproto-embed--discussion .atproto-embed__images--4 img{height:100%}.atproto-embed--post .atproto-embed__video,.atproto-embed--discussion .atproto-embed__video{border-radius:var(--atproto-video-radius);overflow:hidden;background:#000}.atproto-embed--post .atproto-embed__video video,.atproto-embed--post .atproto-embed__video iframe,.atproto-embed--discussion .atproto-embed__video video,.atproto-embed--discussion .atproto-embed__video iframe{width:100%;max-height:530px;display:block;border:none}.atproto-embed--post .atproto-embed__gif,.atproto-embed--discussion .atproto-embed__gif{display:block;border-radius:var(--atproto-image-radius);overflow:hidden;text-decoration:none}.atproto-embed--post .atproto-embed__gif-img,.atproto-embed--discussion .atproto-embed__gif-img{width:100%;max-height:400px;display:block;object-fit:contain;object-position:center;background:var(--neutral-0)}.atproto-embed--post .atproto-embed__external,.atproto-embed--discussion .atproto-embed__external{border-radius:16px;overflow:hidden;text-decoration:none;display:block;color:inherit;transition:box-shadow 0.18s,border-color 0.18s}.atproto-embed--post .atproto-embed__external:hover,.atproto-embed--discussion .atproto-embed__external:hover{box-shadow:0 3px 12px rgba(0,0,0,0.1)}.atproto-embed--post .atproto-embed__external-thumb,.atproto-embed--discussion .atproto-embed__external-thumb{width:100%;height:auto;aspect-ratio:var(--atproto-external-thumb-ratio);object-fit:cover;object-position:center;display:block;background:var(--neutral-0)}.atproto-embed--post .atproto-embed__external-content,.atproto-embed--discussion .atproto-embed__external-content{padding:16px;background:var(--atproto-card-bg)}.atproto-embed--post .atproto-embed__external--horizontal,.atproto-embed--discussion .atproto-embed__external--horizontal{display:flex;align-items:stretch}.atproto-embed--post .atproto-embed__external--horizontal .atproto-embed__external-thumb,.atproto-embed--discussion .atproto-embed__external--horizontal .atproto-embed__external-thumb{width:38%;max-width:220px;flex:0 0 auto}@media (max-width:520px){.atproto-embed--post .atproto-embed__external--horizontal,.atproto-embed--discussion .atproto-embed__external--horizontal{display:block}.atproto-embed--post .atproto-embed__external--horizontal .atproto-embed__external-thumb,.atproto-embed--discussion .atproto-embed__external--horizontal .atproto-embed__external-thumb{width:100%;max-width:none}}.atproto-embed--post .atproto-embed__external-title,.atproto-embed--discussion .atproto-embed__external-title{color:var(--atproto-card-title-color);font-size:var(--atproto-card-title-size);line-height:1.4;font-weight:600;margin-bottom:2px;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.atproto-embed--post .atproto-embed__external-desc,.atproto-embed--discussion .atproto-embed__external-desc{color:var(--atproto-card-desc-color);font-size:var(--atproto-card-desc-size);display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;margin-bottom:8px;line-height:1.5}.atproto-embed--post .atproto-embed__external-domain,.atproto-embed--discussion .atproto-embed__external-domain{color:var(--atproto-card-domain-color);font-size:var(--atproto-card-domain-size);font-weight:600}.atproto-embed--post .atproto-embed__quote,.atproto-embed--discussion .atproto-embed__quote{border:2px solid var(--atproto-quote-border-color);border-radius:16px;padding:16px;background:var(--atproto-quote-bg)}.atproto-embed--post .atproto-embed__quote .atproto-post,.atproto-embed--discussion .atproto-embed__quote .atproto-post{border:none;padding:0;background:transparent;box-shadow:none}.atproto-embed--post .atproto-post__footer,.atproto-embed--discussion .atproto-post__footer{margin-top:16px}.atproto-embed--post .atproto-post__metrics-row,.atproto-embed--post .atproto-post__info-row,.atproto-embed--discussion .atproto-post__metrics-row,.atproto-embed--discussion .atproto-post__info-row{display:flex;align-items:center;justify-content:space-between;gap:12px}.atproto-embed--post .atproto-post__info-row{padding-top:12px;margin-top:12px;border-top:1px dashed var(--neutral-3)}.atproto-embed--post .atproto-post__metrics-left,.atproto-embed--post .atproto-post__metrics-right,.atproto-embed--discussion .atproto-post__metrics-left,.atproto-embed--discussion .atproto-post__metrics-right{display:flex;align-items:center;gap:16px}.atproto-embed--post .atproto-post__stat--likes,.atproto-embed--post .atproto-post__stat--reposts,.atproto-embed--post .atproto-post__stat--replies,.atproto-embed--post .atproto-post__stat--quotes,.atproto-embed--post .atproto-post__stat--bookmarks,.atproto-embed--discussion .atproto-post__stat--likes,.atproto-embed--discussion .atproto-post__stat--reposts,.atproto-embed--discussion .atproto-post__stat--replies,.atproto-embed--discussion .atproto-post__stat--quotes,.atproto-embed--discussion .atproto-post__stat--bookmarks{display:flex;align-items:center;gap:4px;color:var(--atproto-stat-color);font-size:var(--atproto-stat-size);font-weight:500}.atproto-embed--post .atproto-post__stat--quotes,.atproto-embed--discussion .atproto-post__stat--quotes{text-decoration:none}.atproto-embed--post .atproto-post__metrics-row img,.atproto-embed--discussion .atproto-post__metrics-row img{width:18px;height:18px}.atproto-embed--post .atproto-post__timestamp,.atproto-embed--discussion .atproto-post__timestamp{color:var(--atproto-timestamp-color);font-size:var(--atproto-timestamp-size);text-decoration:none;font-weight:500}.atproto-embed--post .atproto-post__via,.atproto-embed--discussion .atproto-post__via{font-size:var(--atproto-via-size);color:var(--atproto-handle-color)}.atproto-embed--post .atproto-post__via a,.atproto-embed--discussion .atproto-post__via a{color:var(--atproto-via-color);text-decoration:none;font-weight:600}.atproto-embed--post .atproto-post__label,.atproto-embed--discussion .atproto-post__label{background:var(--atproto-label-bg);color:var(--atproto-label-color);border:1px solid var(--atproto-label-border-color);border-radius:100px;padding:4px 12px;font-size:var(--font-label);margin-top:12px;font-weight:500;width:fit-content}.atproto-embed--post .atproto-embed--loading,.atproto-embed--discussion .atproto-embed--loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:40px 32px;color:var(--atproto-handle-color);font-size:14px;font-weight:500;text-align:center}.atproto-embed--post .atproto-spinner,.atproto-embed--discussion .atproto-spinner{width:24px;height:24px;animation:atproto-spin 1s linear infinite;opacity:0.6}@keyframes atproto-spin{100%{transform:rotate(360deg)}}.atproto-embed--post .atproto-embed--error,.atproto-embed--discussion .atproto-embed--error{display:flex;align-items:center;justify-content:center;padding:24px;color:#cf222e;font-size:14px;border:1px solid #fdd;border-radius:var(--atproto-border-radius);background:#fff5f5;text-align:center;white-space:nowrap}.atproto-embed--post .atproto-post__actions,.atproto-embed--discussion .atproto-post__actions{display:flex;gap:10px;margin-top:16px}.atproto-embed--post .atproto-post__action,.atproto-embed--discussion .atproto-post__action{display:inline-flex;align-items:center;justify-content:center;padding:10px 20px;border-radius:100px;font-size:var(--atproto-action-size);font-weight:600;font-family:var(--atproto-font-family);text-decoration:none;transition:background 0.15s,box-shadow 0.15s,opacity 0.15s;cursor:pointer;border:none;line-height:1;letter-spacing:-0.01em;white-space:nowrap}.atproto-embed--post .atproto-post__action--primary,.atproto-embed--discussion .atproto-post__action--primary{background:var(--primary-base);color:var(--primary-light)}.atproto-embed--post .atproto-post__action--primary:hover,.atproto-embed--discussion .atproto-post__action--primary:hover{background:var(--primary-hover)}.atproto-embed--post .atproto-post__action--secondary,.atproto-embed--discussion .atproto-post__action--secondary{background:var(--neutral-2);color:var(--neutral-11)}.atproto-embed--post .atproto-post__action--secondary:hover,.atproto-embed--discussion .atproto-post__action--secondary:hover{background:var(--neutral-4)}.atproto-embed--post .atproto-post__avatar{width:42px;height:42px;border-radius:50%;object-fit:cover;flex-shrink:0;border:none}.atproto-embed--post .atproto-post__header-main-link:hover .atproto-post__display-name{text-decoration:none}.atproto-embed--post .atproto-post__actions{justify-content:space-between}.atproto-embed--post .atproto-post__action{width:100%}.atproto-embed--discussion .atproto-post__avatar{width:var(--atproto-avatar-size);height:var(--atproto-avatar-size);border-radius:var(--atproto-avatar-radius);object-fit:cover;flex-shrink:0;border:none}@media (hover:hover){.atproto-embed--discussion .atproto-post__header-main-link:hover .atproto-post__display-name{text-decoration:underline}.atproto-embed--discussion .atproto-post__header-right:hover{text-decoration:underline}}.atproto-embed--discussion .atproto-post--compact .atproto-post__header{gap:10px}.atproto-embed--discussion .atproto-post--compact .atproto-post__author-simple{display:none}.atproto-embed--discussion .atproto-post--compact .atproto-post__avatar{width:24px;height:24px}.atproto-embed--discussion .atproto-post--compact .atproto-post__author-inline{display:inline-flex;align-items:center;gap:2px;min-width:0;max-width:100%}.atproto-embed--discussion .atproto-post--compact .atproto-post__display-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.atproto-embed--discussion .atproto-post--compact .atproto-post__handle{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:80%;line-height:1.2}.atproto-embed--discussion .atproto-post--compact .atproto-post__header-main{min-width:0;max-width:100%}.atproto-embed--discussion .atproto-post--compact .atproto-post__header-main-link{max-width:calc(100% - 48px)}.atproto-embed--discussion .atproto-post--compact .atproto-post__text{font-size:var(--font-body)}.atproto-embed--discussion .atproto-post--compact .atproto-post__text:last-child{margin-bottom:0}.atproto-embed--discussion .atproto-post__badges-inline{display:inline-flex;align-items:center;gap:4px}.atproto-embed--discussion .atproto-post__header-right{margin-left:auto;color:var(--atproto-timestamp-color);font-size:var(--font-caption);text-decoration:none;font-weight:500;white-space:nowrap}.atproto-embed--discussion .atproto-discussion{background:var(--atproto-bg);border:1px solid var(--neutral-3);border-radius:20px;overflow:hidden;box-shadow:0 0 0 3px var(--neutral-1)}.atproto-embed--discussion .atproto-discussion>.atproto-post.atproto-post--main{border:none;border-bottom:2px solid var(--atproto-border-color);border-radius:0;box-shadow:none;padding:16px}.atproto-embed--discussion .atproto-post--main .atproto-post__header{gap:8px;margin-bottom:8px}.atproto-embed--discussion .atproto-post--main .atproto-post__avatar{border:none}.atproto-embed--discussion .atproto-post--main .atproto-post__metrics-row{padding-bottom:12px;margin-bottom:12px;border-bottom:1px dashed var(--neutral-3)}.atproto-embed--discussion .atproto-post--main .atproto-post__footer{margin-top:16px;padding-top:0;border-top:0}.atproto-embed--discussion .atproto-post--main .atproto-post__metrics-row img{width:18px;height:18px;opacity:1}.atproto-embed--discussion .atproto-post__metrics-row--simple{padding-bottom:0;margin-bottom:0;border-bottom:0;font-size:var(--font-caption);color:var(--atproto-stat-color)}.atproto-embed--discussion .atproto-post__stat-text{color:var(--atproto-stat-color);font-weight:600;font-size:var(--font-caption)}.atproto-embed--discussion .atproto-post__stat-link{color:var(--atproto-via-color);font-weight:600;text-decoration:none;font-size:var(--font-caption)}.atproto-embed--discussion .atproto-replies-toggle{margin-top:10px}.atproto-embed--discussion .atproto-replies-toggle__btn{display:inline-flex;align-items:center;gap:6px;padding:0;border:none;background:transparent;color:var(--atproto-via-color);font-size:var(--font-caption);font-weight:600;cursor:pointer}@media (hover:hover){.atproto-embed--discussion .atproto-post__stat-link:hover,.atproto-embed--discussion .atproto-replies-toggle__btn:hover{text-decoration:underline}}.atproto-embed--discussion .atproto-replies-toggle__icon{width:12px;height:12px;flex-shrink:0}.atproto-embed--discussion .atproto-post--no-embed .atproto-post__text{margin-bottom:12px}.atproto-embed--discussion .atproto-post--no-embed.atproto-post--no-footer .atproto-post__text,.atproto-embed--post .atproto-post--no-embed.atproto-post--no-footer .atproto-post__text{margin-bottom:0}.atproto-embed--discussion .atproto-post--no-embed .atproto-post__footer{margin-top:8px}.atproto-embed--discussion .atproto-discussion__liked-by{padding:0 16px 24px;background:var(--neutral-0)}.atproto-embed--discussion .atproto-liked-by__label{color:var(--neutral-8);font-size:var(--font-tagline);font-weight:600;margin-bottom:10px}.atproto-embed--discussion .atproto-liked-by__avatars{display:grid;align-items:center;gap:var(--atproto-liked-by-avatar-gap);grid-template-columns:repeat(auto-fill,minmax(var(--atproto-liked-by-avatar-size),1fr))}.atproto-embed--discussion .atproto-liked-by__avatars a{display:block;justify-self:center;border-radius:50%;transition:transform 0.15s}.atproto-embed--discussion .atproto-liked-by__avatars a:hover{transform:scale(1.12);z-index:1;position:relative}.atproto-embed--discussion .atproto-liked-by__avatars img{width:var(--atproto-liked-by-avatar-size);height:var(--atproto-liked-by-avatar-size);border-radius:50%;object-fit:cover;display:block;box-shadow:0 0 0 1px rgba(0,0,0,0.08)}.atproto-embed--discussion .atproto-liked-by__overflow{display:inline-flex;align-items:center;justify-content:center;min-width:var(--atproto-liked-by-avatar-size);height:var(--atproto-liked-by-avatar-size);border-radius:50%;background:var(--neutral-1);color:var(--neutral-11);font-size:var(--font-tagline);font-weight:700;padding:0 6px;border:2px solid var(--neutral-0);box-shadow:0 0 0 1px var(--neutral-3);width:fit-content;justify-self:start}.atproto-embed--discussion .atproto-liked-by__overflow--pill{border-radius:10px;min-width:auto;padding:0 10px}.atproto-embed--discussion .atproto-discussion__join{display:flex;justify-content:center;padding:20px;border-bottom:1px solid var(--neutral-2);background:var(--atproto-bg)}.atproto-embed--discussion .atproto-discussion__join .atproto-post__action{width:100%}.atproto-embed--discussion .atproto-discussion__comments-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:20px 16px 12px;background:var(--atproto-bg)}.atproto-embed--discussion .atproto-discussion__comments-title{font-size:var(--font-heading4);font-weight:600;color:var(--neutral-10)}.atproto-embed--discussion .atproto-discussion__comments-bar .atproto-post__action{width:auto;padding:8px 16px}@media (max-width:768px){.atproto-embed--post .atproto-post__action,.atproto-embed--discussion .atproto-post__action,.atproto-embed--discussion .atproto-discussion__comments-bar .atproto-post__action{font-size:var(--font-caption)}}.atproto-embed--discussion .atproto-discussion__tabs{display:flex;flex-direction:column}.atproto-embed--discussion .atproto-discussion__tab-bar{display:flex;align-items:center;justify-content:flex-start;gap:0;background:var(--neutral-1);border-bottom:2px solid var(--neutral-2);border-top:2px solid var(--neutral-2);padding:8px 16px}.atproto-embed--discussion .atproto-discussion__tab-bar button{padding:6px 12px;background:transparent;cursor:pointer;font-size:var(--font-caption);font-weight:600;color:var(--neutral-7);border:2px solid transparent;transition:color 0.15s,border-color 0.15s;border-radius:100px;width:fit-content;white-space:nowrap}.atproto-embed--discussion .atproto-discussion__tab-bar button[aria-selected=\"true\"]{color:var(--neutral-11);background:var(--neutral-0);border-color:var(--neutral-2)}.atproto-embed--discussion .atproto-discussion__tab-bar button:hover:not([aria-selected=\"true\"]){color:var(--neutral-10)}.atproto-embed--discussion .atproto-discussion__sort-bar{display:flex;align-items:center;justify-content:flex-end;padding:8px 12px;background:var(--atproto-bg);border-bottom:2px solid var(--neutral-1);font-size:var(--font-caption);color:var(--neutral-7)}.atproto-embed--discussion .atproto-discussion__sort-select{padding:8px 4px;border-radius:8px;border:none;background:transparent;font-size:var(--font-caption);font-weight:600;color:var(--neutral-9);cursor:pointer;transition:var(--transition);outline:1px solid transparent;width:100%}.atproto-embed--discussion .atproto-discussion__sort-select:hover,.atproto-embed--discussion .atproto-discussion__sort-select:focus{outline:1px solid var(--neutral-4)}.atproto-embed--discussion .atproto-discussion__tab-content{min-height:60px}.atproto-embed--discussion .atproto-replies,.atproto-embed--discussion .atproto-quotes{padding:0;content-visibility:auto;contain-intrinsic-size:1px 600px}.atproto-embed--discussion .atproto-quotes>.atproto-post{border:none;border-radius:0;border-bottom:2px solid var(--atproto-border-color);box-shadow:none;padding:16px}.atproto-embed--discussion .atproto-quotes>.atproto-post:last-child{border-bottom:none}.atproto-embed--discussion .atproto-replies{background:var(--atproto-bg)}.atproto-embed--discussion .atproto-reply-thread{position:relative;display:flex;flex-direction:column}.atproto-embed--discussion .atproto-reply-thread .atproto-post{border:none !important;border-radius:0 !important;padding:16px !important;box-shadow:none !important;background:transparent !important}.atproto-embed--discussion .atproto-reply-children .atproto-post{padding:12px !important}.atproto-embed--discussion .atproto-reply-children .atproto-post__header{margin-bottom:6px}.atproto-embed--discussion .atproto-reply-children .atproto-post__embed{margin-top:8px}.atproto-embed--discussion .atproto-post--compact .atproto-embed__quote{padding:0}.atproto-embed--discussion .atproto-post--compact .atproto-embed__quote .atproto-post{padding:10px}.atproto-embed--discussion .atproto-reply-children{margin-left:18px;border-left:2px solid var(--neutral-2);padding-left:10px;transition:border-left-color 0.1s ease}.atproto-embed--discussion .atproto-reply-children:hover{border-left-color:var(--neutral-2)}.atproto-embed--discussion .atproto-replies>.atproto-reply-thread{border-bottom:2px solid var(--atproto-border-color)}.atproto-embed--discussion .atproto-replies>.atproto-reply-thread:last-child{border-bottom:none}.atproto-embed--discussion .atproto-reply-children .atproto-reply-thread{margin-top:4px}.atproto-embed--discussion .atproto-reply-thread[data-depth=\"0\"]{margin-top:0}.atproto-embed--discussion .atproto-load-more{display:flex;justify-content:center;padding:14px;border-top:2px solid var(--atproto-border-color)}.atproto-embed--discussion .atproto-load-more button{background:var(--atproto-load-more-bg);color:var(--atproto-load-more-color);border:1px solid var(--atproto-load-more-border-color);border-radius:100px;padding:8px 24px;font-size:13px;font-weight:600;font-family:var(--atproto-font-family);cursor:pointer;transition:background 0.15s,border-color 0.15s;white-space:nowrap}@media (hover:hover){.atproto-embed--discussion .atproto-load-more button:hover{background:#f0f5ff;border-color:#b0c8e8}}.atproto-embed--discussion .atproto-load-more button:disabled{opacity:0.5;cursor:default}.atproto-embed--discussion .atproto-discussion__empty{padding:36px 24px;text-align:center;color:var(--atproto-handle-color);font-size:14px}@media (max-width:768px){.atproto-embed--discussion .atproto-discussion__comments-title{font-size:var(--font-heading6)}}@media (max-width:480px){.atproto-embed--post .atproto-post,.atproto-embed--discussion .atproto-post{padding:12px}.atproto-embed--post .atproto-post__metrics-left,.atproto-embed--discussion .atproto-post__metrics-left{gap:12px}.atproto-embed--discussion .atproto-reply-children{margin-left:8px;padding-left:6px}.atproto-embed--discussion .atproto-liked-by__avatars{gap:4px}.atproto-embed--discussion .atproto-discussion__sort-bar{justify-content:flex-start}}";
1868
+ root.appendChild(style);
1869
+ }
1870
+
1871
+ function ensureFontLoaded() {
1872
+ if (typeof document === "undefined") return;
1873
+ if (document.getElementById("atproto-font-google-sans")) return;
1874
+ var link = document.createElement("link");
1875
+ link.id = "atproto-font-google-sans";
1876
+ link.setAttribute("rel", "stylesheet");
1877
+ link.href =
1878
+ "https://fonts.googleapis.com/css2?family=Google+Sans+Flex:opsz,wght@6..144,1..1000&display=swap";
1879
+ document.head.appendChild(link);
1880
+ }
1881
+
1882
+ /* ───── Init ───── */
1883
+
1884
+ async function initContainer(container) {
1885
+ var raw = container.getAttribute("data-uri");
1886
+ if (!raw) return;
1887
+
1888
+ var previous = CONTAINER_ABORTS.get(container);
1889
+ if (previous) previous.abort();
1890
+ var controller = new AbortController();
1891
+ CONTAINER_ABORTS.set(container, controller);
1892
+
1893
+ var config = parseConfig(container);
1894
+
1895
+ container.classList.add("atproto-embed-host");
1896
+ container.classList.remove("atproto-embed-host--post", "atproto-embed-host--discussion");
1897
+ container.classList.add(
1898
+ config.mode === "discussion"
1899
+ ? "atproto-embed-host--discussion"
1900
+ : "atproto-embed-host--post"
1901
+ );
1902
+ applySizeConfig(container, config);
1903
+
1904
+ var shadow = container.shadowRoot;
1905
+ if (!shadow) {
1906
+ shadow = container.attachShadow({ mode: "open" });
1907
+ } else {
1908
+ shadow.innerHTML = "";
1909
+ }
1910
+
1911
+ ensureFontLoaded();
1912
+ injectStyles(shadow);
1913
+
1914
+ var wrapper = el("div", "atproto-embed-inner");
1915
+ shadow.appendChild(wrapper);
1916
+
1917
+ try {
1918
+ var resolved = await resolveUri(raw);
1919
+
1920
+ if (config.mode === "discussion") {
1921
+ wrapper.classList.add("atproto-embed--discussion");
1922
+ renderDiscussionEmbed(wrapper, resolved, config, container, controller.signal);
1923
+ } else {
1924
+ wrapper.classList.add("atproto-embed--post");
1925
+ renderPostEmbed(wrapper, resolved, config, controller.signal);
1926
+ }
1927
+ } catch (err) {
1928
+ if (err && err.name === "AbortError") return;
1929
+ wrapper.innerHTML = "";
1930
+ wrapper.appendChild(
1931
+ el("div", "atproto-embed--error", {
1932
+ textContent: "Failed to load embed",
1933
+ })
1934
+ );
1935
+ console.error("[atproto-embed]", err);
1936
+ }
1937
+ }
1938
+
1939
+ function init(force) {
1940
+ var containers = document.querySelectorAll(
1941
+ ".atproto-embed:not([data-embed-child])"
1942
+ );
1943
+ containers.forEach(function (c) {
1944
+ if (!force && c.getAttribute("data-loaded")) return;
1945
+ var lazy = c.getAttribute("data-lazy");
1946
+ if (lazy === "true" && typeof IntersectionObserver !== "undefined") {
1947
+ if (c.getAttribute("data-loaded") === "pending") return;
1948
+ c.setAttribute("data-loaded", "pending");
1949
+ var observer = new IntersectionObserver(function (entries) {
1950
+ entries.forEach(function (entry) {
1951
+ if (!entry.isIntersecting) return;
1952
+ observer.disconnect();
1953
+ c.setAttribute("data-loaded", "true");
1954
+ initContainer(c);
1955
+ });
1956
+ }, { rootMargin: "200px 0px" });
1957
+ observer.observe(c);
1958
+ } else {
1959
+ c.setAttribute("data-loaded", "true");
1960
+ initContainer(c);
1961
+ }
1962
+ });
1963
+ }
1964
+
1965
+ if (document.readyState === "loading") {
1966
+ document.addEventListener("DOMContentLoaded", init);
1967
+ } else {
1968
+ init();
1969
+ }
1970
+
1971
+ if (typeof window !== "undefined") {
1972
+ window.AtProtoEmbed = window.AtProtoEmbed || {};
1973
+ window.AtProtoEmbed.init = function (force) {
1974
+ init(!!force);
1975
+ };
1976
+ window.AtProtoEmbed.refresh = function () {
1977
+ init(true);
1978
+ };
1979
+ }
1980
+ })();