cablefyi-embed 1.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.
@@ -0,0 +1,1977 @@
1
+ /* cablefyi-embed v1.0.0 | MIT | https://widget.cablefyi.com */
2
+
3
+ // src/styles/modern.ts
4
+ function getModernCSS() {
5
+ return `
6
+ /* Modern: gradient accent header */
7
+ .networkfyi-header {
8
+ background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 70%, #000));
9
+ border-radius: 12px 12px 0 0;
10
+ padding: 16px 20px;
11
+ display: flex;
12
+ align-items: flex-start;
13
+ gap: 14px;
14
+ }
15
+
16
+ .networkfyi-header-title {
17
+ font-size: 15px;
18
+ font-weight: 700;
19
+ color: #fff;
20
+ margin: 0 0 4px 0;
21
+ line-height: 1.3;
22
+ }
23
+
24
+ .networkfyi-header-subtitle {
25
+ font-size: 12px;
26
+ color: rgba(255, 255, 255, 0.8);
27
+ margin: 0;
28
+ }
29
+
30
+ /* Icon area */
31
+ .networkfyi-img {
32
+ width: 56px;
33
+ height: 56px;
34
+ border-radius: 8px;
35
+ object-fit: cover;
36
+ background: rgba(255, 255, 255, 0.15);
37
+ flex-shrink: 0;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ overflow: hidden;
42
+ font-size: 24px;
43
+ color: #fff;
44
+ font-weight: 700;
45
+ }
46
+
47
+ .networkfyi-img img {
48
+ width: 100%;
49
+ height: 100%;
50
+ object-fit: cover;
51
+ border-radius: 8px;
52
+ }
53
+
54
+ /* Body area */
55
+ .networkfyi-body {
56
+ padding: 16px 20px;
57
+ }
58
+
59
+ /* Key-value rows \u2014 spacious */
60
+ .networkfyi-row {
61
+ display: flex;
62
+ justify-content: space-between;
63
+ align-items: flex-start;
64
+ gap: 12px;
65
+ padding: 8px 0;
66
+ border-bottom: 1px solid var(--border);
67
+ }
68
+
69
+ .networkfyi-row:last-child {
70
+ border-bottom: none;
71
+ }
72
+
73
+ .networkfyi-label {
74
+ font-size: 12px;
75
+ font-weight: 500;
76
+ color: var(--muted);
77
+ white-space: nowrap;
78
+ flex-shrink: 0;
79
+ min-width: 30%;
80
+ }
81
+
82
+ .networkfyi-value {
83
+ font-size: 13px;
84
+ color: var(--text);
85
+ text-align: right;
86
+ word-break: break-word;
87
+ }
88
+
89
+ /* Section title */
90
+ .networkfyi-section-title {
91
+ font-size: 11px;
92
+ font-weight: 600;
93
+ color: var(--muted);
94
+ text-transform: uppercase;
95
+ letter-spacing: 0.06em;
96
+ margin: 0 0 10px 0;
97
+ }
98
+
99
+ /* Tags \u2014 colored rounded badges */
100
+ .networkfyi-tag {
101
+ display: inline-block;
102
+ font-size: 11px;
103
+ font-weight: 600;
104
+ padding: 3px 10px;
105
+ border-radius: 12px;
106
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
107
+ color: var(--accent);
108
+ margin: 2px 3px 2px 0;
109
+ letter-spacing: 0.02em;
110
+ }
111
+
112
+ /* Link */
113
+ .networkfyi-link {
114
+ font-size: 13px;
115
+ font-weight: 500;
116
+ color: var(--link);
117
+ text-decoration: none;
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: 4px;
121
+ }
122
+
123
+ .networkfyi-link:hover {
124
+ opacity: 0.8;
125
+ text-decoration: underline;
126
+ }
127
+
128
+ .networkfyi-link svg {
129
+ width: 12px;
130
+ height: 12px;
131
+ flex-shrink: 0;
132
+ }
133
+
134
+ /* Footer link row */
135
+ .networkfyi-footer-link {
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: space-between;
139
+ padding: 12px 20px;
140
+ border-top: 1px solid var(--border);
141
+ gap: 8px;
142
+ }
143
+
144
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
145
+ Card shared: stats row (horizontal flex)
146
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
147
+ .networkfyi-stats-row { display:flex; gap:12px; padding:10px 18px; border-bottom:1px solid var(--border); }
148
+ .networkfyi-stat { text-align:center; flex:1; }
149
+ .networkfyi-stat-value { font-size:18px; font-weight:700; color:var(--accent); }
150
+ .networkfyi-stat-label { font-size:9px; color:var(--muted); text-transform:uppercase; letter-spacing:0.03em; }
151
+
152
+ /* Card shared: stats grid (2x2 boxes) */
153
+ .networkfyi-stats-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; padding:10px 18px; border-bottom:1px solid var(--border); }
154
+ .networkfyi-stat-box { padding:6px 8px; background:color-mix(in srgb, var(--accent) 8%, var(--bg)); border-radius:8px; }
155
+ .networkfyi-stat-box-label { font-size:9px; color:color-mix(in srgb, var(--accent) 80%, var(--text)); text-transform:uppercase; }
156
+ .networkfyi-stat-box-value { font-size:13px; font-weight:700; color:var(--text); margin-top:1px; }
157
+
158
+ /* Card shared: key-value dotted rows */
159
+ .networkfyi-kv-rows { padding:10px 18px; border-bottom:1px solid var(--border); }
160
+ .networkfyi-kv-row { display:flex; justify-content:space-between; align-items:baseline; padding:4px 0; border-bottom:1px dotted var(--border); }
161
+ .networkfyi-kv-row:last-child { border-bottom:none; }
162
+ .networkfyi-kv-label { font-size:11px; color:var(--muted); }
163
+ .networkfyi-kv-value { font-size:11px; font-weight:600; color:var(--text); }
164
+
165
+ /* Card shared: pill tags */
166
+ .networkfyi-pills { display:flex; flex-wrap:wrap; gap:4px; padding:10px 18px; border-bottom:1px solid var(--border); }
167
+ .networkfyi-pill { padding:2px 8px; border-radius:10px; font-size:11px; font-weight:500; background:color-mix(in srgb, var(--accent) 10%, var(--bg)); color:var(--accent); }
168
+
169
+ /* Card shared: section label */
170
+ .networkfyi-section-label { font-size:10px; text-transform:uppercase; letter-spacing:0.05em; color:var(--accent); font-weight:600; margin-bottom:3px; }
171
+
172
+ /* Card shared: description */
173
+ .networkfyi-desc { padding:10px 18px; font-size:14px; color:var(--muted); line-height:1.5; border-bottom:1px solid var(--border); }
174
+
175
+ /* Card shared: view link */
176
+ .networkfyi-view-link { display:block; text-align:center; padding:10px 18px; border-bottom:1px solid var(--border); }
177
+ .networkfyi-view-link a { color:var(--link); text-decoration:none; font-size:12px; font-weight:500; display:inline-flex; align-items:center; gap:4px; }
178
+ .networkfyi-view-link a:hover { text-decoration:underline; }
179
+ .networkfyi-view-link svg { width:12px; height:12px; }
180
+
181
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
182
+ Domain: code/status display (large number)
183
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
184
+ .networkfyi-code-display { padding:14px 18px; text-align:center; border-bottom:1px solid var(--border); }
185
+ .networkfyi-code-big { font-size:36px; font-weight:800; color:var(--accent); line-height:1; font-family:ui-monospace, 'SF Mono', monospace; }
186
+ .networkfyi-code-name { font-size:13px; color:var(--text); font-weight:600; margin-top:4px; }
187
+ .networkfyi-code-protocol { font-size:11px; color:var(--muted); margin-top:2px; }
188
+
189
+ /* Domain: speed bar visualization */
190
+ .networkfyi-speed-bar-wrap { padding:10px 18px; border-bottom:1px solid var(--border); }
191
+ .networkfyi-speed-bar-track { width:100%; height:8px; background:var(--surface); border-radius:4px; overflow:hidden; }
192
+ .networkfyi-speed-bar-fill { height:100%; border-radius:4px; transition:width 0.3s; }
193
+ .networkfyi-speed-bar-label { font-size:11px; color:var(--muted); margin-top:4px; display:flex; justify-content:space-between; }
194
+
195
+ /* Domain: connector arrow display */
196
+ .networkfyi-connector-arrow { display:flex; align-items:center; justify-content:center; gap:8px; padding:12px 18px; border-bottom:1px solid var(--border); font-size:13px; font-weight:600; color:var(--text); }
197
+ .networkfyi-connector-arrow-icon { color:var(--accent); font-size:16px; }
198
+
199
+ /* Domain: compatibility result */
200
+ .networkfyi-compat-result { padding:14px 18px; text-align:center; border-bottom:1px solid var(--border); }
201
+ .networkfyi-compat-status { font-size:14px; font-weight:700; margin-bottom:4px; }
202
+ .networkfyi-compat-detail { font-size:12px; color:var(--muted); }
203
+
204
+ /* Domain: scenario card */
205
+ .networkfyi-scenario-header { padding:12px 18px; border-bottom:1px solid var(--border); }
206
+ .networkfyi-scenario-title { font-size:14px; font-weight:700; color:var(--text); margin:0 0 6px 0; }
207
+ .networkfyi-scenario-meta { display:flex; gap:6px; align-items:center; flex-wrap:wrap; }
208
+
209
+ /* Domain: form inputs (compatibility tool) */
210
+ .networkfyi-tool-form { padding:12px 18px; border-bottom:1px solid var(--border); }
211
+ .networkfyi-tool-row { display:flex; gap:6px; align-items:center; margin-bottom:6px; }
212
+ .networkfyi-tool-row:last-child { margin-bottom:0; }
213
+ .networkfyi-tool-input { flex:1; padding:6px 10px; border:1px solid var(--input-border); border-radius:6px; background:var(--input-bg); color:var(--text); font-size:13px; font-family:inherit; outline:none; }
214
+ .networkfyi-tool-input:focus { border-color:var(--input-focus); box-shadow:0 0 0 2px color-mix(in srgb, var(--input-focus) 20%, transparent); }
215
+ .networkfyi-tool-label { font-size:11px; color:var(--muted); min-width:72px; }
216
+ .networkfyi-tool-btn { background:var(--accent); color:#fff; border:none; border-radius:6px; padding:7px 14px; font-size:13px; font-weight:500; cursor:pointer; font-family:inherit; transition:opacity 0.15s; }
217
+ .networkfyi-tool-btn:hover { opacity:0.9; }
218
+
219
+ /* Inline widget host */
220
+ :host([data-inline]) {
221
+ display: inline-flex;
222
+ align-items: center;
223
+ gap: 4px;
224
+ }
225
+ `;
226
+ }
227
+
228
+ // src/styles/clean.ts
229
+ function getCleanCSS() {
230
+ return `
231
+ /* Clean: subtle top accent border instead of gradient header */
232
+ .networkfyi-header {
233
+ border-top: 3px solid var(--accent);
234
+ border-radius: 8px 8px 0 0;
235
+ padding: 14px 18px;
236
+ display: flex;
237
+ align-items: flex-start;
238
+ gap: 12px;
239
+ background: color-mix(in srgb, var(--accent) 5%, var(--surface));
240
+ }
241
+
242
+ .networkfyi-header-title {
243
+ font-size: 15px;
244
+ font-weight: 700;
245
+ color: var(--text);
246
+ margin: 0 0 4px 0;
247
+ line-height: 1.3;
248
+ }
249
+
250
+ .networkfyi-header-subtitle {
251
+ font-size: 12px;
252
+ color: var(--muted);
253
+ margin: 0;
254
+ }
255
+
256
+ /* Icon area \u2014 smaller, muted */
257
+ .networkfyi-img {
258
+ width: 44px;
259
+ height: 44px;
260
+ border-radius: 8px;
261
+ background: color-mix(in srgb, var(--accent) 12%, var(--bg));
262
+ flex-shrink: 0;
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ overflow: hidden;
267
+ font-size: 20px;
268
+ color: var(--accent);
269
+ font-weight: 700;
270
+ }
271
+
272
+ .networkfyi-img img {
273
+ width: 100%;
274
+ height: 100%;
275
+ object-fit: cover;
276
+ border-radius: 8px;
277
+ }
278
+
279
+ /* Body area */
280
+ .networkfyi-body {
281
+ padding: 14px 18px;
282
+ }
283
+
284
+ /* Key-value rows */
285
+ .networkfyi-row {
286
+ display: flex;
287
+ justify-content: space-between;
288
+ align-items: flex-start;
289
+ gap: 12px;
290
+ padding: 6px 0;
291
+ border-bottom: 1px solid var(--border);
292
+ }
293
+
294
+ .networkfyi-row:last-child {
295
+ border-bottom: none;
296
+ }
297
+
298
+ .networkfyi-label {
299
+ font-size: 12px;
300
+ font-weight: 500;
301
+ color: var(--muted);
302
+ white-space: nowrap;
303
+ flex-shrink: 0;
304
+ min-width: 30%;
305
+ }
306
+
307
+ .networkfyi-value {
308
+ font-size: 13px;
309
+ color: var(--text);
310
+ text-align: right;
311
+ word-break: break-word;
312
+ }
313
+
314
+ /* Section title */
315
+ .networkfyi-section-title {
316
+ font-size: 11px;
317
+ font-weight: 600;
318
+ color: var(--muted);
319
+ text-transform: uppercase;
320
+ letter-spacing: 0.06em;
321
+ margin: 0 0 8px 0;
322
+ }
323
+
324
+ /* Tags \u2014 subtle pill badges */
325
+ .networkfyi-tag {
326
+ display: inline-block;
327
+ font-size: 11px;
328
+ font-weight: 500;
329
+ padding: 2px 8px;
330
+ border-radius: 4px;
331
+ background: var(--badge-bg);
332
+ color: var(--badge-text);
333
+ margin: 2px 3px 2px 0;
334
+ }
335
+
336
+ /* Link */
337
+ .networkfyi-link {
338
+ font-size: 13px;
339
+ font-weight: 500;
340
+ color: var(--link);
341
+ text-decoration: none;
342
+ display: inline-flex;
343
+ align-items: center;
344
+ gap: 4px;
345
+ }
346
+
347
+ .networkfyi-link:hover {
348
+ text-decoration: underline;
349
+ }
350
+
351
+ .networkfyi-link svg {
352
+ width: 12px;
353
+ height: 12px;
354
+ flex-shrink: 0;
355
+ }
356
+
357
+ /* Footer link row */
358
+ .networkfyi-footer-link {
359
+ display: flex;
360
+ align-items: center;
361
+ justify-content: space-between;
362
+ padding: 10px 18px;
363
+ border-top: 1px solid var(--border);
364
+ gap: 8px;
365
+ }
366
+
367
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
368
+ Card shared: stats row (horizontal flex)
369
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
370
+ .networkfyi-stats-row { display:flex; gap:10px; padding:8px 16px; border-bottom:1px solid var(--border); }
371
+ .networkfyi-stat { text-align:center; flex:1; }
372
+ .networkfyi-stat-value { font-size:16px; font-weight:700; color:var(--accent); }
373
+ .networkfyi-stat-label { font-size:9px; color:var(--muted); text-transform:uppercase; letter-spacing:0.03em; }
374
+
375
+ /* Card shared: stats grid (2x2 boxes) */
376
+ .networkfyi-stats-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; padding:8px 16px; border-bottom:1px solid var(--border); }
377
+ .networkfyi-stat-box { padding:5px 7px; background:var(--surface); border-radius:6px; }
378
+ .networkfyi-stat-box-label { font-size:9px; color:var(--muted); text-transform:uppercase; }
379
+ .networkfyi-stat-box-value { font-size:12px; font-weight:700; color:var(--text); margin-top:1px; }
380
+
381
+ /* Card shared: key-value dotted rows */
382
+ .networkfyi-kv-rows { padding:8px 16px; border-bottom:1px solid var(--border); }
383
+ .networkfyi-kv-row { display:flex; justify-content:space-between; align-items:baseline; padding:3px 0; border-bottom:1px dotted var(--border); }
384
+ .networkfyi-kv-row:last-child { border-bottom:none; }
385
+ .networkfyi-kv-label { font-size:11px; color:var(--muted); }
386
+ .networkfyi-kv-value { font-size:11px; font-weight:600; color:var(--text); }
387
+
388
+ /* Card shared: pill tags */
389
+ .networkfyi-pills { display:flex; flex-wrap:wrap; gap:4px; padding:8px 16px; border-bottom:1px solid var(--border); }
390
+ .networkfyi-pill { padding:2px 7px; border-radius:4px; font-size:11px; font-weight:500; background:var(--badge-bg); color:var(--badge-text); }
391
+
392
+ /* Card shared: section label */
393
+ .networkfyi-section-label { font-size:10px; text-transform:uppercase; letter-spacing:0.05em; color:var(--accent); font-weight:600; margin-bottom:3px; }
394
+
395
+ /* Card shared: description */
396
+ .networkfyi-desc { padding:8px 16px; font-size:13px; color:var(--muted); line-height:1.5; border-bottom:1px solid var(--border); }
397
+
398
+ /* Card shared: view link */
399
+ .networkfyi-view-link { display:block; text-align:center; padding:8px 16px; border-bottom:1px solid var(--border); }
400
+ .networkfyi-view-link a { color:var(--link); text-decoration:none; font-size:12px; font-weight:500; display:inline-flex; align-items:center; gap:4px; }
401
+ .networkfyi-view-link a:hover { text-decoration:underline; }
402
+ .networkfyi-view-link svg { width:12px; height:12px; }
403
+
404
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
405
+ Domain: code/status display (large number)
406
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
407
+ .networkfyi-code-display { padding:12px 16px; text-align:center; border-bottom:1px solid var(--border); }
408
+ .networkfyi-code-big { font-size:28px; font-weight:700; color:var(--accent); line-height:1; font-family:ui-monospace, 'SF Mono', monospace; }
409
+ .networkfyi-code-name { font-size:12px; color:var(--text); font-weight:600; margin-top:3px; }
410
+ .networkfyi-code-protocol { font-size:10px; color:var(--muted); margin-top:2px; }
411
+
412
+ /* Domain: speed bar visualization */
413
+ .networkfyi-speed-bar-wrap { padding:8px 16px; border-bottom:1px solid var(--border); }
414
+ .networkfyi-speed-bar-track { width:100%; height:6px; background:var(--surface); border-radius:3px; overflow:hidden; }
415
+ .networkfyi-speed-bar-fill { height:100%; border-radius:3px; transition:width 0.3s; }
416
+ .networkfyi-speed-bar-label { font-size:10px; color:var(--muted); margin-top:3px; display:flex; justify-content:space-between; }
417
+
418
+ /* Domain: connector arrow display */
419
+ .networkfyi-connector-arrow { display:flex; align-items:center; justify-content:center; gap:6px; padding:10px 16px; border-bottom:1px solid var(--border); font-size:12px; font-weight:600; color:var(--text); }
420
+ .networkfyi-connector-arrow-icon { color:var(--accent); font-size:14px; }
421
+
422
+ /* Domain: compatibility result */
423
+ .networkfyi-compat-result { padding:12px 16px; text-align:center; border-bottom:1px solid var(--border); }
424
+ .networkfyi-compat-status { font-size:13px; font-weight:700; margin-bottom:3px; }
425
+ .networkfyi-compat-detail { font-size:11px; color:var(--muted); }
426
+
427
+ /* Domain: scenario card */
428
+ .networkfyi-scenario-header { padding:10px 16px; border-bottom:1px solid var(--border); }
429
+ .networkfyi-scenario-title { font-size:13px; font-weight:700; color:var(--text); margin:0 0 5px 0; }
430
+ .networkfyi-scenario-meta { display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
431
+
432
+ /* Domain: form inputs (compatibility tool) */
433
+ .networkfyi-tool-form { padding:10px 16px; border-bottom:1px solid var(--border); }
434
+ .networkfyi-tool-row { display:flex; gap:6px; align-items:center; margin-bottom:5px; }
435
+ .networkfyi-tool-row:last-child { margin-bottom:0; }
436
+ .networkfyi-tool-input { flex:1; padding:5px 8px; border:1px solid var(--input-border); border-radius:4px; background:var(--input-bg); color:var(--text); font-size:12px; font-family:inherit; outline:none; }
437
+ .networkfyi-tool-input:focus { border-color:var(--input-focus); box-shadow:0 0 0 2px color-mix(in srgb, var(--input-focus) 20%, transparent); }
438
+ .networkfyi-tool-label { font-size:10px; color:var(--muted); min-width:64px; }
439
+ .networkfyi-tool-btn { background:var(--accent); color:#fff; border:none; border-radius:4px; padding:6px 12px; font-size:12px; font-weight:500; cursor:pointer; font-family:inherit; transition:opacity 0.15s; }
440
+ .networkfyi-tool-btn:hover { opacity:0.9; }
441
+
442
+ /* Clean: copy button \u2014 minimal style */
443
+ .networkfyi-copy-btn {
444
+ background: var(--surface);
445
+ color: var(--muted);
446
+ border: 1px solid var(--border);
447
+ border-radius: 4px;
448
+ padding: 3px 8px;
449
+ font-size: 11px;
450
+ cursor: pointer;
451
+ display: inline-flex;
452
+ align-items: center;
453
+ gap: 3px;
454
+ transition: all 0.15s;
455
+ font-family: inherit;
456
+ }
457
+
458
+ .networkfyi-copy-btn:hover {
459
+ background: var(--copy-bg);
460
+ border-color: var(--input-focus);
461
+ color: var(--text);
462
+ }
463
+
464
+ .networkfyi-copy-btn svg {
465
+ width: 10px;
466
+ height: 10px;
467
+ }
468
+
469
+ /* Inline widget host */
470
+ :host([data-inline]) {
471
+ display: inline-flex;
472
+ align-items: center;
473
+ gap: 4px;
474
+ }
475
+ `;
476
+ }
477
+
478
+ // src/themes.ts
479
+ function getStyleCSS(style) {
480
+ switch (style) {
481
+ case "clean":
482
+ return getCleanCSS();
483
+ case "modern":
484
+ default:
485
+ return getModernCSS();
486
+ }
487
+ }
488
+ function getThemeCSS(accent, style = "modern") {
489
+ return `
490
+ :host {
491
+ display: block;
492
+ --site-accent: ${accent};
493
+ }
494
+
495
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
496
+ Size variants
497
+ compact=280px, default=420px, large=720px
498
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
499
+ .networkfyi-widget {
500
+ box-sizing: border-box;
501
+ min-width: 240px;
502
+ max-width: 420px;
503
+ border-radius: 8px;
504
+ overflow: hidden;
505
+ border: 1px solid var(--border);
506
+ background: var(--bg);
507
+ color: var(--text);
508
+ font-size: 14px;
509
+ line-height: 1.6;
510
+ transition: border-color 0.2s;
511
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
512
+ }
513
+
514
+ .networkfyi-widget:hover {
515
+ border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
516
+ }
517
+
518
+ .networkfyi-widget[data-size="compact"] {
519
+ max-width: 280px;
520
+ font-size: 13px;
521
+ }
522
+
523
+ .networkfyi-widget[data-size="default"] {
524
+ max-width: 420px;
525
+ }
526
+
527
+ .networkfyi-widget[data-size="large"] {
528
+ max-width: 720px;
529
+ }
530
+
531
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
532
+ Light theme (default)
533
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
534
+ .networkfyi-widget[data-theme="light"] {
535
+ --bg: #fff;
536
+ --text: #1e293b;
537
+ --border: #e2e8f0;
538
+ --accent: var(--site-accent);
539
+ --muted: #64748b;
540
+ --surface: #f8fafc;
541
+ --badge-bg: #f1f5f9;
542
+ --badge-text: #374151;
543
+ --link: var(--site-accent);
544
+ --copy-bg: #f3f4f6;
545
+ --copy-hover: #e5e7eb;
546
+ --input-bg: #ffffff;
547
+ --input-border: #d1d5db;
548
+ --input-focus: var(--site-accent);
549
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
550
+ }
551
+
552
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
553
+ Dark theme
554
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
555
+ .networkfyi-widget[data-theme="dark"] {
556
+ --bg: #1a1a1a;
557
+ --text: #f3f4f6;
558
+ --border: #374151;
559
+ --accent: var(--site-accent);
560
+ --muted: #9ca3af;
561
+ --surface: #111827;
562
+ --badge-bg: #374151;
563
+ --badge-text: #d1d5db;
564
+ --link: color-mix(in srgb, var(--site-accent) 80%, #fff);
565
+ --copy-bg: #374151;
566
+ --copy-hover: #4b5563;
567
+ --input-bg: #111111;
568
+ --input-border: #4b5563;
569
+ --input-focus: var(--site-accent);
570
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
571
+ }
572
+
573
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
574
+ Sepia theme
575
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
576
+ .networkfyi-widget[data-theme="sepia"] {
577
+ --bg: #f5f0e8;
578
+ --text: #3d3529;
579
+ --border: #d4c5a9;
580
+ --accent: var(--site-accent);
581
+ --muted: #8b7d6b;
582
+ --surface: #ede8df;
583
+ --badge-bg: #e8e0d0;
584
+ --badge-text: #5c4f3d;
585
+ --link: color-mix(in srgb, var(--site-accent) 70%, #3d3529);
586
+ --copy-bg: #e8e0d0;
587
+ --copy-hover: #ddd4c0;
588
+ --input-bg: #f5f0e8;
589
+ --input-border: #c4b49a;
590
+ --input-focus: var(--site-accent);
591
+ --shadow: 0 1px 3px rgba(61, 53, 41, 0.12);
592
+ }
593
+
594
+ .networkfyi-widget *, .networkfyi-widget *::before, .networkfyi-widget *::after {
595
+ box-sizing: border-box;
596
+ }
597
+
598
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
599
+ Loading state
600
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
601
+ .networkfyi-loading {
602
+ padding: 20px 16px;
603
+ text-align: center;
604
+ color: var(--muted);
605
+ font-size: 13px;
606
+ display: flex;
607
+ align-items: center;
608
+ justify-content: center;
609
+ gap: 8px;
610
+ }
611
+
612
+ .networkfyi-spinner {
613
+ width: 16px;
614
+ height: 16px;
615
+ border: 2px solid var(--border);
616
+ border-top-color: var(--accent);
617
+ border-radius: 50%;
618
+ animation: networkfyi-spin 0.7s linear infinite;
619
+ display: inline-block;
620
+ flex-shrink: 0;
621
+ }
622
+
623
+ @keyframes networkfyi-spin {
624
+ to { transform: rotate(360deg); }
625
+ }
626
+
627
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
628
+ Error state
629
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
630
+ .networkfyi-error {
631
+ padding: 16px;
632
+ color: var(--muted);
633
+ font-size: 13px;
634
+ text-align: center;
635
+ }
636
+
637
+ .networkfyi-error p {
638
+ margin: 0 0 8px 0;
639
+ }
640
+
641
+ .networkfyi-error a {
642
+ color: var(--link);
643
+ text-decoration: none;
644
+ }
645
+
646
+ .networkfyi-error a:hover {
647
+ text-decoration: underline;
648
+ }
649
+
650
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
651
+ Badge (generic)
652
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
653
+ .networkfyi-badge {
654
+ display: inline-block;
655
+ font-size: 10px;
656
+ font-weight: 600;
657
+ padding: 2px 7px;
658
+ border-radius: 4px;
659
+ background: var(--badge-bg);
660
+ color: var(--badge-text);
661
+ text-transform: uppercase;
662
+ letter-spacing: 0.04em;
663
+ }
664
+
665
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
666
+ Search inputs
667
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
668
+ .networkfyi-search-wrap {
669
+ padding: 12px 16px;
670
+ }
671
+
672
+ .networkfyi-search-form {
673
+ display: flex;
674
+ gap: 8px;
675
+ }
676
+
677
+ .networkfyi-search-input {
678
+ flex: 1;
679
+ padding: 8px 12px;
680
+ border: 1px solid var(--input-border);
681
+ border-radius: 6px;
682
+ background: var(--input-bg);
683
+ color: var(--text);
684
+ font-size: 13px;
685
+ font-family: inherit;
686
+ outline: none;
687
+ transition: border-color 0.15s;
688
+ }
689
+
690
+ .networkfyi-search-input:focus {
691
+ border-color: var(--input-focus);
692
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--input-focus) 20%, transparent);
693
+ }
694
+
695
+ .networkfyi-search-input::placeholder {
696
+ color: var(--muted);
697
+ }
698
+
699
+ .networkfyi-search-btn {
700
+ background: var(--accent);
701
+ color: #fff;
702
+ border: none;
703
+ border-radius: 6px;
704
+ padding: 8px 14px;
705
+ font-size: 13px;
706
+ font-weight: 500;
707
+ cursor: pointer;
708
+ font-family: inherit;
709
+ transition: opacity 0.15s;
710
+ white-space: nowrap;
711
+ }
712
+
713
+ .networkfyi-search-btn:hover {
714
+ opacity: 0.9;
715
+ }
716
+
717
+ .networkfyi-search-results {
718
+ padding: 0 16px 12px;
719
+ }
720
+
721
+ .networkfyi-result-item {
722
+ padding: 8px 0;
723
+ border-bottom: 1px solid var(--border);
724
+ }
725
+
726
+ .networkfyi-result-item:last-child {
727
+ border-bottom: none;
728
+ }
729
+
730
+ .networkfyi-result-title {
731
+ font-size: 13px;
732
+ font-weight: 600;
733
+ color: var(--text);
734
+ margin: 0 0 3px 0;
735
+ }
736
+
737
+ .networkfyi-result-meta {
738
+ font-size: 11px;
739
+ color: var(--muted);
740
+ display: flex;
741
+ align-items: center;
742
+ gap: 6px;
743
+ flex-wrap: wrap;
744
+ }
745
+
746
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
747
+ Powered by footer
748
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
749
+ .networkfyi-powered {
750
+ display: block;
751
+ text-align: center;
752
+ padding: 8px 16px;
753
+ font-size: 11px;
754
+ color: var(--muted);
755
+ border-top: 1px solid var(--border);
756
+ }
757
+
758
+ .networkfyi-powered a {
759
+ color: var(--link);
760
+ text-decoration: none;
761
+ font-weight: 500;
762
+ }
763
+
764
+ .networkfyi-powered a:hover {
765
+ text-decoration: underline;
766
+ }
767
+
768
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
769
+ Copy button
770
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
771
+ .networkfyi-copy-btn {
772
+ background: var(--copy-bg);
773
+ color: var(--text);
774
+ border: none;
775
+ border-radius: 5px;
776
+ padding: 4px 9px;
777
+ font-size: 11px;
778
+ cursor: pointer;
779
+ display: inline-flex;
780
+ align-items: center;
781
+ gap: 4px;
782
+ transition: background 0.15s;
783
+ font-family: inherit;
784
+ }
785
+
786
+ .networkfyi-copy-btn:hover {
787
+ background: var(--copy-hover);
788
+ }
789
+
790
+ .networkfyi-copy-btn svg {
791
+ width: 11px;
792
+ height: 11px;
793
+ }
794
+
795
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
796
+ Monospace (for values, codes, IPs)
797
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
798
+ .networkfyi-mono {
799
+ font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
800
+ font-size: 13px;
801
+ }
802
+
803
+ ${getStyleCSS(style)}
804
+ `;
805
+ }
806
+
807
+ // src/shadow.ts
808
+ function createShadow(el, config) {
809
+ const widgetStyle = el.dataset.styleVariant || "modern";
810
+ const shadow = el.attachShadow({ mode: "open" });
811
+ const style = document.createElement("style");
812
+ style.textContent = getThemeCSS(config.accent, widgetStyle);
813
+ shadow.appendChild(style);
814
+ return shadow;
815
+ }
816
+ function resolveTheme(el) {
817
+ const raw = el.dataset.theme || "light";
818
+ if (raw === "auto") {
819
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
820
+ }
821
+ return raw;
822
+ }
823
+ function createWidgetRoot(shadow, el, extraClass) {
824
+ const theme = resolveTheme(el);
825
+ const size = el.dataset.size || "default";
826
+ const div = document.createElement("div");
827
+ div.className = ["networkfyi-widget", extraClass].filter(Boolean).join(" ");
828
+ div.setAttribute("data-theme", theme);
829
+ div.setAttribute("data-size", size);
830
+ shadow.appendChild(div);
831
+ if (el.dataset.theme === "auto") {
832
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
833
+ div.setAttribute("data-theme", e.matches ? "dark" : "light");
834
+ });
835
+ }
836
+ return div;
837
+ }
838
+ function renderLoading(container) {
839
+ container.innerHTML = `
840
+ <div class="networkfyi-loading">
841
+ <span class="networkfyi-spinner"></span>
842
+ Loading\u2026
843
+ </div>
844
+ `;
845
+ }
846
+ function renderError(container, message, config) {
847
+ container.innerHTML = `
848
+ <div class="networkfyi-error">
849
+ <p>${message}</p>
850
+ <a href="https://${config.domain}" target="_blank" rel="noopener">
851
+ Visit ${config.name}
852
+ </a>
853
+ </div>
854
+ `;
855
+ }
856
+ var externalLinkIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`;
857
+ function poweredByHTML(config) {
858
+ return `<span class="networkfyi-powered">Powered by <a href="https://${config.domain}" target="_blank" rel="noopener">${config.name}</a></span>`;
859
+ }
860
+
861
+ // src/api.ts
862
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
863
+ function cacheKey(url) {
864
+ return `networkfyi_embed_${url}`;
865
+ }
866
+ function getFromCache(url) {
867
+ try {
868
+ const raw = sessionStorage.getItem(cacheKey(url));
869
+ if (!raw) return null;
870
+ const entry = JSON.parse(raw);
871
+ if (Date.now() - entry.ts > CACHE_TTL_MS) {
872
+ sessionStorage.removeItem(cacheKey(url));
873
+ return null;
874
+ }
875
+ return entry.data;
876
+ } catch (e) {
877
+ return null;
878
+ }
879
+ }
880
+ function setInCache(url, data) {
881
+ try {
882
+ const entry = { data, ts: Date.now() };
883
+ sessionStorage.setItem(cacheKey(url), JSON.stringify(entry));
884
+ } catch (e) {
885
+ }
886
+ }
887
+ async function fetchAPI(baseUrl, path, params) {
888
+ const base = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
889
+ const relativePath = path.startsWith("/") ? path.slice(1) : path;
890
+ const url = new URL(relativePath, base);
891
+ if (params) {
892
+ Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
893
+ }
894
+ const urlStr = url.toString();
895
+ const cached = getFromCache(urlStr);
896
+ if (cached !== null) return cached;
897
+ const response = await fetch(urlStr, {
898
+ headers: { Accept: "application/json" }
899
+ });
900
+ if (!response.ok) {
901
+ throw new Error(`API error ${response.status}: ${urlStr}`);
902
+ }
903
+ const data = await response.json();
904
+ setInCache(urlStr, data);
905
+ return data;
906
+ }
907
+
908
+ // src/cards/shared.ts
909
+ function esc(s) {
910
+ if (!s) return "";
911
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
912
+ }
913
+ function kvRow(label, value) {
914
+ if (value === null || value === void 0 || value === "") return "";
915
+ return `<div class="networkfyi-kv-row"><span class="networkfyi-kv-label">${esc(label)}</span><span class="networkfyi-kv-value">${esc(String(value))}</span></div>`;
916
+ }
917
+ function badge(text, bg, fg = "#fff") {
918
+ return `<span class="networkfyi-badge" style="background:${bg};color:${fg}">${esc(text)}</span>`;
919
+ }
920
+ function renderGlossaryCard(data, config) {
921
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
922
+ const name = String((_c = (_b = (_a = data.name) != null ? _a : data.term) != null ? _b : data.slug) != null ? _c : "");
923
+ const definition = String((_d = data.definition) != null ? _d : "");
924
+ const extended = String((_f = (_e = data.extended_definition) != null ? _e : data.extended_description) != null ? _f : "");
925
+ const categoryLabel2 = String((_h = (_g = data.category_name) != null ? _g : data.category) != null ? _h : "");
926
+ const slug = String((_i = data.slug) != null ? _i : "");
927
+ const glossaryBase = config.site === "cablefyi" ? "terms" : "glossary";
928
+ const termUrl = `https://${config.domain}/${glossaryBase}/${esc(slug)}/`;
929
+ const glossaryUrl = `https://${config.domain}/glossary/`;
930
+ const relatedTerms = data.related_terms;
931
+ const relatedPills = relatedTerms && relatedTerms.length > 0 ? `<div class="networkfyi-pills">${relatedTerms.map(
932
+ (rt) => `<a class="networkfyi-pill" href="https://${config.domain}/${glossaryBase}/${esc(rt.slug)}/" target="_blank" rel="noopener" style="text-decoration:none;color:inherit;">${esc(rt.name)}</a>`
933
+ ).join("")}</div>` : "";
934
+ return `
935
+ <div class="networkfyi-header">
936
+ <div>
937
+ <div class="networkfyi-header-title">${esc(name)}</div>
938
+ ${categoryLabel2 ? `<span class="networkfyi-badge" style="background:${config.accent};color:#fff;margin-top:4px;display:inline-block;">${esc(categoryLabel2)}</span>` : ""}
939
+ </div>
940
+ </div>
941
+ <div class="networkfyi-body" style="font-size:0.9rem;line-height:1.5;">
942
+ ${esc(definition)}
943
+ </div>
944
+ ${extended ? `<div style="padding:0 18px 10px;font-size:0.85rem;color:var(--muted);line-height:1.5;">${esc(extended)}</div>` : ""}
945
+ ${relatedPills}
946
+ <div class="networkfyi-view-link"><a href="${termUrl}" target="_blank" rel="noopener">${esc(name)} ${externalLinkIcon}</a></div>
947
+ <div class="networkfyi-view-link"><a href="${glossaryUrl}" target="_blank" rel="noopener">Full glossary on ${esc(config.name)} ${externalLinkIcon}</a></div>
948
+ ${poweredByHTML(config)}
949
+ `;
950
+ }
951
+ function renderFAQCard(faqs, config) {
952
+ if (!faqs || !faqs.length) return `<div class="networkfyi-body">No FAQs available.</div>${poweredByHTML(config)}`;
953
+ const items = faqs.map(
954
+ (faq) => `
955
+ <details class="networkfyi-faq-item" style="border-bottom:1px solid var(--border);padding:10px 18px;">
956
+ <summary style="cursor:pointer;font-size:0.9rem;font-weight:600;color:var(--text);list-style:none;display:flex;justify-content:space-between;align-items:center;">
957
+ ${esc(faq.question)}
958
+ <span style="flex-shrink:0;margin-left:8px;font-size:0.75rem;color:var(--muted);">+</span>
959
+ </summary>
960
+ <div style="margin-top:8px;font-size:0.85rem;color:var(--muted);line-height:1.5;">
961
+ ${esc(faq.answer)}
962
+ </div>
963
+ </details>
964
+ `
965
+ ).join("");
966
+ return `
967
+ <div class="networkfyi-header">
968
+ <div>
969
+ <div class="networkfyi-header-title">Frequently Asked Questions</div>
970
+ <div class="networkfyi-header-subtitle">${faqs.length} questions</div>
971
+ </div>
972
+ </div>
973
+ ${items}
974
+ ${poweredByHTML(config)}
975
+ `;
976
+ }
977
+
978
+ // src/cards/cable.ts
979
+ function renderCableCard(data, config) {
980
+ var _a, _b, _c, _d, _e, _f;
981
+ const name = String((_a = data.name) != null ? _a : "");
982
+ const slug = String((_b = data.slug) != null ? _b : "");
983
+ const description = String((_c = data.description) != null ? _c : "");
984
+ const connA = String((_d = data.connector_a) != null ? _d : "");
985
+ const connB = String((_e = data.connector_b) != null ? _e : "");
986
+ const maxRate = data.max_data_rate_gbps;
987
+ const maxRes = String((_f = data.max_video_resolution) != null ? _f : "");
988
+ const maxPower = data.max_power_delivery_watts;
989
+ const maxLen = data.max_recommended_length_m;
990
+ const standards = data.supported_standards;
991
+ const cableUrl = `https://${config.domain}/cables/${esc(slug)}/`;
992
+ const connectorDisplay = connA && connB ? `<div class="networkfyi-connector-arrow">
993
+ <span>${esc(connA)}</span>
994
+ <span class="networkfyi-connector-arrow-icon">\u2194</span>
995
+ <span>${esc(connB)}</span>
996
+ </div>` : "";
997
+ const standardsPills = standards && standards.length > 0 ? `<div class="networkfyi-pills">${standards.map((s) => `<span class="networkfyi-pill">${esc(s)}</span>`).join("")}</div>` : "";
998
+ return `
999
+ <div class="networkfyi-header">
1000
+ <div class="networkfyi-img">\u{1F50C}</div>
1001
+ <div>
1002
+ <div class="networkfyi-header-title">${esc(name)}</div>
1003
+ <div class="networkfyi-header-subtitle">Cable</div>
1004
+ </div>
1005
+ </div>
1006
+ ${connectorDisplay}
1007
+ <div class="networkfyi-kv-rows">
1008
+ ${maxRate !== null && maxRate !== void 0 ? kvRow("Max Data Rate", `${maxRate} Gbps`) : ""}
1009
+ ${maxPower !== null && maxPower !== void 0 ? kvRow("Max Power", `${maxPower}W`) : ""}
1010
+ ${maxRes ? kvRow("Max Resolution", maxRes) : ""}
1011
+ ${maxLen !== null && maxLen !== void 0 ? kvRow("Max Length", `${maxLen}m`) : ""}
1012
+ </div>
1013
+ ${description ? `<div class="networkfyi-desc">${esc(description.length > 200 ? description.slice(0, 200) + "..." : description)}</div>` : ""}
1014
+ ${standardsPills}
1015
+ <div class="networkfyi-view-link"><a href="${cableUrl}" target="_blank" rel="noopener">View on ${esc(config.name)} ${externalLinkIcon}</a></div>
1016
+ ${poweredByHTML(config)}
1017
+ `;
1018
+ }
1019
+ function renderConnectorCard(data, config) {
1020
+ var _a, _b, _c, _d, _e;
1021
+ const name = String((_a = data.name) != null ? _a : "");
1022
+ const slug = String((_b = data.slug) != null ? _b : "");
1023
+ const description = String((_c = data.description) != null ? _c : "");
1024
+ const family = String((_d = data.family) != null ? _d : "");
1025
+ const formFactor = String((_e = data.form_factor) != null ? _e : "");
1026
+ const pinCount = data.pin_count;
1027
+ const reversible = data.reversible;
1028
+ const firstYear = data.first_released_year;
1029
+ const widthMm = data.width_mm;
1030
+ const heightMm = data.height_mm;
1031
+ const connUrl = `https://${config.domain}/connectors/${esc(slug)}/`;
1032
+ return `
1033
+ <div class="networkfyi-header">
1034
+ <div class="networkfyi-img">\u{1F50C}</div>
1035
+ <div>
1036
+ <div class="networkfyi-header-title">${esc(name)}</div>
1037
+ <div class="networkfyi-header-subtitle">${esc(family || "Connector")}</div>
1038
+ </div>
1039
+ </div>
1040
+ <div class="networkfyi-kv-rows">
1041
+ ${family ? kvRow("Family", family) : ""}
1042
+ ${formFactor ? kvRow("Form Factor", formFactor) : ""}
1043
+ ${pinCount !== null && pinCount !== void 0 ? kvRow("Pin Count", String(pinCount)) : ""}
1044
+ ${reversible !== null ? kvRow("Reversible", reversible ? "Yes" : "No") : ""}
1045
+ ${firstYear !== null && firstYear !== void 0 ? kvRow("First Released", String(firstYear)) : ""}
1046
+ ${widthMm !== null && widthMm !== void 0 && heightMm !== null && heightMm !== void 0 ? kvRow("Size", `${widthMm} x ${heightMm} mm`) : ""}
1047
+ </div>
1048
+ ${description ? `<div class="networkfyi-desc">${esc(description.length > 200 ? description.slice(0, 200) + "..." : description)}</div>` : ""}
1049
+ <div class="networkfyi-view-link"><a href="${connUrl}" target="_blank" rel="noopener">View on ${esc(config.name)} ${externalLinkIcon}</a></div>
1050
+ ${poweredByHTML(config)}
1051
+ `;
1052
+ }
1053
+ function renderStandardCard(data, config) {
1054
+ var _a, _b, _c, _d, _e, _f;
1055
+ const name = String((_a = data.name) != null ? _a : "");
1056
+ const slug = String((_b = data.slug) != null ? _b : "");
1057
+ const description = String((_c = data.description) != null ? _c : "");
1058
+ const family = String((_d = data.family) != null ? _d : "");
1059
+ const version = String((_e = data.version) != null ? _e : "");
1060
+ const maxRate = data.max_data_rate_gbps;
1061
+ const maxRes = String((_f = data.max_video_resolution) != null ? _f : "");
1062
+ const maxPower = data.max_power_delivery_watts;
1063
+ const releaseYear = data.release_year;
1064
+ const supportsAudio = data.supports_audio;
1065
+ const supportsVideo = data.supports_video;
1066
+ const supportsData = data.supports_data;
1067
+ const supportsPower = data.supports_power;
1068
+ const supportsEthernet = data.supports_ethernet;
1069
+ const stdUrl = `https://${config.domain}/standards/${esc(slug)}/`;
1070
+ const supportBadges = [];
1071
+ if (supportsAudio) supportBadges.push("Audio");
1072
+ if (supportsVideo) supportBadges.push("Video");
1073
+ if (supportsData) supportBadges.push("Data");
1074
+ if (supportsPower) supportBadges.push("Power");
1075
+ if (supportsEthernet) supportBadges.push("Ethernet");
1076
+ return `
1077
+ <div class="networkfyi-header">
1078
+ <div class="networkfyi-img">\u26A1</div>
1079
+ <div>
1080
+ <div class="networkfyi-header-title">${esc(name)}${version ? ` ${esc(version)}` : ""}</div>
1081
+ <div class="networkfyi-header-subtitle">${esc(family || "Standard")}</div>
1082
+ </div>
1083
+ </div>
1084
+ ${supportBadges.length > 0 ? `<div class="networkfyi-pills">${supportBadges.map((s) => badge(s, config.accent)).join(" ")}</div>` : ""}
1085
+ <div class="networkfyi-kv-rows">
1086
+ ${maxRate !== null && maxRate !== void 0 ? kvRow("Max Data Rate", `${maxRate} Gbps`) : ""}
1087
+ ${maxPower !== null && maxPower !== void 0 ? kvRow("Max Power", `${maxPower}W`) : ""}
1088
+ ${maxRes ? kvRow("Max Resolution", maxRes) : ""}
1089
+ ${releaseYear !== null && releaseYear !== void 0 ? kvRow("Release Year", String(releaseYear)) : ""}
1090
+ </div>
1091
+ ${description ? `<div class="networkfyi-desc">${esc(description.length > 200 ? description.slice(0, 200) + "..." : description)}</div>` : ""}
1092
+ <div class="networkfyi-view-link"><a href="${stdUrl}" target="_blank" rel="noopener">View on ${esc(config.name)} ${externalLinkIcon}</a></div>
1093
+ ${poweredByHTML(config)}
1094
+ `;
1095
+ }
1096
+
1097
+ // src/compute/status.ts
1098
+ function statusCategory(code) {
1099
+ if (code >= 100 && code < 200) return "informational";
1100
+ if (code >= 200 && code < 300) return "success";
1101
+ if (code >= 300 && code < 400) return "redirection";
1102
+ if (code >= 400 && code < 500) return "client-error";
1103
+ if (code >= 500 && code < 600) return "server-error";
1104
+ return "unknown";
1105
+ }
1106
+ function categoryColor(category) {
1107
+ switch (category) {
1108
+ case "informational":
1109
+ return "#3B82F6";
1110
+ // blue
1111
+ case "success":
1112
+ return "#10B981";
1113
+ // green
1114
+ case "redirection":
1115
+ return "#F59E0B";
1116
+ // amber
1117
+ case "client-error":
1118
+ return "#EF4444";
1119
+ // red
1120
+ case "server-error":
1121
+ return "#7C3AED";
1122
+ // violet
1123
+ default:
1124
+ return "#6B7280";
1125
+ }
1126
+ }
1127
+ function categoryLabel(category) {
1128
+ switch (category) {
1129
+ case "informational":
1130
+ return "Info";
1131
+ case "success":
1132
+ return "Success";
1133
+ case "redirection":
1134
+ return "Redirect";
1135
+ case "client-error":
1136
+ return "Client Error";
1137
+ case "server-error":
1138
+ return "Server Error";
1139
+ default:
1140
+ return "Unknown";
1141
+ }
1142
+ }
1143
+ var ASN_CATEGORY_COLORS = {
1144
+ cloud: "#3B82F6",
1145
+ isp: "#10B981",
1146
+ cdn: "#F59E0B",
1147
+ enterprise: "#6366F1",
1148
+ hosting: "#EC4899",
1149
+ exchange: "#14B8A6",
1150
+ education: "#8B5CF6",
1151
+ government: "#EF4444"
1152
+ };
1153
+ function asnCategoryColor(category) {
1154
+ return ASN_CATEGORY_COLORS[category.toLowerCase()] || "#6B7280";
1155
+ }
1156
+
1157
+ // src/cards/asn.ts
1158
+ function renderASNCard(data, config) {
1159
+ var _a, _b, _c, _d, _e, _f;
1160
+ const number = data.number;
1161
+ const name = String((_a = data.name) != null ? _a : "");
1162
+ const org = String((_b = data.org) != null ? _b : "");
1163
+ const countryCode = String((_c = data.country_code) != null ? _c : "");
1164
+ const category = String((_d = data.category) != null ? _d : "");
1165
+ const website = String((_e = data.website) != null ? _e : "");
1166
+ const description = String((_f = data.description) != null ? _f : "");
1167
+ const prefixCount = data.prefix_count;
1168
+ const asnUrl = `https://${config.domain}/asn/AS${number}/`;
1169
+ const catColor = asnCategoryColor(category);
1170
+ return `
1171
+ <div class="networkfyi-header">
1172
+ <div class="networkfyi-img" style="font-size:16px;">AS</div>
1173
+ <div>
1174
+ <div class="networkfyi-header-title">AS${esc(String(number))}</div>
1175
+ <div class="networkfyi-header-subtitle">${esc(name || org)}</div>
1176
+ </div>
1177
+ </div>
1178
+ <div class="networkfyi-stats-row">
1179
+ <div class="networkfyi-stat">
1180
+ <div class="networkfyi-stat-value">${esc(String(number))}</div>
1181
+ <div class="networkfyi-stat-label">ASN</div>
1182
+ </div>
1183
+ ${prefixCount !== null && prefixCount !== void 0 ? `<div class="networkfyi-stat">
1184
+ <div class="networkfyi-stat-value">${prefixCount.toLocaleString()}</div>
1185
+ <div class="networkfyi-stat-label">Prefixes</div>
1186
+ </div>` : ""}
1187
+ ${countryCode ? `<div class="networkfyi-stat">
1188
+ <div class="networkfyi-stat-value">${esc(countryCode)}</div>
1189
+ <div class="networkfyi-stat-label">Country</div>
1190
+ </div>` : ""}
1191
+ </div>
1192
+ <div class="networkfyi-kv-rows">
1193
+ ${org ? kvRow("Organization", org) : ""}
1194
+ ${category ? `<div class="networkfyi-kv-row"><span class="networkfyi-kv-label">Category</span><span class="networkfyi-kv-value">${badge(category, catColor)}</span></div>` : ""}
1195
+ ${website ? kvRow("Website", website) : ""}
1196
+ </div>
1197
+ ${description ? `<div class="networkfyi-desc">${esc(description.length > 200 ? description.slice(0, 200) + "..." : description)}</div>` : ""}
1198
+ <div class="networkfyi-view-link"><a href="${asnUrl}" target="_blank" rel="noopener">View on ${esc(config.name)} ${externalLinkIcon}</a></div>
1199
+ ${poweredByHTML(config)}
1200
+ `;
1201
+ }
1202
+
1203
+ // src/cards/code.ts
1204
+ function renderStatusCodeCard(data, config) {
1205
+ var _a, _b, _c, _d, _e, _f;
1206
+ const code = data.code;
1207
+ const name = String((_a = data.name) != null ? _a : "");
1208
+ const slug = String((_b = data.slug) != null ? _b : "");
1209
+ const description = String((_c = data.description) != null ? _c : "");
1210
+ const whenYouSeeIt = String((_d = data.when_you_see_it) != null ? _d : "");
1211
+ const protocol = data.protocol;
1212
+ const category = data.category;
1213
+ const relatedCodes = data.related_codes;
1214
+ const protocolName = protocol ? String((_e = protocol.name) != null ? _e : "") : "";
1215
+ const protocolSlug = protocol ? String((_f = protocol.slug) != null ? _f : "") : "";
1216
+ const cat = statusCategory(code);
1217
+ const color = categoryColor(cat);
1218
+ const catLabel = categoryLabel(cat);
1219
+ const codeUrl = protocolSlug ? `https://${config.domain}/${esc(protocolSlug)}/${esc(slug)}/` : `https://${config.domain}/codes/${esc(slug)}/`;
1220
+ return `
1221
+ <div class="networkfyi-header">
1222
+ <div class="networkfyi-img" style="background:${color};font-size:18px;">${code}</div>
1223
+ <div>
1224
+ <div class="networkfyi-header-title">${esc(name)}</div>
1225
+ <div class="networkfyi-header-subtitle">${esc(protocolName)} ${badge(catLabel, color)}</div>
1226
+ </div>
1227
+ </div>
1228
+ <div class="networkfyi-code-display">
1229
+ <div class="networkfyi-code-big" style="color:${color}">${code}</div>
1230
+ <div class="networkfyi-code-name">${esc(name)}</div>
1231
+ ${protocolName ? `<div class="networkfyi-code-protocol">${esc(protocolName)}</div>` : ""}
1232
+ </div>
1233
+ ${description ? `<div class="networkfyi-desc">${esc(description.length > 200 ? description.slice(0, 200) + "..." : description)}</div>` : ""}
1234
+ ${whenYouSeeIt ? `<div style="padding:10px 18px;border-bottom:1px solid var(--border);">
1235
+ <div class="networkfyi-section-label">When You See It</div>
1236
+ <div style="font-size:0.85rem;color:var(--muted);line-height:1.5;">${esc(whenYouSeeIt.length > 150 ? whenYouSeeIt.slice(0, 150) + "..." : whenYouSeeIt)}</div>
1237
+ </div>` : ""}
1238
+ ${relatedCodes && relatedCodes.length > 0 ? `<div class="networkfyi-pills">${relatedCodes.slice(0, 5).map((rc) => {
1239
+ var _a2;
1240
+ const rcCode = rc.code;
1241
+ const rcName = String((_a2 = rc.name) != null ? _a2 : "");
1242
+ const rcColor = categoryColor(statusCategory(rcCode));
1243
+ return badge(`${rcCode} ${rcName}`, rcColor);
1244
+ }).join(" ")}</div>` : ""}
1245
+ <div class="networkfyi-view-link"><a href="${codeUrl}" target="_blank" rel="noopener">View on ${esc(config.name)} ${externalLinkIcon}</a></div>
1246
+ ${poweredByHTML(config)}
1247
+ `;
1248
+ }
1249
+
1250
+ // src/widgets/entity.ts
1251
+ function getEntityPath(config, slug) {
1252
+ switch (config.site) {
1253
+ case "cablefyi":
1254
+ return `cable/${slug}/`;
1255
+ case "ipfyi":
1256
+ return `asn/${slug}/`;
1257
+ case "statuscodefyi":
1258
+ return `code/${slug}/`;
1259
+ default:
1260
+ return `${config.entitySlug}/${slug}/`;
1261
+ }
1262
+ }
1263
+ function initEntityWidget(el, config) {
1264
+ var _a;
1265
+ const dataset = el.dataset;
1266
+ const slug = (_a = dataset.slug) != null ? _a : "";
1267
+ if (!slug) {
1268
+ const shadow2 = createShadow(el, config);
1269
+ const container2 = createWidgetRoot(shadow2, el, "networkfyi-entity-widget");
1270
+ renderError(container2, "Missing data-slug attribute.", config);
1271
+ return;
1272
+ }
1273
+ const shadow = createShadow(el, config);
1274
+ const container = createWidgetRoot(shadow, el, "networkfyi-entity-widget");
1275
+ renderLoading(container);
1276
+ const path = getEntityPath(config, slug);
1277
+ fetchAPI(config.apiBase, path).then((data) => {
1278
+ var _a2;
1279
+ let html;
1280
+ switch (config.site) {
1281
+ case "cablefyi":
1282
+ html = renderCableCard(data, config);
1283
+ break;
1284
+ case "ipfyi":
1285
+ html = renderASNCard(data, config);
1286
+ break;
1287
+ case "statuscodefyi":
1288
+ html = renderStatusCodeCard(data, config);
1289
+ break;
1290
+ default: {
1291
+ const name = String((_a2 = data.name) != null ? _a2 : slug);
1292
+ const entityUrl = `https://${config.domain}/${config.entitySlug}/${esc(slug)}/`;
1293
+ html = `
1294
+ <div style="padding:16px;">
1295
+ <div style="font-size:1rem;font-weight:600;margin-bottom:8px;">${esc(name)}</div>
1296
+ <a href="${esc(entityUrl)}" target="_blank" rel="noopener"
1297
+ style="color:${config.accent};text-decoration:none;font-size:0.85rem;">
1298
+ View on ${esc(config.name)} ${externalLinkIcon}
1299
+ </a>
1300
+ </div>
1301
+ `;
1302
+ break;
1303
+ }
1304
+ }
1305
+ container.innerHTML = html;
1306
+ }).catch(() => {
1307
+ renderError(container, `Unable to load "${esc(slug)}". Please try again later.`, config);
1308
+ });
1309
+ }
1310
+
1311
+ // src/widgets/compare.ts
1312
+ function initCompareWidget(el, config) {
1313
+ var _a, _b;
1314
+ const dataset = el.dataset;
1315
+ const slugA = (_a = dataset.slugA) != null ? _a : "";
1316
+ const slugB = (_b = dataset.slugB) != null ? _b : "";
1317
+ if (!slugA || !slugB) {
1318
+ const shadow2 = createShadow(el, config);
1319
+ const container2 = createWidgetRoot(shadow2, el, "networkfyi-compare-widget");
1320
+ renderError(container2, "Missing data-slug-a and data-slug-b attributes.", config);
1321
+ return;
1322
+ }
1323
+ const shadow = createShadow(el, config);
1324
+ const container = createWidgetRoot(shadow, el, "networkfyi-compare-widget");
1325
+ renderLoading(container);
1326
+ fetchAPI(config.apiBase, "compare/", { slug1: slugA, slug2: slugB }).then((data) => {
1327
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1328
+ const nameA = String((_b2 = (_a2 = data.name_a) != null ? _a2 : data.slug_a) != null ? _b2 : slugA);
1329
+ const nameB = String((_d = (_c = data.name_b) != null ? _c : data.slug_b) != null ? _d : slugB);
1330
+ const differences = data.differences;
1331
+ const summary = String((_e = data.summary) != null ? _e : "");
1332
+ const compareUrl = `https://${config.domain}/compare/?slug1=${encodeURIComponent(slugA)}&slug2=${encodeURIComponent(slugB)}`;
1333
+ let diffRows = "";
1334
+ if (differences && differences.length > 0) {
1335
+ diffRows = `<div class="networkfyi-kv-rows">`;
1336
+ for (const diff of differences.slice(0, 8)) {
1337
+ const label = String((_g = (_f = diff.field) != null ? _f : diff.label) != null ? _g : "");
1338
+ const valA = String((_i = (_h = diff.value_a) != null ? _h : diff.a) != null ? _i : "");
1339
+ const valB = String((_k = (_j = diff.value_b) != null ? _j : diff.b) != null ? _k : "");
1340
+ diffRows += `
1341
+ <div class="networkfyi-kv-row">
1342
+ <span class="networkfyi-kv-label">${esc(label)}</span>
1343
+ <span class="networkfyi-kv-value">${esc(valA)} vs ${esc(valB)}</span>
1344
+ </div>`;
1345
+ }
1346
+ diffRows += `</div>`;
1347
+ }
1348
+ container.innerHTML = `
1349
+ <div class="networkfyi-header">
1350
+ <div>
1351
+ <div class="networkfyi-header-title">${esc(nameA)} vs ${esc(nameB)}</div>
1352
+ <div class="networkfyi-header-subtitle">Comparison</div>
1353
+ </div>
1354
+ </div>
1355
+ <div class="networkfyi-stats-row">
1356
+ <div class="networkfyi-stat">
1357
+ <div class="networkfyi-stat-value" style="font-size:14px;">${esc(nameA)}</div>
1358
+ <div class="networkfyi-stat-label">A</div>
1359
+ </div>
1360
+ <div class="networkfyi-stat">
1361
+ <div class="networkfyi-stat-value" style="font-size:14px;">${esc(nameB)}</div>
1362
+ <div class="networkfyi-stat-label">B</div>
1363
+ </div>
1364
+ </div>
1365
+ ${diffRows}
1366
+ ${summary ? `<div class="networkfyi-desc">${esc(summary)}</div>` : ""}
1367
+ <div class="networkfyi-view-link"><a href="${esc(compareUrl)}" target="_blank" rel="noopener">Compare on ${esc(config.name)} ${externalLinkIcon}</a></div>
1368
+ ${poweredByHTML(config)}
1369
+ `;
1370
+ }).catch(() => {
1371
+ renderError(container, "Unable to load comparison data.", config);
1372
+ });
1373
+ }
1374
+
1375
+ // src/rich-snippets.ts
1376
+ function injectDefinedTerm(data, domain, siteName) {
1377
+ if (document.querySelector('script[data-networkfyi-snippet="term"]')) return;
1378
+ const jsonLd = {
1379
+ "@context": "https://schema.org",
1380
+ "@type": "DefinedTerm",
1381
+ name: data.name,
1382
+ description: data.definition,
1383
+ inDefinedTermSet: {
1384
+ "@type": "DefinedTermSet",
1385
+ name: `${siteName} Glossary`,
1386
+ url: `https://${domain}/glossary/`
1387
+ }
1388
+ };
1389
+ const script = document.createElement("script");
1390
+ script.type = "application/ld+json";
1391
+ script.setAttribute("data-networkfyi-snippet", "term");
1392
+ script.textContent = JSON.stringify(jsonLd);
1393
+ document.head.appendChild(script);
1394
+ }
1395
+ function injectFAQPage(faqs, domain, _siteName) {
1396
+ if (document.querySelector('script[data-networkfyi-snippet="faq"]')) return;
1397
+ const jsonLd = {
1398
+ "@context": "https://schema.org",
1399
+ "@type": "FAQPage",
1400
+ mainEntity: faqs.map((faq) => ({
1401
+ "@type": "Question",
1402
+ name: faq.question,
1403
+ acceptedAnswer: {
1404
+ "@type": "Answer",
1405
+ text: faq.answer
1406
+ }
1407
+ })),
1408
+ url: `https://${domain}/`
1409
+ };
1410
+ const script = document.createElement("script");
1411
+ script.type = "application/ld+json";
1412
+ script.setAttribute("data-networkfyi-snippet", "faq");
1413
+ script.textContent = JSON.stringify(jsonLd);
1414
+ document.head.appendChild(script);
1415
+ }
1416
+
1417
+ // src/widgets/glossary.ts
1418
+ function initGlossaryWidget(el, config) {
1419
+ var _a;
1420
+ const dataset = el.dataset;
1421
+ const slug = (_a = dataset.slug) != null ? _a : "";
1422
+ if (!slug) {
1423
+ const shadow2 = createShadow(el, config);
1424
+ const container2 = createWidgetRoot(shadow2, el, "networkfyi-glossary-widget");
1425
+ renderError(container2, "Missing data-slug attribute.", config);
1426
+ return;
1427
+ }
1428
+ const shadow = createShadow(el, config);
1429
+ const container = createWidgetRoot(shadow, el, "networkfyi-glossary-widget");
1430
+ renderLoading(container);
1431
+ const glossaryPath = `term/${slug}/`;
1432
+ fetchAPI(config.apiBase, glossaryPath).then((data) => {
1433
+ var _a2, _b, _c;
1434
+ container.innerHTML = renderGlossaryCard(data, config);
1435
+ if (el.dataset.noSnippet !== "true") {
1436
+ const name = String((_b = (_a2 = data.name) != null ? _a2 : data.term) != null ? _b : slug);
1437
+ const definition = String((_c = data.definition) != null ? _c : "");
1438
+ injectDefinedTerm({ name, definition }, config.domain, config.name);
1439
+ }
1440
+ }).catch(() => {
1441
+ renderError(
1442
+ container,
1443
+ `Unable to load glossary term "${esc(slug)}".`,
1444
+ config
1445
+ );
1446
+ });
1447
+ }
1448
+
1449
+ // src/widgets/search.ts
1450
+ var TYPE_LABELS = {
1451
+ cable: "Cable",
1452
+ connector: "Connector",
1453
+ standard: "Standard",
1454
+ asn: "ASN",
1455
+ isp: "ISP",
1456
+ "ip-range": "IP Range",
1457
+ code: "Status Code",
1458
+ protocol: "Protocol",
1459
+ scenario: "Scenario",
1460
+ spec: "Spec",
1461
+ glossary: "Glossary",
1462
+ term: "Glossary",
1463
+ guide: "Guide",
1464
+ faq: "FAQ"
1465
+ };
1466
+ var SEARCH_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`;
1467
+ function initSearchWidget(el, config) {
1468
+ var _a;
1469
+ const dataset = el.dataset;
1470
+ const placeholder = (_a = dataset.placeholder) != null ? _a : `Search ${config.entityName}...`;
1471
+ const shadow = createShadow(el, config);
1472
+ const container = createWidgetRoot(shadow, el, "networkfyi-search-widget");
1473
+ let isOpen = false;
1474
+ let query = "";
1475
+ let results = [];
1476
+ let selectedIndex = -1;
1477
+ let debounceTimer = null;
1478
+ container.innerHTML = `
1479
+ <div class="networkfyi-search-wrap">
1480
+ <div class="networkfyi-search-form" style="position:relative;display:flex;align-items:center;">
1481
+ <span style="position:absolute;left:10px;color:var(--muted);pointer-events:none;">${SEARCH_ICON}</span>
1482
+ <input
1483
+ class="networkfyi-search-input"
1484
+ type="search"
1485
+ autocomplete="off"
1486
+ spellcheck="false"
1487
+ placeholder="${esc(placeholder)}"
1488
+ aria-label="Search ${esc(config.name)}"
1489
+ aria-autocomplete="list"
1490
+ aria-expanded="false"
1491
+ role="combobox"
1492
+ style="padding-left:32px;"
1493
+ >
1494
+ </div>
1495
+ <div class="networkfyi-search-dropdown" role="listbox" hidden
1496
+ style="margin-top:4px;border:1px solid var(--border);border-radius:6px;background:var(--bg);box-shadow:var(--shadow);max-height:280px;overflow-y:auto;"></div>
1497
+ </div>
1498
+ ${poweredByHTML(config)}
1499
+ `;
1500
+ const input = container.querySelector(".networkfyi-search-input");
1501
+ const dropdown = container.querySelector(".networkfyi-search-dropdown");
1502
+ function getAllItems() {
1503
+ return Array.from(dropdown.querySelectorAll(".networkfyi-result-item"));
1504
+ }
1505
+ function setSelected(idx) {
1506
+ const items = getAllItems();
1507
+ items.forEach((item, i) => {
1508
+ if (i === idx) {
1509
+ item.style.background = `color-mix(in srgb, ${config.accent} 10%, var(--bg))`;
1510
+ } else {
1511
+ item.style.background = "";
1512
+ }
1513
+ });
1514
+ selectedIndex = idx;
1515
+ }
1516
+ function openDropdown() {
1517
+ isOpen = true;
1518
+ dropdown.hidden = false;
1519
+ input.setAttribute("aria-expanded", "true");
1520
+ }
1521
+ function closeDropdown() {
1522
+ isOpen = false;
1523
+ dropdown.hidden = true;
1524
+ input.setAttribute("aria-expanded", "false");
1525
+ selectedIndex = -1;
1526
+ }
1527
+ function renderDropdown() {
1528
+ var _a2, _b, _c, _d;
1529
+ if (results.length === 0) {
1530
+ dropdown.innerHTML = `<div style="padding:12px 14px;font-size:0.85rem;color:var(--muted);">No results for <strong>${esc(query)}</strong></div>`;
1531
+ return;
1532
+ }
1533
+ let html = "";
1534
+ for (const item of results) {
1535
+ const typeLabel = item.type ? (_a2 = TYPE_LABELS[item.type]) != null ? _a2 : item.type : null;
1536
+ const itemDesc = (_d = (_c = (_b = item.description) != null ? _b : item.excerpt) != null ? _c : item.subtitle) != null ? _d : "";
1537
+ const href = item.url ? item.url.startsWith("http") ? item.url : `https://${config.domain}${item.url}` : `https://${config.domain}/${config.entitySlug}/${esc(item.slug || "")}/`;
1538
+ html += `
1539
+ <a class="networkfyi-result-item"
1540
+ href="${esc(href)}" target="_blank" rel="noopener" role="option" tabindex="-1"
1541
+ style="display:block;padding:8px 14px;text-decoration:none;color:var(--text);border-bottom:1px solid var(--border);transition:background 0.1s;">
1542
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
1543
+ <span style="font-size:0.875rem;font-weight:500;">${esc(item.name)}</span>
1544
+ ${typeLabel ? `<span class="networkfyi-badge" style="flex-shrink:0;">${esc(typeLabel)}</span>` : ""}
1545
+ </div>
1546
+ ${itemDesc ? `<div style="font-size:0.75rem;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${esc(itemDesc)}</div>` : ""}
1547
+ </a>
1548
+ `;
1549
+ }
1550
+ dropdown.innerHTML = html;
1551
+ }
1552
+ async function doSearch(q) {
1553
+ var _a2;
1554
+ if (!q.trim()) {
1555
+ closeDropdown();
1556
+ return;
1557
+ }
1558
+ const searchUrl = `${config.apiBase}search/?q=${encodeURIComponent(q)}&limit=10`;
1559
+ try {
1560
+ const response = await fetch(searchUrl, { headers: { Accept: "application/json" } });
1561
+ if (!response.ok) throw new Error(`Search failed: ${response.status}`);
1562
+ const data = await response.json();
1563
+ results = (_a2 = data.results) != null ? _a2 : [];
1564
+ } catch (e) {
1565
+ results = [];
1566
+ }
1567
+ renderDropdown();
1568
+ openDropdown();
1569
+ setSelected(-1);
1570
+ }
1571
+ input.addEventListener("input", () => {
1572
+ query = input.value;
1573
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
1574
+ if (!query.trim()) {
1575
+ closeDropdown();
1576
+ return;
1577
+ }
1578
+ debounceTimer = setTimeout(() => {
1579
+ void doSearch(query);
1580
+ }, 300);
1581
+ });
1582
+ input.addEventListener("keydown", (e) => {
1583
+ if (!isOpen) return;
1584
+ const items = getAllItems();
1585
+ const total = items.length;
1586
+ if (e.key === "ArrowDown") {
1587
+ e.preventDefault();
1588
+ setSelected(selectedIndex < total - 1 ? selectedIndex + 1 : 0);
1589
+ } else if (e.key === "ArrowUp") {
1590
+ e.preventDefault();
1591
+ setSelected(selectedIndex > 0 ? selectedIndex - 1 : total - 1);
1592
+ } else if (e.key === "Enter") {
1593
+ e.preventDefault();
1594
+ if (selectedIndex >= 0 && items[selectedIndex]) {
1595
+ items[selectedIndex].click();
1596
+ } else {
1597
+ window.open(`https://${config.domain}${config.searchPath}?q=${encodeURIComponent(query)}`, "_blank", "noopener");
1598
+ }
1599
+ } else if (e.key === "Escape") {
1600
+ closeDropdown();
1601
+ input.blur();
1602
+ }
1603
+ });
1604
+ document.addEventListener("click", (e) => {
1605
+ if (isOpen && !el.contains(e.target)) closeDropdown();
1606
+ });
1607
+ input.addEventListener("focus", () => {
1608
+ input.style.borderColor = config.accent;
1609
+ input.style.boxShadow = `0 0 0 2px color-mix(in srgb, ${config.accent} 20%, transparent)`;
1610
+ });
1611
+ input.addEventListener("blur", () => {
1612
+ input.style.borderColor = "";
1613
+ input.style.boxShadow = "";
1614
+ });
1615
+ }
1616
+
1617
+ // src/widgets/faq.ts
1618
+ function initFAQWidget(el, config) {
1619
+ var _a;
1620
+ const dataset = el.dataset;
1621
+ const slug = (_a = dataset.slug) != null ? _a : "";
1622
+ if (!slug) {
1623
+ const shadow2 = createShadow(el, config);
1624
+ const container2 = createWidgetRoot(shadow2, el, "networkfyi-faq-widget");
1625
+ renderError(container2, "Missing data-slug attribute.", config);
1626
+ return;
1627
+ }
1628
+ const shadow = createShadow(el, config);
1629
+ const container = createWidgetRoot(shadow, el, "networkfyi-faq-widget");
1630
+ renderLoading(container);
1631
+ const faqPath = `faqs/?topic=${encodeURIComponent(slug)}`;
1632
+ fetchAPI(config.apiBase, faqPath).then((data) => {
1633
+ let faqs;
1634
+ if (data.faqs && Array.isArray(data.faqs)) {
1635
+ faqs = data.faqs;
1636
+ } else if (data.results && Array.isArray(data.results)) {
1637
+ faqs = data.results;
1638
+ } else if (data.question && data.answer) {
1639
+ faqs = [{ question: String(data.question), answer: String(data.answer) }];
1640
+ } else {
1641
+ faqs = [];
1642
+ }
1643
+ container.innerHTML = renderFAQCard(faqs, config);
1644
+ if (el.dataset.noSnippet !== "true" && faqs.length > 0) {
1645
+ injectFAQPage(faqs, config.domain, config.name);
1646
+ }
1647
+ }).catch(() => {
1648
+ renderError(container, `Unable to load FAQs for "${esc(slug)}".`, config);
1649
+ });
1650
+ }
1651
+
1652
+ // src/tools/connector.ts
1653
+ function initConnectorTool(el, config) {
1654
+ var _a;
1655
+ const slug = (_a = el.dataset.slug) != null ? _a : "";
1656
+ if (!slug) {
1657
+ const shadow2 = createShadow(el, config);
1658
+ const container2 = createWidgetRoot(shadow2, el, "networkfyi-connector-widget");
1659
+ renderError(container2, "Missing data-slug attribute.", config);
1660
+ return;
1661
+ }
1662
+ const shadow = createShadow(el, config);
1663
+ const container = createWidgetRoot(shadow, el, "networkfyi-connector-widget");
1664
+ renderLoading(container);
1665
+ fetchAPI(config.apiBase, `connector/${slug}/`).then((data) => {
1666
+ container.innerHTML = renderConnectorCard(data, config);
1667
+ }).catch(() => {
1668
+ renderError(container, `Unable to load connector "${esc(slug)}".`, config);
1669
+ });
1670
+ }
1671
+
1672
+ // src/tools/standard.ts
1673
+ function initStandardTool(el, config) {
1674
+ var _a;
1675
+ const slug = (_a = el.dataset.slug) != null ? _a : "";
1676
+ if (!slug) {
1677
+ const shadow2 = createShadow(el, config);
1678
+ const container2 = createWidgetRoot(shadow2, el, "networkfyi-standard-widget");
1679
+ renderError(container2, "Missing data-slug attribute.", config);
1680
+ return;
1681
+ }
1682
+ const shadow = createShadow(el, config);
1683
+ const container = createWidgetRoot(shadow, el, "networkfyi-standard-widget");
1684
+ renderLoading(container);
1685
+ fetchAPI(config.apiBase, `standard/${slug}/`).then((data) => {
1686
+ container.innerHTML = renderStandardCard(data, config);
1687
+ }).catch(() => {
1688
+ renderError(container, `Unable to load standard "${esc(slug)}".`, config);
1689
+ });
1690
+ }
1691
+
1692
+ // src/tools/compatibility.ts
1693
+ function renderCompatResult(data, config) {
1694
+ var _a, _b, _c, _d, _e, _f;
1695
+ const compatible = data.compatible;
1696
+ const adapterRequired = (_a = data.adapter_required) != null ? _a : false;
1697
+ const adapterType = (_b = data.adapter_type) != null ? _b : "";
1698
+ const degradation = (_c = data.signal_degradation) != null ? _c : "";
1699
+ const notes = (_d = data.notes) != null ? _d : "";
1700
+ let statusText;
1701
+ let statusColor;
1702
+ if (compatible && !adapterRequired) {
1703
+ statusText = "Directly Compatible";
1704
+ statusColor = "#10B981";
1705
+ } else if (compatible && adapterRequired) {
1706
+ statusText = "Compatible (Adapter Required)";
1707
+ statusColor = "#F59E0B";
1708
+ } else {
1709
+ statusText = "Not Compatible";
1710
+ statusColor = "#EF4444";
1711
+ }
1712
+ return `
1713
+ <div class="networkfyi-header">
1714
+ <div>
1715
+ <div class="networkfyi-header-title">Connector Compatibility</div>
1716
+ <div class="networkfyi-header-subtitle">${esc(String((_e = data.connector_a) != null ? _e : ""))} \u2194 ${esc(String((_f = data.connector_b) != null ? _f : ""))}</div>
1717
+ </div>
1718
+ </div>
1719
+ <div class="networkfyi-compat-result">
1720
+ <div class="networkfyi-compat-status" style="color:${statusColor}">${statusText}</div>
1721
+ ${adapterType ? `<div class="networkfyi-compat-detail">Adapter: ${esc(adapterType)}</div>` : ""}
1722
+ ${degradation ? `<div class="networkfyi-compat-detail">Signal: ${esc(degradation)}</div>` : ""}
1723
+ </div>
1724
+ ${notes ? `<div class="networkfyi-desc">${esc(notes)}</div>` : ""}
1725
+ ${poweredByHTML(config)}
1726
+ `;
1727
+ }
1728
+ function initCompatibilityTool(el, config) {
1729
+ var _a, _b;
1730
+ const dataset = el.dataset;
1731
+ const connA = (_a = dataset.connectorA) != null ? _a : "";
1732
+ const connB = (_b = dataset.connectorB) != null ? _b : "";
1733
+ const shadow = createShadow(el, config);
1734
+ const container = createWidgetRoot(shadow, el, "networkfyi-compatibility-widget");
1735
+ if (connA && connB) {
1736
+ renderLoading(container);
1737
+ fetchAPI(config.apiBase, "compatibility/", {
1738
+ connector_a: connA,
1739
+ connector_b: connB
1740
+ }).then((data) => {
1741
+ data.connector_a = connA;
1742
+ data.connector_b = connB;
1743
+ container.innerHTML = renderCompatResult(data, config);
1744
+ }).catch(() => {
1745
+ renderError(container, "Unable to check compatibility.", config);
1746
+ });
1747
+ return;
1748
+ }
1749
+ container.innerHTML = `
1750
+ <div class="networkfyi-header">
1751
+ <div>
1752
+ <div class="networkfyi-header-title">Connector Compatibility</div>
1753
+ <div class="networkfyi-header-subtitle">Check if two connectors are compatible</div>
1754
+ </div>
1755
+ </div>
1756
+ <div class="networkfyi-tool-form">
1757
+ <div class="networkfyi-tool-row">
1758
+ <span class="networkfyi-tool-label">Connector A</span>
1759
+ <input class="networkfyi-tool-input" data-role="conn-a" placeholder="e.g. usb-type-c" />
1760
+ </div>
1761
+ <div class="networkfyi-tool-row">
1762
+ <span class="networkfyi-tool-label">Connector B</span>
1763
+ <input class="networkfyi-tool-input" data-role="conn-b" placeholder="e.g. hdmi-type-a" />
1764
+ </div>
1765
+ <div class="networkfyi-tool-row" style="justify-content:flex-end;">
1766
+ <button class="networkfyi-tool-btn" data-role="check-btn">Check</button>
1767
+ </div>
1768
+ </div>
1769
+ <div data-role="result"></div>
1770
+ ${poweredByHTML(config)}
1771
+ `;
1772
+ const inputA = container.querySelector('[data-role="conn-a"]');
1773
+ const inputB = container.querySelector('[data-role="conn-b"]');
1774
+ const btn = container.querySelector('[data-role="check-btn"]');
1775
+ const resultDiv = container.querySelector('[data-role="result"]');
1776
+ btn.addEventListener("click", () => {
1777
+ const a = inputA.value.trim();
1778
+ const b = inputB.value.trim();
1779
+ if (!a || !b) return;
1780
+ resultDiv.innerHTML = `<div class="networkfyi-loading"><span class="networkfyi-spinner"></span> Checking\u2026</div>`;
1781
+ fetchAPI(config.apiBase, "compatibility/", {
1782
+ connector_a: a,
1783
+ connector_b: b
1784
+ }).then((data) => {
1785
+ var _a2;
1786
+ data.connector_a = a;
1787
+ data.connector_b = b;
1788
+ const compatible = data.compatible;
1789
+ const adapterRequired = (_a2 = data.adapter_required) != null ? _a2 : false;
1790
+ let statusText;
1791
+ let statusColor;
1792
+ if (compatible && !adapterRequired) {
1793
+ statusText = "Directly Compatible";
1794
+ statusColor = "#10B981";
1795
+ } else if (compatible && adapterRequired) {
1796
+ statusText = "Compatible (Adapter Required)";
1797
+ statusColor = "#F59E0B";
1798
+ } else {
1799
+ statusText = "Not Compatible";
1800
+ statusColor = "#EF4444";
1801
+ }
1802
+ resultDiv.innerHTML = `
1803
+ <div class="networkfyi-compat-result">
1804
+ <div class="networkfyi-compat-status" style="color:${statusColor}">${statusText}</div>
1805
+ ${data.adapter_type ? `<div class="networkfyi-compat-detail">Adapter: ${esc(data.adapter_type)}</div>` : ""}
1806
+ ${data.signal_degradation ? `<div class="networkfyi-compat-detail">Signal: ${esc(data.signal_degradation)}</div>` : ""}
1807
+ </div>
1808
+ `;
1809
+ }).catch(() => {
1810
+ resultDiv.innerHTML = `<div class="networkfyi-error"><p>Unable to check compatibility.</p></div>`;
1811
+ });
1812
+ });
1813
+ }
1814
+
1815
+ // src/inline/speed-bar.ts
1816
+ var MAX_RATE_GBPS = 40;
1817
+ function initSpeedBarInline(el, config) {
1818
+ var _a, _b;
1819
+ const rate = parseFloat((_a = el.dataset.rate) != null ? _a : "0");
1820
+ const label = (_b = el.dataset.label) != null ? _b : `${rate} Gbps`;
1821
+ el.setAttribute("data-inline", "");
1822
+ const shadow = createShadow(el, config);
1823
+ const container = createWidgetRoot(shadow, el, "networkfyi-inline");
1824
+ container.setAttribute("data-theme", el.dataset.theme || "light");
1825
+ const pct = Math.min(100, rate / MAX_RATE_GBPS * 100);
1826
+ const hue = Math.round(pct / 100 * 120);
1827
+ container.innerHTML = `
1828
+ <div class="networkfyi-speed-bar-wrap" style="min-width:120px;padding:4px 0;">
1829
+ <div class="networkfyi-speed-bar-track">
1830
+ <div class="networkfyi-speed-bar-fill" style="width:${pct}%;background:hsl(${hue}, 70%, 50%);"></div>
1831
+ </div>
1832
+ <div class="networkfyi-speed-bar-label">
1833
+ <span>${label}</span>
1834
+ <span>${MAX_RATE_GBPS} Gbps</span>
1835
+ </div>
1836
+ </div>
1837
+ `;
1838
+ }
1839
+
1840
+ // src/_entry_cablefyi.ts
1841
+ function initWidget(el, type, config) {
1842
+ const widgetStyle = el.dataset.styleVariant || "modern";
1843
+ void widgetStyle;
1844
+ switch (type) {
1845
+ case "entity":
1846
+ initEntityWidget(el, config);
1847
+ break;
1848
+ case "compare":
1849
+ initCompareWidget(el, config);
1850
+ break;
1851
+ case "glossary":
1852
+ initGlossaryWidget(el, config);
1853
+ break;
1854
+ case "search":
1855
+ initSearchWidget(el, config);
1856
+ break;
1857
+ case "faq":
1858
+ initFAQWidget(el, config);
1859
+ break;
1860
+ case "connector":
1861
+ initConnectorTool(el, config);
1862
+ break;
1863
+ case "standard":
1864
+ initStandardTool(el, config);
1865
+ break;
1866
+ case "compatibility":
1867
+ initCompatibilityTool(el, config);
1868
+ break;
1869
+ case "speed-bar":
1870
+ initSpeedBarInline(el, config);
1871
+ break;
1872
+ default:
1873
+ break;
1874
+ }
1875
+ }
1876
+ function lazyInit(el, callback) {
1877
+ if ("IntersectionObserver" in window) {
1878
+ const observer = new IntersectionObserver((entries) => {
1879
+ entries.forEach((entry) => {
1880
+ if (entry.isIntersecting) {
1881
+ observer.unobserve(el);
1882
+ callback();
1883
+ }
1884
+ });
1885
+ }, { rootMargin: "200px" });
1886
+ observer.observe(el);
1887
+ } else {
1888
+ callback();
1889
+ }
1890
+ }
1891
+ function processElement(el, config) {
1892
+ if (el.shadowRoot) return;
1893
+ const dataKey = config.attribute.replace("data-", "");
1894
+ const camelKey = dataKey.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1895
+ const widgetType = el.dataset[camelKey];
1896
+ if (!widgetType) return;
1897
+ lazyInit(el, () => {
1898
+ if (!el.shadowRoot) initWidget(el, widgetType, config);
1899
+ });
1900
+ }
1901
+ function initAll(config) {
1902
+ document.querySelectorAll(`[${config.attribute}]`).forEach((el) => processElement(el, config));
1903
+ }
1904
+ (function bootstrap() {
1905
+ const config = '{"site":"cablefyi","name":"CableFYI","domain":"cablefyi.com","accent":"#4338CA","attribute":"data-cablefyi","apiBase":"https://cablefyi.com/api/v1/","searchPath":"/search/","entityName":"Cables","entitySlug":"cables"}';
1906
+ if (document.readyState === "loading") {
1907
+ document.addEventListener("DOMContentLoaded", () => initAll(config));
1908
+ } else {
1909
+ initAll(config);
1910
+ }
1911
+ const observer = new MutationObserver((mutations) => {
1912
+ mutations.forEach((mutation) => {
1913
+ mutation.addedNodes.forEach((node) => {
1914
+ var _a;
1915
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
1916
+ const el = node;
1917
+ if (el.hasAttribute(config.attribute)) processElement(el, config);
1918
+ (_a = el.querySelectorAll) == null ? void 0 : _a.call(el, `[${config.attribute}]`).forEach((child) => processElement(child, config));
1919
+ });
1920
+ });
1921
+ });
1922
+ observer.observe(document.body || document.documentElement, { childList: true, subtree: true });
1923
+ })();
1924
+ function makeWidgetElement(widgetType, initFn, domainAttrs) {
1925
+ const observed = [...domainAttrs, "theme", "style-variant", "size"];
1926
+ return class extends HTMLElement {
1927
+ static get observedAttributes() {
1928
+ return observed;
1929
+ }
1930
+ connectedCallback() {
1931
+ if (this.shadowRoot) return;
1932
+ this._syncDataAttrs();
1933
+ initFn(this, '{"site":"cablefyi","name":"CableFYI","domain":"cablefyi.com","accent":"#4338CA","attribute":"data-cablefyi","apiBase":"https://cablefyi.com/api/v1/","searchPath":"/search/","entityName":"Cables","entitySlug":"cables"}');
1934
+ }
1935
+ attributeChangedCallback(_name, oldVal, newVal) {
1936
+ if (oldVal === newVal || !this.shadowRoot) return;
1937
+ const shadow = this.shadowRoot;
1938
+ while (shadow.firstChild) shadow.firstChild.remove();
1939
+ this._syncDataAttrs();
1940
+ initFn(this, '{"site":"cablefyi","name":"CableFYI","domain":"cablefyi.com","accent":"#4338CA","attribute":"data-cablefyi","apiBase":"https://cablefyi.com/api/v1/","searchPath":"/search/","entityName":"Cables","entitySlug":"cables"}');
1941
+ }
1942
+ _syncDataAttrs() {
1943
+ const attrKey = '{"site":"cablefyi","name":"CableFYI","domain":"cablefyi.com","accent":"#4338CA","attribute":"data-cablefyi","apiBase":"https://cablefyi.com/api/v1/","searchPath":"/search/","entityName":"Cables","entitySlug":"cables"}'.attribute.replace("data-", "");
1944
+ this.dataset[attrKey] = widgetType;
1945
+ for (const a of domainAttrs) {
1946
+ const val = this.getAttribute(a);
1947
+ if (val !== null) this.dataset[a] = val;
1948
+ }
1949
+ const theme = this.getAttribute("theme");
1950
+ if (theme !== null) this.dataset.theme = theme;
1951
+ const styleVariant = this.getAttribute("style-variant");
1952
+ if (styleVariant !== null) this.dataset.styleVariant = styleVariant;
1953
+ const size = this.getAttribute("size");
1954
+ if (size !== null) this.dataset.size = size;
1955
+ }
1956
+ };
1957
+ }
1958
+ (function registerElements() {
1959
+ if (typeof customElements === "undefined") return;
1960
+ const site = '{"site":"cablefyi","name":"CableFYI","domain":"cablefyi.com","accent":"#4338CA","attribute":"data-cablefyi","apiBase":"https://cablefyi.com/api/v1/","searchPath":"/search/","entityName":"Cables","entitySlug":"cables"}'.site;
1961
+ const defs = [
1962
+ [`${site}-entity`, initEntityWidget, ["slug"]],
1963
+ [`${site}-compare`, initCompareWidget, ["slug-a", "slug-b"]],
1964
+ [`${site}-glossary`, initGlossaryWidget, ["slug"]],
1965
+ [`${site}-search`, initSearchWidget, ["placeholder"]],
1966
+ [`${site}-faq`, initFAQWidget, ["slug"]],
1967
+ [`${site}-connector`, initConnectorTool, ["slug"]],
1968
+ [`${site}-standard`, initStandardTool, ["slug"]],
1969
+ [`${site}-compatibility`, initCompatibilityTool, ["connector-a", "connector-b"]]
1970
+ ];
1971
+ for (const [tagName, initFn, attrs] of defs) {
1972
+ if (!customElements.get(tagName)) {
1973
+ const widgetType = tagName.slice(site.length + 1);
1974
+ customElements.define(tagName, makeWidgetElement(widgetType, initFn, attrs));
1975
+ }
1976
+ }
1977
+ })();