hal-search 0.2.0 → 0.3.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.
@@ -4,6 +4,7 @@ export declare class HalSearch {
4
4
  private options;
5
5
  private pagination;
6
6
  private currentUid;
7
+ private colorOverrides;
7
8
  constructor(options: HalSearchOptions);
8
9
  /** Start a new search, resetting to page 1. */
9
10
  search(params: SearchParams): Promise<SVGSVGElement | void>;
@@ -183,24 +183,42 @@ var y = "http://www.w3.org/2000/svg", b = 800, x = 16, S = 8, C = 50, w = 28, T
183
183
  tagColor: "#444444",
184
184
  domainBg: "#dbeafe",
185
185
  domainColor: "#1a56db"
186
- }, D = 11, O = 15, k = 3, A = 18, j = 10;
187
- function M(e) {
186
+ };
187
+ function D(e) {
188
+ return e ? {
189
+ ...E,
190
+ ...e.backgroundColor && {
191
+ bg: e.backgroundColor,
192
+ cardBg: e.backgroundColor
193
+ },
194
+ ...e.textColor && {
195
+ text: e.textColor,
196
+ muted: e.textColor
197
+ },
198
+ ...e.mainColor && {
199
+ accent: e.mainColor,
200
+ link: e.mainColor
201
+ }
202
+ } : E;
203
+ }
204
+ var O = 11, k = 15, A = 3, j = 18, M = 10;
205
+ function N(e) {
188
206
  return document.createElementNS(y, e);
189
207
  }
