fishfyi-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,2303 @@
1
+ /* fishfyi-embed v1.0.0 | MIT | https://widget.fishfyi.com */
2
+
3
+ // src/styles/modern.ts
4
+ function getModernCSS() {
5
+ return `
6
+ /* Modern: gradient accent header */
7
+ .naturefyi-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
+ .naturefyi-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
+ .naturefyi-header-subtitle {
25
+ font-size: 12px;
26
+ color: rgba(255, 255, 255, 0.8);
27
+ margin: 0;
28
+ }
29
+
30
+ /* Image/preview area \u2014 for species photos, bird images, plant illustrations */
31
+ .naturefyi-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
+ }
43
+
44
+ .naturefyi-img img {
45
+ width: 100%;
46
+ height: 100%;
47
+ object-fit: cover;
48
+ border-radius: 8px;
49
+ }
50
+
51
+ /* Body area */
52
+ .naturefyi-body {
53
+ padding: 16px 20px;
54
+ }
55
+
56
+ /* Key-value rows \u2014 spacious */
57
+ .naturefyi-row {
58
+ display: flex;
59
+ justify-content: space-between;
60
+ align-items: flex-start;
61
+ gap: 12px;
62
+ padding: 8px 0;
63
+ border-bottom: 1px solid var(--border);
64
+ }
65
+
66
+ .naturefyi-row:last-child {
67
+ border-bottom: none;
68
+ }
69
+
70
+ .naturefyi-label {
71
+ font-size: 12px;
72
+ font-weight: 500;
73
+ color: var(--muted);
74
+ white-space: nowrap;
75
+ flex-shrink: 0;
76
+ min-width: 30%;
77
+ }
78
+
79
+ .naturefyi-value {
80
+ font-size: 13px;
81
+ color: var(--text);
82
+ text-align: right;
83
+ word-break: break-word;
84
+ }
85
+
86
+ /* Section title */
87
+ .naturefyi-section-title {
88
+ font-size: 11px;
89
+ font-weight: 600;
90
+ color: var(--muted);
91
+ text-transform: uppercase;
92
+ letter-spacing: 0.06em;
93
+ margin: 0 0 10px 0;
94
+ }
95
+
96
+ /* Tags \u2014 colored rounded badges */
97
+ .naturefyi-tag {
98
+ display: inline-block;
99
+ font-size: 11px;
100
+ font-weight: 600;
101
+ padding: 3px 10px;
102
+ border-radius: 12px;
103
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
104
+ color: var(--accent);
105
+ margin: 2px 3px 2px 0;
106
+ letter-spacing: 0.02em;
107
+ }
108
+
109
+ /* Link */
110
+ .naturefyi-link {
111
+ font-size: 13px;
112
+ font-weight: 500;
113
+ color: var(--link);
114
+ text-decoration: none;
115
+ display: inline-flex;
116
+ align-items: center;
117
+ gap: 4px;
118
+ }
119
+
120
+ .naturefyi-link:hover {
121
+ opacity: 0.8;
122
+ text-decoration: underline;
123
+ }
124
+
125
+ .naturefyi-link svg {
126
+ width: 12px;
127
+ height: 12px;
128
+ flex-shrink: 0;
129
+ }
130
+
131
+ /* Footer link row */
132
+ .naturefyi-footer-link {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: space-between;
136
+ padding: 12px 20px;
137
+ border-top: 1px solid var(--border);
138
+ gap: 8px;
139
+ }
140
+
141
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
142
+ Card shared: stats row (horizontal flex)
143
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
144
+ .naturefyi-stats-row { display:flex; gap:12px; padding:10px 18px; border-bottom:1px solid var(--border); }
145
+ .naturefyi-stat { text-align:center; flex:1; }
146
+ .naturefyi-stat-value { font-size:18px; font-weight:700; color:var(--accent); }
147
+ .naturefyi-stat-label { font-size:9px; color:var(--muted); text-transform:uppercase; letter-spacing:0.03em; }
148
+
149
+ /* Card shared: stats grid (2x2 boxes) */
150
+ .naturefyi-stats-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; padding:10px 18px; border-bottom:1px solid var(--border); }
151
+ .naturefyi-stat-box { padding:6px 8px; background:color-mix(in srgb, var(--accent) 8%, var(--bg)); border-radius:8px; }
152
+ .naturefyi-stat-box-label { font-size:9px; color:color-mix(in srgb, var(--accent) 80%, var(--text)); text-transform:uppercase; }
153
+ .naturefyi-stat-box-value { font-size:13px; font-weight:700; color:var(--text); margin-top:1px; }
154
+
155
+ /* Card shared: key-value dotted rows */
156
+ .naturefyi-kv-rows { padding:10px 18px; border-bottom:1px solid var(--border); }
157
+ .naturefyi-kv-row { display:flex; justify-content:space-between; align-items:baseline; padding:4px 0; border-bottom:1px dotted var(--border); }
158
+ .naturefyi-kv-row:last-child { border-bottom:none; }
159
+ .naturefyi-kv-label { font-size:11px; color:var(--muted); }
160
+ .naturefyi-kv-value { font-size:11px; font-weight:600; color:var(--text); }
161
+
162
+ /* Card shared: pill tags */
163
+ .naturefyi-pills { display:flex; flex-wrap:wrap; gap:4px; padding:10px 18px; border-bottom:1px solid var(--border); }
164
+ .naturefyi-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); }
165
+
166
+ /* Card shared: section label */
167
+ .naturefyi-section-label { font-size:10px; text-transform:uppercase; letter-spacing:0.05em; color:var(--accent); font-weight:600; margin-bottom:3px; }
168
+
169
+ /* Card shared: description */
170
+ .naturefyi-desc { padding:10px 18px; font-size:14px; color:var(--muted); line-height:1.5; border-bottom:1px solid var(--border); }
171
+
172
+ /* Card shared: view link */
173
+ .naturefyi-view-link { display:block; text-align:center; padding:10px 18px; border-bottom:1px solid var(--border); }
174
+ .naturefyi-view-link a { color:var(--link); text-decoration:none; font-size:12px; font-weight:500; display:inline-flex; align-items:center; gap:4px; }
175
+ .naturefyi-view-link a:hover { text-decoration:underline; }
176
+ .naturefyi-view-link svg { width:12px; height:12px; }
177
+
178
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
179
+ Card domain: species taxonomy (speciesfyi)
180
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
181
+ .naturefyi-taxonomy-row { width:100%; padding:14px 18px; display:flex; align-items:center; gap:16px; border-bottom:1px solid var(--border); }
182
+ .naturefyi-taxonomy-icon { width:72px; height:72px; border-radius:8px; background:color-mix(in srgb, var(--accent) 15%, var(--bg)); border:2px solid var(--accent); display:flex; flex-direction:column; align-items:center; justify-content:center; flex-shrink:0; overflow:hidden; }
183
+ .naturefyi-taxonomy-icon img { width:100%; height:100%; object-fit:cover; }
184
+ .naturefyi-taxonomy-rank { font-size:11px; color:var(--muted); font-weight:600; }
185
+ .naturefyi-taxonomy-name { font-size:16px; font-weight:700; color:var(--accent); line-height:1; }
186
+ .naturefyi-taxonomy-common { font-size:10px; color:var(--muted); }
187
+ .naturefyi-taxonomy-info { flex:1; }
188
+ .naturefyi-taxonomy-title { font-size:16px; font-weight:700; color:var(--text); margin-bottom:2px; }
189
+ .naturefyi-taxonomy-scientific { font-size:11px; color:var(--muted); font-style:italic; }
190
+
191
+ /* Card domain: conservation status badge */
192
+ .naturefyi-conservation-badge { display:inline-flex; align-items:center; gap:4px; padding:3px 8px; border-radius:4px; font-size:11px; font-weight:600; background:color-mix(in srgb, var(--accent) 10%, var(--bg)); color:var(--accent); border:1px solid color-mix(in srgb, var(--accent) 25%, transparent); }
193
+
194
+ /* Card domain: population/size display */
195
+ .naturefyi-population { padding:10px 18px; border-bottom:1px solid var(--border); display:flex; align-items:baseline; gap:8px; }
196
+ .naturefyi-population-val { font-size:28px; font-weight:800; color:var(--accent); line-height:1; }
197
+ .naturefyi-population-label { font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:0.04em; }
198
+
199
+ /* Card domain: color swatch (plumage, flower color) */
200
+ .naturefyi-color-swatch-row { display:flex; gap:4px; padding:8px 18px; border-bottom:1px solid var(--border); flex-wrap:wrap; }
201
+ .naturefyi-color-swatch { width:20px; height:20px; border-radius:50%; border:2px solid var(--border); flex-shrink:0; }
202
+
203
+ /* Card domain: property bar (size, weight, wingspan comparisons) */
204
+ .naturefyi-prop-bar-row { padding:6px 18px; border-bottom:1px dotted var(--border); display:flex; align-items:center; gap:10px; }
205
+ .naturefyi-prop-bar-row:last-child { border-bottom:none; }
206
+ .naturefyi-prop-bar-label { font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:0.04em; width:80px; flex-shrink:0; }
207
+ .naturefyi-prop-bar-track { flex:1; height:6px; background:var(--surface); border-radius:3px; overflow:hidden; }
208
+ .naturefyi-prop-bar-fill { height:100%; background:var(--accent); border-radius:3px; transition:width 0.4s ease; }
209
+ .naturefyi-prop-bar-val { font-size:10px; font-weight:600; color:var(--text); width:36px; text-align:right; flex-shrink:0; }
210
+
211
+ /* Inline widget host \u2014 for badge-style inline rendering */
212
+ :host([data-inline]) {
213
+ display: inline-flex;
214
+ align-items: center;
215
+ gap: 4px;
216
+ }
217
+ `;
218
+ }
219
+
220
+ // src/styles/organic.ts
221
+ function getOrganicCSS() {
222
+ return `
223
+ /* Organic: lighter gradient header with softer corners */
224
+ .naturefyi-header {
225
+ background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 85%, #fff), var(--accent));
226
+ border-radius: 16px 16px 0 0;
227
+ padding: 16px 20px;
228
+ display: flex;
229
+ align-items: flex-start;
230
+ gap: 14px;
231
+ }
232
+
233
+ .naturefyi-header-title {
234
+ font-size: 15px;
235
+ font-weight: 700;
236
+ color: #fff;
237
+ margin: 0 0 4px 0;
238
+ line-height: 1.3;
239
+ }
240
+
241
+ .naturefyi-header-subtitle {
242
+ font-size: 12px;
243
+ color: rgba(255, 255, 255, 0.85);
244
+ margin: 0;
245
+ }
246
+
247
+ /* Image/preview area \u2014 softer corners for organic feel */
248
+ .naturefyi-img {
249
+ width: 56px;
250
+ height: 56px;
251
+ border-radius: 12px;
252
+ object-fit: cover;
253
+ background: rgba(255, 255, 255, 0.2);
254
+ flex-shrink: 0;
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ overflow: hidden;
259
+ }
260
+
261
+ .naturefyi-img img {
262
+ width: 100%;
263
+ height: 100%;
264
+ object-fit: cover;
265
+ border-radius: 12px;
266
+ }
267
+
268
+ /* Body area \u2014 warmer surface */
269
+ .naturefyi-body {
270
+ padding: 16px 20px;
271
+ background: color-mix(in srgb, var(--accent) 5%, var(--surface));
272
+ }
273
+
274
+ /* Key-value rows \u2014 spacious */
275
+ .naturefyi-row {
276
+ display: flex;
277
+ justify-content: space-between;
278
+ align-items: flex-start;
279
+ gap: 12px;
280
+ padding: 8px 0;
281
+ border-bottom: 1px solid var(--border);
282
+ }
283
+
284
+ .naturefyi-row:last-child {
285
+ border-bottom: none;
286
+ }
287
+
288
+ .naturefyi-label {
289
+ font-size: 12px;
290
+ font-weight: 500;
291
+ color: var(--muted);
292
+ white-space: nowrap;
293
+ flex-shrink: 0;
294
+ min-width: 30%;
295
+ }
296
+
297
+ .naturefyi-value {
298
+ font-size: 14px;
299
+ color: var(--text);
300
+ text-align: right;
301
+ word-break: break-word;
302
+ }
303
+
304
+ /* Section title */
305
+ .naturefyi-section-title {
306
+ font-size: 11px;
307
+ font-weight: 600;
308
+ color: var(--muted);
309
+ text-transform: uppercase;
310
+ letter-spacing: 0.06em;
311
+ margin: 0 0 10px 0;
312
+ }
313
+
314
+ /* Tags \u2014 colored rounded badges with extra rounding for organic feel */
315
+ .naturefyi-tag {
316
+ display: inline-block;
317
+ font-size: 11px;
318
+ font-weight: 600;
319
+ padding: 3px 10px;
320
+ border-radius: 16px;
321
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
322
+ color: var(--accent);
323
+ margin: 2px 3px 2px 0;
324
+ letter-spacing: 0.02em;
325
+ }
326
+
327
+ /* Link */
328
+ .naturefyi-link {
329
+ font-size: 13px;
330
+ font-weight: 500;
331
+ color: var(--link);
332
+ text-decoration: none;
333
+ display: inline-flex;
334
+ align-items: center;
335
+ gap: 4px;
336
+ }
337
+
338
+ .naturefyi-link:hover {
339
+ opacity: 0.8;
340
+ text-decoration: underline;
341
+ }
342
+
343
+ .naturefyi-link svg {
344
+ width: 12px;
345
+ height: 12px;
346
+ flex-shrink: 0;
347
+ }
348
+
349
+ /* Footer link row */
350
+ .naturefyi-footer-link {
351
+ display: flex;
352
+ align-items: center;
353
+ justify-content: space-between;
354
+ padding: 12px 20px;
355
+ border-top: 1px solid var(--border);
356
+ gap: 8px;
357
+ }
358
+
359
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
360
+ Card shared: stats row (horizontal flex)
361
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
362
+ .naturefyi-stats-row { display:flex; gap:12px; padding:10px 18px; border-bottom:1px solid var(--border); background:color-mix(in srgb, var(--accent) 5%, var(--surface)); }
363
+ .naturefyi-stat { text-align:center; flex:1; }
364
+ .naturefyi-stat-value { font-size:18px; font-weight:700; color:var(--accent); }
365
+ .naturefyi-stat-label { font-size:9px; color:var(--muted); text-transform:uppercase; letter-spacing:0.03em; }
366
+
367
+ /* Card shared: stats grid (2x2 boxes) \u2014 warmer tint */
368
+ .naturefyi-stats-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; padding:10px 18px; border-bottom:1px solid var(--border); }
369
+ .naturefyi-stat-box { padding:6px 8px; background:color-mix(in srgb, var(--accent) 8%, var(--bg)); border-radius:12px; }
370
+ .naturefyi-stat-box-label { font-size:9px; color:color-mix(in srgb, var(--accent) 80%, var(--text)); text-transform:uppercase; }
371
+ .naturefyi-stat-box-value { font-size:13px; font-weight:700; color:var(--text); margin-top:1px; }
372
+
373
+ /* Card shared: key-value dotted rows */
374
+ .naturefyi-kv-rows { padding:10px 18px; border-bottom:1px solid var(--border); }
375
+ .naturefyi-kv-row { display:flex; justify-content:space-between; align-items:baseline; padding:4px 0; border-bottom:1px dotted var(--border); }
376
+ .naturefyi-kv-row:last-child { border-bottom:none; }
377
+ .naturefyi-kv-label { font-size:11px; color:var(--muted); }
378
+ .naturefyi-kv-value { font-size:11px; font-weight:600; color:var(--text); }
379
+
380
+ /* Card shared: pill tags \u2014 rounder for organic feel */
381
+ .naturefyi-pills { display:flex; flex-wrap:wrap; gap:4px; padding:10px 18px; border-bottom:1px solid var(--border); }
382
+ .naturefyi-pill { padding:2px 10px; border-radius:14px; font-size:11px; font-weight:500; background:color-mix(in srgb, var(--accent) 10%, var(--bg)); color:var(--accent); }
383
+
384
+ /* Card shared: section label */
385
+ .naturefyi-section-label { font-size:10px; text-transform:uppercase; letter-spacing:0.05em; color:var(--accent); font-weight:600; margin-bottom:3px; }
386
+
387
+ /* Card shared: description */
388
+ .naturefyi-desc { padding:10px 18px; font-size:14px; color:var(--muted); line-height:1.5; border-bottom:1px solid var(--border); }
389
+
390
+ /* Card shared: view link */
391
+ .naturefyi-view-link { display:block; text-align:center; padding:10px 18px; border-bottom:1px solid var(--border); }
392
+ .naturefyi-view-link a { color:var(--link); text-decoration:none; font-size:12px; font-weight:500; display:inline-flex; align-items:center; gap:4px; }
393
+ .naturefyi-view-link a:hover { text-decoration:underline; }
394
+ .naturefyi-view-link svg { width:12px; height:12px; }
395
+
396
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
397
+ Card domain: species taxonomy (speciesfyi)
398
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
399
+ .naturefyi-taxonomy-row { width:100%; padding:14px 18px; display:flex; align-items:center; gap:16px; border-bottom:1px solid var(--border); }
400
+ .naturefyi-taxonomy-icon { width:72px; height:72px; border-radius:16px; background:color-mix(in srgb, var(--accent) 15%, var(--bg)); border:2px solid var(--accent); display:flex; flex-direction:column; align-items:center; justify-content:center; flex-shrink:0; overflow:hidden; }
401
+ .naturefyi-taxonomy-icon img { width:100%; height:100%; object-fit:cover; }
402
+ .naturefyi-taxonomy-rank { font-size:11px; color:var(--muted); font-weight:600; }
403
+ .naturefyi-taxonomy-name { font-size:16px; font-weight:700; color:var(--accent); line-height:1; }
404
+ .naturefyi-taxonomy-common { font-size:10px; color:var(--muted); }
405
+ .naturefyi-taxonomy-info { flex:1; }
406
+ .naturefyi-taxonomy-title { font-size:16px; font-weight:700; color:var(--text); margin-bottom:2px; }
407
+ .naturefyi-taxonomy-scientific { font-size:11px; color:var(--muted); font-style:italic; }
408
+
409
+ /* Card domain: conservation status badge \u2014 rounded for organic */
410
+ .naturefyi-conservation-badge { display:inline-flex; align-items:center; gap:4px; padding:3px 10px; border-radius:12px; font-size:11px; font-weight:600; background:color-mix(in srgb, var(--accent) 10%, var(--bg)); color:var(--accent); border:1px solid color-mix(in srgb, var(--accent) 25%, transparent); }
411
+
412
+ /* Card domain: population/size display */
413
+ .naturefyi-population { padding:10px 18px; border-bottom:1px solid var(--border); display:flex; align-items:baseline; gap:8px; }
414
+ .naturefyi-population-val { font-size:28px; font-weight:800; color:var(--accent); line-height:1; }
415
+ .naturefyi-population-label { font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:0.04em; }
416
+
417
+ /* Card domain: color swatch (plumage, flower color) */
418
+ .naturefyi-color-swatch-row { display:flex; gap:4px; padding:8px 18px; border-bottom:1px solid var(--border); flex-wrap:wrap; }
419
+ .naturefyi-color-swatch { width:20px; height:20px; border-radius:50%; border:2px solid var(--border); flex-shrink:0; }
420
+
421
+ /* Card domain: property bar (size, weight, wingspan comparisons) \u2014 rounded */
422
+ .naturefyi-prop-bar-row { padding:6px 18px; border-bottom:1px dotted var(--border); display:flex; align-items:center; gap:10px; }
423
+ .naturefyi-prop-bar-row:last-child { border-bottom:none; }
424
+ .naturefyi-prop-bar-label { font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:0.04em; width:80px; flex-shrink:0; }
425
+ .naturefyi-prop-bar-track { flex:1; height:7px; background:color-mix(in srgb, var(--accent) 5%, var(--surface)); border-radius:4px; overflow:hidden; }
426
+ .naturefyi-prop-bar-fill { height:100%; background:var(--accent); border-radius:4px; transition:width 0.4s ease; }
427
+ .naturefyi-prop-bar-val { font-size:10px; font-weight:600; color:var(--text); width:36px; text-align:right; flex-shrink:0; }
428
+
429
+ /* Organic: copy button \u2014 softer style */
430
+ .naturefyi-copy-btn {
431
+ background: color-mix(in srgb, var(--accent) 8%, var(--copy-bg));
432
+ color: var(--text);
433
+ border: none;
434
+ border-radius: 8px;
435
+ padding: 4px 10px;
436
+ font-size: 11px;
437
+ cursor: pointer;
438
+ display: inline-flex;
439
+ align-items: center;
440
+ gap: 4px;
441
+ transition: background 0.15s;
442
+ font-family: inherit;
443
+ }
444
+
445
+ .naturefyi-copy-btn:hover {
446
+ background: color-mix(in srgb, var(--accent) 15%, var(--copy-hover));
447
+ }
448
+
449
+ .naturefyi-copy-btn svg {
450
+ width: 11px;
451
+ height: 11px;
452
+ }
453
+
454
+ /* Inline widget host \u2014 for badge-style inline rendering */
455
+ :host([data-inline]) {
456
+ display: inline-flex;
457
+ align-items: center;
458
+ gap: 4px;
459
+ }
460
+ `;
461
+ }
462
+
463
+ // src/themes.ts
464
+ function getStyleCSS(style) {
465
+ switch (style) {
466
+ case "organic":
467
+ return getOrganicCSS();
468
+ case "modern":
469
+ default:
470
+ return getModernCSS();
471
+ }
472
+ }
473
+ function getThemeCSS(accent, style = "modern") {
474
+ return `
475
+ :host {
476
+ display: block;
477
+ --site-accent: ${accent};
478
+ }
479
+
480
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
481
+ Size variants
482
+ compact=280px, default=420px, large=720px
483
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
484
+ .naturefyi-widget {
485
+ box-sizing: border-box;
486
+ min-width: 240px;
487
+ max-width: 420px;
488
+ border-radius: 8px;
489
+ overflow: hidden;
490
+ border: 1px solid var(--border);
491
+ background: var(--bg);
492
+ color: var(--text);
493
+ font-size: 14px;
494
+ line-height: 1.6;
495
+ transition: border-color 0.2s;
496
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
497
+ }
498
+
499
+ .naturefyi-widget:hover {
500
+ border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
501
+ }
502
+
503
+ .naturefyi-widget[data-size="compact"] {
504
+ max-width: 280px;
505
+ font-size: 13px;
506
+ }
507
+
508
+ .naturefyi-widget[data-size="default"] {
509
+ max-width: 420px;
510
+ }
511
+
512
+ .naturefyi-widget[data-size="large"] {
513
+ max-width: 720px;
514
+ }
515
+
516
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
517
+ Light theme (default)
518
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
519
+ .naturefyi-widget[data-theme="light"] {
520
+ --bg: #fff;
521
+ --text: #1e293b;
522
+ --border: #e2e8f0;
523
+ --accent: var(--site-accent);
524
+ --muted: #64748b;
525
+ --surface: #f8fafc;
526
+ --badge-bg: #f1f5f9;
527
+ --badge-text: #374151;
528
+ --link: var(--site-accent);
529
+ --copy-bg: #f3f4f6;
530
+ --copy-hover: #e5e7eb;
531
+ --input-bg: #ffffff;
532
+ --input-border: #d1d5db;
533
+ --input-focus: var(--site-accent);
534
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
535
+ }
536
+
537
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
538
+ Dark theme
539
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
540
+ .naturefyi-widget[data-theme="dark"] {
541
+ --bg: #1a1a1a;
542
+ --text: #f3f4f6;
543
+ --border: #374151;
544
+ --accent: var(--site-accent);
545
+ --muted: #9ca3af;
546
+ --surface: #111827;
547
+ --badge-bg: #374151;
548
+ --badge-text: #d1d5db;
549
+ --link: color-mix(in srgb, var(--site-accent) 80%, #fff);
550
+ --copy-bg: #374151;
551
+ --copy-hover: #4b5563;
552
+ --input-bg: #111111;
553
+ --input-border: #4b5563;
554
+ --input-focus: var(--site-accent);
555
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
556
+ }
557
+
558
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
559
+ Sepia theme
560
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
561
+ .naturefyi-widget[data-theme="sepia"] {
562
+ --bg: #f5f0e8;
563
+ --text: #3d3529;
564
+ --border: #d4c5a9;
565
+ --accent: var(--site-accent);
566
+ --muted: #8b7d6b;
567
+ --surface: #ede8df;
568
+ --badge-bg: #e8e0d0;
569
+ --badge-text: #5c4f3d;
570
+ --link: color-mix(in srgb, var(--site-accent) 70%, #3d3529);
571
+ --copy-bg: #e8e0d0;
572
+ --copy-hover: #ddd4c0;
573
+ --input-bg: #f5f0e8;
574
+ --input-border: #c4b49a;
575
+ --input-focus: var(--site-accent);
576
+ --shadow: 0 1px 3px rgba(61, 53, 41, 0.12);
577
+ }
578
+
579
+ .naturefyi-widget *, .naturefyi-widget *::before, .naturefyi-widget *::after {
580
+ box-sizing: border-box;
581
+ }
582
+
583
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
584
+ Loading state
585
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
586
+ .naturefyi-loading {
587
+ padding: 20px 16px;
588
+ text-align: center;
589
+ color: var(--muted);
590
+ font-size: 13px;
591
+ display: flex;
592
+ align-items: center;
593
+ justify-content: center;
594
+ gap: 8px;
595
+ }
596
+
597
+ .naturefyi-spinner {
598
+ width: 16px;
599
+ height: 16px;
600
+ border: 2px solid var(--border);
601
+ border-top-color: var(--accent);
602
+ border-radius: 50%;
603
+ animation: naturefyi-spin 0.7s linear infinite;
604
+ display: inline-block;
605
+ flex-shrink: 0;
606
+ }
607
+
608
+ @keyframes naturefyi-spin {
609
+ to { transform: rotate(360deg); }
610
+ }
611
+
612
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
613
+ Error state
614
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
615
+ .naturefyi-error {
616
+ padding: 16px;
617
+ color: var(--muted);
618
+ font-size: 13px;
619
+ text-align: center;
620
+ }
621
+
622
+ .naturefyi-error p {
623
+ margin: 0 0 8px 0;
624
+ }
625
+
626
+ .naturefyi-error a {
627
+ color: var(--link);
628
+ text-decoration: none;
629
+ }
630
+
631
+ .naturefyi-error a:hover {
632
+ text-decoration: underline;
633
+ }
634
+
635
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
636
+ Badge (generic)
637
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
638
+ .naturefyi-badge {
639
+ display: inline-block;
640
+ font-size: 10px;
641
+ font-weight: 600;
642
+ padding: 2px 7px;
643
+ border-radius: 4px;
644
+ background: var(--badge-bg);
645
+ color: var(--badge-text);
646
+ text-transform: uppercase;
647
+ letter-spacing: 0.04em;
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
+ Search inputs
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
+ .naturefyi-search-wrap {
654
+ padding: 12px 16px;
655
+ }
656
+
657
+ .naturefyi-search-form {
658
+ display: flex;
659
+ gap: 8px;
660
+ }
661
+
662
+ .naturefyi-search-input {
663
+ flex: 1;
664
+ padding: 8px 12px;
665
+ border: 1px solid var(--input-border);
666
+ border-radius: 6px;
667
+ background: var(--input-bg);
668
+ color: var(--text);
669
+ font-size: 13px;
670
+ font-family: inherit;
671
+ outline: none;
672
+ transition: border-color 0.15s;
673
+ }
674
+
675
+ .naturefyi-search-input:focus {
676
+ border-color: var(--input-focus);
677
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--input-focus) 20%, transparent);
678
+ }
679
+
680
+ .naturefyi-search-input::placeholder {
681
+ color: var(--muted);
682
+ }
683
+
684
+ .naturefyi-search-btn {
685
+ background: var(--accent);
686
+ color: #fff;
687
+ border: none;
688
+ border-radius: 6px;
689
+ padding: 8px 14px;
690
+ font-size: 13px;
691
+ font-weight: 500;
692
+ cursor: pointer;
693
+ font-family: inherit;
694
+ transition: opacity 0.15s;
695
+ white-space: nowrap;
696
+ }
697
+
698
+ .naturefyi-search-btn:hover {
699
+ opacity: 0.9;
700
+ }
701
+
702
+ /* Search results list */
703
+ .naturefyi-search-results {
704
+ padding: 0 16px 12px;
705
+ }
706
+
707
+ .naturefyi-result-item {
708
+ padding: 8px 0;
709
+ border-bottom: 1px solid var(--border);
710
+ }
711
+
712
+ .naturefyi-result-item:last-child {
713
+ border-bottom: none;
714
+ }
715
+
716
+ .naturefyi-result-title {
717
+ font-size: 13px;
718
+ font-weight: 600;
719
+ color: var(--text);
720
+ margin: 0 0 3px 0;
721
+ }
722
+
723
+ .naturefyi-result-meta {
724
+ font-size: 11px;
725
+ color: var(--muted);
726
+ display: flex;
727
+ align-items: center;
728
+ gap: 6px;
729
+ flex-wrap: wrap;
730
+ }
731
+
732
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
733
+ Powered by footer
734
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
735
+ .naturefyi-powered {
736
+ display: block;
737
+ text-align: center;
738
+ padding: 8px 16px;
739
+ font-size: 11px;
740
+ color: var(--muted);
741
+ border-top: 1px solid var(--border);
742
+ }
743
+
744
+ .naturefyi-powered a {
745
+ color: var(--link);
746
+ text-decoration: none;
747
+ font-weight: 500;
748
+ }
749
+
750
+ .naturefyi-powered a:hover {
751
+ text-decoration: underline;
752
+ }
753
+
754
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
755
+ Copy button (base \u2014 overridden in organic)
756
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
757
+ .naturefyi-copy-btn {
758
+ background: var(--copy-bg);
759
+ color: var(--text);
760
+ border: none;
761
+ border-radius: 5px;
762
+ padding: 4px 9px;
763
+ font-size: 11px;
764
+ cursor: pointer;
765
+ display: inline-flex;
766
+ align-items: center;
767
+ gap: 4px;
768
+ transition: background 0.15s;
769
+ font-family: inherit;
770
+ }
771
+
772
+ .naturefyi-copy-btn:hover {
773
+ background: var(--copy-hover);
774
+ }
775
+
776
+ .naturefyi-copy-btn svg {
777
+ width: 11px;
778
+ height: 11px;
779
+ }
780
+
781
+ ${getStyleCSS(style)}
782
+ `;
783
+ }
784
+
785
+ // src/shadow.ts
786
+ function createShadow(el, config) {
787
+ const widgetStyle = el.dataset.style || "modern";
788
+ const shadow = el.attachShadow({ mode: "open" });
789
+ const style = document.createElement("style");
790
+ style.textContent = getThemeCSS(config.accent, widgetStyle);
791
+ shadow.appendChild(style);
792
+ return shadow;
793
+ }
794
+ function resolveTheme(el) {
795
+ const raw = el.dataset.theme || "light";
796
+ if (raw === "auto") {
797
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
798
+ }
799
+ return raw;
800
+ }
801
+ function createWidgetRoot(shadow, el, extraClass) {
802
+ const theme = resolveTheme(el);
803
+ const size = el.dataset.size || "default";
804
+ const div = document.createElement("div");
805
+ div.className = ["naturefyi-widget", extraClass].filter(Boolean).join(" ");
806
+ div.setAttribute("data-theme", theme);
807
+ div.setAttribute("data-size", size);
808
+ shadow.appendChild(div);
809
+ if (el.dataset.theme === "auto") {
810
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
811
+ div.setAttribute("data-theme", e.matches ? "dark" : "light");
812
+ });
813
+ }
814
+ return div;
815
+ }
816
+ function renderLoading(container) {
817
+ container.innerHTML = `
818
+ <div class="naturefyi-loading">
819
+ <span class="naturefyi-spinner"></span>
820
+ Loading\u2026
821
+ </div>
822
+ `;
823
+ }
824
+ function renderError(container, message, config) {
825
+ container.innerHTML = `
826
+ <div class="naturefyi-error">
827
+ <p>${message}</p>
828
+ <a href="https://${config.domain}" target="_blank" rel="noopener">
829
+ Visit ${config.name}
830
+ </a>
831
+ </div>
832
+ `;
833
+ }
834
+ 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>`;
835
+ function poweredByHTML(config) {
836
+ return `<span class="naturefyi-powered">Powered by <a href="https://${config.domain}" target="_blank" rel="noopener">${config.name}</a></span>`;
837
+ }
838
+
839
+ // src/api.ts
840
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
841
+ function cacheKey(url) {
842
+ return `naturefyi_embed_${url}`;
843
+ }
844
+ function getFromCache(url) {
845
+ try {
846
+ const raw = sessionStorage.getItem(cacheKey(url));
847
+ if (!raw) return null;
848
+ const entry = JSON.parse(raw);
849
+ if (Date.now() - entry.ts > CACHE_TTL_MS) {
850
+ sessionStorage.removeItem(cacheKey(url));
851
+ return null;
852
+ }
853
+ return entry.data;
854
+ } catch (e) {
855
+ return null;
856
+ }
857
+ }
858
+ function setInCache(url, data) {
859
+ try {
860
+ const entry = { data, ts: Date.now() };
861
+ sessionStorage.setItem(cacheKey(url), JSON.stringify(entry));
862
+ } catch (e) {
863
+ }
864
+ }
865
+ async function fetchAPI(baseUrl, path, params) {
866
+ const base = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
867
+ const relativePath = path.startsWith("/") ? path.slice(1) : path;
868
+ const url = new URL(relativePath, base);
869
+ if (params) {
870
+ Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
871
+ }
872
+ const urlStr = url.toString();
873
+ const cached = getFromCache(urlStr);
874
+ if (cached !== null) return cached;
875
+ const response = await fetch(urlStr, {
876
+ headers: { Accept: "application/json" }
877
+ });
878
+ if (!response.ok) {
879
+ throw new Error(`API error ${response.status}: ${urlStr}`);
880
+ }
881
+ const data = await response.json();
882
+ setInCache(urlStr, data);
883
+ return data;
884
+ }
885
+
886
+ // src/cards/shared.ts
887
+ function esc(s) {
888
+ if (!s) return "";
889
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
890
+ }
891
+ function statsRow(label, value) {
892
+ if (value === null || value === void 0 || value === "") return "";
893
+ return `<div class="naturefyi-kv-row"><span class="naturefyi-kv-label">${esc(label)}</span><span class="naturefyi-kv-value">${esc(String(value))}</span></div>`;
894
+ }
895
+ function pills(items) {
896
+ if (!items || !items.length) return "";
897
+ return `<div class="naturefyi-pills">${items.map((i) => `<span class="naturefyi-pill">${esc(i)}</span>`).join("")}</div>`;
898
+ }
899
+ function viewLink(url, label, config) {
900
+ return `<a href="${esc(url)}" target="_blank" rel="noopener" class="naturefyi-view-link" style="color:${config.accent}">${esc(label)} \u2197</a>`;
901
+ }
902
+ function badge(text, bg, fg = "#fff") {
903
+ return `<span class="naturefyi-badge" style="background:${bg};color:${fg}">${esc(text)}</span>`;
904
+ }
905
+ function description(text, maxLen = 200) {
906
+ if (!text) return "";
907
+ const truncated = text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
908
+ return `<p class="naturefyi-desc">${esc(truncated)}</p>`;
909
+ }
910
+ function valueBar(value, max, color, label = "") {
911
+ const percent = value / max * 100;
912
+ const barHtml = `<div class="naturefyi-value-bar"><div class="naturefyi-bar-fill" style="width:${percent}%;background:${color}"></div></div>`;
913
+ const labelHtml = label ? `<span class="naturefyi-bar-label">${esc(label)}</span>` : "";
914
+ return `<div class="naturefyi-bar-wrapper">${labelHtml}${barHtml}</div>`;
915
+ }
916
+ function formatNumber(value, units = "") {
917
+ if (value === null || value === void 0) return "";
918
+ return `${value.toLocaleString()}${units ? " " + units : ""}`;
919
+ }
920
+
921
+ // src/compute/iucn.ts
922
+ var IUCN_COLORS = {
923
+ LC: { bg: "#22c55e", text: "#fff", label: "Least Concern" },
924
+ NT: { bg: "#84cc16", text: "#fff", label: "Near Threatened" },
925
+ VU: { bg: "#eab308", text: "#fff", label: "Vulnerable" },
926
+ EN: { bg: "#f97316", text: "#fff", label: "Endangered" },
927
+ CR: { bg: "#dc2626", text: "#fff", label: "Critically Endangered" },
928
+ EW: { bg: "#7f1d1d", text: "#fff", label: "Extinct in Wild" },
929
+ EX: { bg: "#1e293b", text: "#fff", label: "Extinct" },
930
+ DD: { bg: "#64748b", text: "#fff", label: "Data Deficient" },
931
+ NE: { bg: "#94a3b8", text: "#fff", label: "Not Evaluated" }
932
+ };
933
+ var UNKNOWN_STATUS = { bg: "#94a3b8", text: "#fff", label: "Unknown" };
934
+ function statusToColor(status) {
935
+ const entry = IUCN_COLORS[status.toUpperCase()];
936
+ if (!entry) return { bg: UNKNOWN_STATUS.bg, text: UNKNOWN_STATUS.text };
937
+ return { bg: entry.bg, text: entry.text };
938
+ }
939
+ function statusToLabel(status) {
940
+ var _a, _b;
941
+ return (_b = (_a = IUCN_COLORS[status.toUpperCase()]) == null ? void 0 : _a.label) != null ? _b : "Unknown";
942
+ }
943
+
944
+ // src/cards/species-card.ts
945
+ function trendArrow(trend) {
946
+ if (!trend) return "";
947
+ const t = String(trend).toLowerCase();
948
+ if (t === "increasing") return `<span style="color:#22c55e" title="Increasing">\u2191</span>`;
949
+ if (t === "stable") return `<span style="color:#eab308" title="Stable">\u2192</span>`;
950
+ if (t === "declining" || t === "decreasing") return `<span style="color:#ef4444" title="Declining">\u2193</span>`;
951
+ return `<span style="color:#94a3b8" title="Unknown">?</span>`;
952
+ }
953
+ function renderSpeciesCard(data, config) {
954
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
955
+ const name = esc(String((_b = (_a = data.common_name) != null ? _a : data.name) != null ? _b : ""));
956
+ const scientificName = esc(String(
957
+ (_d = (_c = data.genus && typeof data.genus === "object" ? data.genus.name : null) != null ? _c : data.scientific_name) != null ? _d : ""
958
+ ));
959
+ const slug = String((_e = data.slug) != null ? _e : "");
960
+ const entityUrl = typeof data.url === "string" && data.url ? data.url.startsWith("http") ? data.url : `https://${config.domain}${data.url}` : `https://${config.domain}/species/${esc(slug)}/`;
961
+ const conservation = String((_g = (_f = data.conservation_status) != null ? _f : data.iucn_status) != null ? _g : "");
962
+ const iucnColors = conservation ? statusToColor(conservation) : null;
963
+ const iucnLabel = conservation ? statusToLabel(conservation) : "";
964
+ const lifespan = data.average_lifespan_years != null ? formatNumber(Number(data.average_lifespan_years), "years") : "";
965
+ const length = data.average_length_m != null ? formatNumber(Number(data.average_length_m), "m") : "";
966
+ const weight = data.average_weight_kg != null ? formatNumber(Number(data.average_weight_kg), "kg") : "";
967
+ const population = data.population_estimate != null ? String(data.population_estimate) : "";
968
+ const trend = String((_h = data.population_trend) != null ? _h : "");
969
+ const diet = esc(String((_i = data.diet) != null ? _i : ""));
970
+ const habitat = String((_k = (_j = data.habitat) != null ? _j : data.habitat_description) != null ? _k : "");
971
+ const iucnBadge = iucnColors ? `<span class="naturefyi-badge" style="background:${iucnColors.bg};color:${iucnColors.text}">${esc(conservation)} ${esc(iucnLabel)}</span>` : "";
972
+ return `
973
+ <div class="naturefyi-species-card">
974
+ <div class="naturefyi-card-header" style="border-left:3px solid ${config.accent};padding-left:10px;margin-bottom:8px;">
975
+ <div style="font-size:1rem;font-weight:600;">${name}</div>
976
+ ${scientificName ? `<div style="font-size:0.85rem;color:#64748b;font-style:italic;">${scientificName}</div>` : ""}
977
+ </div>
978
+
979
+ <div class="naturefyi-badges" style="margin:8px 0;display:flex;flex-wrap:wrap;gap:4px;">
980
+ ${iucnBadge}
981
+ ${diet ? badge(diet, "#6b7280") : ""}
982
+ </div>
983
+
984
+ ${lifespan ? statsRow("Lifespan", lifespan) : ""}
985
+ ${length ? statsRow("Length", length) : ""}
986
+ ${weight ? statsRow("Weight", weight) : ""}
987
+
988
+ ${population ? `<div class="naturefyi-kv-row">
989
+ <span class="naturefyi-kv-label">Population</span>
990
+ <span class="naturefyi-kv-value">${esc(population)} ${trendArrow(trend)}</span>
991
+ </div>` : ""}
992
+
993
+ ${habitat ? description(habitat, 200) : ""}
994
+
995
+ <div class="naturefyi-actions" style="margin-top:8px;">
996
+ ${viewLink(entityUrl, `View on ${config.name}`, config)}
997
+ </div>
998
+ </div>
999
+ ${poweredByHTML(config)}
1000
+ `;
1001
+ }
1002
+
1003
+ // src/cards/bird-card.ts
1004
+ function trendArrow2(trend) {
1005
+ if (!trend) return "";
1006
+ const t = String(trend).toLowerCase();
1007
+ if (t === "increasing") return `<span style="color:#22c55e" title="Increasing">\u2191</span>`;
1008
+ if (t === "stable") return `<span style="color:#eab308" title="Stable">\u2192</span>`;
1009
+ if (t === "declining" || t === "decreasing") return `<span style="color:#ef4444" title="Declining">\u2193</span>`;
1010
+ return `<span style="color:#94a3b8" title="Unknown">?</span>`;
1011
+ }
1012
+ function renderBirdCard(data, config) {
1013
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1014
+ const name = esc(String((_b = (_a = data.common_name) != null ? _a : data.name) != null ? _b : ""));
1015
+ const scientificName = esc(String((_c = data.scientific_name) != null ? _c : ""));
1016
+ const slug = String((_d = data.slug) != null ? _d : "");
1017
+ const entityUrl = typeof data.url === "string" && data.url ? data.url.startsWith("http") ? data.url : `https://${config.domain}${data.url}` : `https://${config.domain}/bird/${esc(slug)}/`;
1018
+ const conservation = String((_f = (_e = data.conservation_status) != null ? _e : data.iucn_status) != null ? _f : "");
1019
+ const iucnColors = conservation ? statusToColor(conservation) : null;
1020
+ const iucnLabel = conservation ? statusToLabel(conservation) : "";
1021
+ const wingspan = data.wingspan_cm != null ? formatNumber(Number(data.wingspan_cm), "cm") : "";
1022
+ const weight = data.weight_g != null ? formatNumber(Number(data.weight_g), "g") : "";
1023
+ const length = data.length_cm != null ? formatNumber(Number(data.length_cm), "cm") : "";
1024
+ const habitat = esc(String((_g = data.habitat) != null ? _g : ""));
1025
+ const geoRange = data.geographic_range;
1026
+ const geoItems = Array.isArray(geoRange) ? geoRange : typeof geoRange === "string" && geoRange ? [geoRange] : [];
1027
+ const xenoCantoId = data.xeno_canto_id != null ? String(data.xeno_canto_id) : "";
1028
+ const population = data.population_estimate != null ? String(data.population_estimate) : "";
1029
+ const trend = String((_h = data.population_trend) != null ? _h : "");
1030
+ const iucnBadge = iucnColors ? `<span class="naturefyi-badge" style="background:${iucnColors.bg};color:${iucnColors.text}">${esc(conservation)} ${esc(iucnLabel)}</span>` : "";
1031
+ return `
1032
+ <div class="naturefyi-bird-card">
1033
+ <div class="naturefyi-card-header" style="border-left:3px solid ${config.accent};padding-left:10px;margin-bottom:8px;">
1034
+ <div style="font-size:1rem;font-weight:600;">${name}</div>
1035
+ ${scientificName ? `<div style="font-size:0.85rem;color:#64748b;font-style:italic;">${scientificName}</div>` : ""}
1036
+ </div>
1037
+
1038
+ <div class="naturefyi-badges" style="margin:8px 0;display:flex;flex-wrap:wrap;gap:4px;">
1039
+ ${iucnBadge}
1040
+ </div>
1041
+
1042
+ ${wingspan ? statsRow("Wingspan", wingspan) : ""}
1043
+ ${weight ? statsRow("Weight", weight) : ""}
1044
+ ${length ? statsRow("Length", length) : ""}
1045
+
1046
+ ${habitat ? statsRow("Habitat", habitat) : ""}
1047
+ ${geoItems.length > 0 ? `<div style="margin:6px 0;">${pills(geoItems)}</div>` : ""}
1048
+
1049
+ ${xenoCantoId ? `<div style="margin:6px 0;">
1050
+ <a href="https://xeno-canto.org/${esc(xenoCantoId)}" target="_blank" rel="noopener"
1051
+ class="naturefyi-view-link" style="color:${config.accent};font-size:0.85rem;">
1052
+ \u{1F50A} Listen on Xeno-canto \u2197
1053
+ </a>
1054
+ </div>` : ""}
1055
+
1056
+ ${population ? `<div class="naturefyi-kv-row">
1057
+ <span class="naturefyi-kv-label">Population</span>
1058
+ <span class="naturefyi-kv-value">${esc(population)} ${trendArrow2(trend)}</span>
1059
+ </div>` : ""}
1060
+
1061
+ <div class="naturefyi-actions" style="margin-top:8px;">
1062
+ ${viewLink(entityUrl, `View on ${config.name}`, config)}
1063
+ </div>
1064
+ </div>
1065
+ ${poweredByHTML(config)}
1066
+ `;
1067
+ }
1068
+
1069
+ // src/compute/water.ts
1070
+ var WATER_TYPES = {
1071
+ freshwater: { color: "#3b82f6", label: "Freshwater", icon: "\u{1F4A7}" },
1072
+ saltwater: { color: "#06b6d4", label: "Saltwater", icon: "\u{1F30A}" },
1073
+ brackish: { color: "#14b8a6", label: "Brackish", icon: "\u{1FAE7}" }
1074
+ };
1075
+ var UNKNOWN = { color: "#94a3b8", label: "Unknown", icon: "\u2753" };
1076
+ function normalise(raw) {
1077
+ const s = raw.toLowerCase().trim();
1078
+ if (s === "freshwater" || s === "fresh") return "freshwater";
1079
+ if (s === "saltwater" || s === "marine" || s === "ocean" || s === "sea") return "saltwater";
1080
+ if (s === "brackish" || s === "estuarine" || s === "estuary") return "brackish";
1081
+ return null;
1082
+ }
1083
+ function waterTypeToColor(waterType) {
1084
+ const key = normalise(waterType);
1085
+ return key ? WATER_TYPES[key].color : UNKNOWN.color;
1086
+ }
1087
+ function waterTypeLabel(waterType) {
1088
+ const key = normalise(waterType);
1089
+ return key ? WATER_TYPES[key].label : UNKNOWN.label;
1090
+ }
1091
+ function waterTypeIcon(waterType) {
1092
+ const key = normalise(waterType);
1093
+ return key ? WATER_TYPES[key].icon : UNKNOWN.icon;
1094
+ }
1095
+
1096
+ // src/cards/fish-card.ts
1097
+ function careLevelColor(level) {
1098
+ const l = (level || "").toLowerCase();
1099
+ if (l === "easy" || l === "beginner") return "#22c55e";
1100
+ if (l === "moderate" || l === "intermediate") return "#eab308";
1101
+ if (l === "difficult" || l === "expert" || l === "advanced") return "#ef4444";
1102
+ return "#6b7280";
1103
+ }
1104
+ function renderFishCard(data, config) {
1105
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
1106
+ const name = esc(String((_b = (_a = data.common_name) != null ? _a : data.name) != null ? _b : ""));
1107
+ const scientificName = esc(String((_c = data.scientific_name) != null ? _c : ""));
1108
+ const slug = String((_d = data.slug) != null ? _d : "");
1109
+ const entityUrl = typeof data.url === "string" && data.url ? data.url.startsWith("http") ? data.url : `https://${config.domain}${data.url}` : `https://${config.domain}/fish/${esc(slug)}/`;
1110
+ const waterType = String((_e = data.water_type) != null ? _e : "");
1111
+ const waterColor = waterType ? waterTypeToColor(waterType) : "";
1112
+ const waterLabel = waterType ? waterTypeLabel(waterType) : "";
1113
+ const conservation = String((_g = (_f = data.conservation_status) != null ? _f : data.iucn_status) != null ? _g : "");
1114
+ const iucnColors = conservation ? statusToColor(conservation) : null;
1115
+ const iucnLabel = conservation ? statusToLabel(conservation) : "";
1116
+ const isGameFish = data.is_game_fish === true;
1117
+ const mercury = esc(String((_h = data.mercury_level) != null ? _h : ""));
1118
+ const iucnBadge = iucnColors ? `<span class="naturefyi-badge" style="background:${iucnColors.bg};color:${iucnColors.text}">${esc(conservation)} ${esc(iucnLabel)}</span>` : "";
1119
+ const waterBadge = waterType ? `<span class="naturefyi-badge" style="background:${waterColor};color:#fff">\u{1F30A} ${esc(waterLabel)}</span>` : "";
1120
+ let modeHtml = "";
1121
+ if (isGameFish) {
1122
+ const fightRating = data.fight_rating != null ? Number(data.fight_rating) : null;
1123
+ const worldRecord = esc(String((_i = data.world_record) != null ? _i : ""));
1124
+ const bestBait = Array.isArray(data.best_bait) ? data.best_bait : [];
1125
+ const fishingMethods = Array.isArray(data.fishing_methods) ? data.fishing_methods : [];
1126
+ modeHtml = `
1127
+ ${fightRating != null ? valueBar(fightRating, 10, config.accent, `Fight Rating: ${fightRating}/10`) : ""}
1128
+ ${worldRecord ? statsRow("World Record", worldRecord) : ""}
1129
+ ${bestBait.length > 0 ? `<div style="margin:6px 0;"><span class="naturefyi-kv-label" style="display:block;margin-bottom:4px;">Best Bait</span>${pills(bestBait)}</div>` : ""}
1130
+ ${fishingMethods.length > 0 ? `<div style="margin:6px 0;"><span class="naturefyi-kv-label" style="display:block;margin-bottom:4px;">Fishing Methods</span>${pills(fishingMethods)}</div>` : ""}
1131
+ `;
1132
+ } else {
1133
+ const tankSize = data.min_tank_liters != null ? `${Number(data.min_tank_liters)} L` : "";
1134
+ const tempRange = esc(String((_j = data.temperature_range) != null ? _j : ""));
1135
+ const phRange = esc(String((_k = data.ph_range) != null ? _k : ""));
1136
+ const careLevel = String((_l = data.care_level) != null ? _l : "");
1137
+ const temperament = esc(String((_m = data.temperament) != null ? _m : ""));
1138
+ modeHtml = `
1139
+ ${tankSize ? statsRow("Min Tank Size", tankSize) : ""}
1140
+ ${tempRange ? statsRow("Temperature", tempRange) : ""}
1141
+ ${phRange ? statsRow("pH Range", phRange) : ""}
1142
+ ${careLevel ? `<div class="naturefyi-kv-row"><span class="naturefyi-kv-label">Care Level</span><span class="naturefyi-kv-value">${badge(esc(careLevel), careLevelColor(careLevel))}</span></div>` : ""}
1143
+ ${temperament ? statsRow("Temperament", temperament) : ""}
1144
+ `;
1145
+ }
1146
+ return `
1147
+ <div class="naturefyi-fish-card">
1148
+ <div class="naturefyi-card-header" style="border-left:3px solid ${config.accent};padding-left:10px;margin-bottom:8px;">
1149
+ <div style="font-size:1rem;font-weight:600;">${name}</div>
1150
+ ${scientificName ? `<div style="font-size:0.85rem;color:#64748b;font-style:italic;">${scientificName}</div>` : ""}
1151
+ </div>
1152
+
1153
+ <div class="naturefyi-badges" style="margin:8px 0;display:flex;flex-wrap:wrap;gap:4px;">
1154
+ ${waterBadge}
1155
+ ${iucnBadge}
1156
+ ${mercury ? badge(`Mercury: ${mercury}`, "#78716c") : ""}
1157
+ </div>
1158
+
1159
+ ${modeHtml}
1160
+
1161
+ <div class="naturefyi-actions" style="margin-top:8px;">
1162
+ ${viewLink(entityUrl, `View on ${config.name}`, config)}
1163
+ </div>
1164
+ </div>
1165
+ ${poweredByHTML(config)}
1166
+ `;
1167
+ }
1168
+
1169
+ // src/compute/hardiness.ts
1170
+ var HARDINESS_ZONES = [
1171
+ { zone: 1, color: "#6366f1", minF: -60, maxF: -50, tempRange: "-60 to -50 \xB0F" },
1172
+ { zone: 2, color: "#818cf8", minF: -50, maxF: -40, tempRange: "-50 to -40 \xB0F" },
1173
+ { zone: 3, color: "#60a5fa", minF: -40, maxF: -30, tempRange: "-40 to -30 \xB0F" },
1174
+ { zone: 4, color: "#38bdf8", minF: -30, maxF: -20, tempRange: "-30 to -20 \xB0F" },
1175
+ { zone: 5, color: "#34d399", minF: -20, maxF: -10, tempRange: "-20 to -10 \xB0F" },
1176
+ { zone: 6, color: "#4ade80", minF: -10, maxF: 0, tempRange: "-10 to 0 \xB0F" },
1177
+ { zone: 7, color: "#a3e635", minF: 0, maxF: 10, tempRange: "0 to 10 \xB0F" },
1178
+ { zone: 8, color: "#facc15", minF: 10, maxF: 20, tempRange: "10 to 20 \xB0F" },
1179
+ { zone: 9, color: "#fb923c", minF: 20, maxF: 30, tempRange: "20 to 30 \xB0F" },
1180
+ { zone: 10, color: "#f97316", minF: 30, maxF: 40, tempRange: "30 to 40 \xB0F" },
1181
+ { zone: 11, color: "#ef4444", minF: 40, maxF: 50, tempRange: "40 to 50 \xB0F" },
1182
+ { zone: 12, color: "#dc2626", minF: 50, maxF: 60, tempRange: "50 to 60 \xB0F" },
1183
+ { zone: 13, color: "#b91c1c", minF: 60, maxF: 70, tempRange: "60 to 70 \xB0F" }
1184
+ ];
1185
+ var ZONE_MAP = new Map(
1186
+ HARDINESS_ZONES.map((z) => [z.zone, z])
1187
+ );
1188
+ function parseZone(raw) {
1189
+ if (typeof raw === "number") return Number.isInteger(raw) ? raw : Math.floor(raw);
1190
+ const s = String(raw).trim().toLowerCase();
1191
+ const stripped = s.replace(/[ab]$/, "");
1192
+ const rangePart = stripped.split("-")[0];
1193
+ const n = parseInt(rangePart, 10);
1194
+ return isNaN(n) ? null : n;
1195
+ }
1196
+ function zoneToColor(zone) {
1197
+ var _a, _b;
1198
+ const n = parseZone(zone);
1199
+ if (n === null) return "#94a3b8";
1200
+ return (_b = (_a = ZONE_MAP.get(n)) == null ? void 0 : _a.color) != null ? _b : "#94a3b8";
1201
+ }
1202
+ function zoneToTemp(zone) {
1203
+ const n = parseZone(zone);
1204
+ if (n === null) return null;
1205
+ const entry = ZONE_MAP.get(n);
1206
+ if (!entry) return null;
1207
+ return { minF: entry.minF, maxF: entry.maxF, tempRange: entry.tempRange };
1208
+ }
1209
+
1210
+ // src/compute/bloom.ts
1211
+ var MONTH_NAMES = [
1212
+ "January",
1213
+ "February",
1214
+ "March",
1215
+ "April",
1216
+ "May",
1217
+ "June",
1218
+ "July",
1219
+ "August",
1220
+ "September",
1221
+ "October",
1222
+ "November",
1223
+ "December"
1224
+ ];
1225
+ var MONTH_NAMES_SHORT = [
1226
+ "Jan",
1227
+ "Feb",
1228
+ "Mar",
1229
+ "Apr",
1230
+ "May",
1231
+ "Jun",
1232
+ "Jul",
1233
+ "Aug",
1234
+ "Sep",
1235
+ "Oct",
1236
+ "Nov",
1237
+ "Dec"
1238
+ ];
1239
+ function monthsToCalendar(activeMonths) {
1240
+ const activeSet = new Set(activeMonths);
1241
+ return MONTH_NAMES.map((name, i) => {
1242
+ const month = i + 1;
1243
+ return {
1244
+ month,
1245
+ name,
1246
+ short: MONTH_NAMES_SHORT[i],
1247
+ active: activeSet.has(month)
1248
+ };
1249
+ });
1250
+ }
1251
+
1252
+ // src/cards/plant-card.ts
1253
+ function sunIcon(requirement) {
1254
+ const r = (requirement || "").toLowerCase();
1255
+ if (r.includes("full")) return '<span title="Full Sun">\u2600\uFE0F</span>';
1256
+ if (r.includes("partial") || r.includes("part")) return '<span title="Partial Sun">\u{1F324}\uFE0F</span>';
1257
+ if (r.includes("shade") || r.includes("low")) return '<span title="Shade">\u{1F311}</span>';
1258
+ return "";
1259
+ }
1260
+ function waterIcon(requirement) {
1261
+ const r = (requirement || "").toLowerCase();
1262
+ if (r.includes("high") || r.includes("moist")) return '<span title="High Water">\u{1F4A7}\u{1F4A7}\u{1F4A7}</span>';
1263
+ if (r.includes("moderate") || r.includes("medium")) return '<span title="Moderate Water">\u{1F4A7}\u{1F4A7}</span>';
1264
+ if (r.includes("low") || r.includes("drought")) return '<span title="Low Water">\u{1F4A7}</span>';
1265
+ return '<span title="Water">\u{1F4A7}</span>';
1266
+ }
1267
+ function renderBloomDots(months, accent) {
1268
+ const calendar = monthsToCalendar(months);
1269
+ const dots = calendar.map((m) => {
1270
+ const bg = m.active ? accent : "#e2e8f0";
1271
+ const textColor = m.active ? "#fff" : "#94a3b8";
1272
+ return `<span title="${esc(m.name)}" style="display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:${bg};color:${textColor};font-size:0.6rem;font-weight:600;">${esc(m.short.charAt(0))}</span>`;
1273
+ }).join("");
1274
+ return `<div style="display:flex;gap:2px;flex-wrap:wrap;">${dots}</div>`;
1275
+ }
1276
+ function renderPlantCard(data, config) {
1277
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1278
+ const scientificName = esc(String((_a = data.scientific_name) != null ? _a : ""));
1279
+ const commonName = esc(String((_c = (_b = data.common_name) != null ? _b : data.name) != null ? _c : ""));
1280
+ const slug = String((_d = data.slug) != null ? _d : "");
1281
+ const entityUrl = typeof data.url === "string" && data.url ? data.url.startsWith("http") ? data.url : `https://${config.domain}${data.url}` : `https://${config.domain}/plant/${esc(slug)}/`;
1282
+ const growthForm = esc(String((_e = data.growth_form) != null ? _e : ""));
1283
+ const hardinessZone = String((_f = data.hardiness_zone) != null ? _f : "");
1284
+ const zoneColor = hardinessZone ? zoneToColor(hardinessZone) : "";
1285
+ const zoneTemp = hardinessZone ? zoneToTemp(hardinessZone) : null;
1286
+ const sunReq = String((_g = data.sun_requirement) != null ? _g : "");
1287
+ const waterReq = String((_h = data.water_requirement) != null ? _h : "");
1288
+ const bloomSeason = data.bloom_season;
1289
+ const bloomMonths = Array.isArray(data.bloom_months) ? data.bloom_months : typeof bloomSeason === "string" && bloomSeason ? [] : [];
1290
+ const flowerColor = esc(String((_i = data.flower_color) != null ? _i : ""));
1291
+ const maxHeight = data.max_height_m != null ? Number(data.max_height_m) : null;
1292
+ const maxSpread = data.max_spread_m != null ? Number(data.max_spread_m) : null;
1293
+ const edibility = esc(String((_j = data.edibility) != null ? _j : ""));
1294
+ const medicinal = esc(String((_k = data.medicinal_uses) != null ? _k : ""));
1295
+ return `
1296
+ <div class="naturefyi-plant-card">
1297
+ <div class="naturefyi-card-header" style="border-left:3px solid ${config.accent};padding-left:10px;margin-bottom:8px;">
1298
+ ${scientificName ? `<div style="font-size:1rem;font-weight:600;font-style:italic;">${scientificName}</div>` : ""}
1299
+ ${commonName ? `<div style="font-size:0.85rem;color:#64748b;">${commonName}</div>` : ""}
1300
+ </div>
1301
+
1302
+ <div class="naturefyi-badges" style="margin:8px 0;display:flex;flex-wrap:wrap;gap:4px;">
1303
+ ${growthForm ? badge(growthForm, "#6b7280") : ""}
1304
+ ${hardinessZone ? `<span class="naturefyi-badge" style="background:${zoneColor};color:#fff" title="${zoneTemp ? esc(zoneTemp.tempRange) : ""}">Zone ${esc(hardinessZone)}</span>` : ""}
1305
+ </div>
1306
+
1307
+ <div style="margin:6px 0;display:flex;gap:12px;align-items:center;font-size:0.85rem;">
1308
+ ${sunReq ? `<span>${sunIcon(sunReq)} ${esc(sunReq)}</span>` : ""}
1309
+ ${waterReq ? `<span>${waterIcon(waterReq)} ${esc(waterReq)}</span>` : ""}
1310
+ </div>
1311
+
1312
+ ${bloomMonths.length > 0 ? `<div style="margin:8px 0;">
1313
+ <span class="naturefyi-kv-label" style="display:block;margin-bottom:4px;">Bloom Season</span>
1314
+ ${renderBloomDots(bloomMonths, config.accent)}
1315
+ </div>` : typeof bloomSeason === "string" && bloomSeason ? statsRow("Bloom Season", bloomSeason) : ""}
1316
+
1317
+ ${flowerColor ? `<div class="naturefyi-kv-row">
1318
+ <span class="naturefyi-kv-label">Flower Color</span>
1319
+ <span class="naturefyi-kv-value">
1320
+ <span style="display:inline-block;width:12px;height:12px;border-radius:50%;background:${esc(flowerColor)};border:1px solid rgba(0,0,0,0.15);vertical-align:middle;margin-right:4px;"></span>
1321
+ ${flowerColor}
1322
+ </span>
1323
+ </div>` : ""}
1324
+
1325
+ ${maxHeight != null || maxSpread != null ? statsRow("Size", `${maxHeight != null ? maxHeight + " m tall" : ""}${maxHeight != null && maxSpread != null ? " \xD7 " : ""}${maxSpread != null ? maxSpread + " m spread" : ""}`) : ""}
1326
+
1327
+ ${edibility ? statsRow("Edibility", edibility) : ""}
1328
+ ${medicinal ? statsRow("Medicinal Uses", medicinal) : ""}
1329
+
1330
+ <div class="naturefyi-actions" style="margin-top:8px;">
1331
+ ${viewLink(entityUrl, `View on ${config.name}`, config)}
1332
+ </div>
1333
+ </div>
1334
+ ${poweredByHTML(config)}
1335
+ `;
1336
+ }
1337
+
1338
+ // src/compute/period.ts
1339
+ var PERIODS = [
1340
+ { name: "Triassic", start: 252, end: 201, color: "#ef4444", era: "Mesozoic" },
1341
+ { name: "Jurassic", start: 201, end: 145, color: "#3b82f6", era: "Mesozoic" },
1342
+ { name: "Cretaceous", start: 145, end: 66, color: "#22c55e", era: "Mesozoic" }
1343
+ ];
1344
+ var MESOZOIC_START = 252;
1345
+ var MESOZOIC_END = 66;
1346
+ var MESOZOIC_SPAN = MESOZOIC_START - MESOZOIC_END;
1347
+ function periodToColor(periodName) {
1348
+ var _a;
1349
+ const match = PERIODS.find(
1350
+ (p) => p.name.toLowerCase() === periodName.toLowerCase()
1351
+ );
1352
+ return (_a = match == null ? void 0 : match.color) != null ? _a : "#94a3b8";
1353
+ }
1354
+ function periodPosition(ageMa) {
1355
+ const clamped = Math.min(MESOZOIC_START, Math.max(MESOZOIC_END, ageMa));
1356
+ return (MESOZOIC_START - clamped) / MESOZOIC_SPAN;
1357
+ }
1358
+ function geologicalTimeline() {
1359
+ return PERIODS.map((p) => ({
1360
+ ...p,
1361
+ posStart: periodPosition(p.start),
1362
+ posEnd: periodPosition(p.end)
1363
+ }));
1364
+ }
1365
+
1366
+ // src/cards/dino-card.ts
1367
+ function dietBadge(diet) {
1368
+ const d = (diet || "").toLowerCase();
1369
+ if (d.includes("herbi")) return `<span class="naturefyi-badge" style="background:#22c55e;color:#fff">\u{1F33F} Herbivore</span>`;
1370
+ if (d.includes("carni")) return `<span class="naturefyi-badge" style="background:#ef4444;color:#fff">\u{1F9B7} Carnivore</span>`;
1371
+ if (d.includes("omni")) return `<span class="naturefyi-badge" style="background:#f59e0b;color:#fff">\u{1F37D}\uFE0F Omnivore</span>`;
1372
+ return diet ? badge(diet, "#6b7280") : "";
1373
+ }
1374
+ function renderTimeline(periodName) {
1375
+ const timeline = geologicalTimeline();
1376
+ const bars = timeline.map((p) => {
1377
+ const width = (p.posEnd - p.posStart) * 100;
1378
+ const isActive = p.name.toLowerCase() === (periodName || "").toLowerCase();
1379
+ const opacity = isActive ? "1" : "0.4";
1380
+ return `<div style="width:${width}%;height:12px;background:${p.color};opacity:${opacity};position:relative;" title="${p.name} (${p.start}\u2013${p.end} Ma)"></div>`;
1381
+ }).join("");
1382
+ let markerHtml = "";
1383
+ const match = timeline.find((p) => p.name.toLowerCase() === (periodName || "").toLowerCase());
1384
+ if (match) {
1385
+ const pos = (match.posStart + match.posEnd) / 2 * 100;
1386
+ markerHtml = `<div style="position:absolute;left:${pos}%;top:-3px;width:8px;height:8px;border-radius:50%;background:#1e293b;border:2px solid #fff;transform:translateX(-50%);box-shadow:0 1px 3px rgba(0,0,0,0.3);"></div>`;
1387
+ }
1388
+ const labels = timeline.map((p) => {
1389
+ const width = (p.posEnd - p.posStart) * 100;
1390
+ return `<span style="width:${width}%;text-align:center;font-size:0.6rem;color:#64748b;">${p.name}<br>${p.start}\u2013${p.end} Ma</span>`;
1391
+ }).join("");
1392
+ return `
1393
+ <div style="margin:8px 0;">
1394
+ <div style="display:flex;border-radius:4px;overflow:hidden;position:relative;">
1395
+ ${bars}
1396
+ ${markerHtml}
1397
+ </div>
1398
+ <div style="display:flex;margin-top:2px;">${labels}</div>
1399
+ </div>
1400
+ `;
1401
+ }
1402
+ function renderDinoCard(data, config) {
1403
+ var _a, _b, _c, _d, _e, _f;
1404
+ const name = esc(String((_a = data.name) != null ? _a : ""));
1405
+ const slug = String((_b = data.slug) != null ? _b : "");
1406
+ const entityUrl = typeof data.url === "string" && data.url ? data.url.startsWith("http") ? data.url : `https://${config.domain}${data.url}` : `https://${config.domain}/dinosaur/${esc(slug)}/`;
1407
+ const period = String((_c = data.period) != null ? _c : "");
1408
+ const periodColor = period ? periodToColor(period.split(" ")[0]) : "#94a3b8";
1409
+ const diet = String((_d = data.diet) != null ? _d : "");
1410
+ const lengthM = data.length_m != null ? formatNumber(Number(data.length_m), "m") : "";
1411
+ const weightKg = data.weight_kg != null ? formatNumber(Number(data.weight_kg), "kg") : "";
1412
+ const heightM = data.height_m != null ? formatNumber(Number(data.height_m), "m") : "";
1413
+ const discoveryYear = data.discovery_year != null ? String(data.discovery_year) : "";
1414
+ const discoveryLocation = esc(String((_e = data.discovery_location) != null ? _e : ""));
1415
+ const namedBy = esc(String((_f = data.named_by) != null ? _f : ""));
1416
+ return `
1417
+ <div class="naturefyi-dino-card">
1418
+ <div class="naturefyi-card-header" style="border-left:3px solid ${config.accent};padding-left:10px;margin-bottom:8px;">
1419
+ <div style="font-size:1rem;font-weight:600;">${name}</div>
1420
+ </div>
1421
+
1422
+ <div class="naturefyi-badges" style="margin:8px 0;display:flex;flex-wrap:wrap;gap:4px;">
1423
+ ${period ? `<span class="naturefyi-badge" style="background:${periodColor};color:#fff">${esc(period)}</span>` : ""}
1424
+ ${dietBadge(diet)}
1425
+ </div>
1426
+
1427
+ ${lengthM ? statsRow("Length", lengthM) : ""}
1428
+ ${weightKg ? statsRow("Weight", weightKg) : ""}
1429
+ ${heightM ? statsRow("Height", heightM) : ""}
1430
+
1431
+ ${period ? renderTimeline(period.split(" ")[0]) : ""}
1432
+
1433
+ ${discoveryYear ? statsRow("Discovered", discoveryYear) : ""}
1434
+ ${discoveryLocation ? statsRow("Location", discoveryLocation) : ""}
1435
+ ${namedBy ? statsRow("Named by", namedBy) : ""}
1436
+
1437
+ <div class="naturefyi-actions" style="margin-top:8px;">
1438
+ ${viewLink(entityUrl, `View on ${config.name}`, config)}
1439
+ </div>
1440
+ </div>
1441
+ ${poweredByHTML(config)}
1442
+ `;
1443
+ }
1444
+
1445
+ // src/widgets/entity.ts
1446
+ function escapeHTML(str) {
1447
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1448
+ }
1449
+ function initEntityWidget(el, config) {
1450
+ var _a;
1451
+ const dataset = el.dataset;
1452
+ const slug = (_a = dataset.slug) != null ? _a : "";
1453
+ if (!slug) {
1454
+ const shadow2 = createShadow(el, config);
1455
+ const container2 = createWidgetRoot(shadow2, el, "naturefyi-entity-widget");
1456
+ renderError(container2, "Missing data-slug attribute.", config);
1457
+ return;
1458
+ }
1459
+ const shadow = createShadow(el, config);
1460
+ const container = createWidgetRoot(shadow, el, "naturefyi-entity-widget");
1461
+ renderLoading(container);
1462
+ fetchAPI(config.apiBase, `${config.entitySlug}/${slug}/`).then((data) => {
1463
+ var _a2;
1464
+ let html;
1465
+ switch (config.site) {
1466
+ case "speciesfyi":
1467
+ html = renderSpeciesCard(data, config);
1468
+ break;
1469
+ case "birdfyi":
1470
+ html = renderBirdCard(data, config);
1471
+ break;
1472
+ case "fishfyi":
1473
+ html = renderFishCard(data, config);
1474
+ break;
1475
+ case "plantfyi":
1476
+ html = renderPlantCard(data, config);
1477
+ break;
1478
+ case "dinofyi":
1479
+ html = renderDinoCard(data, config);
1480
+ break;
1481
+ default:
1482
+ {
1483
+ const name = String((_a2 = data.name) != null ? _a2 : slug);
1484
+ const entityUrl = typeof data.url === "string" && data.url ? data.url.startsWith("http") ? data.url : `https://${config.domain}${data.url}` : `https://${config.domain}/${config.entitySlug}/${escapeHTML(slug)}/`;
1485
+ html = `
1486
+ <div class="naturefyi-entity-fallback">
1487
+ <div style="font-size:1rem;font-weight:600;margin-bottom:8px;">${escapeHTML(name)}</div>
1488
+ <a href="${escapeHTML(entityUrl)}" target="_blank" rel="noopener"
1489
+ class="naturefyi-view-link" style="color:${config.accent}">
1490
+ View on ${escapeHTML(config.name)} \u2197
1491
+ </a>
1492
+ </div>
1493
+ `;
1494
+ }
1495
+ break;
1496
+ }
1497
+ container.innerHTML = html;
1498
+ }).catch(() => {
1499
+ renderError(container, `${escapeHTML(config.entityName.slice(0, -1))} "${escapeHTML(slug)}" not found.`, config);
1500
+ });
1501
+ }
1502
+
1503
+ // src/widgets/compare.ts
1504
+ function escapeHTML2(str) {
1505
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1506
+ }
1507
+ function formatCellValue(val) {
1508
+ if (val === null || val === void 0 || val === "") return "\u2014";
1509
+ return escapeHTML2(String(val));
1510
+ }
1511
+ var SITES_WITH_COMPARISONS = /* @__PURE__ */ new Set(["speciesfyi", "birdfyi", "plantfyi", "dinofyi"]);
1512
+ function initCompareWidget(el, config) {
1513
+ var _a;
1514
+ const dataset = el.dataset;
1515
+ const slug = (_a = dataset.slug) != null ? _a : "";
1516
+ const shadow = createShadow(el, config);
1517
+ const container = createWidgetRoot(shadow, el, "naturefyi-compare-widget");
1518
+ if (!SITES_WITH_COMPARISONS.has(config.site)) {
1519
+ const siteUrl = `https://${config.domain}`;
1520
+ container.innerHTML = `
1521
+ <div class="naturefyi-compare-unavailable" style="padding:12px;text-align:center;">
1522
+ <div style="font-size:0.9rem;color:#64748b;margin-bottom:8px;">
1523
+ Comparison not available for fish. Try the compatibility checker at ${escapeHTML2(config.domain)}
1524
+ </div>
1525
+ <a class="naturefyi-view-link" href="${escapeHTML2(siteUrl)}" target="_blank" rel="noopener"
1526
+ style="color:${config.accent}">
1527
+ View on ${escapeHTML2(config.name)} ${externalLinkIcon}
1528
+ </a>
1529
+ </div>
1530
+ ${poweredByHTML(config)}
1531
+ `;
1532
+ return;
1533
+ }
1534
+ if (!slug) {
1535
+ container.innerHTML = `
1536
+ <div class="naturefyi-error">
1537
+ <p>Missing data-slug attribute.</p>
1538
+ <a href="https://${config.domain}" target="_blank" rel="noopener">Visit ${escapeHTML2(config.name)}</a>
1539
+ </div>
1540
+ `;
1541
+ return;
1542
+ }
1543
+ renderLoading(container);
1544
+ fetchAPI(config.apiBase, `comparisons/${slug}/`).then((data) => {
1545
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
1546
+ const nameA = (_c = (_b = (_a2 = data.entity_a) == null ? void 0 : _a2.name) != null ? _b : data.name_a) != null ? _c : "Item A";
1547
+ const nameB = (_f = (_e = (_d = data.entity_b) == null ? void 0 : _d.name) != null ? _e : data.name_b) != null ? _f : "Item B";
1548
+ const title = (_g = data.title) != null ? _g : `${nameA} vs ${nameB}`;
1549
+ const rows = (_h = data.rows) != null ? _h : [];
1550
+ const keyDiffs = (_i = data.key_differences) != null ? _i : [];
1551
+ const summary = (_j = data.summary) != null ? _j : "";
1552
+ const compareUrl = data.url ? data.url.startsWith("http") ? data.url : `https://${config.domain}${data.url}` : `https://${config.domain}/compare/${escapeHTML2(slug)}/`;
1553
+ const urlA = ((_k = data.entity_a) == null ? void 0 : _k.url) ? data.entity_a.url.startsWith("http") ? data.entity_a.url : `https://${config.domain}${data.entity_a.url}` : ((_l = data.entity_a) == null ? void 0 : _l.slug) ? `https://${config.domain}/${config.entitySlug}/${escapeHTML2(data.entity_a.slug)}/` : `https://${config.domain}`;
1554
+ const urlB = ((_m = data.entity_b) == null ? void 0 : _m.url) ? data.entity_b.url.startsWith("http") ? data.entity_b.url : `https://${config.domain}${data.entity_b.url}` : ((_n = data.entity_b) == null ? void 0 : _n.slug) ? `https://${config.domain}/${config.entitySlug}/${escapeHTML2(data.entity_b.slug)}/` : `https://${config.domain}`;
1555
+ container.innerHTML = `
1556
+ <div class="naturefyi-compare-card">
1557
+ <div class="naturefyi-compare-title" style="font-size:1rem;font-weight:600;margin-bottom:10px;">
1558
+ ${escapeHTML2(title)}
1559
+ </div>
1560
+
1561
+ ${summary ? `<div class="naturefyi-compare-summary" style="font-size:0.85rem;color:#475569;margin-bottom:10px;line-height:1.4;">
1562
+ ${escapeHTML2(summary)}
1563
+ </div>` : ""}
1564
+
1565
+ <div class="naturefyi-compare-scroll" style="overflow-x:auto;">
1566
+ <table style="width:100%;border-collapse:collapse;font-size:0.85rem;">
1567
+ <thead>
1568
+ <tr>
1569
+ <th style="text-align:left;padding:6px 8px;border-bottom:2px solid ${config.accent};color:#64748b;font-weight:600;width:35%;">Property</th>
1570
+ <th style="text-align:left;padding:6px 8px;border-bottom:2px solid ${config.accent};font-weight:600;width:32.5%;">
1571
+ <a href="${escapeHTML2(urlA)}" target="_blank" rel="noopener"
1572
+ style="color:${config.accent};text-decoration:none;">${escapeHTML2(nameA)} \u2197</a>
1573
+ </th>
1574
+ <th style="text-align:left;padding:6px 8px;border-bottom:2px solid ${config.accent};font-weight:600;width:32.5%;">
1575
+ <a href="${escapeHTML2(urlB)}" target="_blank" rel="noopener"
1576
+ style="color:${config.accent};text-decoration:none;">${escapeHTML2(nameB)} \u2197</a>
1577
+ </th>
1578
+ </tr>
1579
+ </thead>
1580
+ <tbody>
1581
+ ${rows.map(
1582
+ (row, i) => `
1583
+ <tr style="background:${i % 2 === 0 ? "transparent" : "#f8fafc"};">
1584
+ <td style="padding:5px 8px;color:#64748b;font-weight:500;">${escapeHTML2(row.label)}</td>
1585
+ <td style="padding:5px 8px;">${formatCellValue(row.value_a)}</td>
1586
+ <td style="padding:5px 8px;">${formatCellValue(row.value_b)}</td>
1587
+ </tr>`
1588
+ ).join("")}
1589
+ </tbody>
1590
+ </table>
1591
+ </div>
1592
+
1593
+ ${keyDiffs.length > 0 ? `<div class="naturefyi-key-diffs" style="margin-top:10px;padding:8px;background:#f0fdf4;border-radius:6px;border-left:3px solid #10b981;">
1594
+ <div style="font-size:0.75rem;font-weight:600;color:#059669;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.05em;">Key Differences</div>
1595
+ <ul style="margin:0;padding-left:16px;font-size:0.8rem;color:#374151;">
1596
+ ${keyDiffs.map((d) => `<li style="margin:2px 0;">${escapeHTML2(d)}</li>`).join("")}
1597
+ </ul>
1598
+ </div>` : ""}
1599
+
1600
+ <div class="naturefyi-actions" style="margin-top:10px;">
1601
+ <a class="naturefyi-view-link" href="${escapeHTML2(compareUrl)}" target="_blank" rel="noopener"
1602
+ style="color:${config.accent}">
1603
+ Full comparison on ${escapeHTML2(config.name)} ${externalLinkIcon}
1604
+ </a>
1605
+ </div>
1606
+ </div>
1607
+ ${poweredByHTML(config)}
1608
+ `;
1609
+ }).catch(() => {
1610
+ const siteUrl = `https://${config.domain}`;
1611
+ container.innerHTML = `
1612
+ <div class="naturefyi-compare-unavailable" style="padding:12px;text-align:center;">
1613
+ <div style="font-size:0.9rem;color:#64748b;margin-bottom:8px;">
1614
+ Comparison not available via API. View on ${escapeHTML2(config.name)}.
1615
+ </div>
1616
+ <a class="naturefyi-view-link" href="${escapeHTML2(siteUrl)}" target="_blank" rel="noopener"
1617
+ style="color:${config.accent}">
1618
+ Visit ${escapeHTML2(config.name)} ${externalLinkIcon}
1619
+ </a>
1620
+ </div>
1621
+ ${poweredByHTML(config)}
1622
+ `;
1623
+ });
1624
+ }
1625
+
1626
+ // src/rich-snippets.ts
1627
+ function injectDefinedTerm(data, domain, siteName) {
1628
+ if (document.querySelector('script[data-naturefyi-snippet="term"]')) return;
1629
+ const jsonLd = {
1630
+ "@context": "https://schema.org",
1631
+ "@type": "DefinedTerm",
1632
+ name: data.name,
1633
+ description: data.definition,
1634
+ inDefinedTermSet: {
1635
+ "@type": "DefinedTermSet",
1636
+ name: `${siteName} Glossary`,
1637
+ url: `https://${domain}/glossary/`
1638
+ }
1639
+ };
1640
+ const script = document.createElement("script");
1641
+ script.type = "application/ld+json";
1642
+ script.setAttribute("data-naturefyi-snippet", "term");
1643
+ script.textContent = JSON.stringify(jsonLd);
1644
+ document.head.appendChild(script);
1645
+ }
1646
+ function injectArticle(data, domain, siteName) {
1647
+ if (document.querySelector('script[data-naturefyi-snippet="article"]')) return;
1648
+ const jsonLd = {
1649
+ "@context": "https://schema.org",
1650
+ "@type": "Article",
1651
+ headline: data.name,
1652
+ description: data.description,
1653
+ url: data.url || `https://${domain}/`,
1654
+ publisher: {
1655
+ "@type": "Organization",
1656
+ name: siteName,
1657
+ url: `https://${domain}/`
1658
+ }
1659
+ };
1660
+ if (data.datePublished) {
1661
+ jsonLd["datePublished"] = data.datePublished;
1662
+ }
1663
+ const script = document.createElement("script");
1664
+ script.type = "application/ld+json";
1665
+ script.setAttribute("data-naturefyi-snippet", "article");
1666
+ script.textContent = JSON.stringify(jsonLd);
1667
+ document.head.appendChild(script);
1668
+ }
1669
+
1670
+ // src/widgets/glossary.ts
1671
+ function escapeHTML3(str) {
1672
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1673
+ }
1674
+ function renderGlossary(container, data, config) {
1675
+ var _a, _b, _c, _d, _e, _f;
1676
+ const name = (_b = (_a = data.name) != null ? _a : data.term) != null ? _b : data.slug;
1677
+ const categoryLabel = (_d = (_c = data.category_name) != null ? _c : data.category) != null ? _d : "";
1678
+ const extended = (_f = (_e = data.extended_definition) != null ? _e : data.extended_description) != null ? _f : "";
1679
+ const termUrl = `https://${config.domain}/glossary/${escapeHTML3(data.slug)}/`;
1680
+ const glossaryUrl = `https://${config.domain}/glossary/`;
1681
+ const relatedPills = data.related_terms && data.related_terms.length > 0 ? data.related_terms.map(
1682
+ (rt) => `<a class="naturefyi-pill naturefyi-pill--link"
1683
+ href="https://${config.domain}/glossary/${escapeHTML3(rt.slug)}/"
1684
+ target="_blank" rel="noopener">${escapeHTML3(rt.name)}</a>`
1685
+ ).join("") : "";
1686
+ container.innerHTML = `
1687
+ <div class="naturefyi-glossary-card">
1688
+ <div class="naturefyi-glossary-header">
1689
+ <div style="font-size:1rem;font-weight:600;">${escapeHTML3(name)}</div>
1690
+ ${categoryLabel ? `<span class="naturefyi-badge" style="background:${config.accent};color:#fff;margin-top:4px;display:inline-block;">
1691
+ ${escapeHTML3(categoryLabel)}
1692
+ </span>` : ""}
1693
+ </div>
1694
+
1695
+ <div class="naturefyi-glossary-definition" style="margin:10px 0;font-size:0.9rem;line-height:1.5;">
1696
+ ${escapeHTML3(data.definition)}
1697
+ </div>
1698
+
1699
+ ${extended ? `<div class="naturefyi-glossary-extended" style="margin-bottom:8px;font-size:0.85rem;color:#64748b;line-height:1.5;">
1700
+ ${escapeHTML3(extended)}
1701
+ </div>` : ""}
1702
+
1703
+ ${relatedPills ? `<div class="naturefyi-pills" style="margin:8px 0;">
1704
+ ${relatedPills}
1705
+ </div>` : ""}
1706
+
1707
+ <div class="naturefyi-actions" style="margin-top:10px;display:flex;flex-wrap:wrap;gap:8px;">
1708
+ <a class="naturefyi-view-link" href="${termUrl}" target="_blank" rel="noopener"
1709
+ style="color:${config.accent}">
1710
+ ${escapeHTML3(name)} ${externalLinkIcon}
1711
+ </a>
1712
+ <a class="naturefyi-view-link" href="${glossaryUrl}" target="_blank" rel="noopener"
1713
+ style="color:${config.accent}">
1714
+ Full glossary on ${escapeHTML3(config.name)} ${externalLinkIcon}
1715
+ </a>
1716
+ </div>
1717
+ </div>
1718
+ ${poweredByHTML(config)}
1719
+ `;
1720
+ }
1721
+ function initGlossaryWidget(el, config) {
1722
+ var _a;
1723
+ const dataset = el.dataset;
1724
+ const slug = (_a = dataset.slug) != null ? _a : "";
1725
+ if (!slug) {
1726
+ const shadow2 = createShadow(el, config);
1727
+ const container2 = createWidgetRoot(shadow2, el, "naturefyi-glossary-widget");
1728
+ renderError(container2, "Missing data-slug attribute.", config);
1729
+ return;
1730
+ }
1731
+ const shadow = createShadow(el, config);
1732
+ const container = createWidgetRoot(shadow, el, "naturefyi-glossary-widget");
1733
+ renderLoading(container);
1734
+ fetchAPI(config.apiBase, `glossary/${slug}/`).then((data) => {
1735
+ var _a2, _b;
1736
+ renderGlossary(container, data, config);
1737
+ if (el.dataset.noSnippet !== "true") {
1738
+ const name = (_b = (_a2 = data.name) != null ? _a2 : data.term) != null ? _b : data.slug;
1739
+ injectDefinedTerm(
1740
+ { name, definition: data.definition },
1741
+ config.domain,
1742
+ config.name
1743
+ );
1744
+ }
1745
+ }).catch(() => {
1746
+ renderError(
1747
+ container,
1748
+ `Unable to load glossary term "${escapeHTML3(slug)}". Please try again later.`,
1749
+ config
1750
+ );
1751
+ });
1752
+ }
1753
+
1754
+ // src/widgets/guide.ts
1755
+ function escapeHTML4(str) {
1756
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1757
+ }
1758
+ function initGuideWidget(el, config) {
1759
+ var _a;
1760
+ const dataset = el.dataset;
1761
+ const slug = (_a = dataset.slug) != null ? _a : "";
1762
+ if (!slug) {
1763
+ const shadow2 = createShadow(el, config);
1764
+ const container2 = createWidgetRoot(shadow2, el, "naturefyi-guide-widget");
1765
+ renderError(container2, "Missing data-slug attribute.", config);
1766
+ return;
1767
+ }
1768
+ const shadow = createShadow(el, config);
1769
+ const container = createWidgetRoot(shadow, el, "naturefyi-guide-widget");
1770
+ renderLoading(container);
1771
+ fetchAPI(config.apiBase, `guides/${slug}/`).then((guide) => {
1772
+ var _a2, _b, _c, _d, _e, _f, _g, _h;
1773
+ const guideUrl = guide.url ? guide.url.startsWith("http") ? guide.url : `https://${config.domain}${guide.url}` : `https://${config.domain}/guides/${escapeHTML4(guide.slug)}/`;
1774
+ const summary = (_d = (_c = (_b = (_a2 = guide.summary) != null ? _a2 : guide.subtitle) != null ? _b : guide.description) != null ? _c : guide.excerpt) != null ? _d : "";
1775
+ const readingTime = (_e = guide.reading_time_minutes) != null ? _e : null;
1776
+ const seriesName = (_f = guide.series_name) != null ? _f : null;
1777
+ const datePublished = (_h = (_g = guide.date_published) != null ? _g : guide.published_at) != null ? _h : null;
1778
+ const toc = guide.toc && guide.toc.length > 0 ? guide.toc.slice(0, 5) : null;
1779
+ container.innerHTML = `
1780
+ <div class="naturefyi-guide-card">
1781
+ <div class="naturefyi-guide-badges" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px;">
1782
+ ${seriesName ? `<span class="naturefyi-badge" style="background:${config.accent};color:#fff;">${escapeHTML4(seriesName)}</span>` : ""}
1783
+ ${readingTime != null ? `<span class="naturefyi-badge" style="background:#f1f5f9;color:#475569;border:1px solid #e2e8f0;">
1784
+ ${escapeHTML4(String(readingTime))} min read
1785
+ </span>` : ""}
1786
+ </div>
1787
+
1788
+ <div class="naturefyi-guide-title" style="font-size:1rem;font-weight:600;margin-bottom:8px;">
1789
+ ${escapeHTML4(guide.title)}
1790
+ </div>
1791
+
1792
+ ${summary ? `<div class="naturefyi-guide-summary" style="font-size:0.875rem;color:#475569;line-height:1.5;margin-bottom:8px;">
1793
+ ${escapeHTML4(summary)}
1794
+ </div>` : ""}
1795
+
1796
+ ${toc ? `<div class="naturefyi-guide-toc" style="margin:8px 0;padding:8px;background:#f8fafc;border-radius:6px;border-left:3px solid ${config.accent};">
1797
+ <div style="font-size:0.75rem;font-weight:600;color:#64748b;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.05em;">Contents</div>
1798
+ <ol style="margin:0;padding-left:16px;font-size:0.8rem;color:#475569;">
1799
+ ${toc.map(
1800
+ (entry) => `<li style="margin:2px 0;">${escapeHTML4(entry.title)}</li>`
1801
+ ).join("")}
1802
+ </ol>
1803
+ </div>` : ""}
1804
+
1805
+ <div class="naturefyi-actions" style="margin-top:10px;">
1806
+ <a class="naturefyi-view-link" href="${escapeHTML4(guideUrl)}" target="_blank" rel="noopener"
1807
+ style="color:${config.accent}">
1808
+ Read guide on ${escapeHTML4(config.name)} ${externalLinkIcon}
1809
+ </a>
1810
+ </div>
1811
+ </div>
1812
+ ${poweredByHTML(config)}
1813
+ `;
1814
+ if (el.dataset.noSnippet !== "true") {
1815
+ injectArticle(
1816
+ {
1817
+ name: guide.title,
1818
+ description: summary,
1819
+ url: guideUrl,
1820
+ ...datePublished ? { datePublished } : {}
1821
+ },
1822
+ config.domain,
1823
+ config.name
1824
+ );
1825
+ }
1826
+ }).catch(() => {
1827
+ renderError(container, `Guide "${escapeHTML4(slug)}" not found.`, config);
1828
+ });
1829
+ }
1830
+
1831
+ // src/widgets/search.ts
1832
+ function escapeHTML5(str) {
1833
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1834
+ }
1835
+ var TYPE_LABELS = {
1836
+ species: "Species",
1837
+ bird: "Bird",
1838
+ fish: "Fish",
1839
+ plant: "Plant",
1840
+ dinosaur: "Dinosaur",
1841
+ glossary: "Glossary",
1842
+ guide: "Guide",
1843
+ faq: "FAQ"
1844
+ };
1845
+ 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>`;
1846
+ function initSearchWidget(el, config) {
1847
+ var _a;
1848
+ const dataset = el.dataset;
1849
+ const placeholder = (_a = dataset.placeholder) != null ? _a : `Search ${config.entityName}...`;
1850
+ const shadow = createShadow(el, config);
1851
+ const container = createWidgetRoot(shadow, el, "naturefyi-search-widget");
1852
+ let isOpen = false;
1853
+ let query = "";
1854
+ let results = [];
1855
+ let selectedIndex = -1;
1856
+ let debounceTimer = null;
1857
+ container.innerHTML = `
1858
+ <div class="naturefyi-search-wrap">
1859
+ <div class="naturefyi-search-form" style="position:relative;display:flex;align-items:center;">
1860
+ <span class="naturefyi-search-icon" aria-hidden="true"
1861
+ style="position:absolute;left:10px;color:#94a3b8;pointer-events:none;">${SEARCH_ICON}</span>
1862
+ <input
1863
+ class="naturefyi-search-input"
1864
+ type="search"
1865
+ autocomplete="off"
1866
+ spellcheck="false"
1867
+ placeholder="${escapeHTML5(placeholder)}"
1868
+ aria-label="Search ${escapeHTML5(config.name)}"
1869
+ aria-autocomplete="list"
1870
+ aria-expanded="false"
1871
+ role="combobox"
1872
+ style="width:100%;padding:8px 10px 8px 32px;border:1px solid #e2e8f0;border-radius:6px;font-size:0.875rem;outline:none;background:#fff;color:#1e293b;box-sizing:border-box;"
1873
+ >
1874
+ </div>
1875
+ <div class="naturefyi-search-dropdown" role="listbox" hidden
1876
+ style="margin-top:4px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;box-shadow:0 4px 16px rgba(0,0,0,0.1);max-height:280px;overflow-y:auto;"></div>
1877
+ </div>
1878
+ ${poweredByHTML(config)}
1879
+ `;
1880
+ const input = container.querySelector(".naturefyi-search-input");
1881
+ const dropdown = container.querySelector(".naturefyi-search-dropdown");
1882
+ function getAllItems() {
1883
+ return Array.from(dropdown.querySelectorAll(".naturefyi-search-result-item"));
1884
+ }
1885
+ function setSelectedIndex(idx) {
1886
+ const items = getAllItems();
1887
+ items.forEach((item, i) => {
1888
+ const anchor = item;
1889
+ if (i === idx) {
1890
+ anchor.style.background = `${config.accent}15`;
1891
+ anchor.style.outline = `2px solid ${config.accent}`;
1892
+ anchor.style.outlineOffset = "-2px";
1893
+ } else {
1894
+ anchor.style.background = "";
1895
+ anchor.style.outline = "";
1896
+ }
1897
+ });
1898
+ selectedIndex = idx;
1899
+ }
1900
+ function openDropdown() {
1901
+ isOpen = true;
1902
+ dropdown.hidden = false;
1903
+ input.setAttribute("aria-expanded", "true");
1904
+ }
1905
+ function closeDropdown() {
1906
+ isOpen = false;
1907
+ dropdown.hidden = true;
1908
+ input.setAttribute("aria-expanded", "false");
1909
+ selectedIndex = -1;
1910
+ }
1911
+ function renderDropdown() {
1912
+ var _a2, _b, _c, _d;
1913
+ if (results.length === 0) {
1914
+ dropdown.innerHTML = `
1915
+ <div style="padding:12px 14px;font-size:0.85rem;color:#64748b;">
1916
+ No results for <strong>${escapeHTML5(query)}</strong>
1917
+ </div>
1918
+ `;
1919
+ return;
1920
+ }
1921
+ let html = "";
1922
+ for (const item of results) {
1923
+ const typeLabel = item.type ? (_a2 = TYPE_LABELS[item.type]) != null ? _a2 : item.type : null;
1924
+ const desc = (_d = (_c = (_b = item.description) != null ? _b : item.excerpt) != null ? _c : item.subtitle) != null ? _d : "";
1925
+ const href = item.url ? item.url.startsWith("http") ? item.url : `https://${config.domain}${item.url}` : `https://${config.domain}/${config.entitySlug}/${escapeHTML5(item.slug)}/`;
1926
+ html += `
1927
+ <a
1928
+ class="naturefyi-search-result-item"
1929
+ href="${escapeHTML5(href)}"
1930
+ target="_blank"
1931
+ rel="noopener"
1932
+ role="option"
1933
+ tabindex="-1"
1934
+ style="display:block;padding:8px 14px;text-decoration:none;color:inherit;border-bottom:1px solid #f1f5f9;transition:background 0.1s;"
1935
+ >
1936
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
1937
+ <span style="font-size:0.875rem;font-weight:500;color:#1e293b;">${escapeHTML5(item.name)}</span>
1938
+ ${typeLabel ? `<span style="font-size:0.7rem;padding:1px 6px;border-radius:10px;background:${config.accent}20;color:${config.accent};white-space:nowrap;flex-shrink:0;font-weight:500;">${escapeHTML5(typeLabel)}</span>` : ""}
1939
+ </div>
1940
+ ${desc ? `<div style="font-size:0.75rem;color:#64748b;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escapeHTML5(desc)}</div>` : ""}
1941
+ </a>
1942
+ `;
1943
+ }
1944
+ dropdown.innerHTML = html;
1945
+ }
1946
+ async function doSearch(q) {
1947
+ var _a2;
1948
+ if (!q.trim()) {
1949
+ closeDropdown();
1950
+ return;
1951
+ }
1952
+ const searchUrl = `https://${config.domain}/api/v1/search/?q=${encodeURIComponent(q)}&limit=10`;
1953
+ try {
1954
+ const response = await fetch(searchUrl, {
1955
+ headers: { Accept: "application/json" }
1956
+ });
1957
+ if (!response.ok) throw new Error(`Search failed: ${response.status}`);
1958
+ const data = await response.json();
1959
+ results = (_a2 = data.results) != null ? _a2 : [];
1960
+ } catch (e) {
1961
+ results = [];
1962
+ }
1963
+ renderDropdown();
1964
+ openDropdown();
1965
+ setSelectedIndex(-1);
1966
+ }
1967
+ input.addEventListener("input", () => {
1968
+ query = input.value;
1969
+ if (debounceTimer !== null) {
1970
+ clearTimeout(debounceTimer);
1971
+ }
1972
+ if (!query.trim()) {
1973
+ closeDropdown();
1974
+ return;
1975
+ }
1976
+ debounceTimer = setTimeout(() => {
1977
+ void doSearch(query);
1978
+ }, 300);
1979
+ });
1980
+ input.addEventListener("keydown", (e) => {
1981
+ if (!isOpen) return;
1982
+ const items = getAllItems();
1983
+ const total = items.length;
1984
+ if (e.key === "ArrowDown") {
1985
+ e.preventDefault();
1986
+ setSelectedIndex(selectedIndex < total - 1 ? selectedIndex + 1 : 0);
1987
+ } else if (e.key === "ArrowUp") {
1988
+ e.preventDefault();
1989
+ setSelectedIndex(selectedIndex > 0 ? selectedIndex - 1 : total - 1);
1990
+ } else if (e.key === "Enter") {
1991
+ e.preventDefault();
1992
+ if (selectedIndex >= 0 && items[selectedIndex]) {
1993
+ items[selectedIndex].click();
1994
+ } else {
1995
+ const siteSearchUrl = `https://${config.domain}${config.searchPath}?q=${encodeURIComponent(query)}`;
1996
+ window.open(siteSearchUrl, "_blank", "noopener");
1997
+ }
1998
+ } else if (e.key === "Escape") {
1999
+ closeDropdown();
2000
+ input.blur();
2001
+ }
2002
+ });
2003
+ document.addEventListener("click", (e) => {
2004
+ if (!isOpen) return;
2005
+ if (!el.contains(e.target)) {
2006
+ closeDropdown();
2007
+ }
2008
+ });
2009
+ input.addEventListener("focus", () => {
2010
+ input.style.borderColor = config.accent;
2011
+ input.style.boxShadow = `0 0 0 2px ${config.accent}30`;
2012
+ });
2013
+ input.addEventListener("blur", () => {
2014
+ input.style.borderColor = "#e2e8f0";
2015
+ input.style.boxShadow = "";
2016
+ });
2017
+ }
2018
+
2019
+ // src/tools/iucn-badge.ts
2020
+ function escapeHTML6(str) {
2021
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2022
+ }
2023
+ function renderBadge(status, showLabel, config) {
2024
+ const colors = statusToColor(status);
2025
+ const label = statusToLabel(status);
2026
+ const code = status.toUpperCase();
2027
+ return `
2028
+ <div class="naturefyi-iucn-badge" style="display:inline-flex;align-items:center;gap:6px;">
2029
+ <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 8px;border-radius:4px;background:${colors.bg};color:${colors.text};font-size:0.8rem;font-weight:700;letter-spacing:0.05em;">
2030
+ ${escapeHTML6(code)}
2031
+ </span>
2032
+ ${showLabel ? `<span style="font-size:0.85rem;color:#374151;">${escapeHTML6(label)}</span>` : ""}
2033
+ </div>
2034
+ ${poweredByHTML(config)}
2035
+ `;
2036
+ }
2037
+ function initIUCNBadge(el, config) {
2038
+ var _a, _b;
2039
+ const dataset = el.dataset;
2040
+ const status = (_a = dataset.status) != null ? _a : "";
2041
+ const slug = (_b = dataset.slug) != null ? _b : "";
2042
+ const showLabel = dataset.showLabel !== "false";
2043
+ const shadow = createShadow(el, config);
2044
+ const container = createWidgetRoot(shadow, el, "naturefyi-iucn-badge-widget");
2045
+ if (status) {
2046
+ container.innerHTML = renderBadge(status, showLabel, config);
2047
+ return;
2048
+ }
2049
+ if (slug) {
2050
+ renderLoading(container);
2051
+ fetchAPI(config.apiBase, `${config.entitySlug}/${slug}/`).then((data) => {
2052
+ var _a2, _b2;
2053
+ const fetchedStatus = String((_b2 = (_a2 = data.conservation_status) != null ? _a2 : data.iucn_status) != null ? _b2 : "NE");
2054
+ container.innerHTML = renderBadge(fetchedStatus, showLabel, config);
2055
+ }).catch(() => {
2056
+ renderError(container, `Could not load status for "${escapeHTML6(slug)}".`, config);
2057
+ });
2058
+ return;
2059
+ }
2060
+ renderError(container, "Missing data-status or data-slug attribute.", config);
2061
+ }
2062
+
2063
+ // src/tools/water-type.ts
2064
+ function escapeHTML7(str) {
2065
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2066
+ }
2067
+ function initWaterType(el, config) {
2068
+ var _a;
2069
+ const dataset = el.dataset;
2070
+ const waterType = (_a = dataset.type) != null ? _a : "";
2071
+ const shadow = createShadow(el, config);
2072
+ const container = createWidgetRoot(shadow, el, "naturefyi-water-type-widget");
2073
+ if (!waterType) {
2074
+ renderError(container, "Missing data-type attribute.", config);
2075
+ return;
2076
+ }
2077
+ const color = waterTypeToColor(waterType);
2078
+ const label = waterTypeLabel(waterType);
2079
+ const icon = waterTypeIcon(waterType);
2080
+ container.innerHTML = `
2081
+ <div class="naturefyi-water-type-badge" style="display:inline-flex;align-items:center;gap:6px;">
2082
+ <span style="display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:6px;background:${color};color:#fff;font-size:0.85rem;font-weight:600;">
2083
+ ${icon} ${escapeHTML7(label)}
2084
+ </span>
2085
+ </div>
2086
+ ${poweredByHTML(config)}
2087
+ `;
2088
+ }
2089
+
2090
+ // src/inline/iucn-inline.ts
2091
+ function escapeHTML8(str) {
2092
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2093
+ }
2094
+ function initIUCNInline(el, config) {
2095
+ var _a;
2096
+ const dataset = el.dataset;
2097
+ const status = ((_a = dataset.status) != null ? _a : "").toUpperCase();
2098
+ const shadow = createShadow(el, config);
2099
+ const hostStyle = document.createElement("style");
2100
+ hostStyle.textContent = `:host { display: inline-flex; vertical-align: baseline; }`;
2101
+ shadow.appendChild(hostStyle);
2102
+ const wrapper = document.createElement("span");
2103
+ if (!status) {
2104
+ wrapper.textContent = "[?]";
2105
+ shadow.appendChild(wrapper);
2106
+ return;
2107
+ }
2108
+ const colors = statusToColor(status);
2109
+ const label = statusToLabel(status);
2110
+ wrapper.innerHTML = `<span style="display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:10px;background:${colors.bg};color:${colors.text};font-size:0.75rem;font-weight:600;line-height:1.4;white-space:nowrap;">[${escapeHTML8(status)}] ${escapeHTML8(label)}</span>`;
2111
+ shadow.appendChild(wrapper);
2112
+ }
2113
+
2114
+ // src/inline/water-inline.ts
2115
+ function escapeHTML9(str) {
2116
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2117
+ }
2118
+ function initWaterInline(el, config) {
2119
+ var _a;
2120
+ const dataset = el.dataset;
2121
+ const waterType = (_a = dataset.type) != null ? _a : "";
2122
+ const shadow = createShadow(el, config);
2123
+ const hostStyle = document.createElement("style");
2124
+ hostStyle.textContent = `:host { display: inline-flex; vertical-align: baseline; }`;
2125
+ shadow.appendChild(hostStyle);
2126
+ const wrapper = document.createElement("span");
2127
+ if (!waterType) {
2128
+ wrapper.textContent = "[?]";
2129
+ shadow.appendChild(wrapper);
2130
+ return;
2131
+ }
2132
+ const color = waterTypeToColor(waterType);
2133
+ const label = waterTypeLabel(waterType);
2134
+ wrapper.innerHTML = `<span style="display:inline-flex;align-items:center;padding:1px 6px;border-radius:10px;background:${color};color:#fff;font-size:0.75rem;font-weight:600;line-height:1.4;white-space:nowrap;">${escapeHTML9(label)}</span>`;
2135
+ shadow.appendChild(wrapper);
2136
+ }
2137
+
2138
+ // src/inline/taxonomy-inline.ts
2139
+ function escapeHTML10(str) {
2140
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2141
+ }
2142
+ function initTaxonomyInline(el, config) {
2143
+ var _a, _b;
2144
+ const dataset = el.dataset;
2145
+ const name = (_a = dataset.name) != null ? _a : "";
2146
+ const author = (_b = dataset.author) != null ? _b : "";
2147
+ const isExtinct = dataset.extinct === "true";
2148
+ const shadow = createShadow(el, config);
2149
+ const hostStyle = document.createElement("style");
2150
+ hostStyle.textContent = `:host { display: inline-flex; vertical-align: baseline; }`;
2151
+ shadow.appendChild(hostStyle);
2152
+ const wrapper = document.createElement("span");
2153
+ if (!name) {
2154
+ wrapper.textContent = "[?]";
2155
+ shadow.appendChild(wrapper);
2156
+ return;
2157
+ }
2158
+ const prefix = isExtinct ? "\u2020" : "";
2159
+ const authorSuffix = author ? ` <span style="font-style:normal;font-weight:400;color:#64748b;">${escapeHTML10(author)}</span>` : "";
2160
+ wrapper.innerHTML = `<span style="font-family:Georgia,'Times New Roman',serif;font-style:italic;font-size:inherit;color:inherit;">${prefix ? `<span style="font-style:normal;">${prefix}</span>\u200A` : ""}${escapeHTML10(name)}</span>${authorSuffix}`;
2161
+ shadow.appendChild(wrapper);
2162
+ }
2163
+
2164
+ // src/_entry_fishfyi.ts
2165
+ function initWidget(el, type, config) {
2166
+ const widgetStyle = el.dataset.style || "modern";
2167
+ void widgetStyle;
2168
+ switch (type) {
2169
+ case "entity":
2170
+ initEntityWidget(el, config);
2171
+ break;
2172
+ case "compare":
2173
+ initCompareWidget(el, config);
2174
+ break;
2175
+ case "glossary":
2176
+ initGlossaryWidget(el, config);
2177
+ break;
2178
+ case "guide":
2179
+ initGuideWidget(el, config);
2180
+ break;
2181
+ case "search":
2182
+ initSearchWidget(el, config);
2183
+ break;
2184
+ case "iucn-badge":
2185
+ initIUCNBadge(el, config);
2186
+ break;
2187
+ case "water-type":
2188
+ initWaterType(el, config);
2189
+ break;
2190
+ case "iucn-inline":
2191
+ initIUCNInline(el, config);
2192
+ break;
2193
+ case "water-inline":
2194
+ initWaterInline(el, config);
2195
+ break;
2196
+ case "taxonomy-inline":
2197
+ initTaxonomyInline(el, config);
2198
+ break;
2199
+ default:
2200
+ break;
2201
+ }
2202
+ }
2203
+ function lazyInit(el, callback) {
2204
+ if ("IntersectionObserver" in window) {
2205
+ const observer = new IntersectionObserver((entries) => {
2206
+ entries.forEach((entry) => {
2207
+ if (entry.isIntersecting) {
2208
+ observer.unobserve(el);
2209
+ callback();
2210
+ }
2211
+ });
2212
+ }, { rootMargin: "200px" });
2213
+ observer.observe(el);
2214
+ } else {
2215
+ callback();
2216
+ }
2217
+ }
2218
+ function processElement(el, config) {
2219
+ if (el.shadowRoot) return;
2220
+ const dataKey = config.attribute.replace("data-", "");
2221
+ const camelKey = dataKey.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
2222
+ const widgetType = el.dataset[camelKey];
2223
+ if (!widgetType) return;
2224
+ lazyInit(el, () => {
2225
+ if (!el.shadowRoot) initWidget(el, widgetType, config);
2226
+ });
2227
+ }
2228
+ function initAll(config) {
2229
+ document.querySelectorAll(`[${config.attribute}]`).forEach((el) => processElement(el, config));
2230
+ }
2231
+ (function bootstrap() {
2232
+ const config = '{"site":"fishfyi","name":"FishFYI","domain":"fishfyi.com","accent":"#06B6D4","attribute":"data-fishfyi","apiBase":"https://fishfyi.com/api/v1/","searchPath":"/search/","entityName":"Fish","entitySlug":"fish"}';
2233
+ if (document.readyState === "loading") {
2234
+ document.addEventListener("DOMContentLoaded", () => initAll(config));
2235
+ } else {
2236
+ initAll(config);
2237
+ }
2238
+ const observer = new MutationObserver((mutations) => {
2239
+ mutations.forEach((mutation) => {
2240
+ mutation.addedNodes.forEach((node) => {
2241
+ var _a;
2242
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
2243
+ const el = node;
2244
+ if (el.hasAttribute(config.attribute)) processElement(el, config);
2245
+ (_a = el.querySelectorAll) == null ? void 0 : _a.call(el, `[${config.attribute}]`).forEach((child) => processElement(child, config));
2246
+ });
2247
+ });
2248
+ });
2249
+ observer.observe(document.body || document.documentElement, { childList: true, subtree: true });
2250
+ })();
2251
+ function makeWidgetElement(widgetType, initFn, domainAttrs) {
2252
+ const observed = [...domainAttrs, "theme", "style-variant", "size"];
2253
+ return class extends HTMLElement {
2254
+ static get observedAttributes() {
2255
+ return observed;
2256
+ }
2257
+ connectedCallback() {
2258
+ if (this.shadowRoot) return;
2259
+ this._syncDataAttrs();
2260
+ initFn(this, '{"site":"fishfyi","name":"FishFYI","domain":"fishfyi.com","accent":"#06B6D4","attribute":"data-fishfyi","apiBase":"https://fishfyi.com/api/v1/","searchPath":"/search/","entityName":"Fish","entitySlug":"fish"}');
2261
+ }
2262
+ attributeChangedCallback(_name, oldVal, newVal) {
2263
+ if (oldVal === newVal || !this.shadowRoot) return;
2264
+ const shadow = this.shadowRoot;
2265
+ while (shadow.firstChild) shadow.firstChild.remove();
2266
+ this._syncDataAttrs();
2267
+ initFn(this, '{"site":"fishfyi","name":"FishFYI","domain":"fishfyi.com","accent":"#06B6D4","attribute":"data-fishfyi","apiBase":"https://fishfyi.com/api/v1/","searchPath":"/search/","entityName":"Fish","entitySlug":"fish"}');
2268
+ }
2269
+ _syncDataAttrs() {
2270
+ const attrKey = '{"site":"fishfyi","name":"FishFYI","domain":"fishfyi.com","accent":"#06B6D4","attribute":"data-fishfyi","apiBase":"https://fishfyi.com/api/v1/","searchPath":"/search/","entityName":"Fish","entitySlug":"fish"}'.attribute.replace("data-", "");
2271
+ this.dataset[attrKey] = widgetType;
2272
+ for (const a of domainAttrs) {
2273
+ const val = this.getAttribute(a);
2274
+ if (val !== null) this.dataset[a] = val;
2275
+ }
2276
+ const theme = this.getAttribute("theme");
2277
+ if (theme !== null) this.dataset.theme = theme;
2278
+ const styleVariant = this.getAttribute("style-variant");
2279
+ if (styleVariant !== null) this.dataset.style = styleVariant;
2280
+ const size = this.getAttribute("size");
2281
+ if (size !== null) this.dataset.size = size;
2282
+ }
2283
+ };
2284
+ }
2285
+ (function registerElements() {
2286
+ if (typeof customElements === "undefined") return;
2287
+ const site = '{"site":"fishfyi","name":"FishFYI","domain":"fishfyi.com","accent":"#06B6D4","attribute":"data-fishfyi","apiBase":"https://fishfyi.com/api/v1/","searchPath":"/search/","entityName":"Fish","entitySlug":"fish"}'.site;
2288
+ const defs = [
2289
+ [`${site}-entity`, initEntityWidget, ["slug"]],
2290
+ [`${site}-compare`, initCompareWidget, ["slugs"]],
2291
+ [`${site}-glossary`, initGlossaryWidget, ["slug", "letter"]],
2292
+ [`${site}-guide`, initGuideWidget, ["slug"]],
2293
+ [`${site}-search`, initSearchWidget, ["placeholder", "query"]],
2294
+ [`${site}-iucn-badge`, initIUCNBadge, ["slug", "value"]],
2295
+ [`${site}-water-type`, initWaterType, ["slug", "value"]]
2296
+ ];
2297
+ for (const [tagName, initFn, attrs] of defs) {
2298
+ if (!customElements.get(tagName)) {
2299
+ const widgetType = tagName.slice(site.length + 1);
2300
+ customElements.define(tagName, makeWidgetElement(widgetType, initFn, attrs));
2301
+ }
2302
+ }
2303
+ })();