190
- function N(e, t) {
208
+ function P(e, t) {
191
209
  for (let [n, r] of Object.entries(t)) e.setAttribute(n, String(r));
192
210
  }
193
- function P(e) {
211
+ function F(e) {
194
212
  let t = document.createElement("textarea");
195
213
  return t.innerHTML = e, t.value;
196
214
  }
197
- function F(e, t, n) {
215
+ function I(e, t, n) {
198
216
  let r = n * .56, i = Math.floor(t / r);
199
217
  return e.length > i ? e.slice(0, i - 1) + "…" : e;
200
218
  }
201
- function I(e, t, n, r, i, a) {
202
- let o = M("rect");
203
- return N(o, {
219
+ function L(e, t, n, r, i, a) {
220
+ let o = N("rect");
221
+ return P(o, {
204
222
  x: e,
205
223
  y: t,
206
224
  width: n,
@@ -209,27 +227,27 @@ function I(e, t, n, r, i, a) {
209
227
  ...a
210
228
  }), o;
211
229
  }
212
- function L(e, t, n, r) {
213
- let i = M("text");
214
- return N(i, {
230
+ function R(e, t, n, r) {
231
+ let i = N("text");
232
+ return P(i, {
215
233
  x: e,
216
234
  y: t,
217
235
  "font-family": "system-ui,-apple-system,sans-serif",
218
236
  ...r
219
237
  }), i.textContent = n, i;
220
238
  }
221
- function R(e, t) {
222
- let n = M("a");
239
+ function z(e, t) {
240
+ let n = N("a");
223
241
  return n.setAttribute("href", e), n.setAttribute("target", "_blank"), n.appendChild(t), n;
224
242
  }
225
- function z(e, t, n, r, i, a) {
243
+ function B(e, t, n, r, i, a) {
226
244
  let o = r.length * 11 * .6 + 10;
227
- return e.appendChild(I(t, n - 11, o, 17, i, { rx: 3 })), e.appendChild(L(t + 5, n - 1, r, {
245
+ return e.appendChild(L(t, n - 11, o, 17, i, { rx: 3 })), e.appendChild(R(t + 5, n - 1, r, {
228
246
  "font-size": 11,
229
247
  fill: a
230
248
  })), o + 4;
231
249
  }
232
- function B(e, t, n, r, i, a, o, s, c = k) {
250
+ function V(e, t, n, r, i, a, o, s, c = A) {
233
251
  let l = Math.floor(i / (a * .52)), u = r.split(/\s+/), d = [], f = "", p = !1;
234
252
  for (let e = 0; e < u.length; e++) {
235
253
  let t = u[e], n = f ? `${f} ${t}` : t;
@@ -243,8 +261,8 @@ function B(e, t, n, r, i, a, o, s, c = k) {
243
261
  }
244
262
  }
245
263
  !p && f ? d.push(f) : p && d.length > 0 && (d[d.length - 1] += "…");
246
- let m = M("text");
247
- N(m, {
264
+ let m = N("text");
265
+ P(m, {
248
266
  x: t,
249
267
  y: n,
250
268
  "font-size": a,
@@ -252,149 +270,149 @@ function B(e, t, n, r, i, a, o, s, c = k) {
252
270
  "font-family": "system-ui,-apple-system,sans-serif"
253
271
  });
254
272
  for (let e = 0; e < d.length; e++) {
255
- let n = M("tspan");
273
+ let n = N("tspan");
256
274
  n.setAttribute("x", String(t)), e > 0 && n.setAttribute("dy", String(o)), n.textContent = d[e], m.appendChild(n);
257
275
  }
258
276
  return e.appendChild(m), d.length * o;
259
277
  }
260
- function V(e, t) {
278
+ function H(e, t) {
261
279
  if (t !== 3 || !e.abstract_s?.[0]) return 0;
262
- let n = b - x * 4, r = Math.floor(n / (D * .52)), i = e.abstract_s[0].split(/\s+/), a = 1, o = 0;
280
+ let n = b - x * 4, r = Math.floor(n / (O * .52)), i = e.abstract_s[0].split(/\s+/), a = 1, o = 0;
263
281
  for (let e of i) if (o + e.length + (o ? 1 : 0) > r) {
264
- if (a++, o = e.length, a >= k) break;
282
+ if (a++, o = e.length, a >= A) break;
265
283
  } else o += e.length + (o ? 1 : 0);
266
- return j + A + Math.min(a, k) * O;
284
+ return M + j + Math.min(a, A) * k;
267
285
  }
268
- function H(e, t) {
269
- return t === 0 ? 44 : (t >= 2 && ((e.keyword_s?.length ?? 0) > 0 || (e.domain_s?.length ?? 0) > 0 || e.conferenceTitle_s) ? 86 : 62) + V(e, t);
286
+ function U(e, t) {
287
+ return t === 0 ? 44 : (t >= 2 && ((e.keyword_s?.length ?? 0) > 0 || (e.domain_s?.length ?? 0) > 0 || e.conferenceTitle_s) ? 86 : 62) + H(e, t);
270
288
  }
271
- function U(e, t, n) {
272
- let r = M("g"), i = H(e, t), a = x, o = b - x * 2, s = x * 2;
273
- if (r.appendChild(I(a, n, o, i, E.cardBg, {
289
+ function W(e, t, n, r) {
290
+ let i = N("g"), a = U(e, t), o = x, s = b - x * 2, c = x * 2;
291
+ if (i.appendChild(L(o, n, s, a, r.cardBg, {
274
292
  rx: 6,
275
- stroke: E.border,
293
+ stroke: r.border,
276
294
  "stroke-width": 1
277
295
  })), t === 0) {
278
- let t = F(P(e.label_s ?? e.docid ?? ""), o - x * 2, 12), i = L(s, n + 26, t, {
296
+ let t = I(F(e.label_s ?? e.docid ?? ""), s - x * 2, 12), a = R(c, n + 26, t, {
279
297
  "font-size": 12,
280
- fill: E.link
298
+ fill: r.link
281
299
  });
282
- return e.uri_s?.startsWith("http") ? r.appendChild(R(e.uri_s, i)) : (i.setAttribute("fill", E.text), r.appendChild(i)), r;
300
+ return e.uri_s?.startsWith("http") ? i.appendChild(z(e.uri_s, a)) : (a.setAttribute("fill", r.text), i.appendChild(a)), i;
283
301
  }
284
- let c = F(e.title_s?.[0] ?? e.label_s ?? "Untitled", o - x * 2, 14), l = L(s, n + 22, c, {
302
+ let l = I(e.title_s?.[0] ?? e.label_s ?? "Untitled", s - x * 2, 14), u = R(c, n + 22, l, {
285
303
  "font-size": 14,
286
304
  "font-weight": "bold",
287
- fill: e.uri_s?.startsWith("http") ? E.link : E.text
305
+ fill: e.uri_s?.startsWith("http") ? r.link : r.text
288
306
  });
289
- r.appendChild(e.uri_s?.startsWith("http") ? R(e.uri_s, l) : l);
290
- let u = n + 44, d = [];
291
- e.authFullName_s?.length && d.push(F(e.authFullName_s.join(", "), o * .5, 12)), e.publicationDate_s && d.push(e.publicationDate_s.slice(0, 4)), d.length && r.appendChild(L(s, u, d.join(" · "), {
307
+ i.appendChild(e.uri_s?.startsWith("http") ? z(e.uri_s, u) : u);
308
+ let d = n + 44, f = [];
309
+ e.authFullName_s?.length && f.push(I(e.authFullName_s.join(", "), s * .5, 12)), e.publicationDate_s && f.push(e.publicationDate_s.slice(0, 4)), f.length && i.appendChild(R(c, d, f.join(" · "), {
292
310
  "font-size": 12,
293
- fill: E.muted
311
+ fill: r.muted
294
312
  }));
295
- let f = a + o - x;
296
- if (e.openAccess_bool === !0 && (f -= 86.6, z(r, f, u, "Open Access", E.oaBg, E.oaColor)), e.docType_s && e.docType_s.toUpperCase() !== "UNDEFINED") {
313
+ let p = o + s - x;
314
+ if (e.openAccess_bool === !0 && (p -= 86.6, B(i, p, d, "Open Access", r.oaBg, r.oaColor)), e.docType_s && e.docType_s.toUpperCase() !== "UNDEFINED") {
297
315
  let t = e.docType_s;
298
- f -= t.length * 11 * .6 + 10 + 4, z(r, f, u, t, E.tagBg, E.tagColor);
316
+ p -= t.length * 11 * .6 + 10 + 4, B(i, p, d, t, r.tagBg, r.tagColor);
299
317
  }
300
- let p = u + x;
318
+ let m = d + x;
301
319
  if (t >= 2) {
302
- let t = s, i = n + 68, c = a + o - x;
303
- p = i + x;
320
+ let t = c, a = n + 68, l = o + s - x;
321
+ m = a + x;
304
322
  for (let n of e.keyword_s ?? []) {
305
323
  let e = n.length * 11 * .6 + 14;
306
- if (t + e > c) break;
307
- t += z(r, t, i, n, E.tagBg, E.tagColor);
324
+ if (t + e > l) break;
325
+ t += B(i, t, a, n, r.tagBg, r.tagColor);
308
326
  }
309
327
  for (let n of e.domain_s ?? []) {
310
328
  let e = n.length * 11 * .6 + 14;
311
- if (t + e > c) break;
312
- t += z(r, t, i, n, E.domainBg, E.domainColor);
329
+ if (t + e > l) break;
330
+ t += B(i, t, a, n, r.domainBg, r.domainColor);
313
331
  }
314
- if (e.conferenceTitle_s && t < c) {
315
- let n = F(e.conferenceTitle_s, c - t, 11);
316
- r.appendChild(L(t, i, n, {
332
+ if (e.conferenceTitle_s && t < l) {
333
+ let n = I(e.conferenceTitle_s, l - t, 11);
334
+ i.appendChild(R(t, a, n, {
317
335
  "font-size": 11,
318
- fill: E.muted,
336
+ fill: r.muted,
319
337
  "font-style": "italic"
320
338
  }));
321
339
  }
322
340
  }
323
341
  if (t === 3 && e.abstract_s?.[0]) {
324
- let t = p + j;
325
- r.appendChild(I(s, t - 4, o - x * 2, 1, E.border)), r.appendChild(L(s, t + A - 4, "Abstract", {
342
+ let t = m + M;
343
+ i.appendChild(L(c, t - 4, s - x * 2, 1, r.border)), i.appendChild(R(c, t + j - 4, "Abstract", {
326
344
  "font-size": 11,
327
- fill: E.muted,
345
+ fill: r.muted,
328
346
  "font-style": "italic"
329
- })), B(r, s, t + A + O - 2, e.abstract_s[0], o - x * 2, D, O, E.text);
347
+ })), V(i, c, t + j + k - 2, e.abstract_s[0], s - x * 2, O, k, r.text);
330
348
  }
331
- return r;
349
+ return i;
332
350
  }
333
- function W(e, t, n) {
334
- let r = e.reduce((e, n) => e + H(n, t) + S, 0), i = C + S + r + w, a = M("svg");
335
- N(a, {
351
+ function G(e, t, n, r) {
352
+ let i = D(r), a = e.reduce((e, n) => e + U(n, t) + S, 0), o = C + S + a + w, s = N("svg");
353
+ P(s, {
336
354
  width: b,
337
- height: i,
338
- viewBox: `0 0 ${b} ${i}`,
355
+ height: o,
356
+ viewBox: `0 0 ${b} ${o}`,
339
357
  xmlns: y,
340
358
  role: "img",
341
359
  "aria-label": "HAL Search Results"
342
- }), a.appendChild(I(0, 0, b, i, E.bg)), a.appendChild(I(0, 0, b, C, E.accent)), a.appendChild(L(x, 32, "HAL Search Results", {
360
+ }), s.appendChild(L(0, 0, b, o, i.bg)), s.appendChild(L(0, 0, b, C, i.accent)), s.appendChild(R(x, 32, "HAL Search Results", {
343
361
  "font-size": 18,
344
362
  "font-weight": "bold",
345
- fill: E.accentText
363
+ fill: i.accentText
346
364
  }));
347
- let { totalFound: o, currentPage: s, rows: c } = n, l = `Page ${s} / ${Math.max(1, Math.ceil(o / c))} · ${o.toLocaleString()} results`;
348
- a.appendChild(L(b - x, 32, l, {
365
+ let { totalFound: c, currentPage: l, rows: u } = n, d = `Page ${l} / ${Math.max(1, Math.ceil(c / u))} · ${c.toLocaleString()} results`;
366
+ s.appendChild(R(b - x, 32, d, {
349
367
  "font-size": 12,
350
- fill: E.accentText,
368
+ fill: i.accentText,
351
369
  "text-anchor": "end"
352
370
  }));
353
- let u = C + S;
354
- for (let n of e) a.appendChild(U(n, t, u)), u += H(n, t) + S;
355
- let d = i - w / 2 + 4;
356
- return a.appendChild(L(x, d, l, {
371
+ let f = C + S;
372
+ for (let n of e) s.appendChild(W(n, t, f, i)), f += U(n, t) + S;
373
+ let p = o - w / 2 + 4;
374
+ return s.appendChild(R(x, p, d, {
357
375
  "font-size": 11,
358
- fill: E.muted
359
- })), a.appendChild(R(T, L(b - x, d, "hal-search", {
376
+ fill: i.muted
377
+ })), s.appendChild(z(T, R(b - x, p, "hal-search", {
360
378
  "font-size": 11,
361
- fill: E.muted,
379
+ fill: i.muted,
362
380
  "text-anchor": "end"
363
- }))), a;
381
+ }))), s;
364
382
  }
365
- function G(e, t, n, r) {
383
+ function K(e, t, n, r, i) {
366
384
  if (e.innerHTML = "", t.length === 0) {
367
385
  let t = document.createElement("p");
368
386
  t.className = "hal-empty", t.textContent = "No results found.", e.appendChild(t);
369
387
  return;
370
388
  }
371
- e.appendChild(W(t, n, r));
389
+ e.appendChild(G(t, n, r, i));
372
390
  }
373
391
  //#endregion
374
392
  //#region src/styles.ts
375
- var K = "\n:root {\n --hal-accent: #0052cc;\n --hal-accent-hover: #003d99;\n --hal-bg: #ffffff;\n --hal-bg-article: #fafafa;\n --hal-border: #e0e0e0;\n --hal-text: #1a1a1a;\n --hal-text-muted: #666666;\n --hal-oa-color: #006644;\n --hal-oa-bg: #e3f5ee;\n --hal-tag-bg: #f0f0f0;\n --hal-tag-color: #444444;\n --hal-font: system-ui, -apple-system, sans-serif;\n --hal-radius: 6px;\n --hal-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.hal-results {\n font-family: var(--hal-font);\n color: var(--hal-text);\n width: 100%;\n}\n\n/* Loading state */\n.hal-loading {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 24px 0;\n color: var(--hal-text-muted);\n font-size: 0.95rem;\n}\n\n.hal-spinner {\n width: 18px;\n height: 18px;\n border: 2px solid var(--hal-border);\n border-top-color: var(--hal-accent);\n border-radius: 50%;\n animation: hal-spin 0.7s linear infinite;\n}\n\n@keyframes hal-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Error and empty states */\n.hal-error,\n.hal-empty {\n padding: 16px;\n border-radius: var(--hal-radius);\n font-size: 0.9rem;\n}\n\n.hal-error {\n background: #fff5f5;\n border: 1px solid #ffc9c9;\n color: #c92a2a;\n}\n\n.hal-empty {\n background: var(--hal-bg-article);\n border: 1px solid var(--hal-border);\n color: var(--hal-text-muted);\n}\n\n/* Article card */\n.hal-article {\n background: var(--hal-bg);\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n padding: 18px 20px;\n margin-bottom: 12px;\n box-shadow: var(--hal-shadow);\n}\n\n.hal-article:last-of-type {\n margin-bottom: 0;\n}\n\n/* Title */\n.hal-article__title {\n margin: 0 0 8px 0;\n font-size: 1rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.hal-article__title a {\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__title a:hover {\n color: var(--hal-accent-hover);\n text-decoration: underline;\n}\n\n/* Label (minimal mode) */\n.hal-article__label {\n margin: 0 0 8px 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: var(--hal-text);\n}\n\n/* Meta row */\n.hal-article__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n margin-bottom: 4px;\n}\n\n.hal-article__authors {\n font-weight: 500;\n color: var(--hal-text);\n}\n\n.hal-article__date::before {\n content: '·';\n margin-right: 6px;\n}\n\n/* Badges */\n.hal-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n letter-spacing: 0.02em;\n text-transform: uppercase;\n}\n\n.hal-badge--oa {\n background: var(--hal-oa-bg);\n color: var(--hal-oa-color);\n}\n\n/* Details section (lvl >= 2) */\n.hal-article__details {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--hal-border);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.hal-article__tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n.hal-tag {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.78rem;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n}\n\n.hal-tag--domain {\n background: #e8f0fe;\n color: #1a56c4;\n}\n\n.hal-article__conference {\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n font-style: italic;\n}\n\n.hal-article__abstract {\n font-size: 0.85rem;\n color: var(--hal-text);\n line-height: 1.55;\n margin-top: 4px;\n}\n\n.hal-article__link {\n font-size: 0.82rem;\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__link:hover {\n text-decoration: underline;\n}\n\n/* Footer credit */\n.hal-footer {\n margin-top: 12px;\n text-align: right;\n font-size: 0.75rem;\n}\n\n.hal-footer__link {\n color: var(--hal-text-muted);\n text-decoration: none;\n}\n\n.hal-footer__link:hover {\n color: var(--hal-accent);\n text-decoration: underline;\n}\n\n/* Pagination */\n.hal-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px solid var(--hal-border);\n font-size: 0.875rem;\n}\n\n.hal-pagination__info {\n color: var(--hal-text-muted);\n}\n\n.hal-pagination__btn {\n padding: 7px 16px;\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n background: var(--hal-bg);\n color: var(--hal-accent);\n font-size: 0.875rem;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.hal-pagination__btn:hover:not(:disabled) {\n background: #f0f4ff;\n border-color: var(--hal-accent);\n}\n\n.hal-pagination__btn:disabled {\n color: var(--hal-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n";
376
- function q() {
393
+ var q = "\n:root {\n --hal-accent: #0052cc;\n --hal-accent-hover: #003d99;\n --hal-bg: #ffffff;\n --hal-bg-article: #fafafa;\n --hal-border: #e0e0e0;\n --hal-text: #1a1a1a;\n --hal-text-muted: #666666;\n --hal-oa-color: #006644;\n --hal-oa-bg: #e3f5ee;\n --hal-tag-bg: #f0f0f0;\n --hal-tag-color: #444444;\n --hal-font: system-ui, -apple-system, sans-serif;\n --hal-radius: 6px;\n --hal-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.hal-results {\n font-family: var(--hal-font);\n color: var(--hal-text);\n width: 100%;\n}\n\n/* Loading state */\n.hal-loading {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 24px 0;\n color: var(--hal-text-muted);\n font-size: 0.95rem;\n}\n\n.hal-spinner {\n width: 18px;\n height: 18px;\n border: 2px solid var(--hal-border);\n border-top-color: var(--hal-accent);\n border-radius: 50%;\n animation: hal-spin 0.7s linear infinite;\n}\n\n@keyframes hal-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Error and empty states */\n.hal-error,\n.hal-empty {\n padding: 16px;\n border-radius: var(--hal-radius);\n font-size: 0.9rem;\n}\n\n.hal-error {\n background: #fff5f5;\n border: 1px solid #ffc9c9;\n color: #c92a2a;\n}\n\n.hal-empty {\n background: var(--hal-bg-article);\n border: 1px solid var(--hal-border);\n color: var(--hal-text-muted);\n}\n\n/* Article card */\n.hal-article {\n background: var(--hal-bg);\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n padding: 18px 20px;\n margin-bottom: 12px;\n box-shadow: var(--hal-shadow);\n}\n\n.hal-article:last-of-type {\n margin-bottom: 0;\n}\n\n/* Title */\n.hal-article__title {\n margin: 0 0 8px 0;\n font-size: 1rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.hal-article__title a {\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__title a:hover {\n color: var(--hal-accent-hover);\n text-decoration: underline;\n}\n\n/* Label (minimal mode) */\n.hal-article__label {\n margin: 0 0 8px 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: var(--hal-text);\n}\n\n/* Meta row */\n.hal-article__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n margin-bottom: 4px;\n}\n\n.hal-article__authors {\n font-weight: 500;\n color: var(--hal-text);\n}\n\n.hal-article__date::before {\n content: '·';\n margin-right: 6px;\n}\n\n/* Badges */\n.hal-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n letter-spacing: 0.02em;\n text-transform: uppercase;\n}\n\n.hal-badge--oa {\n background: var(--hal-oa-bg);\n color: var(--hal-oa-color);\n}\n\n/* Details section (lvl >= 2) */\n.hal-article__details {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--hal-border);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.hal-article__tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n.hal-tag {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.78rem;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n}\n\n.hal-tag--domain {\n background: #e8f0fe;\n color: #1a56c4;\n}\n\n.hal-article__conference {\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n font-style: italic;\n}\n\n.hal-article__abstract {\n font-size: 0.85rem;\n color: var(--hal-text);\n line-height: 1.55;\n margin-top: 4px;\n}\n\n.hal-article__link {\n font-size: 0.82rem;\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__link:hover {\n text-decoration: underline;\n}\n\n/* Footer credit */\n.hal-footer {\n margin-top: 12px;\n text-align: right;\n font-size: 0.75rem;\n}\n\n.hal-footer__link {\n color: var(--hal-text-muted);\n text-decoration: none;\n}\n\n.hal-footer__link:hover {\n color: var(--hal-accent);\n text-decoration: underline;\n}\n\n/* Pagination */\n.hal-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px solid var(--hal-border);\n font-size: 0.875rem;\n}\n\n.hal-pagination__info {\n color: var(--hal-text-muted);\n}\n\n.hal-pagination__btn {\n padding: 7px 16px;\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n background: var(--hal-bg);\n color: var(--hal-accent);\n font-size: 0.875rem;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.hal-pagination__btn:hover:not(:disabled) {\n background: #f0f4ff;\n border-color: var(--hal-accent);\n}\n\n.hal-pagination__btn:disabled {\n color: var(--hal-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n";
394
+ function J() {
377
395
  let e = "hal-search-styles";
378
396
  if (document.getElementById(e)) return;
379
397
  let t = document.createElement("style");
380
- t.id = e, t.textContent = K, document.head.appendChild(t);
398
+ t.id = e, t.textContent = q, document.head.appendChild(t);
381
399
  }
382
400
  //#endregion
383
401
  //#region src/HalSearch.ts
384
- var J = {
402
+ var Y = {
385
403
  lvl: 1,
386
404
  rows: 10,
387
405
  apiBase: o,
388
406
  injectStyles: !0,
389
407
  output: "html"
390
- }, Y = class {
408
+ }, X = class {
391
409
  constructor(e) {
392
- this.currentUid = "", e.container && (this.container = this._resolveContainer(e.container)), this.options = {
393
- lvl: e.lvl ?? J.lvl,
394
- rows: e.rows ?? J.rows,
395
- apiBase: e.apiBase ?? J.apiBase,
396
- injectStyles: e.injectStyles ?? J.injectStyles,
397
- output: e.output ?? J.output,
410
+ this.currentUid = "", this.colorOverrides = {}, e.container && (this.container = this._resolveContainer(e.container)), this.options = {
411
+ lvl: e.lvl ?? Y.lvl,
412
+ rows: e.rows ?? Y.rows,
413
+ apiBase: e.apiBase ?? Y.apiBase,
414
+ injectStyles: e.injectStyles ?? Y.injectStyles,
415
+ output: e.output ?? Y.output,
398
416
  onResults: e.onResults,
399
417
  onError: e.onError
400
418
  }, this.pagination = {
@@ -402,7 +420,11 @@ var J = {
402
420
  totalFound: 0,
403
421
  rows: this.options.rows,
404
422
  start: 0
405
- }, this.options.injectStyles && q(), this._applyColors(e);
423
+ }, this.options.injectStyles && J(), this.colorOverrides = {
424
+ backgroundColor: e.backgroundColor,
425
+ textColor: e.textColor,
426
+ mainColor: e.mainColor
427
+ }, this._applyColors(this.colorOverrides);
406
428
  }
407
429
  async search(e) {
408
430
  return this.currentUid = e.uid, e.rows !== void 0 && (this.options.rows = e.rows), this.pagination = {
@@ -426,7 +448,10 @@ var J = {
426
448
  return this.options.lvl = e, this._fetch(this.currentUid, this.pagination.start);
427
449
  }
428
450
  setColors(e) {
429
- this._applyColors(e);
451
+ this.colorOverrides = {
452
+ ...this.colorOverrides,
453
+ ...e
454
+ }, this._applyColors(e);
430
455
  }
431
456
  destroy() {
432
457
  this.container && (this.container.innerHTML = "");
@@ -439,9 +464,9 @@ var J = {
439
464
  this.container && p(this.container);
440
465
  try {
441
466
  let n = await c(e, this.options.lvl, this.options.rows, t, this.options.apiBase);
442
- if (this._updatePagination(n, t), this.options.output === "svg") if (this.container) G(this.container, n.response.docs, this.options.lvl, this.pagination);
467
+ if (this._updatePagination(n, t), this.options.output === "svg") if (this.container) K(this.container, n.response.docs, this.options.lvl, this.pagination, this.colorOverrides);
443
468
  else {
444
- let e = W(n.response.docs, this.options.lvl, this.pagination);
469
+ let e = G(n.response.docs, this.options.lvl, this.pagination, this.colorOverrides);
445
470
  return this.options.onResults?.(n), e;
446
471
  }
447
472
  else if (this.container) v(this.container, n.response.docs, this.options.lvl, this.pagination, (e) => {
@@ -475,14 +500,14 @@ var J = {
475
500
  };
476
501
  //#endregion
477
502
  //#region src/embed.ts
478
- function X(e) {
503
+ function Z(e) {
479
504
  let t = new URLSearchParams({ uid: e.uid });
480
505
  return e.lvl !== void 0 && t.set("lvl", String(e.lvl)), e.rows !== void 0 && t.set("rows", String(e.rows)), e.type && t.set("type", e.type), e.backgroundColor && t.set("bg", e.backgroundColor), e.textColor && t.set("text", e.textColor), e.mainColor && t.set("main", e.mainColor), `${e.embedBase}/embed.html?${t.toString()}`;
481
506
  }
482
- function Z(e) {
483
- return `<iframe src="${X(e)}" width="${e.width ?? "100%"}" height="${e.height ?? "600"}"></iframe>`;
507
+ function Q(e) {
508
+ return `<iframe src="${Z(e)}" width="${e.width ?? "100%"}" height="${e.height ?? "600"}"></iframe>`;
484
509
  }
485
510
  //#endregion
486
- export { o as DEFAULT_BASE, K as DEFAULT_CSS, Y as HalSearch, r as LEVEL_FIELDS, i as LEVEL_NAMES, W as buildArticlesSvg, Z as buildEmbedSnippet, X as buildEmbedUrl, s as buildUrl, c as fetchArticles, q as injectDefaultStyles, G as renderResultsSvg, a as resolveFields };
511
+ export { o as DEFAULT_BASE, q as DEFAULT_CSS, X as HalSearch, r as LEVEL_FIELDS, i as LEVEL_NAMES, G as buildArticlesSvg, Q as buildEmbedSnippet, Z as buildEmbedUrl, s as buildUrl, c as fetchArticles, J as injectDefaultStyles, K as renderResultsSvg, a as resolveFields, D as resolvePalette };
487
512
 
488
513
  //# sourceMappingURL=hal-search.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hal-search.es.js","names":[],"sources":["../src/levels.ts","../src/api.ts","../src/renderer.ts","../src/svg-renderer.ts","../src/styles.ts","../src/HalSearch.ts","../src/embed.ts"],"sourcesContent":["import type { DetailLevel, LevelName } from './types';\n\nconst MINIMAL_FIELDS = ['docid', 'label_s', 'uri_s'] as const;\n\nconst BASIC_FIELDS = [\n ...MINIMAL_FIELDS,\n 'title_s',\n 'authFullName_s',\n 'publicationDate_s',\n 'docType_s',\n] as const;\n\nconst DETAILED_FIELDS = [\n ...BASIC_FIELDS,\n 'keyword_s',\n 'domain_s',\n 'openAccess_bool',\n 'language_s',\n 'peerReviewing_s',\n 'conferenceTitle_s',\n] as const;\n\n/** Maps a DetailLevel to its comma-joined `fl` parameter string */\nexport const LEVEL_FIELDS: Record<DetailLevel, string> = {\n 0: MINIMAL_FIELDS.join(','),\n 1: BASIC_FIELDS.join(','),\n 2: DETAILED_FIELDS.join(','),\n 3: '*',\n};\n\nexport const LEVEL_NAMES: Record<DetailLevel, LevelName> = {\n 0: 'minimal',\n 1: 'basic',\n 2: 'detailed',\n 3: 'full',\n};\n\n/** Returns the `fl` field string for the given detail level */\nexport function resolveFields(lvl: DetailLevel): string {\n return LEVEL_FIELDS[lvl] ?? LEVEL_FIELDS[1];\n}\n","import type { HalApiResponse, DetailLevel } from './types';\nimport { resolveFields } from './levels';\n\nexport const DEFAULT_BASE = 'https://api.archives-ouvertes.fr/search/';\n\n/**\n * Builds a HAL API search URL from the given parameters.\n * Uses URLSearchParams to safely encode special characters in uid.\n */\nexport function buildUrl(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): string {\n const fl = resolveFields(lvl);\n const params = new URLSearchParams({\n q: `\"${uid}\"`,\n wt: 'json',\n fl,\n rows: String(rows),\n start: String(start),\n });\n return `${base}?${params.toString()}`;\n}\n\n/**\n * Fetches articles from the HAL API.\n * Throws on HTTP errors or non-zero API status codes.\n */\nexport async function fetchArticles(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): Promise<HalApiResponse> {\n const url = buildUrl(uid, lvl, rows, start, base);\n const res = await fetch(url, {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`HAL API error: ${res.status} ${res.statusText}`);\n }\n\n const data: HalApiResponse = await res.json();\n\n if (data.responseHeader?.status !== undefined && data.responseHeader.status !== 0) {\n throw new Error(`HAL API returned non-zero status: ${data.responseHeader.status}`);\n }\n\n return data;\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className) node.className = className;\n return node;\n}\n\nfunction text(content: string): Text {\n return document.createTextNode(content);\n}\n\n/** Decodes HTML entities (e.g. &#x27E8;) into their actual characters. */\nfunction decodeEntities(raw: string): string {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = raw;\n return textarea.value;\n}\n\n/** Creates an <a> element with href validated to start with https:// */\nfunction safeLink(href: string | undefined, label: string, className?: string): HTMLAnchorElement {\n const a = el('a', className);\n if (href && (href.startsWith('https://') || href.startsWith('http://'))) {\n a.href = href;\n }\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.textContent = label;\n return a;\n}\n\n// ---------------------------------------------------------------------------\n// State renderers\n// ---------------------------------------------------------------------------\n\nexport function renderLoading(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-loading');\n const spinner = el('div', 'hal-spinner');\n wrap.appendChild(spinner);\n wrap.appendChild(text('Loading…'));\n container.appendChild(wrap);\n}\n\nexport function renderError(container: HTMLElement, err: Error): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-error');\n wrap.textContent = `Error: ${err.message}`;\n container.appendChild(wrap);\n}\n\nexport function renderEmpty(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-empty');\n wrap.textContent = 'No results found.';\n container.appendChild(wrap);\n}\n\n// ---------------------------------------------------------------------------\n// Article card\n// ---------------------------------------------------------------------------\n\nfunction buildArticleCard(doc: HalDoc, lvl: DetailLevel): HTMLElement {\n const article = el('article', 'hal-article');\n if (doc.docid) article.dataset.docid = doc.docid;\n if (doc.docType_s) article.dataset.doctype = doc.docType_s;\n\n // --- Header ---\n const header = el('header');\n\n if (lvl >= 1) {\n // Title + link\n const h3 = el('h3', 'hal-article__title');\n const titleText = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n h3.appendChild(safeLink(doc.uri_s, titleText));\n header.appendChild(h3);\n\n // Meta row\n const meta = el('div', 'hal-article__meta');\n\n if (doc.authFullName_s?.length) {\n const authors = el('span', 'hal-article__authors');\n authors.textContent = doc.authFullName_s.join(', ');\n meta.appendChild(authors);\n }\n\n if (doc.publicationDate_s) {\n const date = el('span', 'hal-article__date');\n // Show only the year if it's a full date string\n date.textContent = doc.publicationDate_s.slice(0, 4);\n meta.appendChild(date);\n }\n\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const badge = el('span', 'hal-badge');\n badge.textContent = doc.docType_s;\n meta.appendChild(badge);\n }\n\n if (doc.openAccess_bool === true) {\n const oaBadge = el('span', 'hal-badge hal-badge--oa');\n oaBadge.textContent = 'Open Access';\n meta.appendChild(oaBadge);\n }\n\n header.appendChild(meta);\n } else {\n // lvl 0: just the full citation\n const div = el('div', 'hal-article__label');\n div.appendChild(safeLink(doc.uri_s, decodeEntities(doc.label_s ?? doc.docid ?? ''), 'hal-article__link'));\n header.appendChild(div);\n }\n\n article.appendChild(header);\n\n // --- Details (lvl >= 2) ---\n if (lvl >= 2) {\n const hasKeywords = doc.keyword_s && doc.keyword_s.length > 0;\n const hasDomains = doc.domain_s && doc.domain_s.length > 0;\n const hasConference = Boolean(doc.conferenceTitle_s);\n\n if (hasKeywords || hasDomains || hasConference) {\n const details = el('section', 'hal-article__details');\n\n if (hasKeywords) {\n const tagsWrap = el('div', 'hal-article__tags');\n for (const kw of doc.keyword_s!) {\n const tag = el('span', 'hal-tag');\n tag.textContent = kw;\n tagsWrap.appendChild(tag);\n }\n details.appendChild(tagsWrap);\n }\n\n if (hasDomains) {\n const domainsWrap = el('div', 'hal-article__tags');\n for (const domain of doc.domain_s!) {\n const tag = el('span', 'hal-tag hal-tag--domain');\n tag.textContent = domain;\n domainsWrap.appendChild(tag);\n }\n details.appendChild(domainsWrap);\n }\n\n if (hasConference) {\n const conf = el('div', 'hal-article__conference');\n conf.textContent = doc.conferenceTitle_s!;\n details.appendChild(conf);\n }\n\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const abstract = el('div', 'hal-article__abstract');\n abstract.textContent = doc.abstract_s[0];\n details.appendChild(abstract);\n }\n\n article.appendChild(details);\n }\n }\n\n return article;\n}\n\n// ---------------------------------------------------------------------------\n// Pagination bar\n// ---------------------------------------------------------------------------\n\nfunction buildPagination(\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): HTMLElement {\n const totalPages = Math.max(1, Math.ceil(pagination.totalFound / pagination.rows));\n const { currentPage } = pagination;\n\n const nav = el('nav', 'hal-pagination');\n nav.setAttribute('aria-label', 'Search results pages');\n\n const prevBtn = el('button', 'hal-pagination__btn');\n prevBtn.textContent = '← Previous';\n prevBtn.disabled = currentPage <= 1;\n prevBtn.addEventListener('click', () => onPageChange(currentPage - 1));\n nav.appendChild(prevBtn);\n\n const info = el('span', 'hal-pagination__info');\n info.textContent = `Page ${currentPage} of ${totalPages} (${pagination.totalFound.toLocaleString()} results)`;\n nav.appendChild(info);\n\n const nextBtn = el('button', 'hal-pagination__btn');\n nextBtn.textContent = 'Next →';\n nextBtn.disabled = currentPage >= totalPages;\n nextBtn.addEventListener('click', () => onPageChange(currentPage + 1));\n nav.appendChild(nextBtn);\n\n return nav;\n}\n\n// ---------------------------------------------------------------------------\n// Main results renderer\n// ---------------------------------------------------------------------------\n\nexport function renderResults(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): void {\n container.innerHTML = '';\n\n if (docs.length === 0) {\n renderEmpty(container);\n return;\n }\n\n const wrapper = el('div', 'hal-results');\n\n for (const doc of docs) {\n wrapper.appendChild(buildArticleCard(doc, lvl));\n }\n\n if (pagination.totalFound > pagination.rows) {\n wrapper.appendChild(buildPagination(pagination, onPageChange));\n }\n\n const footer = el('div', 'hal-footer');\n const credit = safeLink('https://github.com/JPugetGil/hal-search', 'hal-search', 'hal-footer__link');\n footer.appendChild(credit);\n wrapper.appendChild(footer);\n\n container.appendChild(wrapper);\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\nconst NS = 'http://www.w3.org/2000/svg';\nconst W = 800;\nconst PAD = 16;\nconst CARD_GAP = 8;\nconst HEADER_H = 50;\nconst FOOTER_H = 28;\nconst GITHUB_URL = 'https://github.com/JPugetGil/hal-search';\n\n/** Default colour palette — mirrors CSS-variable defaults from styles.ts */\nconst C = {\n accent: '#0052cc',\n accentText: '#ffffff',\n bg: '#f2f4f8',\n cardBg: '#ffffff',\n border: '#e0e0e0',\n text: '#1a1a1a',\n muted: '#666666',\n link: '#0052cc',\n oaBg: '#e3f5ee',\n oaColor: '#006644',\n tagBg: '#f0f0f0',\n tagColor: '#444444',\n domainBg: '#dbeafe',\n domainColor: '#1a56db',\n};\n\n// Abstract rendering constants\nconst ABSTRACT_FS = 11;\nconst ABSTRACT_LINE_H = 15;\nconst ABSTRACT_MAX_LINES = 3;\nconst ABSTRACT_LABEL_H = 18;\nconst ABSTRACT_TOP_GAP = 10;\n\n// ---------------------------------------------------------------------------\n// SVG helpers\n// ---------------------------------------------------------------------------\n\nfunction svgEl<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {\n return document.createElementNS(NS, tag) as SVGElementTagNameMap[K];\n}\n\nfunction set(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));\n}\n\nfunction decodeEntities(raw: string): string {\n const ta = document.createElement('textarea');\n ta.innerHTML = raw;\n return ta.value;\n}\n\n/** Approximate truncation (SVG has no native text-measurement API). */\nfunction truncate(text: string, maxPx: number, fontSize: number): string {\n const charW = fontSize * 0.56;\n const max = Math.floor(maxPx / charW);\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n\nfunction mkRect(\n x: number, y: number, w: number, h: number,\n fill: string,\n extra?: Record<string, string | number>,\n): SVGRectElement {\n const r = svgEl('rect');\n set(r, { x, y, width: w, height: h, fill, ...extra });\n return r;\n}\n\nfunction mkText(\n x: number, y: number, content: string,\n extra?: Record<string, string | number>,\n): SVGTextElement {\n const t = svgEl('text');\n set(t, { x, y, 'font-family': 'system-ui,-apple-system,sans-serif', ...extra });\n t.textContent = content;\n return t;\n}\n\nfunction mkLink(href: string, child: SVGElement): SVGAElement {\n const a = svgEl('a');\n a.setAttribute('href', href);\n a.setAttribute('target', '_blank');\n a.appendChild(child);\n return a;\n}\n\n/**\n * Renders a pill badge anchored at (x, baseline-y).\n * Returns the total pixel width consumed, including a 4 px trailing gap.\n */\nfunction pill(\n parent: SVGElement,\n x: number, y: number,\n label: string,\n bg: string, color: string,\n): number {\n const fs = 11;\n const ph = 5, pv = 3;\n const bw = label.length * fs * 0.6 + ph * 2;\n const bh = fs + pv * 2;\n parent.appendChild(mkRect(x, y - fs, bw, bh, bg, { rx: 3 }));\n parent.appendChild(mkText(x + ph, y - 1, label, { 'font-size': fs, fill: color }));\n return bw + 4;\n}\n\n/**\n * Word-wraps `content` into SVG tspan elements appended to a <text> node.\n * Returns the total pixel height consumed (lines × lineHeight).\n */\nfunction wrapText(\n parent: SVGElement,\n x: number, baseY: number,\n content: string,\n maxPx: number,\n fontSize: number,\n lineHeight: number,\n fill: string,\n maxLines = ABSTRACT_MAX_LINES,\n): number {\n const maxChars = Math.floor(maxPx / (fontSize * 0.52));\n const words = content.split(/\\s+/);\n const lines: string[] = [];\n let cur = '';\n let truncated = false;\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i]!;\n const test = cur ? `${cur} ${word}` : word;\n if (test.length <= maxChars) {\n cur = test;\n } else {\n if (cur) lines.push(cur);\n if (lines.length >= maxLines) { truncated = true; break; }\n cur = word;\n }\n }\n if (!truncated && cur) {\n lines.push(cur);\n } else if (truncated && lines.length > 0) {\n lines[lines.length - 1] += '…';\n }\n\n const t = svgEl('text');\n set(t, { x, y: baseY, 'font-size': fontSize, fill, 'font-family': 'system-ui,-apple-system,sans-serif' });\n for (let i = 0; i < lines.length; i++) {\n const ts = svgEl('tspan');\n ts.setAttribute('x', String(x));\n if (i > 0) ts.setAttribute('dy', String(lineHeight));\n ts.textContent = lines[i]!;\n t.appendChild(ts);\n }\n parent.appendChild(t);\n return lines.length * lineHeight;\n}\n\n// ---------------------------------------------------------------------------\n// Card geometry\n// ---------------------------------------------------------------------------\n\n/** Returns the extra height added by the abstract section, or 0 if absent. */\nfunction abstractExtraHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl !== 3 || !doc.abstract_s?.[0]) return 0;\n // Pre-estimate the number of wrapped lines\n const maxPx = W - PAD * 4;\n const maxChars = Math.floor(maxPx / (ABSTRACT_FS * 0.52));\n const words = doc.abstract_s[0].split(/\\s+/);\n let lines = 1;\n let chars = 0;\n for (const word of words) {\n if (chars + word.length + (chars ? 1 : 0) > maxChars) {\n lines++;\n chars = word.length;\n if (lines >= ABSTRACT_MAX_LINES) break;\n } else {\n chars += word.length + (chars ? 1 : 0);\n }\n }\n return ABSTRACT_TOP_GAP + ABSTRACT_LABEL_H + Math.min(lines, ABSTRACT_MAX_LINES) * ABSTRACT_LINE_H;\n}\n\nfunction cardHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl === 0) return 44;\n const hasTagRow =\n lvl >= 2 &&\n ((doc.keyword_s?.length ?? 0) > 0 ||\n (doc.domain_s?.length ?? 0) > 0 ||\n Boolean(doc.conferenceTitle_s));\n return (hasTagRow ? 86 : 62) + abstractExtraHeight(doc, lvl);\n}\n\n// ---------------------------------------------------------------------------\n// Card builder\n// ---------------------------------------------------------------------------\n\nfunction buildCard(doc: HalDoc, lvl: DetailLevel, cardY: number): SVGElement {\n const g = svgEl('g');\n const h = cardHeight(doc, lvl);\n const cx = PAD;\n const cw = W - PAD * 2;\n const ix = PAD * 2; // inner-x (text left margin)\n\n // Card background\n g.appendChild(mkRect(cx, cardY, cw, h, C.cardBg, {\n rx: 6, stroke: C.border, 'stroke-width': 1,\n }));\n\n // ── Level 0: citation label only ──────────────────────────────────────────\n if (lvl === 0) {\n const raw = decodeEntities(doc.label_s ?? doc.docid ?? '');\n const label = truncate(raw, cw - PAD * 2, 12);\n const t = mkText(ix, cardY + 26, label, { 'font-size': 12, fill: C.link });\n if (doc.uri_s?.startsWith('http')) {\n g.appendChild(mkLink(doc.uri_s, t));\n } else {\n t.setAttribute('fill', C.text);\n g.appendChild(t);\n }\n return g;\n }\n\n // ── Level 1+: title row ──────────────────────────────────────────────────\n const titleRaw = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n const titleStr = truncate(titleRaw, cw - PAD * 2, 14);\n const titleEl = mkText(ix, cardY + 22, titleStr, {\n 'font-size': 14,\n 'font-weight': 'bold',\n fill: doc.uri_s?.startsWith('http') ? C.link : C.text,\n });\n g.appendChild(doc.uri_s?.startsWith('http') ? mkLink(doc.uri_s, titleEl) : titleEl);\n\n // ── Meta row ─────────────────────────────────────────────────────────────\n const metaY = cardY + 44;\n\n // Left: \"authors · year\"\n const metaParts: string[] = [];\n if (doc.authFullName_s?.length) {\n metaParts.push(truncate(doc.authFullName_s.join(', '), cw * 0.5, 12));\n }\n if (doc.publicationDate_s) {\n metaParts.push(doc.publicationDate_s.slice(0, 4));\n }\n if (metaParts.length) {\n g.appendChild(mkText(ix, metaY, metaParts.join(' · '), {\n 'font-size': 12, fill: C.muted,\n }));\n }\n\n // Right: badges (rendered right-to-left so order is docType | OA visually)\n let badgeRight = cx + cw - PAD;\n if (doc.openAccess_bool === true) {\n const label = 'Open Access';\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.oaBg, C.oaColor);\n }\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const label = doc.docType_s;\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.tagBg, C.tagColor);\n }\n\n // ── Tags row (lvl ≥ 2) ───────────────────────────────────────────────────\n let nextSectionY = metaY + PAD;\n if (lvl >= 2) {\n let tagX = ix;\n const tagY = cardY + 68;\n const tagRight = cx + cw - PAD;\n nextSectionY = tagY + PAD;\n\n for (const kw of (doc.keyword_s ?? [])) {\n const bw = kw.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, kw, C.tagBg, C.tagColor);\n }\n for (const domain of (doc.domain_s ?? [])) {\n const bw = domain.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, domain, C.domainBg, C.domainColor);\n }\n if (doc.conferenceTitle_s && tagX < tagRight) {\n const label = truncate(doc.conferenceTitle_s, tagRight - tagX, 11);\n g.appendChild(mkText(tagX, tagY, label, {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n }\n }\n\n // ── Abstract (lvl 3 only) ─────────────────────────────────────────────────\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const absTop = nextSectionY + ABSTRACT_TOP_GAP;\n // Separator line\n g.appendChild(mkRect(ix, absTop - 4, cw - PAD * 2, 1, C.border));\n // \"Abstract\" label\n g.appendChild(mkText(ix, absTop + ABSTRACT_LABEL_H - 4, 'Abstract', {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n // Wrapped text\n wrapText(\n g,\n ix, absTop + ABSTRACT_LABEL_H + ABSTRACT_LINE_H - 2,\n doc.abstract_s[0],\n cw - PAD * 2,\n ABSTRACT_FS,\n ABSTRACT_LINE_H,\n C.text,\n );\n }\n\n return g;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Builds an SVG element representing the article list.\n * The SVG is self-contained and can be inserted into the DOM or serialised.\n */\nexport function buildArticlesSvg(\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n): SVGSVGElement {\n const cardsH = docs.reduce((s, d) => s + cardHeight(d, lvl) + CARD_GAP, 0);\n const totalH = HEADER_H + CARD_GAP + cardsH + FOOTER_H;\n\n const svg = svgEl('svg');\n set(svg, {\n width: W,\n height: totalH,\n viewBox: `0 0 ${W} ${totalH}`,\n xmlns: NS,\n role: 'img',\n 'aria-label': 'HAL Search Results',\n });\n\n // Background\n svg.appendChild(mkRect(0, 0, W, totalH, C.bg));\n\n // Header bar\n svg.appendChild(mkRect(0, 0, W, HEADER_H, C.accent));\n svg.appendChild(mkText(PAD, 32, 'HAL Search Results', {\n 'font-size': 18, 'font-weight': 'bold', fill: C.accentText,\n }));\n\n const { totalFound, currentPage, rows } = pagination;\n const totalPages = Math.max(1, Math.ceil(totalFound / rows));\n const pageInfo = `Page ${currentPage} / ${totalPages} · ${totalFound.toLocaleString()} results`;\n\n svg.appendChild(mkText(W - PAD, 32, pageInfo, {\n 'font-size': 12, fill: C.accentText, 'text-anchor': 'end',\n }));\n\n // Article cards\n let y = HEADER_H + CARD_GAP;\n for (const doc of docs) {\n svg.appendChild(buildCard(doc, lvl, y));\n y += cardHeight(doc, lvl) + CARD_GAP;\n }\n\n // Footer: pagination info (left) + GitHub credit (right)\n const footerY = totalH - FOOTER_H / 2 + 4;\n svg.appendChild(mkText(PAD, footerY, pageInfo, {\n 'font-size': 11, fill: C.muted,\n }));\n svg.appendChild(mkLink(\n GITHUB_URL,\n mkText(W - PAD, footerY, 'hal-search', {\n 'font-size': 11, fill: C.muted, 'text-anchor': 'end',\n }),\n ));\n\n return svg;\n}\n\n/**\n * Clears `container` and renders the article list as an inline SVG.\n * Falls back to the standard `.hal-empty` paragraph when `docs` is empty.\n */\nexport function renderResultsSvg(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n): void {\n container.innerHTML = '';\n if (docs.length === 0) {\n const p = document.createElement('p');\n p.className = 'hal-empty';\n p.textContent = 'No results found.';\n container.appendChild(p);\n return;\n }\n container.appendChild(buildArticlesSvg(docs, lvl, pagination));\n}\n","export const DEFAULT_CSS = `\n:root {\n --hal-accent: #0052cc;\n --hal-accent-hover: #003d99;\n --hal-bg: #ffffff;\n --hal-bg-article: #fafafa;\n --hal-border: #e0e0e0;\n --hal-text: #1a1a1a;\n --hal-text-muted: #666666;\n --hal-oa-color: #006644;\n --hal-oa-bg: #e3f5ee;\n --hal-tag-bg: #f0f0f0;\n --hal-tag-color: #444444;\n --hal-font: system-ui, -apple-system, sans-serif;\n --hal-radius: 6px;\n --hal-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.hal-results {\n font-family: var(--hal-font);\n color: var(--hal-text);\n width: 100%;\n}\n\n/* Loading state */\n.hal-loading {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 24px 0;\n color: var(--hal-text-muted);\n font-size: 0.95rem;\n}\n\n.hal-spinner {\n width: 18px;\n height: 18px;\n border: 2px solid var(--hal-border);\n border-top-color: var(--hal-accent);\n border-radius: 50%;\n animation: hal-spin 0.7s linear infinite;\n}\n\n@keyframes hal-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Error and empty states */\n.hal-error,\n.hal-empty {\n padding: 16px;\n border-radius: var(--hal-radius);\n font-size: 0.9rem;\n}\n\n.hal-error {\n background: #fff5f5;\n border: 1px solid #ffc9c9;\n color: #c92a2a;\n}\n\n.hal-empty {\n background: var(--hal-bg-article);\n border: 1px solid var(--hal-border);\n color: var(--hal-text-muted);\n}\n\n/* Article card */\n.hal-article {\n background: var(--hal-bg);\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n padding: 18px 20px;\n margin-bottom: 12px;\n box-shadow: var(--hal-shadow);\n}\n\n.hal-article:last-of-type {\n margin-bottom: 0;\n}\n\n/* Title */\n.hal-article__title {\n margin: 0 0 8px 0;\n font-size: 1rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.hal-article__title a {\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__title a:hover {\n color: var(--hal-accent-hover);\n text-decoration: underline;\n}\n\n/* Label (minimal mode) */\n.hal-article__label {\n margin: 0 0 8px 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: var(--hal-text);\n}\n\n/* Meta row */\n.hal-article__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n margin-bottom: 4px;\n}\n\n.hal-article__authors {\n font-weight: 500;\n color: var(--hal-text);\n}\n\n.hal-article__date::before {\n content: '·';\n margin-right: 6px;\n}\n\n/* Badges */\n.hal-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n letter-spacing: 0.02em;\n text-transform: uppercase;\n}\n\n.hal-badge--oa {\n background: var(--hal-oa-bg);\n color: var(--hal-oa-color);\n}\n\n/* Details section (lvl >= 2) */\n.hal-article__details {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--hal-border);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.hal-article__tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n.hal-tag {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.78rem;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n}\n\n.hal-tag--domain {\n background: #e8f0fe;\n color: #1a56c4;\n}\n\n.hal-article__conference {\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n font-style: italic;\n}\n\n.hal-article__abstract {\n font-size: 0.85rem;\n color: var(--hal-text);\n line-height: 1.55;\n margin-top: 4px;\n}\n\n.hal-article__link {\n font-size: 0.82rem;\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__link:hover {\n text-decoration: underline;\n}\n\n/* Footer credit */\n.hal-footer {\n margin-top: 12px;\n text-align: right;\n font-size: 0.75rem;\n}\n\n.hal-footer__link {\n color: var(--hal-text-muted);\n text-decoration: none;\n}\n\n.hal-footer__link:hover {\n color: var(--hal-accent);\n text-decoration: underline;\n}\n\n/* Pagination */\n.hal-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px solid var(--hal-border);\n font-size: 0.875rem;\n}\n\n.hal-pagination__info {\n color: var(--hal-text-muted);\n}\n\n.hal-pagination__btn {\n padding: 7px 16px;\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n background: var(--hal-bg);\n color: var(--hal-accent);\n font-size: 0.875rem;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.hal-pagination__btn:hover:not(:disabled) {\n background: #f0f4ff;\n border-color: var(--hal-accent);\n}\n\n.hal-pagination__btn:disabled {\n color: var(--hal-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n`;\n\n/** Injects the default CSS into <head> once. Idempotent — safe to call multiple times. */\nexport function injectDefaultStyles(): void {\n const ID = 'hal-search-styles';\n if (document.getElementById(ID)) return;\n const style = document.createElement('style');\n style.id = ID;\n style.textContent = DEFAULT_CSS;\n document.head.appendChild(style);\n}\n","import type {\n HalSearchOptions,\n SearchParams,\n PaginationState,\n HalApiResponse,\n DetailLevel,\n} from './types';\nimport { fetchArticles, DEFAULT_BASE } from './api';\nimport { renderResults, renderLoading, renderError } from './renderer';\nimport { renderResultsSvg, buildArticlesSvg } from './svg-renderer';\nimport { injectDefaultStyles } from './styles';\n\nconst DEFAULTS = {\n lvl: 1 as DetailLevel,\n rows: 10,\n apiBase: DEFAULT_BASE,\n injectStyles: true,\n output: 'html' as 'html' | 'svg',\n};\n\nexport class HalSearch {\n private readonly container?: HTMLElement;\n private options: Required<Omit<HalSearchOptions, 'container' | 'onResults' | 'onError' | 'backgroundColor' | 'textColor' | 'mainColor'>> & {\n onResults?: HalSearchOptions['onResults'];\n onError?: HalSearchOptions['onError'];\n };\n private pagination: PaginationState;\n private currentUid: string = '';\n\n constructor(options: HalSearchOptions) {\n if (options.container) {\n this.container = this._resolveContainer(options.container);\n }\n\n this.options = {\n lvl: options.lvl ?? DEFAULTS.lvl,\n rows: options.rows ?? DEFAULTS.rows,\n apiBase: options.apiBase ?? DEFAULTS.apiBase,\n injectStyles: options.injectStyles ?? DEFAULTS.injectStyles,\n output: options.output ?? DEFAULTS.output,\n onResults: options.onResults,\n onError: options.onError,\n };\n\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: 0,\n };\n\n if (this.options.injectStyles) {\n injectDefaultStyles();\n }\n\n this._applyColors(options);\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Start a new search, resetting to page 1. */\n async search(params: SearchParams): Promise<SVGSVGElement | void> {\n this.currentUid = params.uid;\n if (params.rows !== undefined) this.options.rows = params.rows;\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: params.start ?? 0,\n };\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Navigate to a specific page number (1-based). */\n async goToPage(page: number): Promise<SVGSVGElement | void> {\n const totalPages = Math.max(1, Math.ceil(this.pagination.totalFound / this.options.rows));\n const clampedPage = Math.min(Math.max(1, page), totalPages);\n const start = (clampedPage - 1) * this.options.rows;\n return this._fetch(this.currentUid, start);\n }\n\n /** Navigate to the next page. */\n async nextPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage + 1);\n }\n\n /** Navigate to the previous page. */\n async prevPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage - 1);\n }\n\n /** Change the detail level and re-fetch the current results. */\n async setLevel(lvl: DetailLevel): Promise<SVGSVGElement | void> {\n this.options.lvl = lvl;\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Update the color theme at runtime. Only provided colors are changed. */\n setColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n this._applyColors(colors);\n }\n\n /** Clear the container and remove rendered content. */\n destroy(): void {\n if (this.container) {\n this.container.innerHTML = '';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private _applyColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n if (!this.container) return;\n if (colors.backgroundColor) {\n this.container.style.setProperty('--hal-bg', colors.backgroundColor);\n }\n if (colors.textColor) {\n this.container.style.setProperty('--hal-text', colors.textColor);\n }\n if (colors.mainColor) {\n this.container.style.setProperty('--hal-accent', colors.mainColor);\n }\n }\n\n private async _fetch(uid: string, start: number): Promise<SVGSVGElement | void> {\n if (!uid) return;\n\n if (this.container) {\n renderLoading(this.container);\n }\n\n try {\n const response: HalApiResponse = await fetchArticles(\n uid,\n this.options.lvl,\n this.options.rows,\n start,\n this.options.apiBase,\n );\n\n this._updatePagination(response, start);\n\n if (this.options.output === 'svg') {\n if (this.container) {\n renderResultsSvg(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n );\n } else {\n const svg = buildArticlesSvg(\n response.response.docs,\n this.options.lvl,\n this.pagination,\n );\n this.options.onResults?.(response);\n return svg;\n }\n } else {\n if (this.container) {\n renderResults(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n (page) => { void this.goToPage(page); },\n );\n } else {\n throw new Error('HalSearch: container is required for HTML output');\n }\n }\n\n this.options.onResults?.(response);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (this.container) {\n renderError(this.container, error);\n }\n this.options.onError?.(error);\n if (!this.container && !this.options.onError) {\n throw error;\n }\n }\n }\n\n private _resolveContainer(target: HTMLElement | string): HTMLElement {\n if (typeof target === 'string') {\n const found = document.querySelector<HTMLElement>(target);\n if (!found) {\n throw new Error(`HalSearch: container not found for selector \"${target}\"`);\n }\n return found;\n }\n return target;\n }\n\n private _updatePagination(response: HalApiResponse, start: number): void {\n const { numFound } = response.response;\n this.pagination = {\n currentPage: Math.floor(start / this.options.rows) + 1,\n totalFound: numFound,\n rows: this.options.rows,\n start,\n };\n }\n}\n","import type { DetailLevel } from './types';\n\nexport interface EmbedOptions {\n /** Base URL where embed.html is hosted */\n embedBase: string;\n /** Search query or author UID */\n uid: string;\n /** Detail level 0-3 */\n lvl?: DetailLevel;\n /** Results per page */\n rows?: number;\n /** Render mode: 'html' (default) for interactive cards, 'svg' for a static SVG */\n type?: 'html' | 'svg';\n /** iframe width (CSS value) */\n width?: string;\n /** iframe height (CSS value) */\n height?: string;\n /** Background color for article cards */\n backgroundColor?: string;\n /** Text color for article content */\n textColor?: string;\n /** Main accent color for links and buttons */\n mainColor?: string;\n}\n\n/** Builds the URL for the embeddable page with query parameters. */\nexport function buildEmbedUrl(options: EmbedOptions): string {\n const params = new URLSearchParams({ uid: options.uid });\n if (options.lvl !== undefined) params.set('lvl', String(options.lvl));\n if (options.rows !== undefined) params.set('rows', String(options.rows));\n if (options.type) params.set('type', options.type);\n if (options.backgroundColor) params.set('bg', options.backgroundColor);\n if (options.textColor) params.set('text', options.textColor);\n if (options.mainColor) params.set('main', options.mainColor);\n return `${options.embedBase}/embed.html?${params.toString()}`;\n}\n\n/** Returns a ready-to-paste `<iframe>` HTML snippet. */\nexport function buildEmbedSnippet(options: EmbedOptions): string {\n const src = buildEmbedUrl(options);\n const width = options.width ?? '100%';\n const height = options.height ?? '600';\n return `<iframe src=\"${src}\" width=\"${width}\" height=\"${height}\"></iframe>`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hal-search.es.js","names":[],"sources":["../src/levels.ts","../src/api.ts","../src/renderer.ts","../src/svg-renderer.ts","../src/styles.ts","../src/HalSearch.ts","../src/embed.ts"],"sourcesContent":["import type { DetailLevel, LevelName } from './types';\n\nconst MINIMAL_FIELDS = ['docid', 'label_s', 'uri_s'] as const;\n\nconst BASIC_FIELDS = [\n ...MINIMAL_FIELDS,\n 'title_s',\n 'authFullName_s',\n 'publicationDate_s',\n 'docType_s',\n] as const;\n\nconst DETAILED_FIELDS = [\n ...BASIC_FIELDS,\n 'keyword_s',\n 'domain_s',\n 'openAccess_bool',\n 'language_s',\n 'peerReviewing_s',\n 'conferenceTitle_s',\n] as const;\n\n/** Maps a DetailLevel to its comma-joined `fl` parameter string */\nexport const LEVEL_FIELDS: Record<DetailLevel, string> = {\n 0: MINIMAL_FIELDS.join(','),\n 1: BASIC_FIELDS.join(','),\n 2: DETAILED_FIELDS.join(','),\n 3: '*',\n};\n\nexport const LEVEL_NAMES: Record<DetailLevel, LevelName> = {\n 0: 'minimal',\n 1: 'basic',\n 2: 'detailed',\n 3: 'full',\n};\n\n/** Returns the `fl` field string for the given detail level */\nexport function resolveFields(lvl: DetailLevel): string {\n return LEVEL_FIELDS[lvl] ?? LEVEL_FIELDS[1];\n}\n","import type { HalApiResponse, DetailLevel } from './types';\nimport { resolveFields } from './levels';\n\nexport const DEFAULT_BASE = 'https://api.archives-ouvertes.fr/search/';\n\n/**\n * Builds a HAL API search URL from the given parameters.\n * Uses URLSearchParams to safely encode special characters in uid.\n */\nexport function buildUrl(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): string {\n const fl = resolveFields(lvl);\n const params = new URLSearchParams({\n q: `\"${uid}\"`,\n wt: 'json',\n fl,\n rows: String(rows),\n start: String(start),\n });\n return `${base}?${params.toString()}`;\n}\n\n/**\n * Fetches articles from the HAL API.\n * Throws on HTTP errors or non-zero API status codes.\n */\nexport async function fetchArticles(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): Promise<HalApiResponse> {\n const url = buildUrl(uid, lvl, rows, start, base);\n const res = await fetch(url, {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`HAL API error: ${res.status} ${res.statusText}`);\n }\n\n const data: HalApiResponse = await res.json();\n\n if (data.responseHeader?.status !== undefined && data.responseHeader.status !== 0) {\n throw new Error(`HAL API returned non-zero status: ${data.responseHeader.status}`);\n }\n\n return data;\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className) node.className = className;\n return node;\n}\n\nfunction text(content: string): Text {\n return document.createTextNode(content);\n}\n\n/** Decodes HTML entities (e.g. &#x27E8;) into their actual characters. */\nfunction decodeEntities(raw: string): string {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = raw;\n return textarea.value;\n}\n\n/** Creates an <a> element with href validated to start with https:// */\nfunction safeLink(href: string | undefined, label: string, className?: string): HTMLAnchorElement {\n const a = el('a', className);\n if (href && (href.startsWith('https://') || href.startsWith('http://'))) {\n a.href = href;\n }\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.textContent = label;\n return a;\n}\n\n// ---------------------------------------------------------------------------\n// State renderers\n// ---------------------------------------------------------------------------\n\nexport function renderLoading(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-loading');\n const spinner = el('div', 'hal-spinner');\n wrap.appendChild(spinner);\n wrap.appendChild(text('Loading…'));\n container.appendChild(wrap);\n}\n\nexport function renderError(container: HTMLElement, err: Error): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-error');\n wrap.textContent = `Error: ${err.message}`;\n container.appendChild(wrap);\n}\n\nexport function renderEmpty(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-empty');\n wrap.textContent = 'No results found.';\n container.appendChild(wrap);\n}\n\n// ---------------------------------------------------------------------------\n// Article card\n// ---------------------------------------------------------------------------\n\nfunction buildArticleCard(doc: HalDoc, lvl: DetailLevel): HTMLElement {\n const article = el('article', 'hal-article');\n if (doc.docid) article.dataset.docid = doc.docid;\n if (doc.docType_s) article.dataset.doctype = doc.docType_s;\n\n // --- Header ---\n const header = el('header');\n\n if (lvl >= 1) {\n // Title + link\n const h3 = el('h3', 'hal-article__title');\n const titleText = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n h3.appendChild(safeLink(doc.uri_s, titleText));\n header.appendChild(h3);\n\n // Meta row\n const meta = el('div', 'hal-article__meta');\n\n if (doc.authFullName_s?.length) {\n const authors = el('span', 'hal-article__authors');\n authors.textContent = doc.authFullName_s.join(', ');\n meta.appendChild(authors);\n }\n\n if (doc.publicationDate_s) {\n const date = el('span', 'hal-article__date');\n // Show only the year if it's a full date string\n date.textContent = doc.publicationDate_s.slice(0, 4);\n meta.appendChild(date);\n }\n\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const badge = el('span', 'hal-badge');\n badge.textContent = doc.docType_s;\n meta.appendChild(badge);\n }\n\n if (doc.openAccess_bool === true) {\n const oaBadge = el('span', 'hal-badge hal-badge--oa');\n oaBadge.textContent = 'Open Access';\n meta.appendChild(oaBadge);\n }\n\n header.appendChild(meta);\n } else {\n // lvl 0: just the full citation\n const div = el('div', 'hal-article__label');\n div.appendChild(safeLink(doc.uri_s, decodeEntities(doc.label_s ?? doc.docid ?? ''), 'hal-article__link'));\n header.appendChild(div);\n }\n\n article.appendChild(header);\n\n // --- Details (lvl >= 2) ---\n if (lvl >= 2) {\n const hasKeywords = doc.keyword_s && doc.keyword_s.length > 0;\n const hasDomains = doc.domain_s && doc.domain_s.length > 0;\n const hasConference = Boolean(doc.conferenceTitle_s);\n\n if (hasKeywords || hasDomains || hasConference) {\n const details = el('section', 'hal-article__details');\n\n if (hasKeywords) {\n const tagsWrap = el('div', 'hal-article__tags');\n for (const kw of doc.keyword_s!) {\n const tag = el('span', 'hal-tag');\n tag.textContent = kw;\n tagsWrap.appendChild(tag);\n }\n details.appendChild(tagsWrap);\n }\n\n if (hasDomains) {\n const domainsWrap = el('div', 'hal-article__tags');\n for (const domain of doc.domain_s!) {\n const tag = el('span', 'hal-tag hal-tag--domain');\n tag.textContent = domain;\n domainsWrap.appendChild(tag);\n }\n details.appendChild(domainsWrap);\n }\n\n if (hasConference) {\n const conf = el('div', 'hal-article__conference');\n conf.textContent = doc.conferenceTitle_s!;\n details.appendChild(conf);\n }\n\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const abstract = el('div', 'hal-article__abstract');\n abstract.textContent = doc.abstract_s[0];\n details.appendChild(abstract);\n }\n\n article.appendChild(details);\n }\n }\n\n return article;\n}\n\n// ---------------------------------------------------------------------------\n// Pagination bar\n// ---------------------------------------------------------------------------\n\nfunction buildPagination(\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): HTMLElement {\n const totalPages = Math.max(1, Math.ceil(pagination.totalFound / pagination.rows));\n const { currentPage } = pagination;\n\n const nav = el('nav', 'hal-pagination');\n nav.setAttribute('aria-label', 'Search results pages');\n\n const prevBtn = el('button', 'hal-pagination__btn');\n prevBtn.textContent = '← Previous';\n prevBtn.disabled = currentPage <= 1;\n prevBtn.addEventListener('click', () => onPageChange(currentPage - 1));\n nav.appendChild(prevBtn);\n\n const info = el('span', 'hal-pagination__info');\n info.textContent = `Page ${currentPage} of ${totalPages} (${pagination.totalFound.toLocaleString()} results)`;\n nav.appendChild(info);\n\n const nextBtn = el('button', 'hal-pagination__btn');\n nextBtn.textContent = 'Next →';\n nextBtn.disabled = currentPage >= totalPages;\n nextBtn.addEventListener('click', () => onPageChange(currentPage + 1));\n nav.appendChild(nextBtn);\n\n return nav;\n}\n\n// ---------------------------------------------------------------------------\n// Main results renderer\n// ---------------------------------------------------------------------------\n\nexport function renderResults(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): void {\n container.innerHTML = '';\n\n if (docs.length === 0) {\n renderEmpty(container);\n return;\n }\n\n const wrapper = el('div', 'hal-results');\n\n for (const doc of docs) {\n wrapper.appendChild(buildArticleCard(doc, lvl));\n }\n\n if (pagination.totalFound > pagination.rows) {\n wrapper.appendChild(buildPagination(pagination, onPageChange));\n }\n\n const footer = el('div', 'hal-footer');\n const credit = safeLink('https://github.com/JPugetGil/hal-search', 'hal-search', 'hal-footer__link');\n footer.appendChild(credit);\n wrapper.appendChild(footer);\n\n container.appendChild(wrapper);\n}\n","import type { HalDoc, DetailLevel, PaginationState, SvgColorOverrides } from './types';\n\nconst NS = 'http://www.w3.org/2000/svg';\nconst W = 800;\nconst PAD = 16;\nconst CARD_GAP = 8;\nconst HEADER_H = 50;\nconst FOOTER_H = 28;\nconst GITHUB_URL = 'https://github.com/JPugetGil/hal-search';\n\n/** Default colour palette — mirrors CSS-variable defaults from styles.ts */\nconst DEFAULT_C = {\n accent: '#0052cc',\n accentText: '#ffffff',\n bg: '#f2f4f8',\n cardBg: '#ffffff',\n border: '#e0e0e0',\n text: '#1a1a1a',\n muted: '#666666',\n link: '#0052cc',\n oaBg: '#e3f5ee',\n oaColor: '#006644',\n tagBg: '#f0f0f0',\n tagColor: '#444444',\n domainBg: '#dbeafe',\n domainColor: '#1a56db',\n};\n\ntype Palette = typeof DEFAULT_C;\n\nexport function resolvePalette(overrides?: SvgColorOverrides): Palette {\n if (!overrides) return DEFAULT_C;\n return {\n ...DEFAULT_C,\n ...(overrides.backgroundColor && { bg: overrides.backgroundColor, cardBg: overrides.backgroundColor }),\n ...(overrides.textColor && { text: overrides.textColor, muted: overrides.textColor }),\n ...(overrides.mainColor && { accent: overrides.mainColor, link: overrides.mainColor }),\n };\n}\n\n// Abstract rendering constants\nconst ABSTRACT_FS = 11;\nconst ABSTRACT_LINE_H = 15;\nconst ABSTRACT_MAX_LINES = 3;\nconst ABSTRACT_LABEL_H = 18;\nconst ABSTRACT_TOP_GAP = 10;\n\n// ---------------------------------------------------------------------------\n// SVG helpers\n// ---------------------------------------------------------------------------\n\nfunction svgEl<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {\n return document.createElementNS(NS, tag) as SVGElementTagNameMap[K];\n}\n\nfunction set(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));\n}\n\nfunction decodeEntities(raw: string): string {\n const ta = document.createElement('textarea');\n ta.innerHTML = raw;\n return ta.value;\n}\n\n/** Approximate truncation (SVG has no native text-measurement API). */\nfunction truncate(text: string, maxPx: number, fontSize: number): string {\n const charW = fontSize * 0.56;\n const max = Math.floor(maxPx / charW);\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n\nfunction mkRect(\n x: number, y: number, w: number, h: number,\n fill: string,\n extra?: Record<string, string | number>,\n): SVGRectElement {\n const r = svgEl('rect');\n set(r, { x, y, width: w, height: h, fill, ...extra });\n return r;\n}\n\nfunction mkText(\n x: number, y: number, content: string,\n extra?: Record<string, string | number>,\n): SVGTextElement {\n const t = svgEl('text');\n set(t, { x, y, 'font-family': 'system-ui,-apple-system,sans-serif', ...extra });\n t.textContent = content;\n return t;\n}\n\nfunction mkLink(href: string, child: SVGElement): SVGAElement {\n const a = svgEl('a');\n a.setAttribute('href', href);\n a.setAttribute('target', '_blank');\n a.appendChild(child);\n return a;\n}\n\n/**\n * Renders a pill badge anchored at (x, baseline-y).\n * Returns the total pixel width consumed, including a 4 px trailing gap.\n */\nfunction pill(\n parent: SVGElement,\n x: number, y: number,\n label: string,\n bg: string, color: string,\n): number {\n const fs = 11;\n const ph = 5, pv = 3;\n const bw = label.length * fs * 0.6 + ph * 2;\n const bh = fs + pv * 2;\n parent.appendChild(mkRect(x, y - fs, bw, bh, bg, { rx: 3 }));\n parent.appendChild(mkText(x + ph, y - 1, label, { 'font-size': fs, fill: color }));\n return bw + 4;\n}\n\n/**\n * Word-wraps `content` into SVG tspan elements appended to a <text> node.\n * Returns the total pixel height consumed (lines × lineHeight).\n */\nfunction wrapText(\n parent: SVGElement,\n x: number, baseY: number,\n content: string,\n maxPx: number,\n fontSize: number,\n lineHeight: number,\n fill: string,\n maxLines = ABSTRACT_MAX_LINES,\n): number {\n const maxChars = Math.floor(maxPx / (fontSize * 0.52));\n const words = content.split(/\\s+/);\n const lines: string[] = [];\n let cur = '';\n let truncated = false;\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i]!;\n const test = cur ? `${cur} ${word}` : word;\n if (test.length <= maxChars) {\n cur = test;\n } else {\n if (cur) lines.push(cur);\n if (lines.length >= maxLines) { truncated = true; break; }\n cur = word;\n }\n }\n if (!truncated && cur) {\n lines.push(cur);\n } else if (truncated && lines.length > 0) {\n lines[lines.length - 1] += '…';\n }\n\n const t = svgEl('text');\n set(t, { x, y: baseY, 'font-size': fontSize, fill, 'font-family': 'system-ui,-apple-system,sans-serif' });\n for (let i = 0; i < lines.length; i++) {\n const ts = svgEl('tspan');\n ts.setAttribute('x', String(x));\n if (i > 0) ts.setAttribute('dy', String(lineHeight));\n ts.textContent = lines[i]!;\n t.appendChild(ts);\n }\n parent.appendChild(t);\n return lines.length * lineHeight;\n}\n\n// ---------------------------------------------------------------------------\n// Card geometry\n// ---------------------------------------------------------------------------\n\n/** Returns the extra height added by the abstract section, or 0 if absent. */\nfunction abstractExtraHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl !== 3 || !doc.abstract_s?.[0]) return 0;\n // Pre-estimate the number of wrapped lines\n const maxPx = W - PAD * 4;\n const maxChars = Math.floor(maxPx / (ABSTRACT_FS * 0.52));\n const words = doc.abstract_s[0].split(/\\s+/);\n let lines = 1;\n let chars = 0;\n for (const word of words) {\n if (chars + word.length + (chars ? 1 : 0) > maxChars) {\n lines++;\n chars = word.length;\n if (lines >= ABSTRACT_MAX_LINES) break;\n } else {\n chars += word.length + (chars ? 1 : 0);\n }\n }\n return ABSTRACT_TOP_GAP + ABSTRACT_LABEL_H + Math.min(lines, ABSTRACT_MAX_LINES) * ABSTRACT_LINE_H;\n}\n\nfunction cardHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl === 0) return 44;\n const hasTagRow =\n lvl >= 2 &&\n ((doc.keyword_s?.length ?? 0) > 0 ||\n (doc.domain_s?.length ?? 0) > 0 ||\n Boolean(doc.conferenceTitle_s));\n return (hasTagRow ? 86 : 62) + abstractExtraHeight(doc, lvl);\n}\n\n// ---------------------------------------------------------------------------\n// Card builder\n// ---------------------------------------------------------------------------\n\nfunction buildCard(doc: HalDoc, lvl: DetailLevel, cardY: number, C: Palette): SVGElement {\n const g = svgEl('g');\n const h = cardHeight(doc, lvl);\n const cx = PAD;\n const cw = W - PAD * 2;\n const ix = PAD * 2; // inner-x (text left margin)\n\n // Card background\n g.appendChild(mkRect(cx, cardY, cw, h, C.cardBg, {\n rx: 6, stroke: C.border, 'stroke-width': 1,\n }));\n\n // ── Level 0: citation label only ──────────────────────────────────────────\n if (lvl === 0) {\n const raw = decodeEntities(doc.label_s ?? doc.docid ?? '');\n const label = truncate(raw, cw - PAD * 2, 12);\n const t = mkText(ix, cardY + 26, label, { 'font-size': 12, fill: C.link });\n if (doc.uri_s?.startsWith('http')) {\n g.appendChild(mkLink(doc.uri_s, t));\n } else {\n t.setAttribute('fill', C.text);\n g.appendChild(t);\n }\n return g;\n }\n\n // ── Level 1+: title row ──────────────────────────────────────────────────\n const titleRaw = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n const titleStr = truncate(titleRaw, cw - PAD * 2, 14);\n const titleEl = mkText(ix, cardY + 22, titleStr, {\n 'font-size': 14,\n 'font-weight': 'bold',\n fill: doc.uri_s?.startsWith('http') ? C.link : C.text,\n });\n g.appendChild(doc.uri_s?.startsWith('http') ? mkLink(doc.uri_s, titleEl) : titleEl);\n\n // ── Meta row ─────────────────────────────────────────────────────────────\n const metaY = cardY + 44;\n\n // Left: \"authors · year\"\n const metaParts: string[] = [];\n if (doc.authFullName_s?.length) {\n metaParts.push(truncate(doc.authFullName_s.join(', '), cw * 0.5, 12));\n }\n if (doc.publicationDate_s) {\n metaParts.push(doc.publicationDate_s.slice(0, 4));\n }\n if (metaParts.length) {\n g.appendChild(mkText(ix, metaY, metaParts.join(' · '), {\n 'font-size': 12, fill: C.muted,\n }));\n }\n\n // Right: badges (rendered right-to-left so order is docType | OA visually)\n let badgeRight = cx + cw - PAD;\n if (doc.openAccess_bool === true) {\n const label = 'Open Access';\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.oaBg, C.oaColor);\n }\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const label = doc.docType_s;\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.tagBg, C.tagColor);\n }\n\n // ── Tags row (lvl ≥ 2) ───────────────────────────────────────────────────\n let nextSectionY = metaY + PAD;\n if (lvl >= 2) {\n let tagX = ix;\n const tagY = cardY + 68;\n const tagRight = cx + cw - PAD;\n nextSectionY = tagY + PAD;\n\n for (const kw of (doc.keyword_s ?? [])) {\n const bw = kw.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, kw, C.tagBg, C.tagColor);\n }\n for (const domain of (doc.domain_s ?? [])) {\n const bw = domain.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, domain, C.domainBg, C.domainColor);\n }\n if (doc.conferenceTitle_s && tagX < tagRight) {\n const label = truncate(doc.conferenceTitle_s, tagRight - tagX, 11);\n g.appendChild(mkText(tagX, tagY, label, {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n }\n }\n\n // ── Abstract (lvl 3 only) ─────────────────────────────────────────────────\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const absTop = nextSectionY + ABSTRACT_TOP_GAP;\n // Separator line\n g.appendChild(mkRect(ix, absTop - 4, cw - PAD * 2, 1, C.border));\n // \"Abstract\" label\n g.appendChild(mkText(ix, absTop + ABSTRACT_LABEL_H - 4, 'Abstract', {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n // Wrapped text\n wrapText(\n g,\n ix, absTop + ABSTRACT_LABEL_H + ABSTRACT_LINE_H - 2,\n doc.abstract_s[0],\n cw - PAD * 2,\n ABSTRACT_FS,\n ABSTRACT_LINE_H,\n C.text,\n );\n }\n\n return g;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Builds an SVG element representing the article list.\n * The SVG is self-contained and can be inserted into the DOM or serialised.\n */\nexport function buildArticlesSvg(\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n colors?: SvgColorOverrides,\n): SVGSVGElement {\n const C = resolvePalette(colors);\n const cardsH = docs.reduce((s, d) => s + cardHeight(d, lvl) + CARD_GAP, 0);\n const totalH = HEADER_H + CARD_GAP + cardsH + FOOTER_H;\n\n const svg = svgEl('svg');\n set(svg, {\n width: W,\n height: totalH,\n viewBox: `0 0 ${W} ${totalH}`,\n xmlns: NS,\n role: 'img',\n 'aria-label': 'HAL Search Results',\n });\n\n // Background\n svg.appendChild(mkRect(0, 0, W, totalH, C.bg));\n\n // Header bar\n svg.appendChild(mkRect(0, 0, W, HEADER_H, C.accent));\n svg.appendChild(mkText(PAD, 32, 'HAL Search Results', {\n 'font-size': 18, 'font-weight': 'bold', fill: C.accentText,\n }));\n\n const { totalFound, currentPage, rows } = pagination;\n const totalPages = Math.max(1, Math.ceil(totalFound / rows));\n const pageInfo = `Page ${currentPage} / ${totalPages} · ${totalFound.toLocaleString()} results`;\n\n svg.appendChild(mkText(W - PAD, 32, pageInfo, {\n 'font-size': 12, fill: C.accentText, 'text-anchor': 'end',\n }));\n\n // Article cards\n let y = HEADER_H + CARD_GAP;\n for (const doc of docs) {\n svg.appendChild(buildCard(doc, lvl, y, C));\n y += cardHeight(doc, lvl) + CARD_GAP;\n }\n\n // Footer: pagination info (left) + GitHub credit (right)\n const footerY = totalH - FOOTER_H / 2 + 4;\n svg.appendChild(mkText(PAD, footerY, pageInfo, {\n 'font-size': 11, fill: C.muted,\n }));\n svg.appendChild(mkLink(\n GITHUB_URL,\n mkText(W - PAD, footerY, 'hal-search', {\n 'font-size': 11, fill: C.muted, 'text-anchor': 'end',\n }),\n ));\n\n return svg;\n}\n\n/**\n * Clears `container` and renders the article list as an inline SVG.\n * Falls back to the standard `.hal-empty` paragraph when `docs` is empty.\n */\nexport function renderResultsSvg(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n colors?: SvgColorOverrides,\n): void {\n container.innerHTML = '';\n if (docs.length === 0) {\n const p = document.createElement('p');\n p.className = 'hal-empty';\n p.textContent = 'No results found.';\n container.appendChild(p);\n return;\n }\n container.appendChild(buildArticlesSvg(docs, lvl, pagination, colors));\n}\n","export const DEFAULT_CSS = `\n:root {\n --hal-accent: #0052cc;\n --hal-accent-hover: #003d99;\n --hal-bg: #ffffff;\n --hal-bg-article: #fafafa;\n --hal-border: #e0e0e0;\n --hal-text: #1a1a1a;\n --hal-text-muted: #666666;\n --hal-oa-color: #006644;\n --hal-oa-bg: #e3f5ee;\n --hal-tag-bg: #f0f0f0;\n --hal-tag-color: #444444;\n --hal-font: system-ui, -apple-system, sans-serif;\n --hal-radius: 6px;\n --hal-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.hal-results {\n font-family: var(--hal-font);\n color: var(--hal-text);\n width: 100%;\n}\n\n/* Loading state */\n.hal-loading {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 24px 0;\n color: var(--hal-text-muted);\n font-size: 0.95rem;\n}\n\n.hal-spinner {\n width: 18px;\n height: 18px;\n border: 2px solid var(--hal-border);\n border-top-color: var(--hal-accent);\n border-radius: 50%;\n animation: hal-spin 0.7s linear infinite;\n}\n\n@keyframes hal-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Error and empty states */\n.hal-error,\n.hal-empty {\n padding: 16px;\n border-radius: var(--hal-radius);\n font-size: 0.9rem;\n}\n\n.hal-error {\n background: #fff5f5;\n border: 1px solid #ffc9c9;\n color: #c92a2a;\n}\n\n.hal-empty {\n background: var(--hal-bg-article);\n border: 1px solid var(--hal-border);\n color: var(--hal-text-muted);\n}\n\n/* Article card */\n.hal-article {\n background: var(--hal-bg);\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n padding: 18px 20px;\n margin-bottom: 12px;\n box-shadow: var(--hal-shadow);\n}\n\n.hal-article:last-of-type {\n margin-bottom: 0;\n}\n\n/* Title */\n.hal-article__title {\n margin: 0 0 8px 0;\n font-size: 1rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.hal-article__title a {\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__title a:hover {\n color: var(--hal-accent-hover);\n text-decoration: underline;\n}\n\n/* Label (minimal mode) */\n.hal-article__label {\n margin: 0 0 8px 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: var(--hal-text);\n}\n\n/* Meta row */\n.hal-article__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n margin-bottom: 4px;\n}\n\n.hal-article__authors {\n font-weight: 500;\n color: var(--hal-text);\n}\n\n.hal-article__date::before {\n content: '·';\n margin-right: 6px;\n}\n\n/* Badges */\n.hal-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n letter-spacing: 0.02em;\n text-transform: uppercase;\n}\n\n.hal-badge--oa {\n background: var(--hal-oa-bg);\n color: var(--hal-oa-color);\n}\n\n/* Details section (lvl >= 2) */\n.hal-article__details {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--hal-border);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.hal-article__tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n.hal-tag {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.78rem;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n}\n\n.hal-tag--domain {\n background: #e8f0fe;\n color: #1a56c4;\n}\n\n.hal-article__conference {\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n font-style: italic;\n}\n\n.hal-article__abstract {\n font-size: 0.85rem;\n color: var(--hal-text);\n line-height: 1.55;\n margin-top: 4px;\n}\n\n.hal-article__link {\n font-size: 0.82rem;\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__link:hover {\n text-decoration: underline;\n}\n\n/* Footer credit */\n.hal-footer {\n margin-top: 12px;\n text-align: right;\n font-size: 0.75rem;\n}\n\n.hal-footer__link {\n color: var(--hal-text-muted);\n text-decoration: none;\n}\n\n.hal-footer__link:hover {\n color: var(--hal-accent);\n text-decoration: underline;\n}\n\n/* Pagination */\n.hal-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px solid var(--hal-border);\n font-size: 0.875rem;\n}\n\n.hal-pagination__info {\n color: var(--hal-text-muted);\n}\n\n.hal-pagination__btn {\n padding: 7px 16px;\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n background: var(--hal-bg);\n color: var(--hal-accent);\n font-size: 0.875rem;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.hal-pagination__btn:hover:not(:disabled) {\n background: #f0f4ff;\n border-color: var(--hal-accent);\n}\n\n.hal-pagination__btn:disabled {\n color: var(--hal-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n`;\n\n/** Injects the default CSS into <head> once. Idempotent — safe to call multiple times. */\nexport function injectDefaultStyles(): void {\n const ID = 'hal-search-styles';\n if (document.getElementById(ID)) return;\n const style = document.createElement('style');\n style.id = ID;\n style.textContent = DEFAULT_CSS;\n document.head.appendChild(style);\n}\n","import type {\n HalSearchOptions,\n SearchParams,\n PaginationState,\n HalApiResponse,\n DetailLevel,\n SvgColorOverrides,\n} from './types';\nimport { fetchArticles, DEFAULT_BASE } from './api';\nimport { renderResults, renderLoading, renderError } from './renderer';\nimport { renderResultsSvg, buildArticlesSvg } from './svg-renderer';\nimport { injectDefaultStyles } from './styles';\n\nconst DEFAULTS = {\n lvl: 1 as DetailLevel,\n rows: 10,\n apiBase: DEFAULT_BASE,\n injectStyles: true,\n output: 'html' as 'html' | 'svg',\n};\n\nexport class HalSearch {\n private readonly container?: HTMLElement;\n private options: Required<Omit<HalSearchOptions, 'container' | 'onResults' | 'onError' | 'backgroundColor' | 'textColor' | 'mainColor'>> & {\n onResults?: HalSearchOptions['onResults'];\n onError?: HalSearchOptions['onError'];\n };\n private pagination: PaginationState;\n private currentUid: string = '';\n private colorOverrides: SvgColorOverrides = {};\n\n constructor(options: HalSearchOptions) {\n if (options.container) {\n this.container = this._resolveContainer(options.container);\n }\n\n this.options = {\n lvl: options.lvl ?? DEFAULTS.lvl,\n rows: options.rows ?? DEFAULTS.rows,\n apiBase: options.apiBase ?? DEFAULTS.apiBase,\n injectStyles: options.injectStyles ?? DEFAULTS.injectStyles,\n output: options.output ?? DEFAULTS.output,\n onResults: options.onResults,\n onError: options.onError,\n };\n\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: 0,\n };\n\n if (this.options.injectStyles) {\n injectDefaultStyles();\n }\n\n this.colorOverrides = {\n backgroundColor: options.backgroundColor,\n textColor: options.textColor,\n mainColor: options.mainColor,\n };\n this._applyColors(this.colorOverrides);\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Start a new search, resetting to page 1. */\n async search(params: SearchParams): Promise<SVGSVGElement | void> {\n this.currentUid = params.uid;\n if (params.rows !== undefined) this.options.rows = params.rows;\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: params.start ?? 0,\n };\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Navigate to a specific page number (1-based). */\n async goToPage(page: number): Promise<SVGSVGElement | void> {\n const totalPages = Math.max(1, Math.ceil(this.pagination.totalFound / this.options.rows));\n const clampedPage = Math.min(Math.max(1, page), totalPages);\n const start = (clampedPage - 1) * this.options.rows;\n return this._fetch(this.currentUid, start);\n }\n\n /** Navigate to the next page. */\n async nextPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage + 1);\n }\n\n /** Navigate to the previous page. */\n async prevPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage - 1);\n }\n\n /** Change the detail level and re-fetch the current results. */\n async setLevel(lvl: DetailLevel): Promise<SVGSVGElement | void> {\n this.options.lvl = lvl;\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Update the color theme at runtime. Only provided colors are changed. */\n setColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n this.colorOverrides = { ...this.colorOverrides, ...colors };\n this._applyColors(colors);\n }\n\n /** Clear the container and remove rendered content. */\n destroy(): void {\n if (this.container) {\n this.container.innerHTML = '';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private _applyColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n if (!this.container) return;\n if (colors.backgroundColor) {\n this.container.style.setProperty('--hal-bg', colors.backgroundColor);\n }\n if (colors.textColor) {\n this.container.style.setProperty('--hal-text', colors.textColor);\n }\n if (colors.mainColor) {\n this.container.style.setProperty('--hal-accent', colors.mainColor);\n }\n }\n\n private async _fetch(uid: string, start: number): Promise<SVGSVGElement | void> {\n if (!uid) return;\n\n if (this.container) {\n renderLoading(this.container);\n }\n\n try {\n const response: HalApiResponse = await fetchArticles(\n uid,\n this.options.lvl,\n this.options.rows,\n start,\n this.options.apiBase,\n );\n\n this._updatePagination(response, start);\n\n if (this.options.output === 'svg') {\n if (this.container) {\n renderResultsSvg(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n this.colorOverrides,\n );\n } else {\n const svg = buildArticlesSvg(\n response.response.docs,\n this.options.lvl,\n this.pagination,\n this.colorOverrides,\n );\n this.options.onResults?.(response);\n return svg;\n }\n } else {\n if (this.container) {\n renderResults(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n (page) => { void this.goToPage(page); },\n );\n } else {\n throw new Error('HalSearch: container is required for HTML output');\n }\n }\n\n this.options.onResults?.(response);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (this.container) {\n renderError(this.container, error);\n }\n this.options.onError?.(error);\n if (!this.container && !this.options.onError) {\n throw error;\n }\n }\n }\n\n private _resolveContainer(target: HTMLElement | string): HTMLElement {\n if (typeof target === 'string') {\n const found = document.querySelector<HTMLElement>(target);\n if (!found) {\n throw new Error(`HalSearch: container not found for selector \"${target}\"`);\n }\n return found;\n }\n return target;\n }\n\n private _updatePagination(response: HalApiResponse, start: number): void {\n const { numFound } = response.response;\n this.pagination = {\n currentPage: Math.floor(start / this.options.rows) + 1,\n totalFound: numFound,\n rows: this.options.rows,\n start,\n };\n }\n}\n","import type { DetailLevel } from './types';\n\nexport interface EmbedOptions {\n /** Base URL where embed.html is hosted */\n embedBase: string;\n /** Search query or author UID */\n uid: string;\n /** Detail level 0-3 */\n lvl?: DetailLevel;\n /** Results per page */\n rows?: number;\n /** Render mode: 'html' (default) for interactive cards, 'svg' for a static SVG */\n type?: 'html' | 'svg';\n /** iframe width (CSS value) */\n width?: string;\n /** iframe height (CSS value) */\n height?: string;\n /** Background color for article cards */\n backgroundColor?: string;\n /** Text color for article content */\n textColor?: string;\n /** Main accent color for links and buttons */\n mainColor?: string;\n}\n\n/** Builds the URL for the embeddable page with query parameters. */\nexport function buildEmbedUrl(options: EmbedOptions): string {\n const params = new URLSearchParams({ uid: options.uid });\n if (options.lvl !== undefined) params.set('lvl', String(options.lvl));\n if (options.rows !== undefined) params.set('rows', String(options.rows));\n if (options.type) params.set('type', options.type);\n if (options.backgroundColor) params.set('bg', options.backgroundColor);\n if (options.textColor) params.set('text', options.textColor);\n if (options.mainColor) params.set('main', options.mainColor);\n return `${options.embedBase}/embed.html?${params.toString()}`;\n}\n\n/** Returns a ready-to-paste `<iframe>` HTML snippet. */\nexport function buildEmbedSnippet(options: EmbedOptions): string {\n const src = buildEmbedUrl(options);\n const width = options.width ?? '100%';\n const height = options.height ?? '600';\n return `<iframe src=\"${src}\" width=\"${width}\" height=\"${height}\"></iframe>`;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.HalSearch={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=[`docid`,`label_s`,`uri_s`],n=[...t,`title_s`,`authFullName_s`,`publicationDate_s`,`docType_s`],r=[...n,`keyword_s`,`domain_s`,`openAccess_bool`,`language_s`,`peerReviewing_s`,`conferenceTitle_s`],i={0:t.join(`,`),1:n.join(`,`),2:r.join(`,`),3:`*`},a={0:`minimal`,1:`basic`,2:`detailed`,3:`full`};function o(e){return i[e]??i[1]}var s=`https://api.archives-ouvertes.fr/search/`;function c(e,t,n,r,i=s){let a=o(t);return`${i}?${new URLSearchParams({q:`"${e}"`,wt:`json`,fl:a,rows:String(n),start:String(r)}).toString()}`}async function l(e,t,n,r,i=s){let a=c(e,t,n,r,i),o=await fetch(a,{headers:{Accept:`application/json`}});if(!o.ok)throw Error(`HAL API error: ${o.status} ${o.statusText}`);let l=await o.json();if(l.responseHeader?.status!==void 0&&l.responseHeader.status!==0)throw Error(`HAL API returned non-zero status: ${l.responseHeader.status}`);return l}function u(e,t){let n=document.createElement(e);return t&&(n.className=t),n}function d(e){return document.createTextNode(e)}function f(e){let t=document.createElement(`textarea`);return t.innerHTML=e,t.value}function p(e,t,n){let r=u(`a`,n);return e&&(e.startsWith(`https://`)||e.startsWith(`http://`))&&(r.href=e),r.target=`_blank`,r.rel=`noopener noreferrer`,r.textContent=t,r}function m(e){e.innerHTML=``;let t=u(`div`,`hal-loading`),n=u(`div`,`hal-spinner`);t.appendChild(n),t.appendChild(d(`Loading…`)),e.appendChild(t)}function h(e,t){e.innerHTML=``;let n=u(`div`,`hal-error`);n.textContent=`Error: ${t.message}`,e.appendChild(n)}function g(e){e.innerHTML=``;let t=u(`div`,`hal-empty`);t.textContent=`No results found.`,e.appendChild(t)}function _(e,t){let n=u(`article`,`hal-article`);e.docid&&(n.dataset.docid=e.docid),e.docType_s&&(n.dataset.doctype=e.docType_s);let r=u(`header`);if(t>=1){let t=u(`h3`,`hal-article__title`),n=e.title_s?.[0]??e.label_s??`Untitled`;t.appendChild(p(e.uri_s,n)),r.appendChild(t);let i=u(`div`,`hal-article__meta`);if(e.authFullName_s?.length){let t=u(`span`,`hal-article__authors`);t.textContent=e.authFullName_s.join(`, `),i.appendChild(t)}if(e.publicationDate_s){let t=u(`span`,`hal-article__date`);t.textContent=e.publicationDate_s.slice(0,4),i.appendChild(t)}if(e.docType_s&&e.docType_s.toUpperCase()!==`UNDEFINED`){let t=u(`span`,`hal-badge`);t.textContent=e.docType_s,i.appendChild(t)}if(e.openAccess_bool===!0){let e=u(`span`,`hal-badge hal-badge--oa`);e.textContent=`Open Access`,i.appendChild(e)}r.appendChild(i)}else{let t=u(`div`,`hal-article__label`);t.appendChild(p(e.uri_s,f(e.label_s??e.docid??``),`hal-article__link`)),r.appendChild(t)}if(n.appendChild(r),t>=2){let r=e.keyword_s&&e.keyword_s.length>0,i=e.domain_s&&e.domain_s.length>0,a=!!e.conferenceTitle_s;if(r||i||a){let o=u(`section`,`hal-article__details`);if(r){let t=u(`div`,`hal-article__tags`);for(let n of e.keyword_s){let e=u(`span`,`hal-tag`);e.textContent=n,t.appendChild(e)}o.appendChild(t)}if(i){let t=u(`div`,`hal-article__tags`);for(let n of e.domain_s){let e=u(`span`,`hal-tag hal-tag--domain`);e.textContent=n,t.appendChild(e)}o.appendChild(t)}if(a){let t=u(`div`,`hal-article__conference`);t.textContent=e.conferenceTitle_s,o.appendChild(t)}if(t===3&&e.abstract_s?.[0]){let t=u(`div`,`hal-article__abstract`);t.textContent=e.abstract_s[0],o.appendChild(t)}n.appendChild(o)}}return n}function v(e,t){let n=Math.max(1,Math.ceil(e.totalFound/e.rows)),{currentPage:r}=e,i=u(`nav`,`hal-pagination`);i.setAttribute(`aria-label`,`Search results pages`);let a=u(`button`,`hal-pagination__btn`);a.textContent=`← Previous`,a.disabled=r<=1,a.addEventListener(`click`,()=>t(r-1)),i.appendChild(a);let o=u(`span`,`hal-pagination__info`);o.textContent=`Page ${r} of ${n} (${e.totalFound.toLocaleString()} results)`,i.appendChild(o);let s=u(`button`,`hal-pagination__btn`);return s.textContent=`Next →`,s.disabled=r>=n,s.addEventListener(`click`,()=>t(r+1)),i.appendChild(s),i}function y(e,t,n,r,i){if(e.innerHTML=``,t.length===0){g(e);return}let a=u(`div`,`hal-results`);for(let e of t)a.appendChild(_(e,n));r.totalFound>r.rows&&a.appendChild(v(r,i));let o=u(`div`,`hal-footer`),s=p(`https://github.com/JPugetGil/hal-search`,`hal-search`,`hal-footer__link`);o.appendChild(s),a.appendChild(o),e.appendChild(a)}var b=`http://www.w3.org/2000/svg`,x=800,S=16,C=8,w=50,T=28,E=`https://github.com/JPugetGil/hal-search`,D={accent:`#0052cc`,accentText:`#ffffff`,bg:`#f2f4f8`,cardBg:`#ffffff`,border:`#e0e0e0`,text:`#1a1a1a`,muted:`#666666`,link:`#0052cc`,oaBg:`#e3f5ee`,oaColor:`#006644`,tagBg:`#f0f0f0`,tagColor:`#444444`,domainBg:`#dbeafe`,domainColor:`#1a56db`},O=11,k=15,A=3,j=18,M=10;function N(e){return document.createElementNS(b,e)}function P(e,t){for(let[n,r]of Object.entries(t))e.setAttribute(n,String(r))}function F(e){let t=document.createElement(`textarea`);return t.innerHTML=e,t.value}function I(e,t,n){let r=n*.56,i=Math.floor(t/r);return e.length>i?e.slice(0,i-1)+`…`:e}function L(e,t,n,r,i,a){let o=N(`rect`);return P(o,{x:e,y:t,width:n,height:r,fill:i,...a}),o}function R(e,t,n,r){let i=N(`text`);return P(i,{x:e,y:t,"font-family":`system-ui,-apple-system,sans-serif`,...r}),i.textContent=n,i}function z(e,t){let n=N(`a`);return n.setAttribute(`href`,e),n.setAttribute(`target`,`_blank`),n.appendChild(t),n}function B(e,t,n,r,i,a){let o=r.length*11*.6+10;return e.appendChild(L(t,n-11,o,17,i,{rx:3})),e.appendChild(R(t+5,n-1,r,{"font-size":11,fill:a})),o+4}function V(e,t,n,r,i,a,o,s,c=A){let l=Math.floor(i/(a*.52)),u=r.split(/\s+/),d=[],f=``,p=!1;for(let e=0;e<u.length;e++){let t=u[e],n=f?`${f} ${t}`:t;if(n.length<=l)f=n;else{if(f&&d.push(f),d.length>=c){p=!0;break}f=t}}!p&&f?d.push(f):p&&d.length>0&&(d[d.length-1]+=`…`);let m=N(`text`);P(m,{x:t,y:n,"font-size":a,fill:s,"font-family":`system-ui,-apple-system,sans-serif`});for(let e=0;e<d.length;e++){let n=N(`tspan`);n.setAttribute(`x`,String(t)),e>0&&n.setAttribute(`dy`,String(o)),n.textContent=d[e],m.appendChild(n)}return e.appendChild(m),d.length*o}function H(e,t){if(t!==3||!e.abstract_s?.[0])return 0;let n=x-S*4,r=Math.floor(n/(O*.52)),i=e.abstract_s[0].split(/\s+/),a=1,o=0;for(let e of i)if(o+e.length+(o?1:0)>r){if(a++,o=e.length,a>=A)break}else o+=e.length+(o?1:0);return M+j+Math.min(a,A)*k}function U(e,t){return t===0?44:(t>=2&&((e.keyword_s?.length??0)>0||(e.domain_s?.length??0)>0||e.conferenceTitle_s)?86:62)+H(e,t)}function W(e,t,n){let r=N(`g`),i=U(e,t),a=S,o=x-S*2,s=S*2;if(r.appendChild(L(a,n,o,i,D.cardBg,{rx:6,stroke:D.border,"stroke-width":1})),t===0){let t=I(F(e.label_s??e.docid??``),o-S*2,12),i=R(s,n+26,t,{"font-size":12,fill:D.link});return e.uri_s?.startsWith(`http`)?r.appendChild(z(e.uri_s,i)):(i.setAttribute(`fill`,D.text),r.appendChild(i)),r}let c=I(e.title_s?.[0]??e.label_s??`Untitled`,o-S*2,14),l=R(s,n+22,c,{"font-size":14,"font-weight":`bold`,fill:e.uri_s?.startsWith(`http`)?D.link:D.text});r.appendChild(e.uri_s?.startsWith(`http`)?z(e.uri_s,l):l);let u=n+44,d=[];e.authFullName_s?.length&&d.push(I(e.authFullName_s.join(`, `),o*.5,12)),e.publicationDate_s&&d.push(e.publicationDate_s.slice(0,4)),d.length&&r.appendChild(R(s,u,d.join(` · `),{"font-size":12,fill:D.muted}));let f=a+o-S;if(e.openAccess_bool===!0&&(f-=86.6,B(r,f,u,`Open Access`,D.oaBg,D.oaColor)),e.docType_s&&e.docType_s.toUpperCase()!==`UNDEFINED`){let t=e.docType_s;f-=t.length*11*.6+10+4,B(r,f,u,t,D.tagBg,D.tagColor)}let p=u+S;if(t>=2){let t=s,i=n+68,c=a+o-S;p=i+S;for(let n of e.keyword_s??[]){let e=n.length*11*.6+14;if(t+e>c)break;t+=B(r,t,i,n,D.tagBg,D.tagColor)}for(let n of e.domain_s??[]){let e=n.length*11*.6+14;if(t+e>c)break;t+=B(r,t,i,n,D.domainBg,D.domainColor)}if(e.conferenceTitle_s&&t<c){let n=I(e.conferenceTitle_s,c-t,11);r.appendChild(R(t,i,n,{"font-size":11,fill:D.muted,"font-style":`italic`}))}}if(t===3&&e.abstract_s?.[0]){let t=p+M;r.appendChild(L(s,t-4,o-S*2,1,D.border)),r.appendChild(R(s,t+j-4,`Abstract`,{"font-size":11,fill:D.muted,"font-style":`italic`})),V(r,s,t+j+k-2,e.abstract_s[0],o-S*2,O,k,D.text)}return r}function G(e,t,n){let r=e.reduce((e,n)=>e+U(n,t)+C,0),i=w+C+r+T,a=N(`svg`);P(a,{width:x,height:i,viewBox:`0 0 ${x} ${i}`,xmlns:b,role:`img`,"aria-label":`HAL Search Results`}),a.appendChild(L(0,0,x,i,D.bg)),a.appendChild(L(0,0,x,w,D.accent)),a.appendChild(R(S,32,`HAL Search Results`,{"font-size":18,"font-weight":`bold`,fill:D.accentText}));let{totalFound:o,currentPage:s,rows:c}=n,l=`Page ${s} / ${Math.max(1,Math.ceil(o/c))} · ${o.toLocaleString()} results`;a.appendChild(R(x-S,32,l,{"font-size":12,fill:D.accentText,"text-anchor":`end`}));let u=w+C;for(let n of e)a.appendChild(W(n,t,u)),u+=U(n,t)+C;let d=i-T/2+4;return a.appendChild(R(S,d,l,{"font-size":11,fill:D.muted})),a.appendChild(z(E,R(x-S,d,`hal-search`,{"font-size":11,fill:D.muted,"text-anchor":`end`}))),a}function K(e,t,n,r){if(e.innerHTML=``,t.length===0){let t=document.createElement(`p`);t.className=`hal-empty`,t.textContent=`No results found.`,e.appendChild(t);return}e.appendChild(G(t,n,r))}var q=`
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.HalSearch={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=[`docid`,`label_s`,`uri_s`],n=[...t,`title_s`,`authFullName_s`,`publicationDate_s`,`docType_s`],r=[...n,`keyword_s`,`domain_s`,`openAccess_bool`,`language_s`,`peerReviewing_s`,`conferenceTitle_s`],i={0:t.join(`,`),1:n.join(`,`),2:r.join(`,`),3:`*`},a={0:`minimal`,1:`basic`,2:`detailed`,3:`full`};function o(e){return i[e]??i[1]}var s=`https://api.archives-ouvertes.fr/search/`;function c(e,t,n,r,i=s){let a=o(t);return`${i}?${new URLSearchParams({q:`"${e}"`,wt:`json`,fl:a,rows:String(n),start:String(r)}).toString()}`}async function l(e,t,n,r,i=s){let a=c(e,t,n,r,i),o=await fetch(a,{headers:{Accept:`application/json`}});if(!o.ok)throw Error(`HAL API error: ${o.status} ${o.statusText}`);let l=await o.json();if(l.responseHeader?.status!==void 0&&l.responseHeader.status!==0)throw Error(`HAL API returned non-zero status: ${l.responseHeader.status}`);return l}function u(e,t){let n=document.createElement(e);return t&&(n.className=t),n}function d(e){return document.createTextNode(e)}function f(e){let t=document.createElement(`textarea`);return t.innerHTML=e,t.value}function p(e,t,n){let r=u(`a`,n);return e&&(e.startsWith(`https://`)||e.startsWith(`http://`))&&(r.href=e),r.target=`_blank`,r.rel=`noopener noreferrer`,r.textContent=t,r}function m(e){e.innerHTML=``;let t=u(`div`,`hal-loading`),n=u(`div`,`hal-spinner`);t.appendChild(n),t.appendChild(d(`Loading…`)),e.appendChild(t)}function h(e,t){e.innerHTML=``;let n=u(`div`,`hal-error`);n.textContent=`Error: ${t.message}`,e.appendChild(n)}function g(e){e.innerHTML=``;let t=u(`div`,`hal-empty`);t.textContent=`No results found.`,e.appendChild(t)}function _(e,t){let n=u(`article`,`hal-article`);e.docid&&(n.dataset.docid=e.docid),e.docType_s&&(n.dataset.doctype=e.docType_s);let r=u(`header`);if(t>=1){let t=u(`h3`,`hal-article__title`),n=e.title_s?.[0]??e.label_s??`Untitled`;t.appendChild(p(e.uri_s,n)),r.appendChild(t);let i=u(`div`,`hal-article__meta`);if(e.authFullName_s?.length){let t=u(`span`,`hal-article__authors`);t.textContent=e.authFullName_s.join(`, `),i.appendChild(t)}if(e.publicationDate_s){let t=u(`span`,`hal-article__date`);t.textContent=e.publicationDate_s.slice(0,4),i.appendChild(t)}if(e.docType_s&&e.docType_s.toUpperCase()!==`UNDEFINED`){let t=u(`span`,`hal-badge`);t.textContent=e.docType_s,i.appendChild(t)}if(e.openAccess_bool===!0){let e=u(`span`,`hal-badge hal-badge--oa`);e.textContent=`Open Access`,i.appendChild(e)}r.appendChild(i)}else{let t=u(`div`,`hal-article__label`);t.appendChild(p(e.uri_s,f(e.label_s??e.docid??``),`hal-article__link`)),r.appendChild(t)}if(n.appendChild(r),t>=2){let r=e.keyword_s&&e.keyword_s.length>0,i=e.domain_s&&e.domain_s.length>0,a=!!e.conferenceTitle_s;if(r||i||a){let o=u(`section`,`hal-article__details`);if(r){let t=u(`div`,`hal-article__tags`);for(let n of e.keyword_s){let e=u(`span`,`hal-tag`);e.textContent=n,t.appendChild(e)}o.appendChild(t)}if(i){let t=u(`div`,`hal-article__tags`);for(let n of e.domain_s){let e=u(`span`,`hal-tag hal-tag--domain`);e.textContent=n,t.appendChild(e)}o.appendChild(t)}if(a){let t=u(`div`,`hal-article__conference`);t.textContent=e.conferenceTitle_s,o.appendChild(t)}if(t===3&&e.abstract_s?.[0]){let t=u(`div`,`hal-article__abstract`);t.textContent=e.abstract_s[0],o.appendChild(t)}n.appendChild(o)}}return n}function v(e,t){let n=Math.max(1,Math.ceil(e.totalFound/e.rows)),{currentPage:r}=e,i=u(`nav`,`hal-pagination`);i.setAttribute(`aria-label`,`Search results pages`);let a=u(`button`,`hal-pagination__btn`);a.textContent=`← Previous`,a.disabled=r<=1,a.addEventListener(`click`,()=>t(r-1)),i.appendChild(a);let o=u(`span`,`hal-pagination__info`);o.textContent=`Page ${r} of ${n} (${e.totalFound.toLocaleString()} results)`,i.appendChild(o);let s=u(`button`,`hal-pagination__btn`);return s.textContent=`Next →`,s.disabled=r>=n,s.addEventListener(`click`,()=>t(r+1)),i.appendChild(s),i}function y(e,t,n,r,i){if(e.innerHTML=``,t.length===0){g(e);return}let a=u(`div`,`hal-results`);for(let e of t)a.appendChild(_(e,n));r.totalFound>r.rows&&a.appendChild(v(r,i));let o=u(`div`,`hal-footer`),s=p(`https://github.com/JPugetGil/hal-search`,`hal-search`,`hal-footer__link`);o.appendChild(s),a.appendChild(o),e.appendChild(a)}var b=`http://www.w3.org/2000/svg`,x=800,S=16,C=8,w=50,T=28,E=`https://github.com/JPugetGil/hal-search`,D={accent:`#0052cc`,accentText:`#ffffff`,bg:`#f2f4f8`,cardBg:`#ffffff`,border:`#e0e0e0`,text:`#1a1a1a`,muted:`#666666`,link:`#0052cc`,oaBg:`#e3f5ee`,oaColor:`#006644`,tagBg:`#f0f0f0`,tagColor:`#444444`,domainBg:`#dbeafe`,domainColor:`#1a56db`};function O(e){return e?{...D,...e.backgroundColor&&{bg:e.backgroundColor,cardBg:e.backgroundColor},...e.textColor&&{text:e.textColor,muted:e.textColor},...e.mainColor&&{accent:e.mainColor,link:e.mainColor}}:D}var k=11,A=15,j=3,M=18,N=10;function P(e){return document.createElementNS(b,e)}function F(e,t){for(let[n,r]of Object.entries(t))e.setAttribute(n,String(r))}function I(e){let t=document.createElement(`textarea`);return t.innerHTML=e,t.value}function L(e,t,n){let r=n*.56,i=Math.floor(t/r);return e.length>i?e.slice(0,i-1)+`…`:e}function R(e,t,n,r,i,a){let o=P(`rect`);return F(o,{x:e,y:t,width:n,height:r,fill:i,...a}),o}function z(e,t,n,r){let i=P(`text`);return F(i,{x:e,y:t,"font-family":`system-ui,-apple-system,sans-serif`,...r}),i.textContent=n,i}function B(e,t){let n=P(`a`);return n.setAttribute(`href`,e),n.setAttribute(`target`,`_blank`),n.appendChild(t),n}function V(e,t,n,r,i,a){let o=r.length*11*.6+10;return e.appendChild(R(t,n-11,o,17,i,{rx:3})),e.appendChild(z(t+5,n-1,r,{"font-size":11,fill:a})),o+4}function H(e,t,n,r,i,a,o,s,c=j){let l=Math.floor(i/(a*.52)),u=r.split(/\s+/),d=[],f=``,p=!1;for(let e=0;e<u.length;e++){let t=u[e],n=f?`${f} ${t}`:t;if(n.length<=l)f=n;else{if(f&&d.push(f),d.length>=c){p=!0;break}f=t}}!p&&f?d.push(f):p&&d.length>0&&(d[d.length-1]+=`…`);let m=P(`text`);F(m,{x:t,y:n,"font-size":a,fill:s,"font-family":`system-ui,-apple-system,sans-serif`});for(let e=0;e<d.length;e++){let n=P(`tspan`);n.setAttribute(`x`,String(t)),e>0&&n.setAttribute(`dy`,String(o)),n.textContent=d[e],m.appendChild(n)}return e.appendChild(m),d.length*o}function U(e,t){if(t!==3||!e.abstract_s?.[0])return 0;let n=x-S*4,r=Math.floor(n/(k*.52)),i=e.abstract_s[0].split(/\s+/),a=1,o=0;for(let e of i)if(o+e.length+(o?1:0)>r){if(a++,o=e.length,a>=j)break}else o+=e.length+(o?1:0);return N+M+Math.min(a,j)*A}function W(e,t){return t===0?44:(t>=2&&((e.keyword_s?.length??0)>0||(e.domain_s?.length??0)>0||e.conferenceTitle_s)?86:62)+U(e,t)}function G(e,t,n,r){let i=P(`g`),a=W(e,t),o=S,s=x-S*2,c=S*2;if(i.appendChild(R(o,n,s,a,r.cardBg,{rx:6,stroke:r.border,"stroke-width":1})),t===0){let t=L(I(e.label_s??e.docid??``),s-S*2,12),a=z(c,n+26,t,{"font-size":12,fill:r.link});return e.uri_s?.startsWith(`http`)?i.appendChild(B(e.uri_s,a)):(a.setAttribute(`fill`,r.text),i.appendChild(a)),i}let l=L(e.title_s?.[0]??e.label_s??`Untitled`,s-S*2,14),u=z(c,n+22,l,{"font-size":14,"font-weight":`bold`,fill:e.uri_s?.startsWith(`http`)?r.link:r.text});i.appendChild(e.uri_s?.startsWith(`http`)?B(e.uri_s,u):u);let d=n+44,f=[];e.authFullName_s?.length&&f.push(L(e.authFullName_s.join(`, `),s*.5,12)),e.publicationDate_s&&f.push(e.publicationDate_s.slice(0,4)),f.length&&i.appendChild(z(c,d,f.join(` · `),{"font-size":12,fill:r.muted}));let p=o+s-S;if(e.openAccess_bool===!0&&(p-=86.6,V(i,p,d,`Open Access`,r.oaBg,r.oaColor)),e.docType_s&&e.docType_s.toUpperCase()!==`UNDEFINED`){let t=e.docType_s;p-=t.length*11*.6+10+4,V(i,p,d,t,r.tagBg,r.tagColor)}let m=d+S;if(t>=2){let t=c,a=n+68,l=o+s-S;m=a+S;for(let n of e.keyword_s??[]){let e=n.length*11*.6+14;if(t+e>l)break;t+=V(i,t,a,n,r.tagBg,r.tagColor)}for(let n of e.domain_s??[]){let e=n.length*11*.6+14;if(t+e>l)break;t+=V(i,t,a,n,r.domainBg,r.domainColor)}if(e.conferenceTitle_s&&t<l){let n=L(e.conferenceTitle_s,l-t,11);i.appendChild(z(t,a,n,{"font-size":11,fill:r.muted,"font-style":`italic`}))}}if(t===3&&e.abstract_s?.[0]){let t=m+N;i.appendChild(R(c,t-4,s-S*2,1,r.border)),i.appendChild(z(c,t+M-4,`Abstract`,{"font-size":11,fill:r.muted,"font-style":`italic`})),H(i,c,t+M+A-2,e.abstract_s[0],s-S*2,k,A,r.text)}return i}function K(e,t,n,r){let i=O(r),a=e.reduce((e,n)=>e+W(n,t)+C,0),o=w+C+a+T,s=P(`svg`);F(s,{width:x,height:o,viewBox:`0 0 ${x} ${o}`,xmlns:b,role:`img`,"aria-label":`HAL Search Results`}),s.appendChild(R(0,0,x,o,i.bg)),s.appendChild(R(0,0,x,w,i.accent)),s.appendChild(z(S,32,`HAL Search Results`,{"font-size":18,"font-weight":`bold`,fill:i.accentText}));let{totalFound:c,currentPage:l,rows:u}=n,d=`Page ${l} / ${Math.max(1,Math.ceil(c/u))} · ${c.toLocaleString()} results`;s.appendChild(z(x-S,32,d,{"font-size":12,fill:i.accentText,"text-anchor":`end`}));let f=w+C;for(let n of e)s.appendChild(G(n,t,f,i)),f+=W(n,t)+C;let p=o-T/2+4;return s.appendChild(z(S,p,d,{"font-size":11,fill:i.muted})),s.appendChild(B(E,z(x-S,p,`hal-search`,{"font-size":11,fill:i.muted,"text-anchor":`end`}))),s}function q(e,t,n,r,i){if(e.innerHTML=``,t.length===0){let t=document.createElement(`p`);t.className=`hal-empty`,t.textContent=`No results found.`,e.appendChild(t);return}e.appendChild(K(t,n,r,i))}var J=`
2
2
  :root {
3
3
  --hal-accent: #0052cc;
4
4
  --hal-accent-hover: #003d99;
@@ -250,5 +250,5 @@
250
250
  cursor: not-allowed;
251
251
  opacity: 0.5;
252
252
  }
253
- `;function J(){let e=`hal-search-styles`;if(document.getElementById(e))return;let t=document.createElement(`style`);t.id=e,t.textContent=q,document.head.appendChild(t)}var Y={lvl:1,rows:10,apiBase:s,injectStyles:!0,output:`html`},X=class{constructor(e){this.currentUid=``,e.container&&(this.container=this._resolveContainer(e.container)),this.options={lvl:e.lvl??Y.lvl,rows:e.rows??Y.rows,apiBase:e.apiBase??Y.apiBase,injectStyles:e.injectStyles??Y.injectStyles,output:e.output??Y.output,onResults:e.onResults,onError:e.onError},this.pagination={currentPage:1,totalFound:0,rows:this.options.rows,start:0},this.options.injectStyles&&J(),this._applyColors(e)}async search(e){return this.currentUid=e.uid,e.rows!==void 0&&(this.options.rows=e.rows),this.pagination={currentPage:1,totalFound:0,rows:this.options.rows,start:e.start??0},this._fetch(this.currentUid,this.pagination.start)}async goToPage(e){let t=Math.max(1,Math.ceil(this.pagination.totalFound/this.options.rows)),n=(Math.min(Math.max(1,e),t)-1)*this.options.rows;return this._fetch(this.currentUid,n)}async nextPage(){return this.goToPage(this.pagination.currentPage+1)}async prevPage(){return this.goToPage(this.pagination.currentPage-1)}async setLevel(e){return this.options.lvl=e,this._fetch(this.currentUid,this.pagination.start)}setColors(e){this._applyColors(e)}destroy(){this.container&&(this.container.innerHTML=``)}_applyColors(e){this.container&&(e.backgroundColor&&this.container.style.setProperty(`--hal-bg`,e.backgroundColor),e.textColor&&this.container.style.setProperty(`--hal-text`,e.textColor),e.mainColor&&this.container.style.setProperty(`--hal-accent`,e.mainColor))}async _fetch(e,t){if(e){this.container&&m(this.container);try{let n=await l(e,this.options.lvl,this.options.rows,t,this.options.apiBase);if(this._updatePagination(n,t),this.options.output===`svg`)if(this.container)K(this.container,n.response.docs,this.options.lvl,this.pagination);else{let e=G(n.response.docs,this.options.lvl,this.pagination);return this.options.onResults?.(n),e}else if(this.container)y(this.container,n.response.docs,this.options.lvl,this.pagination,e=>{this.goToPage(e)});else throw Error(`HalSearch: container is required for HTML output`);this.options.onResults?.(n)}catch(e){let t=e instanceof Error?e:Error(String(e));if(this.container&&h(this.container,t),this.options.onError?.(t),!this.container&&!this.options.onError)throw t}}}_resolveContainer(e){if(typeof e==`string`){let t=document.querySelector(e);if(!t)throw Error(`HalSearch: container not found for selector "${e}"`);return t}return e}_updatePagination(e,t){let{numFound:n}=e.response;this.pagination={currentPage:Math.floor(t/this.options.rows)+1,totalFound:n,rows:this.options.rows,start:t}}};function Z(e){let t=new URLSearchParams({uid:e.uid});return e.lvl!==void 0&&t.set(`lvl`,String(e.lvl)),e.rows!==void 0&&t.set(`rows`,String(e.rows)),e.type&&t.set(`type`,e.type),e.backgroundColor&&t.set(`bg`,e.backgroundColor),e.textColor&&t.set(`text`,e.textColor),e.mainColor&&t.set(`main`,e.mainColor),`${e.embedBase}/embed.html?${t.toString()}`}function Q(e){return`<iframe src="${Z(e)}" width="${e.width??`100%`}" height="${e.height??`600`}"></iframe>`}e.DEFAULT_BASE=s,e.DEFAULT_CSS=q,e.HalSearch=X,e.LEVEL_FIELDS=i,e.LEVEL_NAMES=a,e.buildArticlesSvg=G,e.buildEmbedSnippet=Q,e.buildEmbedUrl=Z,e.buildUrl=c,e.fetchArticles=l,e.injectDefaultStyles=J,e.renderResultsSvg=K,e.resolveFields=o});
253
+ `;function Y(){let e=`hal-search-styles`;if(document.getElementById(e))return;let t=document.createElement(`style`);t.id=e,t.textContent=J,document.head.appendChild(t)}var X={lvl:1,rows:10,apiBase:s,injectStyles:!0,output:`html`},Z=class{constructor(e){this.currentUid=``,this.colorOverrides={},e.container&&(this.container=this._resolveContainer(e.container)),this.options={lvl:e.lvl??X.lvl,rows:e.rows??X.rows,apiBase:e.apiBase??X.apiBase,injectStyles:e.injectStyles??X.injectStyles,output:e.output??X.output,onResults:e.onResults,onError:e.onError},this.pagination={currentPage:1,totalFound:0,rows:this.options.rows,start:0},this.options.injectStyles&&Y(),this.colorOverrides={backgroundColor:e.backgroundColor,textColor:e.textColor,mainColor:e.mainColor},this._applyColors(this.colorOverrides)}async search(e){return this.currentUid=e.uid,e.rows!==void 0&&(this.options.rows=e.rows),this.pagination={currentPage:1,totalFound:0,rows:this.options.rows,start:e.start??0},this._fetch(this.currentUid,this.pagination.start)}async goToPage(e){let t=Math.max(1,Math.ceil(this.pagination.totalFound/this.options.rows)),n=(Math.min(Math.max(1,e),t)-1)*this.options.rows;return this._fetch(this.currentUid,n)}async nextPage(){return this.goToPage(this.pagination.currentPage+1)}async prevPage(){return this.goToPage(this.pagination.currentPage-1)}async setLevel(e){return this.options.lvl=e,this._fetch(this.currentUid,this.pagination.start)}setColors(e){this.colorOverrides={...this.colorOverrides,...e},this._applyColors(e)}destroy(){this.container&&(this.container.innerHTML=``)}_applyColors(e){this.container&&(e.backgroundColor&&this.container.style.setProperty(`--hal-bg`,e.backgroundColor),e.textColor&&this.container.style.setProperty(`--hal-text`,e.textColor),e.mainColor&&this.container.style.setProperty(`--hal-accent`,e.mainColor))}async _fetch(e,t){if(e){this.container&&m(this.container);try{let n=await l(e,this.options.lvl,this.options.rows,t,this.options.apiBase);if(this._updatePagination(n,t),this.options.output===`svg`)if(this.container)q(this.container,n.response.docs,this.options.lvl,this.pagination,this.colorOverrides);else{let e=K(n.response.docs,this.options.lvl,this.pagination,this.colorOverrides);return this.options.onResults?.(n),e}else if(this.container)y(this.container,n.response.docs,this.options.lvl,this.pagination,e=>{this.goToPage(e)});else throw Error(`HalSearch: container is required for HTML output`);this.options.onResults?.(n)}catch(e){let t=e instanceof Error?e:Error(String(e));if(this.container&&h(this.container,t),this.options.onError?.(t),!this.container&&!this.options.onError)throw t}}}_resolveContainer(e){if(typeof e==`string`){let t=document.querySelector(e);if(!t)throw Error(`HalSearch: container not found for selector "${e}"`);return t}return e}_updatePagination(e,t){let{numFound:n}=e.response;this.pagination={currentPage:Math.floor(t/this.options.rows)+1,totalFound:n,rows:this.options.rows,start:t}}};function Q(e){let t=new URLSearchParams({uid:e.uid});return e.lvl!==void 0&&t.set(`lvl`,String(e.lvl)),e.rows!==void 0&&t.set(`rows`,String(e.rows)),e.type&&t.set(`type`,e.type),e.backgroundColor&&t.set(`bg`,e.backgroundColor),e.textColor&&t.set(`text`,e.textColor),e.mainColor&&t.set(`main`,e.mainColor),`${e.embedBase}/embed.html?${t.toString()}`}function $(e){return`<iframe src="${Q(e)}" width="${e.width??`100%`}" height="${e.height??`600`}"></iframe>`}e.DEFAULT_BASE=s,e.DEFAULT_CSS=J,e.HalSearch=Z,e.LEVEL_FIELDS=i,e.LEVEL_NAMES=a,e.buildArticlesSvg=K,e.buildEmbedSnippet=$,e.buildEmbedUrl=Q,e.buildUrl=c,e.fetchArticles=l,e.injectDefaultStyles=Y,e.renderResultsSvg=q,e.resolveFields=o,e.resolvePalette=O});
254
254
  //# sourceMappingURL=hal-search.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hal-search.umd.js","names":[],"sources":["../src/levels.ts","../src/api.ts","../src/renderer.ts","../src/svg-renderer.ts","../src/styles.ts","../src/HalSearch.ts","../src/embed.ts"],"sourcesContent":["import type { DetailLevel, LevelName } from './types';\n\nconst MINIMAL_FIELDS = ['docid', 'label_s', 'uri_s'] as const;\n\nconst BASIC_FIELDS = [\n ...MINIMAL_FIELDS,\n 'title_s',\n 'authFullName_s',\n 'publicationDate_s',\n 'docType_s',\n] as const;\n\nconst DETAILED_FIELDS = [\n ...BASIC_FIELDS,\n 'keyword_s',\n 'domain_s',\n 'openAccess_bool',\n 'language_s',\n 'peerReviewing_s',\n 'conferenceTitle_s',\n] as const;\n\n/** Maps a DetailLevel to its comma-joined `fl` parameter string */\nexport const LEVEL_FIELDS: Record<DetailLevel, string> = {\n 0: MINIMAL_FIELDS.join(','),\n 1: BASIC_FIELDS.join(','),\n 2: DETAILED_FIELDS.join(','),\n 3: '*',\n};\n\nexport const LEVEL_NAMES: Record<DetailLevel, LevelName> = {\n 0: 'minimal',\n 1: 'basic',\n 2: 'detailed',\n 3: 'full',\n};\n\n/** Returns the `fl` field string for the given detail level */\nexport function resolveFields(lvl: DetailLevel): string {\n return LEVEL_FIELDS[lvl] ?? LEVEL_FIELDS[1];\n}\n","import type { HalApiResponse, DetailLevel } from './types';\nimport { resolveFields } from './levels';\n\nexport const DEFAULT_BASE = 'https://api.archives-ouvertes.fr/search/';\n\n/**\n * Builds a HAL API search URL from the given parameters.\n * Uses URLSearchParams to safely encode special characters in uid.\n */\nexport function buildUrl(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): string {\n const fl = resolveFields(lvl);\n const params = new URLSearchParams({\n q: `\"${uid}\"`,\n wt: 'json',\n fl,\n rows: String(rows),\n start: String(start),\n });\n return `${base}?${params.toString()}`;\n}\n\n/**\n * Fetches articles from the HAL API.\n * Throws on HTTP errors or non-zero API status codes.\n */\nexport async function fetchArticles(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): Promise<HalApiResponse> {\n const url = buildUrl(uid, lvl, rows, start, base);\n const res = await fetch(url, {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`HAL API error: ${res.status} ${res.statusText}`);\n }\n\n const data: HalApiResponse = await res.json();\n\n if (data.responseHeader?.status !== undefined && data.responseHeader.status !== 0) {\n throw new Error(`HAL API returned non-zero status: ${data.responseHeader.status}`);\n }\n\n return data;\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className) node.className = className;\n return node;\n}\n\nfunction text(content: string): Text {\n return document.createTextNode(content);\n}\n\n/** Decodes HTML entities (e.g. &#x27E8;) into their actual characters. */\nfunction decodeEntities(raw: string): string {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = raw;\n return textarea.value;\n}\n\n/** Creates an <a> element with href validated to start with https:// */\nfunction safeLink(href: string | undefined, label: string, className?: string): HTMLAnchorElement {\n const a = el('a', className);\n if (href && (href.startsWith('https://') || href.startsWith('http://'))) {\n a.href = href;\n }\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.textContent = label;\n return a;\n}\n\n// ---------------------------------------------------------------------------\n// State renderers\n// ---------------------------------------------------------------------------\n\nexport function renderLoading(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-loading');\n const spinner = el('div', 'hal-spinner');\n wrap.appendChild(spinner);\n wrap.appendChild(text('Loading…'));\n container.appendChild(wrap);\n}\n\nexport function renderError(container: HTMLElement, err: Error): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-error');\n wrap.textContent = `Error: ${err.message}`;\n container.appendChild(wrap);\n}\n\nexport function renderEmpty(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-empty');\n wrap.textContent = 'No results found.';\n container.appendChild(wrap);\n}\n\n// ---------------------------------------------------------------------------\n// Article card\n// ---------------------------------------------------------------------------\n\nfunction buildArticleCard(doc: HalDoc, lvl: DetailLevel): HTMLElement {\n const article = el('article', 'hal-article');\n if (doc.docid) article.dataset.docid = doc.docid;\n if (doc.docType_s) article.dataset.doctype = doc.docType_s;\n\n // --- Header ---\n const header = el('header');\n\n if (lvl >= 1) {\n // Title + link\n const h3 = el('h3', 'hal-article__title');\n const titleText = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n h3.appendChild(safeLink(doc.uri_s, titleText));\n header.appendChild(h3);\n\n // Meta row\n const meta = el('div', 'hal-article__meta');\n\n if (doc.authFullName_s?.length) {\n const authors = el('span', 'hal-article__authors');\n authors.textContent = doc.authFullName_s.join(', ');\n meta.appendChild(authors);\n }\n\n if (doc.publicationDate_s) {\n const date = el('span', 'hal-article__date');\n // Show only the year if it's a full date string\n date.textContent = doc.publicationDate_s.slice(0, 4);\n meta.appendChild(date);\n }\n\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const badge = el('span', 'hal-badge');\n badge.textContent = doc.docType_s;\n meta.appendChild(badge);\n }\n\n if (doc.openAccess_bool === true) {\n const oaBadge = el('span', 'hal-badge hal-badge--oa');\n oaBadge.textContent = 'Open Access';\n meta.appendChild(oaBadge);\n }\n\n header.appendChild(meta);\n } else {\n // lvl 0: just the full citation\n const div = el('div', 'hal-article__label');\n div.appendChild(safeLink(doc.uri_s, decodeEntities(doc.label_s ?? doc.docid ?? ''), 'hal-article__link'));\n header.appendChild(div);\n }\n\n article.appendChild(header);\n\n // --- Details (lvl >= 2) ---\n if (lvl >= 2) {\n const hasKeywords = doc.keyword_s && doc.keyword_s.length > 0;\n const hasDomains = doc.domain_s && doc.domain_s.length > 0;\n const hasConference = Boolean(doc.conferenceTitle_s);\n\n if (hasKeywords || hasDomains || hasConference) {\n const details = el('section', 'hal-article__details');\n\n if (hasKeywords) {\n const tagsWrap = el('div', 'hal-article__tags');\n for (const kw of doc.keyword_s!) {\n const tag = el('span', 'hal-tag');\n tag.textContent = kw;\n tagsWrap.appendChild(tag);\n }\n details.appendChild(tagsWrap);\n }\n\n if (hasDomains) {\n const domainsWrap = el('div', 'hal-article__tags');\n for (const domain of doc.domain_s!) {\n const tag = el('span', 'hal-tag hal-tag--domain');\n tag.textContent = domain;\n domainsWrap.appendChild(tag);\n }\n details.appendChild(domainsWrap);\n }\n\n if (hasConference) {\n const conf = el('div', 'hal-article__conference');\n conf.textContent = doc.conferenceTitle_s!;\n details.appendChild(conf);\n }\n\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const abstract = el('div', 'hal-article__abstract');\n abstract.textContent = doc.abstract_s[0];\n details.appendChild(abstract);\n }\n\n article.appendChild(details);\n }\n }\n\n return article;\n}\n\n// ---------------------------------------------------------------------------\n// Pagination bar\n// ---------------------------------------------------------------------------\n\nfunction buildPagination(\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): HTMLElement {\n const totalPages = Math.max(1, Math.ceil(pagination.totalFound / pagination.rows));\n const { currentPage } = pagination;\n\n const nav = el('nav', 'hal-pagination');\n nav.setAttribute('aria-label', 'Search results pages');\n\n const prevBtn = el('button', 'hal-pagination__btn');\n prevBtn.textContent = '← Previous';\n prevBtn.disabled = currentPage <= 1;\n prevBtn.addEventListener('click', () => onPageChange(currentPage - 1));\n nav.appendChild(prevBtn);\n\n const info = el('span', 'hal-pagination__info');\n info.textContent = `Page ${currentPage} of ${totalPages} (${pagination.totalFound.toLocaleString()} results)`;\n nav.appendChild(info);\n\n const nextBtn = el('button', 'hal-pagination__btn');\n nextBtn.textContent = 'Next →';\n nextBtn.disabled = currentPage >= totalPages;\n nextBtn.addEventListener('click', () => onPageChange(currentPage + 1));\n nav.appendChild(nextBtn);\n\n return nav;\n}\n\n// ---------------------------------------------------------------------------\n// Main results renderer\n// ---------------------------------------------------------------------------\n\nexport function renderResults(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): void {\n container.innerHTML = '';\n\n if (docs.length === 0) {\n renderEmpty(container);\n return;\n }\n\n const wrapper = el('div', 'hal-results');\n\n for (const doc of docs) {\n wrapper.appendChild(buildArticleCard(doc, lvl));\n }\n\n if (pagination.totalFound > pagination.rows) {\n wrapper.appendChild(buildPagination(pagination, onPageChange));\n }\n\n const footer = el('div', 'hal-footer');\n const credit = safeLink('https://github.com/JPugetGil/hal-search', 'hal-search', 'hal-footer__link');\n footer.appendChild(credit);\n wrapper.appendChild(footer);\n\n container.appendChild(wrapper);\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\nconst NS = 'http://www.w3.org/2000/svg';\nconst W = 800;\nconst PAD = 16;\nconst CARD_GAP = 8;\nconst HEADER_H = 50;\nconst FOOTER_H = 28;\nconst GITHUB_URL = 'https://github.com/JPugetGil/hal-search';\n\n/** Default colour palette — mirrors CSS-variable defaults from styles.ts */\nconst C = {\n accent: '#0052cc',\n accentText: '#ffffff',\n bg: '#f2f4f8',\n cardBg: '#ffffff',\n border: '#e0e0e0',\n text: '#1a1a1a',\n muted: '#666666',\n link: '#0052cc',\n oaBg: '#e3f5ee',\n oaColor: '#006644',\n tagBg: '#f0f0f0',\n tagColor: '#444444',\n domainBg: '#dbeafe',\n domainColor: '#1a56db',\n};\n\n// Abstract rendering constants\nconst ABSTRACT_FS = 11;\nconst ABSTRACT_LINE_H = 15;\nconst ABSTRACT_MAX_LINES = 3;\nconst ABSTRACT_LABEL_H = 18;\nconst ABSTRACT_TOP_GAP = 10;\n\n// ---------------------------------------------------------------------------\n// SVG helpers\n// ---------------------------------------------------------------------------\n\nfunction svgEl<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {\n return document.createElementNS(NS, tag) as SVGElementTagNameMap[K];\n}\n\nfunction set(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));\n}\n\nfunction decodeEntities(raw: string): string {\n const ta = document.createElement('textarea');\n ta.innerHTML = raw;\n return ta.value;\n}\n\n/** Approximate truncation (SVG has no native text-measurement API). */\nfunction truncate(text: string, maxPx: number, fontSize: number): string {\n const charW = fontSize * 0.56;\n const max = Math.floor(maxPx / charW);\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n\nfunction mkRect(\n x: number, y: number, w: number, h: number,\n fill: string,\n extra?: Record<string, string | number>,\n): SVGRectElement {\n const r = svgEl('rect');\n set(r, { x, y, width: w, height: h, fill, ...extra });\n return r;\n}\n\nfunction mkText(\n x: number, y: number, content: string,\n extra?: Record<string, string | number>,\n): SVGTextElement {\n const t = svgEl('text');\n set(t, { x, y, 'font-family': 'system-ui,-apple-system,sans-serif', ...extra });\n t.textContent = content;\n return t;\n}\n\nfunction mkLink(href: string, child: SVGElement): SVGAElement {\n const a = svgEl('a');\n a.setAttribute('href', href);\n a.setAttribute('target', '_blank');\n a.appendChild(child);\n return a;\n}\n\n/**\n * Renders a pill badge anchored at (x, baseline-y).\n * Returns the total pixel width consumed, including a 4 px trailing gap.\n */\nfunction pill(\n parent: SVGElement,\n x: number, y: number,\n label: string,\n bg: string, color: string,\n): number {\n const fs = 11;\n const ph = 5, pv = 3;\n const bw = label.length * fs * 0.6 + ph * 2;\n const bh = fs + pv * 2;\n parent.appendChild(mkRect(x, y - fs, bw, bh, bg, { rx: 3 }));\n parent.appendChild(mkText(x + ph, y - 1, label, { 'font-size': fs, fill: color }));\n return bw + 4;\n}\n\n/**\n * Word-wraps `content` into SVG tspan elements appended to a <text> node.\n * Returns the total pixel height consumed (lines × lineHeight).\n */\nfunction wrapText(\n parent: SVGElement,\n x: number, baseY: number,\n content: string,\n maxPx: number,\n fontSize: number,\n lineHeight: number,\n fill: string,\n maxLines = ABSTRACT_MAX_LINES,\n): number {\n const maxChars = Math.floor(maxPx / (fontSize * 0.52));\n const words = content.split(/\\s+/);\n const lines: string[] = [];\n let cur = '';\n let truncated = false;\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i]!;\n const test = cur ? `${cur} ${word}` : word;\n if (test.length <= maxChars) {\n cur = test;\n } else {\n if (cur) lines.push(cur);\n if (lines.length >= maxLines) { truncated = true; break; }\n cur = word;\n }\n }\n if (!truncated && cur) {\n lines.push(cur);\n } else if (truncated && lines.length > 0) {\n lines[lines.length - 1] += '…';\n }\n\n const t = svgEl('text');\n set(t, { x, y: baseY, 'font-size': fontSize, fill, 'font-family': 'system-ui,-apple-system,sans-serif' });\n for (let i = 0; i < lines.length; i++) {\n const ts = svgEl('tspan');\n ts.setAttribute('x', String(x));\n if (i > 0) ts.setAttribute('dy', String(lineHeight));\n ts.textContent = lines[i]!;\n t.appendChild(ts);\n }\n parent.appendChild(t);\n return lines.length * lineHeight;\n}\n\n// ---------------------------------------------------------------------------\n// Card geometry\n// ---------------------------------------------------------------------------\n\n/** Returns the extra height added by the abstract section, or 0 if absent. */\nfunction abstractExtraHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl !== 3 || !doc.abstract_s?.[0]) return 0;\n // Pre-estimate the number of wrapped lines\n const maxPx = W - PAD * 4;\n const maxChars = Math.floor(maxPx / (ABSTRACT_FS * 0.52));\n const words = doc.abstract_s[0].split(/\\s+/);\n let lines = 1;\n let chars = 0;\n for (const word of words) {\n if (chars + word.length + (chars ? 1 : 0) > maxChars) {\n lines++;\n chars = word.length;\n if (lines >= ABSTRACT_MAX_LINES) break;\n } else {\n chars += word.length + (chars ? 1 : 0);\n }\n }\n return ABSTRACT_TOP_GAP + ABSTRACT_LABEL_H + Math.min(lines, ABSTRACT_MAX_LINES) * ABSTRACT_LINE_H;\n}\n\nfunction cardHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl === 0) return 44;\n const hasTagRow =\n lvl >= 2 &&\n ((doc.keyword_s?.length ?? 0) > 0 ||\n (doc.domain_s?.length ?? 0) > 0 ||\n Boolean(doc.conferenceTitle_s));\n return (hasTagRow ? 86 : 62) + abstractExtraHeight(doc, lvl);\n}\n\n// ---------------------------------------------------------------------------\n// Card builder\n// ---------------------------------------------------------------------------\n\nfunction buildCard(doc: HalDoc, lvl: DetailLevel, cardY: number): SVGElement {\n const g = svgEl('g');\n const h = cardHeight(doc, lvl);\n const cx = PAD;\n const cw = W - PAD * 2;\n const ix = PAD * 2; // inner-x (text left margin)\n\n // Card background\n g.appendChild(mkRect(cx, cardY, cw, h, C.cardBg, {\n rx: 6, stroke: C.border, 'stroke-width': 1,\n }));\n\n // ── Level 0: citation label only ──────────────────────────────────────────\n if (lvl === 0) {\n const raw = decodeEntities(doc.label_s ?? doc.docid ?? '');\n const label = truncate(raw, cw - PAD * 2, 12);\n const t = mkText(ix, cardY + 26, label, { 'font-size': 12, fill: C.link });\n if (doc.uri_s?.startsWith('http')) {\n g.appendChild(mkLink(doc.uri_s, t));\n } else {\n t.setAttribute('fill', C.text);\n g.appendChild(t);\n }\n return g;\n }\n\n // ── Level 1+: title row ──────────────────────────────────────────────────\n const titleRaw = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n const titleStr = truncate(titleRaw, cw - PAD * 2, 14);\n const titleEl = mkText(ix, cardY + 22, titleStr, {\n 'font-size': 14,\n 'font-weight': 'bold',\n fill: doc.uri_s?.startsWith('http') ? C.link : C.text,\n });\n g.appendChild(doc.uri_s?.startsWith('http') ? mkLink(doc.uri_s, titleEl) : titleEl);\n\n // ── Meta row ─────────────────────────────────────────────────────────────\n const metaY = cardY + 44;\n\n // Left: \"authors · year\"\n const metaParts: string[] = [];\n if (doc.authFullName_s?.length) {\n metaParts.push(truncate(doc.authFullName_s.join(', '), cw * 0.5, 12));\n }\n if (doc.publicationDate_s) {\n metaParts.push(doc.publicationDate_s.slice(0, 4));\n }\n if (metaParts.length) {\n g.appendChild(mkText(ix, metaY, metaParts.join(' · '), {\n 'font-size': 12, fill: C.muted,\n }));\n }\n\n // Right: badges (rendered right-to-left so order is docType | OA visually)\n let badgeRight = cx + cw - PAD;\n if (doc.openAccess_bool === true) {\n const label = 'Open Access';\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.oaBg, C.oaColor);\n }\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const label = doc.docType_s;\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.tagBg, C.tagColor);\n }\n\n // ── Tags row (lvl ≥ 2) ───────────────────────────────────────────────────\n let nextSectionY = metaY + PAD;\n if (lvl >= 2) {\n let tagX = ix;\n const tagY = cardY + 68;\n const tagRight = cx + cw - PAD;\n nextSectionY = tagY + PAD;\n\n for (const kw of (doc.keyword_s ?? [])) {\n const bw = kw.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, kw, C.tagBg, C.tagColor);\n }\n for (const domain of (doc.domain_s ?? [])) {\n const bw = domain.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, domain, C.domainBg, C.domainColor);\n }\n if (doc.conferenceTitle_s && tagX < tagRight) {\n const label = truncate(doc.conferenceTitle_s, tagRight - tagX, 11);\n g.appendChild(mkText(tagX, tagY, label, {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n }\n }\n\n // ── Abstract (lvl 3 only) ─────────────────────────────────────────────────\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const absTop = nextSectionY + ABSTRACT_TOP_GAP;\n // Separator line\n g.appendChild(mkRect(ix, absTop - 4, cw - PAD * 2, 1, C.border));\n // \"Abstract\" label\n g.appendChild(mkText(ix, absTop + ABSTRACT_LABEL_H - 4, 'Abstract', {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n // Wrapped text\n wrapText(\n g,\n ix, absTop + ABSTRACT_LABEL_H + ABSTRACT_LINE_H - 2,\n doc.abstract_s[0],\n cw - PAD * 2,\n ABSTRACT_FS,\n ABSTRACT_LINE_H,\n C.text,\n );\n }\n\n return g;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Builds an SVG element representing the article list.\n * The SVG is self-contained and can be inserted into the DOM or serialised.\n */\nexport function buildArticlesSvg(\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n): SVGSVGElement {\n const cardsH = docs.reduce((s, d) => s + cardHeight(d, lvl) + CARD_GAP, 0);\n const totalH = HEADER_H + CARD_GAP + cardsH + FOOTER_H;\n\n const svg = svgEl('svg');\n set(svg, {\n width: W,\n height: totalH,\n viewBox: `0 0 ${W} ${totalH}`,\n xmlns: NS,\n role: 'img',\n 'aria-label': 'HAL Search Results',\n });\n\n // Background\n svg.appendChild(mkRect(0, 0, W, totalH, C.bg));\n\n // Header bar\n svg.appendChild(mkRect(0, 0, W, HEADER_H, C.accent));\n svg.appendChild(mkText(PAD, 32, 'HAL Search Results', {\n 'font-size': 18, 'font-weight': 'bold', fill: C.accentText,\n }));\n\n const { totalFound, currentPage, rows } = pagination;\n const totalPages = Math.max(1, Math.ceil(totalFound / rows));\n const pageInfo = `Page ${currentPage} / ${totalPages} · ${totalFound.toLocaleString()} results`;\n\n svg.appendChild(mkText(W - PAD, 32, pageInfo, {\n 'font-size': 12, fill: C.accentText, 'text-anchor': 'end',\n }));\n\n // Article cards\n let y = HEADER_H + CARD_GAP;\n for (const doc of docs) {\n svg.appendChild(buildCard(doc, lvl, y));\n y += cardHeight(doc, lvl) + CARD_GAP;\n }\n\n // Footer: pagination info (left) + GitHub credit (right)\n const footerY = totalH - FOOTER_H / 2 + 4;\n svg.appendChild(mkText(PAD, footerY, pageInfo, {\n 'font-size': 11, fill: C.muted,\n }));\n svg.appendChild(mkLink(\n GITHUB_URL,\n mkText(W - PAD, footerY, 'hal-search', {\n 'font-size': 11, fill: C.muted, 'text-anchor': 'end',\n }),\n ));\n\n return svg;\n}\n\n/**\n * Clears `container` and renders the article list as an inline SVG.\n * Falls back to the standard `.hal-empty` paragraph when `docs` is empty.\n */\nexport function renderResultsSvg(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n): void {\n container.innerHTML = '';\n if (docs.length === 0) {\n const p = document.createElement('p');\n p.className = 'hal-empty';\n p.textContent = 'No results found.';\n container.appendChild(p);\n return;\n }\n container.appendChild(buildArticlesSvg(docs, lvl, pagination));\n}\n","export const DEFAULT_CSS = `\n:root {\n --hal-accent: #0052cc;\n --hal-accent-hover: #003d99;\n --hal-bg: #ffffff;\n --hal-bg-article: #fafafa;\n --hal-border: #e0e0e0;\n --hal-text: #1a1a1a;\n --hal-text-muted: #666666;\n --hal-oa-color: #006644;\n --hal-oa-bg: #e3f5ee;\n --hal-tag-bg: #f0f0f0;\n --hal-tag-color: #444444;\n --hal-font: system-ui, -apple-system, sans-serif;\n --hal-radius: 6px;\n --hal-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.hal-results {\n font-family: var(--hal-font);\n color: var(--hal-text);\n width: 100%;\n}\n\n/* Loading state */\n.hal-loading {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 24px 0;\n color: var(--hal-text-muted);\n font-size: 0.95rem;\n}\n\n.hal-spinner {\n width: 18px;\n height: 18px;\n border: 2px solid var(--hal-border);\n border-top-color: var(--hal-accent);\n border-radius: 50%;\n animation: hal-spin 0.7s linear infinite;\n}\n\n@keyframes hal-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Error and empty states */\n.hal-error,\n.hal-empty {\n padding: 16px;\n border-radius: var(--hal-radius);\n font-size: 0.9rem;\n}\n\n.hal-error {\n background: #fff5f5;\n border: 1px solid #ffc9c9;\n color: #c92a2a;\n}\n\n.hal-empty {\n background: var(--hal-bg-article);\n border: 1px solid var(--hal-border);\n color: var(--hal-text-muted);\n}\n\n/* Article card */\n.hal-article {\n background: var(--hal-bg);\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n padding: 18px 20px;\n margin-bottom: 12px;\n box-shadow: var(--hal-shadow);\n}\n\n.hal-article:last-of-type {\n margin-bottom: 0;\n}\n\n/* Title */\n.hal-article__title {\n margin: 0 0 8px 0;\n font-size: 1rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.hal-article__title a {\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__title a:hover {\n color: var(--hal-accent-hover);\n text-decoration: underline;\n}\n\n/* Label (minimal mode) */\n.hal-article__label {\n margin: 0 0 8px 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: var(--hal-text);\n}\n\n/* Meta row */\n.hal-article__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n margin-bottom: 4px;\n}\n\n.hal-article__authors {\n font-weight: 500;\n color: var(--hal-text);\n}\n\n.hal-article__date::before {\n content: '·';\n margin-right: 6px;\n}\n\n/* Badges */\n.hal-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n letter-spacing: 0.02em;\n text-transform: uppercase;\n}\n\n.hal-badge--oa {\n background: var(--hal-oa-bg);\n color: var(--hal-oa-color);\n}\n\n/* Details section (lvl >= 2) */\n.hal-article__details {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--hal-border);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.hal-article__tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n.hal-tag {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.78rem;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n}\n\n.hal-tag--domain {\n background: #e8f0fe;\n color: #1a56c4;\n}\n\n.hal-article__conference {\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n font-style: italic;\n}\n\n.hal-article__abstract {\n font-size: 0.85rem;\n color: var(--hal-text);\n line-height: 1.55;\n margin-top: 4px;\n}\n\n.hal-article__link {\n font-size: 0.82rem;\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__link:hover {\n text-decoration: underline;\n}\n\n/* Footer credit */\n.hal-footer {\n margin-top: 12px;\n text-align: right;\n font-size: 0.75rem;\n}\n\n.hal-footer__link {\n color: var(--hal-text-muted);\n text-decoration: none;\n}\n\n.hal-footer__link:hover {\n color: var(--hal-accent);\n text-decoration: underline;\n}\n\n/* Pagination */\n.hal-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px solid var(--hal-border);\n font-size: 0.875rem;\n}\n\n.hal-pagination__info {\n color: var(--hal-text-muted);\n}\n\n.hal-pagination__btn {\n padding: 7px 16px;\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n background: var(--hal-bg);\n color: var(--hal-accent);\n font-size: 0.875rem;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.hal-pagination__btn:hover:not(:disabled) {\n background: #f0f4ff;\n border-color: var(--hal-accent);\n}\n\n.hal-pagination__btn:disabled {\n color: var(--hal-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n`;\n\n/** Injects the default CSS into <head> once. Idempotent — safe to call multiple times. */\nexport function injectDefaultStyles(): void {\n const ID = 'hal-search-styles';\n if (document.getElementById(ID)) return;\n const style = document.createElement('style');\n style.id = ID;\n style.textContent = DEFAULT_CSS;\n document.head.appendChild(style);\n}\n","import type {\n HalSearchOptions,\n SearchParams,\n PaginationState,\n HalApiResponse,\n DetailLevel,\n} from './types';\nimport { fetchArticles, DEFAULT_BASE } from './api';\nimport { renderResults, renderLoading, renderError } from './renderer';\nimport { renderResultsSvg, buildArticlesSvg } from './svg-renderer';\nimport { injectDefaultStyles } from './styles';\n\nconst DEFAULTS = {\n lvl: 1 as DetailLevel,\n rows: 10,\n apiBase: DEFAULT_BASE,\n injectStyles: true,\n output: 'html' as 'html' | 'svg',\n};\n\nexport class HalSearch {\n private readonly container?: HTMLElement;\n private options: Required<Omit<HalSearchOptions, 'container' | 'onResults' | 'onError' | 'backgroundColor' | 'textColor' | 'mainColor'>> & {\n onResults?: HalSearchOptions['onResults'];\n onError?: HalSearchOptions['onError'];\n };\n private pagination: PaginationState;\n private currentUid: string = '';\n\n constructor(options: HalSearchOptions) {\n if (options.container) {\n this.container = this._resolveContainer(options.container);\n }\n\n this.options = {\n lvl: options.lvl ?? DEFAULTS.lvl,\n rows: options.rows ?? DEFAULTS.rows,\n apiBase: options.apiBase ?? DEFAULTS.apiBase,\n injectStyles: options.injectStyles ?? DEFAULTS.injectStyles,\n output: options.output ?? DEFAULTS.output,\n onResults: options.onResults,\n onError: options.onError,\n };\n\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: 0,\n };\n\n if (this.options.injectStyles) {\n injectDefaultStyles();\n }\n\n this._applyColors(options);\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Start a new search, resetting to page 1. */\n async search(params: SearchParams): Promise<SVGSVGElement | void> {\n this.currentUid = params.uid;\n if (params.rows !== undefined) this.options.rows = params.rows;\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: params.start ?? 0,\n };\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Navigate to a specific page number (1-based). */\n async goToPage(page: number): Promise<SVGSVGElement | void> {\n const totalPages = Math.max(1, Math.ceil(this.pagination.totalFound / this.options.rows));\n const clampedPage = Math.min(Math.max(1, page), totalPages);\n const start = (clampedPage - 1) * this.options.rows;\n return this._fetch(this.currentUid, start);\n }\n\n /** Navigate to the next page. */\n async nextPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage + 1);\n }\n\n /** Navigate to the previous page. */\n async prevPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage - 1);\n }\n\n /** Change the detail level and re-fetch the current results. */\n async setLevel(lvl: DetailLevel): Promise<SVGSVGElement | void> {\n this.options.lvl = lvl;\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Update the color theme at runtime. Only provided colors are changed. */\n setColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n this._applyColors(colors);\n }\n\n /** Clear the container and remove rendered content. */\n destroy(): void {\n if (this.container) {\n this.container.innerHTML = '';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private _applyColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n if (!this.container) return;\n if (colors.backgroundColor) {\n this.container.style.setProperty('--hal-bg', colors.backgroundColor);\n }\n if (colors.textColor) {\n this.container.style.setProperty('--hal-text', colors.textColor);\n }\n if (colors.mainColor) {\n this.container.style.setProperty('--hal-accent', colors.mainColor);\n }\n }\n\n private async _fetch(uid: string, start: number): Promise<SVGSVGElement | void> {\n if (!uid) return;\n\n if (this.container) {\n renderLoading(this.container);\n }\n\n try {\n const response: HalApiResponse = await fetchArticles(\n uid,\n this.options.lvl,\n this.options.rows,\n start,\n this.options.apiBase,\n );\n\n this._updatePagination(response, start);\n\n if (this.options.output === 'svg') {\n if (this.container) {\n renderResultsSvg(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n );\n } else {\n const svg = buildArticlesSvg(\n response.response.docs,\n this.options.lvl,\n this.pagination,\n );\n this.options.onResults?.(response);\n return svg;\n }\n } else {\n if (this.container) {\n renderResults(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n (page) => { void this.goToPage(page); },\n );\n } else {\n throw new Error('HalSearch: container is required for HTML output');\n }\n }\n\n this.options.onResults?.(response);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (this.container) {\n renderError(this.container, error);\n }\n this.options.onError?.(error);\n if (!this.container && !this.options.onError) {\n throw error;\n }\n }\n }\n\n private _resolveContainer(target: HTMLElement | string): HTMLElement {\n if (typeof target === 'string') {\n const found = document.querySelector<HTMLElement>(target);\n if (!found) {\n throw new Error(`HalSearch: container not found for selector \"${target}\"`);\n }\n return found;\n }\n return target;\n }\n\n private _updatePagination(response: HalApiResponse, start: number): void {\n const { numFound } = response.response;\n this.pagination = {\n currentPage: Math.floor(start / this.options.rows) + 1,\n totalFound: numFound,\n rows: this.options.rows,\n start,\n };\n }\n}\n","import type { DetailLevel } from './types';\n\nexport interface EmbedOptions {\n /** Base URL where embed.html is hosted */\n embedBase: string;\n /** Search query or author UID */\n uid: string;\n /** Detail level 0-3 */\n lvl?: DetailLevel;\n /** Results per page */\n rows?: number;\n /** Render mode: 'html' (default) for interactive cards, 'svg' for a static SVG */\n type?: 'html' | 'svg';\n /** iframe width (CSS value) */\n width?: string;\n /** iframe height (CSS value) */\n height?: string;\n /** Background color for article cards */\n backgroundColor?: string;\n /** Text color for article content */\n textColor?: string;\n /** Main accent color for links and buttons */\n mainColor?: string;\n}\n\n/** Builds the URL for the embeddable page with query parameters. */\nexport function buildEmbedUrl(options: EmbedOptions): string {\n const params = new URLSearchParams({ uid: options.uid });\n if (options.lvl !== undefined) params.set('lvl', String(options.lvl));\n if (options.rows !== undefined) params.set('rows', String(options.rows));\n if (options.type) params.set('type', options.type);\n if (options.backgroundColor) params.set('bg', options.backgroundColor);\n if (options.textColor) params.set('text', options.textColor);\n if (options.mainColor) params.set('main', options.mainColor);\n return `${options.embedBase}/embed.html?${params.toString()}`;\n}\n\n/** Returns a ready-to-paste `<iframe>` HTML snippet. */\nexport function buildEmbedSnippet(options: EmbedOptions): string {\n const src = buildEmbedUrl(options);\n const width = options.width ?? '100%';\n const height = options.height ?? '600';\n return `<iframe src=\"${src}\" width=\"${width}\" height=\"${height}\"></iframe>`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hal-search.umd.js","names":[],"sources":["../src/levels.ts","../src/api.ts","../src/renderer.ts","../src/svg-renderer.ts","../src/styles.ts","../src/HalSearch.ts","../src/embed.ts"],"sourcesContent":["import type { DetailLevel, LevelName } from './types';\n\nconst MINIMAL_FIELDS = ['docid', 'label_s', 'uri_s'] as const;\n\nconst BASIC_FIELDS = [\n ...MINIMAL_FIELDS,\n 'title_s',\n 'authFullName_s',\n 'publicationDate_s',\n 'docType_s',\n] as const;\n\nconst DETAILED_FIELDS = [\n ...BASIC_FIELDS,\n 'keyword_s',\n 'domain_s',\n 'openAccess_bool',\n 'language_s',\n 'peerReviewing_s',\n 'conferenceTitle_s',\n] as const;\n\n/** Maps a DetailLevel to its comma-joined `fl` parameter string */\nexport const LEVEL_FIELDS: Record<DetailLevel, string> = {\n 0: MINIMAL_FIELDS.join(','),\n 1: BASIC_FIELDS.join(','),\n 2: DETAILED_FIELDS.join(','),\n 3: '*',\n};\n\nexport const LEVEL_NAMES: Record<DetailLevel, LevelName> = {\n 0: 'minimal',\n 1: 'basic',\n 2: 'detailed',\n 3: 'full',\n};\n\n/** Returns the `fl` field string for the given detail level */\nexport function resolveFields(lvl: DetailLevel): string {\n return LEVEL_FIELDS[lvl] ?? LEVEL_FIELDS[1];\n}\n","import type { HalApiResponse, DetailLevel } from './types';\nimport { resolveFields } from './levels';\n\nexport const DEFAULT_BASE = 'https://api.archives-ouvertes.fr/search/';\n\n/**\n * Builds a HAL API search URL from the given parameters.\n * Uses URLSearchParams to safely encode special characters in uid.\n */\nexport function buildUrl(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): string {\n const fl = resolveFields(lvl);\n const params = new URLSearchParams({\n q: `\"${uid}\"`,\n wt: 'json',\n fl,\n rows: String(rows),\n start: String(start),\n });\n return `${base}?${params.toString()}`;\n}\n\n/**\n * Fetches articles from the HAL API.\n * Throws on HTTP errors or non-zero API status codes.\n */\nexport async function fetchArticles(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): Promise<HalApiResponse> {\n const url = buildUrl(uid, lvl, rows, start, base);\n const res = await fetch(url, {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`HAL API error: ${res.status} ${res.statusText}`);\n }\n\n const data: HalApiResponse = await res.json();\n\n if (data.responseHeader?.status !== undefined && data.responseHeader.status !== 0) {\n throw new Error(`HAL API returned non-zero status: ${data.responseHeader.status}`);\n }\n\n return data;\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className) node.className = className;\n return node;\n}\n\nfunction text(content: string): Text {\n return document.createTextNode(content);\n}\n\n/** Decodes HTML entities (e.g. &#x27E8;) into their actual characters. */\nfunction decodeEntities(raw: string): string {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = raw;\n return textarea.value;\n}\n\n/** Creates an <a> element with href validated to start with https:// */\nfunction safeLink(href: string | undefined, label: string, className?: string): HTMLAnchorElement {\n const a = el('a', className);\n if (href && (href.startsWith('https://') || href.startsWith('http://'))) {\n a.href = href;\n }\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.textContent = label;\n return a;\n}\n\n// ---------------------------------------------------------------------------\n// State renderers\n// ---------------------------------------------------------------------------\n\nexport function renderLoading(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-loading');\n const spinner = el('div', 'hal-spinner');\n wrap.appendChild(spinner);\n wrap.appendChild(text('Loading…'));\n container.appendChild(wrap);\n}\n\nexport function renderError(container: HTMLElement, err: Error): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-error');\n wrap.textContent = `Error: ${err.message}`;\n container.appendChild(wrap);\n}\n\nexport function renderEmpty(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-empty');\n wrap.textContent = 'No results found.';\n container.appendChild(wrap);\n}\n\n// ---------------------------------------------------------------------------\n// Article card\n// ---------------------------------------------------------------------------\n\nfunction buildArticleCard(doc: HalDoc, lvl: DetailLevel): HTMLElement {\n const article = el('article', 'hal-article');\n if (doc.docid) article.dataset.docid = doc.docid;\n if (doc.docType_s) article.dataset.doctype = doc.docType_s;\n\n // --- Header ---\n const header = el('header');\n\n if (lvl >= 1) {\n // Title + link\n const h3 = el('h3', 'hal-article__title');\n const titleText = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n h3.appendChild(safeLink(doc.uri_s, titleText));\n header.appendChild(h3);\n\n // Meta row\n const meta = el('div', 'hal-article__meta');\n\n if (doc.authFullName_s?.length) {\n const authors = el('span', 'hal-article__authors');\n authors.textContent = doc.authFullName_s.join(', ');\n meta.appendChild(authors);\n }\n\n if (doc.publicationDate_s) {\n const date = el('span', 'hal-article__date');\n // Show only the year if it's a full date string\n date.textContent = doc.publicationDate_s.slice(0, 4);\n meta.appendChild(date);\n }\n\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const badge = el('span', 'hal-badge');\n badge.textContent = doc.docType_s;\n meta.appendChild(badge);\n }\n\n if (doc.openAccess_bool === true) {\n const oaBadge = el('span', 'hal-badge hal-badge--oa');\n oaBadge.textContent = 'Open Access';\n meta.appendChild(oaBadge);\n }\n\n header.appendChild(meta);\n } else {\n // lvl 0: just the full citation\n const div = el('div', 'hal-article__label');\n div.appendChild(safeLink(doc.uri_s, decodeEntities(doc.label_s ?? doc.docid ?? ''), 'hal-article__link'));\n header.appendChild(div);\n }\n\n article.appendChild(header);\n\n // --- Details (lvl >= 2) ---\n if (lvl >= 2) {\n const hasKeywords = doc.keyword_s && doc.keyword_s.length > 0;\n const hasDomains = doc.domain_s && doc.domain_s.length > 0;\n const hasConference = Boolean(doc.conferenceTitle_s);\n\n if (hasKeywords || hasDomains || hasConference) {\n const details = el('section', 'hal-article__details');\n\n if (hasKeywords) {\n const tagsWrap = el('div', 'hal-article__tags');\n for (const kw of doc.keyword_s!) {\n const tag = el('span', 'hal-tag');\n tag.textContent = kw;\n tagsWrap.appendChild(tag);\n }\n details.appendChild(tagsWrap);\n }\n\n if (hasDomains) {\n const domainsWrap = el('div', 'hal-article__tags');\n for (const domain of doc.domain_s!) {\n const tag = el('span', 'hal-tag hal-tag--domain');\n tag.textContent = domain;\n domainsWrap.appendChild(tag);\n }\n details.appendChild(domainsWrap);\n }\n\n if (hasConference) {\n const conf = el('div', 'hal-article__conference');\n conf.textContent = doc.conferenceTitle_s!;\n details.appendChild(conf);\n }\n\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const abstract = el('div', 'hal-article__abstract');\n abstract.textContent = doc.abstract_s[0];\n details.appendChild(abstract);\n }\n\n article.appendChild(details);\n }\n }\n\n return article;\n}\n\n// ---------------------------------------------------------------------------\n// Pagination bar\n// ---------------------------------------------------------------------------\n\nfunction buildPagination(\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): HTMLElement {\n const totalPages = Math.max(1, Math.ceil(pagination.totalFound / pagination.rows));\n const { currentPage } = pagination;\n\n const nav = el('nav', 'hal-pagination');\n nav.setAttribute('aria-label', 'Search results pages');\n\n const prevBtn = el('button', 'hal-pagination__btn');\n prevBtn.textContent = '← Previous';\n prevBtn.disabled = currentPage <= 1;\n prevBtn.addEventListener('click', () => onPageChange(currentPage - 1));\n nav.appendChild(prevBtn);\n\n const info = el('span', 'hal-pagination__info');\n info.textContent = `Page ${currentPage} of ${totalPages} (${pagination.totalFound.toLocaleString()} results)`;\n nav.appendChild(info);\n\n const nextBtn = el('button', 'hal-pagination__btn');\n nextBtn.textContent = 'Next →';\n nextBtn.disabled = currentPage >= totalPages;\n nextBtn.addEventListener('click', () => onPageChange(currentPage + 1));\n nav.appendChild(nextBtn);\n\n return nav;\n}\n\n// ---------------------------------------------------------------------------\n// Main results renderer\n// ---------------------------------------------------------------------------\n\nexport function renderResults(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): void {\n container.innerHTML = '';\n\n if (docs.length === 0) {\n renderEmpty(container);\n return;\n }\n\n const wrapper = el('div', 'hal-results');\n\n for (const doc of docs) {\n wrapper.appendChild(buildArticleCard(doc, lvl));\n }\n\n if (pagination.totalFound > pagination.rows) {\n wrapper.appendChild(buildPagination(pagination, onPageChange));\n }\n\n const footer = el('div', 'hal-footer');\n const credit = safeLink('https://github.com/JPugetGil/hal-search', 'hal-search', 'hal-footer__link');\n footer.appendChild(credit);\n wrapper.appendChild(footer);\n\n container.appendChild(wrapper);\n}\n","import type { HalDoc, DetailLevel, PaginationState, SvgColorOverrides } from './types';\n\nconst NS = 'http://www.w3.org/2000/svg';\nconst W = 800;\nconst PAD = 16;\nconst CARD_GAP = 8;\nconst HEADER_H = 50;\nconst FOOTER_H = 28;\nconst GITHUB_URL = 'https://github.com/JPugetGil/hal-search';\n\n/** Default colour palette — mirrors CSS-variable defaults from styles.ts */\nconst DEFAULT_C = {\n accent: '#0052cc',\n accentText: '#ffffff',\n bg: '#f2f4f8',\n cardBg: '#ffffff',\n border: '#e0e0e0',\n text: '#1a1a1a',\n muted: '#666666',\n link: '#0052cc',\n oaBg: '#e3f5ee',\n oaColor: '#006644',\n tagBg: '#f0f0f0',\n tagColor: '#444444',\n domainBg: '#dbeafe',\n domainColor: '#1a56db',\n};\n\ntype Palette = typeof DEFAULT_C;\n\nexport function resolvePalette(overrides?: SvgColorOverrides): Palette {\n if (!overrides) return DEFAULT_C;\n return {\n ...DEFAULT_C,\n ...(overrides.backgroundColor && { bg: overrides.backgroundColor, cardBg: overrides.backgroundColor }),\n ...(overrides.textColor && { text: overrides.textColor, muted: overrides.textColor }),\n ...(overrides.mainColor && { accent: overrides.mainColor, link: overrides.mainColor }),\n };\n}\n\n// Abstract rendering constants\nconst ABSTRACT_FS = 11;\nconst ABSTRACT_LINE_H = 15;\nconst ABSTRACT_MAX_LINES = 3;\nconst ABSTRACT_LABEL_H = 18;\nconst ABSTRACT_TOP_GAP = 10;\n\n// ---------------------------------------------------------------------------\n// SVG helpers\n// ---------------------------------------------------------------------------\n\nfunction svgEl<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {\n return document.createElementNS(NS, tag) as SVGElementTagNameMap[K];\n}\n\nfunction set(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));\n}\n\nfunction decodeEntities(raw: string): string {\n const ta = document.createElement('textarea');\n ta.innerHTML = raw;\n return ta.value;\n}\n\n/** Approximate truncation (SVG has no native text-measurement API). */\nfunction truncate(text: string, maxPx: number, fontSize: number): string {\n const charW = fontSize * 0.56;\n const max = Math.floor(maxPx / charW);\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n\nfunction mkRect(\n x: number, y: number, w: number, h: number,\n fill: string,\n extra?: Record<string, string | number>,\n): SVGRectElement {\n const r = svgEl('rect');\n set(r, { x, y, width: w, height: h, fill, ...extra });\n return r;\n}\n\nfunction mkText(\n x: number, y: number, content: string,\n extra?: Record<string, string | number>,\n): SVGTextElement {\n const t = svgEl('text');\n set(t, { x, y, 'font-family': 'system-ui,-apple-system,sans-serif', ...extra });\n t.textContent = content;\n return t;\n}\n\nfunction mkLink(href: string, child: SVGElement): SVGAElement {\n const a = svgEl('a');\n a.setAttribute('href', href);\n a.setAttribute('target', '_blank');\n a.appendChild(child);\n return a;\n}\n\n/**\n * Renders a pill badge anchored at (x, baseline-y).\n * Returns the total pixel width consumed, including a 4 px trailing gap.\n */\nfunction pill(\n parent: SVGElement,\n x: number, y: number,\n label: string,\n bg: string, color: string,\n): number {\n const fs = 11;\n const ph = 5, pv = 3;\n const bw = label.length * fs * 0.6 + ph * 2;\n const bh = fs + pv * 2;\n parent.appendChild(mkRect(x, y - fs, bw, bh, bg, { rx: 3 }));\n parent.appendChild(mkText(x + ph, y - 1, label, { 'font-size': fs, fill: color }));\n return bw + 4;\n}\n\n/**\n * Word-wraps `content` into SVG tspan elements appended to a <text> node.\n * Returns the total pixel height consumed (lines × lineHeight).\n */\nfunction wrapText(\n parent: SVGElement,\n x: number, baseY: number,\n content: string,\n maxPx: number,\n fontSize: number,\n lineHeight: number,\n fill: string,\n maxLines = ABSTRACT_MAX_LINES,\n): number {\n const maxChars = Math.floor(maxPx / (fontSize * 0.52));\n const words = content.split(/\\s+/);\n const lines: string[] = [];\n let cur = '';\n let truncated = false;\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i]!;\n const test = cur ? `${cur} ${word}` : word;\n if (test.length <= maxChars) {\n cur = test;\n } else {\n if (cur) lines.push(cur);\n if (lines.length >= maxLines) { truncated = true; break; }\n cur = word;\n }\n }\n if (!truncated && cur) {\n lines.push(cur);\n } else if (truncated && lines.length > 0) {\n lines[lines.length - 1] += '…';\n }\n\n const t = svgEl('text');\n set(t, { x, y: baseY, 'font-size': fontSize, fill, 'font-family': 'system-ui,-apple-system,sans-serif' });\n for (let i = 0; i < lines.length; i++) {\n const ts = svgEl('tspan');\n ts.setAttribute('x', String(x));\n if (i > 0) ts.setAttribute('dy', String(lineHeight));\n ts.textContent = lines[i]!;\n t.appendChild(ts);\n }\n parent.appendChild(t);\n return lines.length * lineHeight;\n}\n\n// ---------------------------------------------------------------------------\n// Card geometry\n// ---------------------------------------------------------------------------\n\n/** Returns the extra height added by the abstract section, or 0 if absent. */\nfunction abstractExtraHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl !== 3 || !doc.abstract_s?.[0]) return 0;\n // Pre-estimate the number of wrapped lines\n const maxPx = W - PAD * 4;\n const maxChars = Math.floor(maxPx / (ABSTRACT_FS * 0.52));\n const words = doc.abstract_s[0].split(/\\s+/);\n let lines = 1;\n let chars = 0;\n for (const word of words) {\n if (chars + word.length + (chars ? 1 : 0) > maxChars) {\n lines++;\n chars = word.length;\n if (lines >= ABSTRACT_MAX_LINES) break;\n } else {\n chars += word.length + (chars ? 1 : 0);\n }\n }\n return ABSTRACT_TOP_GAP + ABSTRACT_LABEL_H + Math.min(lines, ABSTRACT_MAX_LINES) * ABSTRACT_LINE_H;\n}\n\nfunction cardHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl === 0) return 44;\n const hasTagRow =\n lvl >= 2 &&\n ((doc.keyword_s?.length ?? 0) > 0 ||\n (doc.domain_s?.length ?? 0) > 0 ||\n Boolean(doc.conferenceTitle_s));\n return (hasTagRow ? 86 : 62) + abstractExtraHeight(doc, lvl);\n}\n\n// ---------------------------------------------------------------------------\n// Card builder\n// ---------------------------------------------------------------------------\n\nfunction buildCard(doc: HalDoc, lvl: DetailLevel, cardY: number, C: Palette): SVGElement {\n const g = svgEl('g');\n const h = cardHeight(doc, lvl);\n const cx = PAD;\n const cw = W - PAD * 2;\n const ix = PAD * 2; // inner-x (text left margin)\n\n // Card background\n g.appendChild(mkRect(cx, cardY, cw, h, C.cardBg, {\n rx: 6, stroke: C.border, 'stroke-width': 1,\n }));\n\n // ── Level 0: citation label only ──────────────────────────────────────────\n if (lvl === 0) {\n const raw = decodeEntities(doc.label_s ?? doc.docid ?? '');\n const label = truncate(raw, cw - PAD * 2, 12);\n const t = mkText(ix, cardY + 26, label, { 'font-size': 12, fill: C.link });\n if (doc.uri_s?.startsWith('http')) {\n g.appendChild(mkLink(doc.uri_s, t));\n } else {\n t.setAttribute('fill', C.text);\n g.appendChild(t);\n }\n return g;\n }\n\n // ── Level 1+: title row ──────────────────────────────────────────────────\n const titleRaw = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n const titleStr = truncate(titleRaw, cw - PAD * 2, 14);\n const titleEl = mkText(ix, cardY + 22, titleStr, {\n 'font-size': 14,\n 'font-weight': 'bold',\n fill: doc.uri_s?.startsWith('http') ? C.link : C.text,\n });\n g.appendChild(doc.uri_s?.startsWith('http') ? mkLink(doc.uri_s, titleEl) : titleEl);\n\n // ── Meta row ─────────────────────────────────────────────────────────────\n const metaY = cardY + 44;\n\n // Left: \"authors · year\"\n const metaParts: string[] = [];\n if (doc.authFullName_s?.length) {\n metaParts.push(truncate(doc.authFullName_s.join(', '), cw * 0.5, 12));\n }\n if (doc.publicationDate_s) {\n metaParts.push(doc.publicationDate_s.slice(0, 4));\n }\n if (metaParts.length) {\n g.appendChild(mkText(ix, metaY, metaParts.join(' · '), {\n 'font-size': 12, fill: C.muted,\n }));\n }\n\n // Right: badges (rendered right-to-left so order is docType | OA visually)\n let badgeRight = cx + cw - PAD;\n if (doc.openAccess_bool === true) {\n const label = 'Open Access';\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.oaBg, C.oaColor);\n }\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const label = doc.docType_s;\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.tagBg, C.tagColor);\n }\n\n // ── Tags row (lvl ≥ 2) ───────────────────────────────────────────────────\n let nextSectionY = metaY + PAD;\n if (lvl >= 2) {\n let tagX = ix;\n const tagY = cardY + 68;\n const tagRight = cx + cw - PAD;\n nextSectionY = tagY + PAD;\n\n for (const kw of (doc.keyword_s ?? [])) {\n const bw = kw.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, kw, C.tagBg, C.tagColor);\n }\n for (const domain of (doc.domain_s ?? [])) {\n const bw = domain.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, domain, C.domainBg, C.domainColor);\n }\n if (doc.conferenceTitle_s && tagX < tagRight) {\n const label = truncate(doc.conferenceTitle_s, tagRight - tagX, 11);\n g.appendChild(mkText(tagX, tagY, label, {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n }\n }\n\n // ── Abstract (lvl 3 only) ─────────────────────────────────────────────────\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const absTop = nextSectionY + ABSTRACT_TOP_GAP;\n // Separator line\n g.appendChild(mkRect(ix, absTop - 4, cw - PAD * 2, 1, C.border));\n // \"Abstract\" label\n g.appendChild(mkText(ix, absTop + ABSTRACT_LABEL_H - 4, 'Abstract', {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n // Wrapped text\n wrapText(\n g,\n ix, absTop + ABSTRACT_LABEL_H + ABSTRACT_LINE_H - 2,\n doc.abstract_s[0],\n cw - PAD * 2,\n ABSTRACT_FS,\n ABSTRACT_LINE_H,\n C.text,\n );\n }\n\n return g;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Builds an SVG element representing the article list.\n * The SVG is self-contained and can be inserted into the DOM or serialised.\n */\nexport function buildArticlesSvg(\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n colors?: SvgColorOverrides,\n): SVGSVGElement {\n const C = resolvePalette(colors);\n const cardsH = docs.reduce((s, d) => s + cardHeight(d, lvl) + CARD_GAP, 0);\n const totalH = HEADER_H + CARD_GAP + cardsH + FOOTER_H;\n\n const svg = svgEl('svg');\n set(svg, {\n width: W,\n height: totalH,\n viewBox: `0 0 ${W} ${totalH}`,\n xmlns: NS,\n role: 'img',\n 'aria-label': 'HAL Search Results',\n });\n\n // Background\n svg.appendChild(mkRect(0, 0, W, totalH, C.bg));\n\n // Header bar\n svg.appendChild(mkRect(0, 0, W, HEADER_H, C.accent));\n svg.appendChild(mkText(PAD, 32, 'HAL Search Results', {\n 'font-size': 18, 'font-weight': 'bold', fill: C.accentText,\n }));\n\n const { totalFound, currentPage, rows } = pagination;\n const totalPages = Math.max(1, Math.ceil(totalFound / rows));\n const pageInfo = `Page ${currentPage} / ${totalPages} · ${totalFound.toLocaleString()} results`;\n\n svg.appendChild(mkText(W - PAD, 32, pageInfo, {\n 'font-size': 12, fill: C.accentText, 'text-anchor': 'end',\n }));\n\n // Article cards\n let y = HEADER_H + CARD_GAP;\n for (const doc of docs) {\n svg.appendChild(buildCard(doc, lvl, y, C));\n y += cardHeight(doc, lvl) + CARD_GAP;\n }\n\n // Footer: pagination info (left) + GitHub credit (right)\n const footerY = totalH - FOOTER_H / 2 + 4;\n svg.appendChild(mkText(PAD, footerY, pageInfo, {\n 'font-size': 11, fill: C.muted,\n }));\n svg.appendChild(mkLink(\n GITHUB_URL,\n mkText(W - PAD, footerY, 'hal-search', {\n 'font-size': 11, fill: C.muted, 'text-anchor': 'end',\n }),\n ));\n\n return svg;\n}\n\n/**\n * Clears `container` and renders the article list as an inline SVG.\n * Falls back to the standard `.hal-empty` paragraph when `docs` is empty.\n */\nexport function renderResultsSvg(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n colors?: SvgColorOverrides,\n): void {\n container.innerHTML = '';\n if (docs.length === 0) {\n const p = document.createElement('p');\n p.className = 'hal-empty';\n p.textContent = 'No results found.';\n container.appendChild(p);\n return;\n }\n container.appendChild(buildArticlesSvg(docs, lvl, pagination, colors));\n}\n","export const DEFAULT_CSS = `\n:root {\n --hal-accent: #0052cc;\n --hal-accent-hover: #003d99;\n --hal-bg: #ffffff;\n --hal-bg-article: #fafafa;\n --hal-border: #e0e0e0;\n --hal-text: #1a1a1a;\n --hal-text-muted: #666666;\n --hal-oa-color: #006644;\n --hal-oa-bg: #e3f5ee;\n --hal-tag-bg: #f0f0f0;\n --hal-tag-color: #444444;\n --hal-font: system-ui, -apple-system, sans-serif;\n --hal-radius: 6px;\n --hal-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.hal-results {\n font-family: var(--hal-font);\n color: var(--hal-text);\n width: 100%;\n}\n\n/* Loading state */\n.hal-loading {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 24px 0;\n color: var(--hal-text-muted);\n font-size: 0.95rem;\n}\n\n.hal-spinner {\n width: 18px;\n height: 18px;\n border: 2px solid var(--hal-border);\n border-top-color: var(--hal-accent);\n border-radius: 50%;\n animation: hal-spin 0.7s linear infinite;\n}\n\n@keyframes hal-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Error and empty states */\n.hal-error,\n.hal-empty {\n padding: 16px;\n border-radius: var(--hal-radius);\n font-size: 0.9rem;\n}\n\n.hal-error {\n background: #fff5f5;\n border: 1px solid #ffc9c9;\n color: #c92a2a;\n}\n\n.hal-empty {\n background: var(--hal-bg-article);\n border: 1px solid var(--hal-border);\n color: var(--hal-text-muted);\n}\n\n/* Article card */\n.hal-article {\n background: var(--hal-bg);\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n padding: 18px 20px;\n margin-bottom: 12px;\n box-shadow: var(--hal-shadow);\n}\n\n.hal-article:last-of-type {\n margin-bottom: 0;\n}\n\n/* Title */\n.hal-article__title {\n margin: 0 0 8px 0;\n font-size: 1rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.hal-article__title a {\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__title a:hover {\n color: var(--hal-accent-hover);\n text-decoration: underline;\n}\n\n/* Label (minimal mode) */\n.hal-article__label {\n margin: 0 0 8px 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: var(--hal-text);\n}\n\n/* Meta row */\n.hal-article__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n margin-bottom: 4px;\n}\n\n.hal-article__authors {\n font-weight: 500;\n color: var(--hal-text);\n}\n\n.hal-article__date::before {\n content: '·';\n margin-right: 6px;\n}\n\n/* Badges */\n.hal-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n letter-spacing: 0.02em;\n text-transform: uppercase;\n}\n\n.hal-badge--oa {\n background: var(--hal-oa-bg);\n color: var(--hal-oa-color);\n}\n\n/* Details section (lvl >= 2) */\n.hal-article__details {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--hal-border);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.hal-article__tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n.hal-tag {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.78rem;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n}\n\n.hal-tag--domain {\n background: #e8f0fe;\n color: #1a56c4;\n}\n\n.hal-article__conference {\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n font-style: italic;\n}\n\n.hal-article__abstract {\n font-size: 0.85rem;\n color: var(--hal-text);\n line-height: 1.55;\n margin-top: 4px;\n}\n\n.hal-article__link {\n font-size: 0.82rem;\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__link:hover {\n text-decoration: underline;\n}\n\n/* Footer credit */\n.hal-footer {\n margin-top: 12px;\n text-align: right;\n font-size: 0.75rem;\n}\n\n.hal-footer__link {\n color: var(--hal-text-muted);\n text-decoration: none;\n}\n\n.hal-footer__link:hover {\n color: var(--hal-accent);\n text-decoration: underline;\n}\n\n/* Pagination */\n.hal-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px solid var(--hal-border);\n font-size: 0.875rem;\n}\n\n.hal-pagination__info {\n color: var(--hal-text-muted);\n}\n\n.hal-pagination__btn {\n padding: 7px 16px;\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n background: var(--hal-bg);\n color: var(--hal-accent);\n font-size: 0.875rem;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.hal-pagination__btn:hover:not(:disabled) {\n background: #f0f4ff;\n border-color: var(--hal-accent);\n}\n\n.hal-pagination__btn:disabled {\n color: var(--hal-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n`;\n\n/** Injects the default CSS into <head> once. Idempotent — safe to call multiple times. */\nexport function injectDefaultStyles(): void {\n const ID = 'hal-search-styles';\n if (document.getElementById(ID)) return;\n const style = document.createElement('style');\n style.id = ID;\n style.textContent = DEFAULT_CSS;\n document.head.appendChild(style);\n}\n","import type {\n HalSearchOptions,\n SearchParams,\n PaginationState,\n HalApiResponse,\n DetailLevel,\n SvgColorOverrides,\n} from './types';\nimport { fetchArticles, DEFAULT_BASE } from './api';\nimport { renderResults, renderLoading, renderError } from './renderer';\nimport { renderResultsSvg, buildArticlesSvg } from './svg-renderer';\nimport { injectDefaultStyles } from './styles';\n\nconst DEFAULTS = {\n lvl: 1 as DetailLevel,\n rows: 10,\n apiBase: DEFAULT_BASE,\n injectStyles: true,\n output: 'html' as 'html' | 'svg',\n};\n\nexport class HalSearch {\n private readonly container?: HTMLElement;\n private options: Required<Omit<HalSearchOptions, 'container' | 'onResults' | 'onError' | 'backgroundColor' | 'textColor' | 'mainColor'>> & {\n onResults?: HalSearchOptions['onResults'];\n onError?: HalSearchOptions['onError'];\n };\n private pagination: PaginationState;\n private currentUid: string = '';\n private colorOverrides: SvgColorOverrides = {};\n\n constructor(options: HalSearchOptions) {\n if (options.container) {\n this.container = this._resolveContainer(options.container);\n }\n\n this.options = {\n lvl: options.lvl ?? DEFAULTS.lvl,\n rows: options.rows ?? DEFAULTS.rows,\n apiBase: options.apiBase ?? DEFAULTS.apiBase,\n injectStyles: options.injectStyles ?? DEFAULTS.injectStyles,\n output: options.output ?? DEFAULTS.output,\n onResults: options.onResults,\n onError: options.onError,\n };\n\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: 0,\n };\n\n if (this.options.injectStyles) {\n injectDefaultStyles();\n }\n\n this.colorOverrides = {\n backgroundColor: options.backgroundColor,\n textColor: options.textColor,\n mainColor: options.mainColor,\n };\n this._applyColors(this.colorOverrides);\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Start a new search, resetting to page 1. */\n async search(params: SearchParams): Promise<SVGSVGElement | void> {\n this.currentUid = params.uid;\n if (params.rows !== undefined) this.options.rows = params.rows;\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: params.start ?? 0,\n };\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Navigate to a specific page number (1-based). */\n async goToPage(page: number): Promise<SVGSVGElement | void> {\n const totalPages = Math.max(1, Math.ceil(this.pagination.totalFound / this.options.rows));\n const clampedPage = Math.min(Math.max(1, page), totalPages);\n const start = (clampedPage - 1) * this.options.rows;\n return this._fetch(this.currentUid, start);\n }\n\n /** Navigate to the next page. */\n async nextPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage + 1);\n }\n\n /** Navigate to the previous page. */\n async prevPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage - 1);\n }\n\n /** Change the detail level and re-fetch the current results. */\n async setLevel(lvl: DetailLevel): Promise<SVGSVGElement | void> {\n this.options.lvl = lvl;\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Update the color theme at runtime. Only provided colors are changed. */\n setColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n this.colorOverrides = { ...this.colorOverrides, ...colors };\n this._applyColors(colors);\n }\n\n /** Clear the container and remove rendered content. */\n destroy(): void {\n if (this.container) {\n this.container.innerHTML = '';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private _applyColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n if (!this.container) return;\n if (colors.backgroundColor) {\n this.container.style.setProperty('--hal-bg', colors.backgroundColor);\n }\n if (colors.textColor) {\n this.container.style.setProperty('--hal-text', colors.textColor);\n }\n if (colors.mainColor) {\n this.container.style.setProperty('--hal-accent', colors.mainColor);\n }\n }\n\n private async _fetch(uid: string, start: number): Promise<SVGSVGElement | void> {\n if (!uid) return;\n\n if (this.container) {\n renderLoading(this.container);\n }\n\n try {\n const response: HalApiResponse = await fetchArticles(\n uid,\n this.options.lvl,\n this.options.rows,\n start,\n this.options.apiBase,\n );\n\n this._updatePagination(response, start);\n\n if (this.options.output === 'svg') {\n if (this.container) {\n renderResultsSvg(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n this.colorOverrides,\n );\n } else {\n const svg = buildArticlesSvg(\n response.response.docs,\n this.options.lvl,\n this.pagination,\n this.colorOverrides,\n );\n this.options.onResults?.(response);\n return svg;\n }\n } else {\n if (this.container) {\n renderResults(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n (page) => { void this.goToPage(page); },\n );\n } else {\n throw new Error('HalSearch: container is required for HTML output');\n }\n }\n\n this.options.onResults?.(response);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (this.container) {\n renderError(this.container, error);\n }\n this.options.onError?.(error);\n if (!this.container && !this.options.onError) {\n throw error;\n }\n }\n }\n\n private _resolveContainer(target: HTMLElement | string): HTMLElement {\n if (typeof target === 'string') {\n const found = document.querySelector<HTMLElement>(target);\n if (!found) {\n throw new Error(`HalSearch: container not found for selector \"${target}\"`);\n }\n return found;\n }\n return target;\n }\n\n private _updatePagination(response: HalApiResponse, start: number): void {\n const { numFound } = response.response;\n this.pagination = {\n currentPage: Math.floor(start / this.options.rows) + 1,\n totalFound: numFound,\n rows: this.options.rows,\n start,\n };\n }\n}\n","import type { DetailLevel } from './types';\n\nexport interface EmbedOptions {\n /** Base URL where embed.html is hosted */\n embedBase: string;\n /** Search query or author UID */\n uid: string;\n /** Detail level 0-3 */\n lvl?: DetailLevel;\n /** Results per page */\n rows?: number;\n /** Render mode: 'html' (default) for interactive cards, 'svg' for a static SVG */\n type?: 'html' | 'svg';\n /** iframe width (CSS value) */\n width?: string;\n /** iframe height (CSS value) */\n height?: string;\n /** Background color for article cards */\n backgroundColor?: string;\n /** Text color for article content */\n textColor?: string;\n /** Main accent color for links and buttons */\n mainColor?: string;\n}\n\n/** Builds the URL for the embeddable page with query parameters. */\nexport function buildEmbedUrl(options: EmbedOptions): string {\n const params = new URLSearchParams({ uid: options.uid });\n if (options.lvl !== undefined) params.set('lvl', String(options.lvl));\n if (options.rows !== undefined) params.set('rows', String(options.rows));\n if (options.type) params.set('type', options.type);\n if (options.backgroundColor) params.set('bg', options.backgroundColor);\n if (options.textColor) params.set('text', options.textColor);\n if (options.mainColor) params.set('main', options.mainColor);\n return `${options.embedBase}/embed.html?${params.toString()}`;\n}\n\n/** Returns a ready-to-paste `<iframe>` HTML snippet. */\nexport function buildEmbedSnippet(options: EmbedOptions): string {\n const src = buildEmbedUrl(options);\n const width = options.width ?? '100%';\n const height = options.height ?? '600';\n return `<iframe src=\"${src}\" width=\"${width}\" height=\"${height}\"></iframe>`;\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,6 @@ export { buildUrl, fetchArticles, DEFAULT_BASE } from './api';
3
3
  export { LEVEL_FIELDS, LEVEL_NAMES, resolveFields } from './levels';
4
4
  export { DEFAULT_CSS, injectDefaultStyles } from './styles';
5
5
  export { buildEmbedUrl, buildEmbedSnippet } from './embed';
6
- export { buildArticlesSvg, renderResultsSvg } from './svg-renderer';
7
- export type { HalSearchOptions, HalDoc, HalApiResponse, HalResponseBody, HalResponseHeader, DetailLevel, LevelName, SearchParams, PaginationState, } from './types';
6
+ export { buildArticlesSvg, renderResultsSvg, resolvePalette } from './svg-renderer';
7
+ export type { HalSearchOptions, HalDoc, HalApiResponse, HalResponseBody, HalResponseHeader, DetailLevel, LevelName, SearchParams, PaginationState, SvgColorOverrides, } from './types';
8
8
  export type { EmbedOptions } from './embed';
@@ -1,11 +1,31 @@
1
- import { HalDoc, DetailLevel, PaginationState } from './types';
1
+ import { HalDoc, DetailLevel, PaginationState, SvgColorOverrides } from './types';
2
+ /** Default colour palette — mirrors CSS-variable defaults from styles.ts */
3
+ declare const DEFAULT_C: {
4
+ accent: string;
5
+ accentText: string;
6
+ bg: string;
7
+ cardBg: string;
8
+ border: string;
9
+ text: string;
10
+ muted: string;
11
+ link: string;
12
+ oaBg: string;
13
+ oaColor: string;
14
+ tagBg: string;
15
+ tagColor: string;
16
+ domainBg: string;
17
+ domainColor: string;
18
+ };
19
+ type Palette = typeof DEFAULT_C;
20
+ export declare function resolvePalette(overrides?: SvgColorOverrides): Palette;
2
21
  /**
3
22
  * Builds an SVG element representing the article list.
4
23
  * The SVG is self-contained and can be inserted into the DOM or serialised.
5
24
  */
6
- export declare function buildArticlesSvg(docs: HalDoc[], lvl: DetailLevel, pagination: PaginationState): SVGSVGElement;
25
+ export declare function buildArticlesSvg(docs: HalDoc[], lvl: DetailLevel, pagination: PaginationState, colors?: SvgColorOverrides): SVGSVGElement;
7
26
  /**
8
27
  * Clears `container` and renders the article list as an inline SVG.
9
28
  * Falls back to the standard `.hal-empty` paragraph when `docs` is empty.
10
29
  */
11
- export declare function renderResultsSvg(container: HTMLElement, docs: HalDoc[], lvl: DetailLevel, pagination: PaginationState): void;
30
+ export declare function renderResultsSvg(container: HTMLElement, docs: HalDoc[], lvl: DetailLevel, pagination: PaginationState, colors?: SvgColorOverrides): void;
31
+ export {};
package/dist/types.d.ts CHANGED
@@ -72,6 +72,15 @@ export interface SearchParams {
72
72
  rows?: number;
73
73
  start?: number;
74
74
  }
75
+ /** Color overrides for the SVG renderer */
76
+ export interface SvgColorOverrides {
77
+ /** Background color (maps to C.bg / C.cardBg) */
78
+ backgroundColor?: string;
79
+ /** Text color (maps to C.text) */
80
+ textColor?: string;
81
+ /** Main accent color (maps to C.accent / C.link) */
82
+ mainColor?: string;
83
+ }
75
84
  /** Internal pagination state */
76
85
  export interface PaginationState {
77
86
  currentPage: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hal-search",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A library to display article descriptions fetched from the HAL Open Archive API",
5
5
  "repository": {
6
6
  "url": "https://github.com/JPugetGil/hal-search"