@aparte/plugin-cards 0.0.0-stage → 0.29.1

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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -2
  3. package/dist/carousel-DwgHvriV.js +63 -0
  4. package/dist/carousel-DwgHvriV.js.map +1 -0
  5. package/dist/controls-BYUm4aRV.js +62 -0
  6. package/dist/controls-BYUm4aRV.js.map +1 -0
  7. package/dist/format-BFq79ydr.js +728 -0
  8. package/dist/format-BFq79ydr.js.map +1 -0
  9. package/dist/index.d.ts +30 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +120 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/items/icons.d.ts +12 -0
  14. package/dist/items/icons.d.ts.map +1 -0
  15. package/dist/items/index.d.ts +100 -0
  16. package/dist/items/index.d.ts.map +1 -0
  17. package/dist/items.js +698 -0
  18. package/dist/items.js.map +1 -0
  19. package/dist/kit/carousel.d.ts +17 -0
  20. package/dist/kit/carousel.d.ts.map +1 -0
  21. package/dist/kit/controls.d.ts +51 -0
  22. package/dist/kit/controls.d.ts.map +1 -0
  23. package/dist/kit/define.d.ts +201 -0
  24. package/dist/kit/define.d.ts.map +1 -0
  25. package/dist/kit/dom.d.ts +40 -0
  26. package/dist/kit/dom.d.ts.map +1 -0
  27. package/dist/kit/fence.d.ts +39 -0
  28. package/dist/kit/fence.d.ts.map +1 -0
  29. package/dist/kit/format.d.ts +49 -0
  30. package/dist/kit/format.d.ts.map +1 -0
  31. package/dist/kit/rating.d.ts +19 -0
  32. package/dist/kit/rating.d.ts.map +1 -0
  33. package/dist/kit/sparkline.d.ts +19 -0
  34. package/dist/kit/sparkline.d.ts.map +1 -0
  35. package/dist/kit/strings.d.ts +139 -0
  36. package/dist/kit/strings.d.ts.map +1 -0
  37. package/dist/link/icons.d.ts +16 -0
  38. package/dist/link/icons.d.ts.map +1 -0
  39. package/dist/link/index.d.ts +98 -0
  40. package/dist/link/index.d.ts.map +1 -0
  41. package/dist/link.js +387 -0
  42. package/dist/link.js.map +1 -0
  43. package/dist/media/icons.d.ts +28 -0
  44. package/dist/media/icons.d.ts.map +1 -0
  45. package/dist/media/index.d.ts +107 -0
  46. package/dist/media/index.d.ts.map +1 -0
  47. package/dist/media.js +610 -0
  48. package/dist/media.js.map +1 -0
  49. package/dist/order/icons.d.ts +49 -0
  50. package/dist/order/icons.d.ts.map +1 -0
  51. package/dist/order/index.d.ts +204 -0
  52. package/dist/order/index.d.ts.map +1 -0
  53. package/dist/order.js +1843 -0
  54. package/dist/order.js.map +1 -0
  55. package/dist/place/icons.d.ts +18 -0
  56. package/dist/place/icons.d.ts.map +1 -0
  57. package/dist/place/index.d.ts +207 -0
  58. package/dist/place/index.d.ts.map +1 -0
  59. package/dist/place.js +1456 -0
  60. package/dist/place.js.map +1 -0
  61. package/dist/plan/icons.d.ts +25 -0
  62. package/dist/plan/icons.d.ts.map +1 -0
  63. package/dist/plan/index.d.ts +153 -0
  64. package/dist/plan/index.d.ts.map +1 -0
  65. package/dist/plan.js +1191 -0
  66. package/dist/plan.js.map +1 -0
  67. package/dist/product/icons.d.ts +16 -0
  68. package/dist/product/icons.d.ts.map +1 -0
  69. package/dist/product/index.d.ts +133 -0
  70. package/dist/product/index.d.ts.map +1 -0
  71. package/dist/product.js +1245 -0
  72. package/dist/product.js.map +1 -0
  73. package/dist/rating-CHVajQ2b.js +35 -0
  74. package/dist/rating-CHVajQ2b.js.map +1 -0
  75. package/dist/score/icons.d.ts +35 -0
  76. package/dist/score/icons.d.ts.map +1 -0
  77. package/dist/score/index.d.ts +185 -0
  78. package/dist/score/index.d.ts.map +1 -0
  79. package/dist/score.js +1843 -0
  80. package/dist/score.js.map +1 -0
  81. package/dist/sparkline-C-cA9obz.js +56 -0
  82. package/dist/sparkline-C-cA9obz.js.map +1 -0
  83. package/dist/stats/icons.d.ts +19 -0
  84. package/dist/stats/icons.d.ts.map +1 -0
  85. package/dist/stats/index.d.ts +137 -0
  86. package/dist/stats/index.d.ts.map +1 -0
  87. package/dist/stats.js +1085 -0
  88. package/dist/stats.js.map +1 -0
  89. package/dist/stock/chart.d.ts +135 -0
  90. package/dist/stock/chart.d.ts.map +1 -0
  91. package/dist/stock/icons.d.ts +15 -0
  92. package/dist/stock/icons.d.ts.map +1 -0
  93. package/dist/stock/index.d.ts +195 -0
  94. package/dist/stock/index.d.ts.map +1 -0
  95. package/dist/stock.js +1013 -0
  96. package/dist/stock.js.map +1 -0
  97. package/dist/weather/conditions.d.ts +25 -0
  98. package/dist/weather/conditions.d.ts.map +1 -0
  99. package/dist/weather/icons.d.ts +12 -0
  100. package/dist/weather/icons.d.ts.map +1 -0
  101. package/dist/weather/index.d.ts +146 -0
  102. package/dist/weather/index.d.ts.map +1 -0
  103. package/dist/weather.js +1162 -0
  104. package/dist/weather.js.map +1 -0
  105. package/package.json +137 -3
package/dist/link.js ADDED
@@ -0,0 +1,387 @@
1
+ import { b as defineCard, q as str, r as word, e as el, h as formatNumber, m as glyph, w as webUrl, t as toDate, p as num, n as intlTag } from "./format-BFq79ydr.js";
2
+ const svg = (body) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">${body}</svg>`;
3
+ const CHAIN = svg('<path d="M10 14a4.2 4.2 0 0 0 6 0l3-3a4.2 4.2 0 0 0-6-6l-1 1"/><path d="M14 10a4.2 4.2 0 0 0-6 0l-3 3a4.2 4.2 0 0 0 6 6l1-1"/>');
4
+ const PLAY = '<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 5.6v12.8a1 1 0 0 0 1.5.86l10.2-6.4a1 1 0 0 0 0-1.72L10 4.74a1 1 0 0 0-1.5.86z"/></svg>';
5
+ const TYPE_GLYPHS = {
6
+ // A page of lines.
7
+ article: svg('<path d="M6.5 3.5h8l3.5 3.5v12.5a1 1 0 0 1-1 1H6.5a1 1 0 0 1-1-1v-15a1 1 0 0 1 1-1z"/><path d="M14 3.5V7h4M8.5 11.5h7M8.5 14.5h7M8.5 17.5h4.5"/>'),
8
+ // A screen with the triangle.
9
+ video: svg('<rect x="3" y="5" width="18" height="14" rx="3"/><path d="m10.2 9.4 4.4 2.6-4.4 2.6z"/>'),
10
+ // A branch: two commits and the line between them.
11
+ repo: svg('<circle cx="7" cy="6" r="2"/><circle cx="7" cy="18" r="2"/><circle cx="17" cy="8" r="2"/><path d="M7 8v8M17 10c0 3.5-4 3.5-8.5 6.5"/>'),
12
+ // A price tag.
13
+ product: svg('<path d="M3.8 12.6 11.4 5a2 2 0 0 1 1.4-.6h5.3a1.5 1.5 0 0 1 1.5 1.5v5.3a2 2 0 0 1-.6 1.4l-7.6 7.6a1.5 1.5 0 0 1-2.1 0l-5.5-5.5a1.5 1.5 0 0 1 0-2.1z"/><circle cx="15.6" cy="8.4" r="1.3"/>')
14
+ };
15
+ function typeGlyph(type) {
16
+ return TYPE_GLYPHS[type];
17
+ }
18
+ const linkStyles = '/* =========================================\n The link card — @aparte/plugin-cards/link\n =========================================\n Three variants of one card, chosen by `data-variant` on its root:\n card the page\'s picture on top (16:9), its site line (icon, name, author, date,\n kind), the title on two lines, the description on three. The whole card is\n the link. Several links are stacked rows, as in `compact`.\n compact one row: the site\'s mark with its kind in the corner, the title, the site and\n the date, the picture as a small square at the end.\n quote the block chat apps draw under an unfurled link: a coloured rule down its\n side, the site, the title, the description, the author and date, the picture\n beside it when there is room, under it when there is not.\n Every colour is a core token or a `--aparte-link-preview-*` one here: six hues a site\'s initial\n and a quote\'s rule are drawn in (picked from the host, so one site keeps its colour), and\n the accent a page\'s own `theme-color` replaces. The block is `aparte-link-preview`, not\n `aparte-link`: that name is core\'s text-link recipe, and wearing it would repaint every\n card as one underlined link.\n */\n\n.aparte-link-card {\n --aparte-link-preview-hue-0: #2f6fdf;\n --aparte-link-preview-hue-1: #d6336c;\n --aparte-link-preview-hue-2: #2f9e44;\n --aparte-link-preview-hue-3: #e8710a;\n --aparte-link-preview-hue-4: #7048e8;\n --aparte-link-preview-hue-5: #0c9488;\n --aparte-link-preview-media-ratio: 16 / 9;\n --aparte-link-preview-thumb-size: 3.25rem;\n --aparte-link-preview-rule-width: 4px;\n}\n\n.aparte-link-card[data-variant="card"]:has(> .aparte-link-preview--card) { --aparte-rich-card-max-inline-size: 22rem; }\n.aparte-link-card[data-variant="quote"] { --aparte-rich-card-max-inline-size: 30rem; }\n\n/* A quote draws its own block, so the card around it steps back. */\n.aparte-link-card[data-variant="quote"]:is([data-state="ready"], [data-state="loading"]) {\n overflow: visible;\n border-color: transparent;\n background: transparent;\n}\n\n/* Each tint reads its hue once; everything inside says `--aparte-link-preview-hue`. */\n.aparte-link-card [data-hue="0"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-0); }\n.aparte-link-card [data-hue="1"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-1); }\n.aparte-link-card [data-hue="2"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-2); }\n.aparte-link-card [data-hue="3"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-3); }\n.aparte-link-card [data-hue="4"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-4); }\n.aparte-link-card [data-hue="5"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-5); }\n\n/* ── What every variant shares ───────────────────────────────────────── */\n\n.aparte-link-preview {\n position: relative;\n display: flex;\n min-inline-size: 0;\n color: var(--aparte-text);\n}\n\n.aparte-link-preview__dot {\n flex: none;\n color: var(--aparte-text-muted);\n opacity: 0.7;\n}\n\n.aparte-link-preview__title {\n font-size: var(--aparte-font-size-base);\n font-weight: var(--aparte-font-weight-semibold);\n letter-spacing: -0.005em;\n line-height: 1.3;\n overflow-wrap: anywhere;\n}\n\n.aparte-link-preview__anchor {\n color: inherit;\n text-decoration: none;\n border-radius: var(--aparte-radius-xs);\n}\n\n/* The title\'s link covers its whole card, row or block: one target, one name. The ring is\n then drawn around the target, inset, because the card clips what passes its edge. */\n.aparte-link-preview__anchor::after {\n content: "";\n position: absolute;\n inset: 0;\n z-index: 2;\n border-radius: inherit;\n}\n\n.aparte-link-preview__anchor:focus-visible { outline: none; }\n\n.aparte-link-preview__anchor:focus-visible::after {\n outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);\n outline-offset: calc(var(--aparte-focus-outline-width) * -1);\n}\n\n.aparte-link-preview:hover .aparte-link-preview__anchor {\n text-decoration: underline;\n text-decoration-thickness: 1px;\n text-underline-offset: 0.18em;\n}\n\n.aparte-link-preview__description {\n display: -webkit-box;\n margin: 0;\n overflow: hidden;\n color: var(--aparte-text-muted);\n font-size: var(--aparte-font-size-md);\n line-height: 1.45;\n overflow-wrap: anywhere;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3;\n}\n\n/* The site line: its mark, its name, who wrote it, when — and the kind of page at the end. */\n.aparte-link-preview__site {\n display: flex;\n align-items: center;\n gap: var(--aparte-space-3);\n min-inline-size: 0;\n color: var(--aparte-text-muted);\n font-size: var(--aparte-font-size-sm);\n}\n\n.aparte-link-preview__site-text,\n.aparte-link-preview__meta,\n.aparte-link-preview__byline {\n display: flex;\n align-items: center;\n gap: var(--aparte-space-2);\n min-inline-size: 0;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.aparte-link-preview__site-name {\n overflow: hidden;\n color: var(--aparte-text);\n font-weight: var(--aparte-font-weight-medium);\n text-overflow: ellipsis;\n}\n\n.aparte-link-preview__author {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.aparte-link-preview__date { flex: none; }\n\n/* ── The site\'s mark: its icon over its initial on a tint ────────────── */\n\n.aparte-link-preview__icon {\n position: relative;\n display: inline-grid;\n flex: none;\n place-items: center;\n overflow: hidden;\n border-radius: var(--aparte-radius-sm);\n background: color-mix(in oklab, var(--aparte-link-preview-hue) 16%, var(--aparte-surface-1));\n /* The hue, pulled toward the text: readable on the tint in either theme. */\n color: color-mix(in oklab, var(--aparte-link-preview-hue) 72%, var(--aparte-text));\n font-weight: var(--aparte-font-weight-bold, 700);\n line-height: 1;\n}\n\n.aparte-link-preview__icon--sm {\n inline-size: 1.125rem;\n block-size: 1.125rem;\n font-size: 0.625rem;\n}\n\n.aparte-link-preview__icon--lg {\n inline-size: 2.5rem;\n block-size: 2.5rem;\n border-radius: var(--aparte-radius-md);\n font-size: var(--aparte-font-size-base);\n}\n\n/* An icon of its own sits on the card\'s ground, not on the tint; the initial stays behind it. */\n.aparte-link-preview__icon[data-favicon] {\n background: var(--aparte-surface-1);\n box-shadow: inset 0 0 0 var(--aparte-border-width) var(--aparte-border);\n}\n\n.aparte-link-preview__favicon {\n position: absolute;\n inset: 0;\n inline-size: 100%;\n block-size: 100%;\n object-fit: contain;\n background: var(--aparte-surface-1);\n}\n\n.aparte-link-preview__icon--lg > .aparte-link-preview__favicon { padding: 0.4rem; }\n\n/* ── The kind of page ────────────────────────────────────────────────── */\n\n.aparte-link-preview__type {\n display: inline-flex;\n flex: none;\n align-items: center;\n gap: var(--aparte-space-1);\n margin-inline-start: auto;\n padding: var(--aparte-space-1) var(--aparte-space-3) var(--aparte-space-1) var(--aparte-space-2);\n border-radius: var(--aparte-radius-full);\n background: color-mix(in oklab, var(--aparte-text) 6%, transparent);\n color: var(--aparte-text-muted);\n font-size: var(--aparte-font-size-xs);\n font-weight: var(--aparte-font-weight-medium);\n line-height: 1.3;\n}\n\n.aparte-link-preview__type-glyph { --aparte-rich-card-glyph-size: 0.85rem; }\n\n/* ── card ────────────────────────────────────────────────────────────── */\n\n.aparte-link-preview--card {\n flex-direction: column;\n transition: background-color var(--aparte-duration-fast, 0.12s) ease;\n}\n\n.aparte-link-preview__media {\n position: relative;\n aspect-ratio: var(--aparte-link-preview-media-ratio);\n overflow: hidden;\n border-block-end: var(--aparte-border-width) solid var(--aparte-border);\n background: var(--aparte-surface-2);\n}\n\n.aparte-link-preview__img {\n display: block;\n inline-size: 100%;\n block-size: 100%;\n object-fit: cover;\n transition: transform var(--aparte-duration-slower, 0.3s) ease;\n}\n\n.aparte-link-preview--card:hover .aparte-link-preview__media > .aparte-link-preview__img { transform: scale(1.03); }\n\n/* A video page: the play disc over its picture. */\n.aparte-link-preview__play {\n position: absolute;\n inset-block-start: 50%;\n inset-inline-start: 50%;\n display: grid;\n place-items: center;\n inline-size: 3.25rem;\n block-size: 3.25rem;\n border-radius: var(--aparte-radius-full);\n background: rgb(0 0 0 / 0.55);\n color: #ffffff;\n box-shadow: 0 0 0 1px rgb(255 255 255 / 0.25) inset, 0 6px 20px -6px rgb(0 0 0 / 0.6);\n backdrop-filter: blur(6px);\n translate: -50% -50%;\n transition: background-color var(--aparte-duration-base, 0.15s) ease;\n}\n\n.aparte-link-preview__play:dir(rtl) { translate: 50% -50%; }\n.aparte-link-preview__play-glyph { --aparte-rich-card-glyph-size: 1.4rem; margin-inline-start: 0.15rem; }\n.aparte-link-preview__play:dir(rtl) .aparte-link-preview__play-glyph { margin-inline: 0 0.15rem; transform: scaleX(-1); }\n.aparte-link-preview--card:hover .aparte-link-preview__play { background: var(--aparte-primary); }\n\n.aparte-link-preview__body {\n display: flex;\n flex-direction: column;\n gap: var(--aparte-space-3);\n min-inline-size: 0;\n padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-6);\n}\n\n.aparte-link-preview--card .aparte-link-preview__title {\n display: -webkit-box;\n overflow: hidden;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n\n.aparte-link-preview--card:hover { background: color-mix(in oklab, var(--aparte-text) 2.5%, transparent); }\n\n/* ── compact: rows ───────────────────────────────────────────────────── */\n\n.aparte-link-preview--row {\n align-items: center;\n gap: var(--aparte-space-5);\n padding: var(--aparte-space-4) var(--aparte-space-6);\n transition: background-color var(--aparte-duration-fast, 0.12s) ease;\n}\n\n.aparte-link-preview--row:hover { background: color-mix(in oklab, var(--aparte-text) 4%, transparent); }\n\n.aparte-link-preview__mark {\n position: relative;\n flex: none;\n display: inline-flex;\n}\n\n/* The kind of page, as a small disc on the mark\'s corner. */\n.aparte-link-preview__corner {\n position: absolute;\n inset-block-end: -0.3rem;\n inset-inline-end: -0.3rem;\n display: grid;\n place-items: center;\n inline-size: 1.15rem;\n block-size: 1.15rem;\n border-radius: var(--aparte-radius-full);\n background: var(--aparte-surface-1);\n box-shadow: 0 0 0 var(--aparte-border-width) var(--aparte-border);\n color: var(--aparte-text-muted);\n}\n\n.aparte-link-preview__corner-glyph { --aparte-rich-card-glyph-size: 0.7rem; }\n\n.aparte-link-preview__text {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: var(--aparte-space-1);\n min-inline-size: 0;\n}\n\n.aparte-link-preview--row .aparte-link-preview__title {\n overflow: hidden;\n font-size: var(--aparte-font-size-md);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.aparte-link-preview--row .aparte-link-preview__meta {\n color: var(--aparte-text-muted);\n font-size: var(--aparte-font-size-sm);\n}\n\n.aparte-link-preview--row .aparte-link-preview__site-name {\n color: inherit;\n font-weight: var(--aparte-font-weight-normal);\n}\n\n.aparte-link-preview__thumb {\n flex: none;\n inline-size: var(--aparte-link-preview-thumb-size);\n block-size: var(--aparte-link-preview-thumb-size);\n overflow: hidden;\n border-radius: var(--aparte-radius-md);\n background: var(--aparte-surface-2);\n box-shadow: inset 0 0 0 var(--aparte-border-width) var(--aparte-border);\n}\n\n.aparte-link-preview__list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.aparte-link-preview__list > li + li { border-block-start: var(--aparte-border-width) solid var(--aparte-border); }\n\n.aparte-link-preview__heading {\n display: flex;\n align-items: center;\n gap: var(--aparte-space-3);\n min-inline-size: 0;\n padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-4);\n border-block-end: var(--aparte-border-width) solid var(--aparte-border);\n font-size: var(--aparte-font-size-md);\n font-weight: var(--aparte-font-weight-semibold);\n}\n\n.aparte-link-preview__heading-glyph {\n --aparte-rich-card-glyph-size: 1rem;\n color: var(--aparte-text-muted);\n}\n\n.aparte-link-preview__heading-text {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ── quote: the unfurled block ───────────────────────────────────────── */\n\n.aparte-link-preview--quote {\n /* The page\'s own colour when it gives one, else its host\'s hue. */\n --aparte-link-preview-accent-default: var(--aparte-link-preview-hue, var(--aparte-primary));\n container-type: inline-size;\n flex-direction: column;\n gap: var(--aparte-space-5);\n padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-6);\n border: var(--aparte-border-width) solid var(--aparte-border);\n border-inline-start: var(--aparte-link-preview-rule-width) solid var(--aparte-link-preview-accent, var(--aparte-link-preview-accent-default));\n border-radius: var(--aparte-radius-md);\n background: color-mix(in oklab, var(--aparte-link-preview-accent, var(--aparte-link-preview-accent-default)) 4%, var(--aparte-surface-1));\n transition: background-color var(--aparte-duration-fast, 0.12s) ease;\n}\n\n.aparte-link-preview--quote:hover {\n background: color-mix(in oklab, var(--aparte-link-preview-accent, var(--aparte-link-preview-accent-default)) 8%, var(--aparte-surface-1));\n}\n\n.aparte-link-preview__quote-text {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: var(--aparte-space-2);\n min-inline-size: 0;\n}\n\n.aparte-link-preview--quote .aparte-link-preview__site { margin-block-end: var(--aparte-space-1); }\n\n/* The title is a link: core\'s link colour, the one every link in the chat is written in. */\n.aparte-link-preview--quote .aparte-link-preview__title {\n color: var(--aparte-link-color, var(--aparte-primary-ink, var(--aparte-text)));\n}\n\n.aparte-link-preview--quote .aparte-link-preview__description { -webkit-line-clamp: 4; }\n\n.aparte-link-preview__byline {\n margin-block-start: var(--aparte-space-1);\n color: var(--aparte-text-muted);\n font-size: var(--aparte-font-size-sm);\n}\n\n/* Narrow: the picture under the words, wide. */\n.aparte-link-preview__quote-media {\n overflow: hidden;\n aspect-ratio: var(--aparte-link-preview-media-ratio);\n border-radius: var(--aparte-radius-md);\n background: var(--aparte-surface-2);\n}\n\n/* With room: the picture beside the words, a square — the shape of a link\'s thumbnail. */\n@container (min-width: 24rem) {\n .aparte-link-preview--quote:has(> .aparte-link-preview__quote-media) {\n flex-direction: row;\n align-items: flex-start;\n }\n\n .aparte-link-preview__quote-media {\n flex: none;\n inline-size: 6.5rem;\n aspect-ratio: 1;\n }\n}\n\n.aparte-link-preview__stack {\n display: grid;\n gap: var(--aparte-space-4);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n/* ── Loading ─────────────────────────────────────────────────────────── */\n\n.aparte-link-card[data-state="loading"] .aparte-link-preview__sk-media {\n block-size: auto;\n border-radius: 0;\n}\n\n.aparte-link-card[data-state="loading"] .aparte-link-preview__sk-icon {\n flex: none;\n inline-size: 1.125rem;\n block-size: 1.125rem;\n border-radius: var(--aparte-radius-sm);\n}\n\n.aparte-link-card[data-state="loading"] .aparte-link-preview__sk-mark {\n flex: none;\n inline-size: 2.5rem;\n block-size: 2.5rem;\n border-radius: var(--aparte-radius-md);\n}\n\n.aparte-link-card[data-state="loading"] .aparte-link-preview__sk-site { inline-size: 40%; }\n.aparte-link-card[data-state="loading"] .aparte-link-preview__sk-title { inline-size: 75%; block-size: 1rem; }\n.aparte-link-card[data-state="loading"] .aparte-link-preview__sk-line { inline-size: 92%; }\n.aparte-link-card[data-state="loading"] .aparte-link-preview__sk-line--short { inline-size: 60%; }\n\n/* ── Small screens, and stillness ────────────────────────────────────── */\n\n@media (max-width: 380px) {\n .aparte-link-preview__body { padding: var(--aparte-space-4) var(--aparte-space-5) var(--aparte-space-5); }\n .aparte-link-preview--row { gap: var(--aparte-space-4); padding: var(--aparte-space-4) var(--aparte-space-5); }\n .aparte-link-preview--quote { padding: var(--aparte-space-4) var(--aparte-space-5) var(--aparte-space-5); }\n .aparte-link-card { --aparte-link-preview-thumb-size: 2.75rem; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .aparte-link-preview--card:hover .aparte-link-preview__media > .aparte-link-preview__img { transform: none; }\n}\n';
19
+ const L = {
20
+ cardsLinkTitle: { en: "Link", fr: "Lien", de: "Link", es: "Enlace", it: "Link", ja: "リンク", ko: "링크", pt: "Link", ru: "Ссылка", zh: "链接" },
21
+ cardsLinkCount: { en: "{n} links", fr: "{n} liens", de: "{n} Links", es: "{n} enlaces", it: "{n} link", ja: "リンク{n}件", ko: "링크 {n}개", pt: "{n} links", ru: "Ссылок: {n}", zh: "{n} 个链接" },
22
+ cardsLinkArticle: { en: "Article", fr: "Article", de: "Artikel", es: "Artículo", it: "Articolo", ja: "記事", ko: "기사", pt: "Artigo", ru: "Статья", zh: "文章" },
23
+ cardsLinkVideo: { en: "Video", fr: "Vidéo", de: "Video", es: "Vídeo", it: "Video", ja: "動画", ko: "동영상", pt: "Vídeo", ru: "Видео", zh: "视频" },
24
+ cardsLinkRepo: { en: "Repository", fr: "Dépôt", de: "Repository", es: "Repositorio", it: "Repository", ja: "リポジトリ", ko: "저장소", pt: "Repositório", ru: "Репозиторий", zh: "代码仓库" },
25
+ cardsLinkProduct: { en: "Product", fr: "Produit", de: "Produkt", es: "Producto", it: "Prodotto", ja: "商品", ko: "상품", pt: "Produto", ru: "Товар", zh: "商品" }
26
+ };
27
+ const TYPE_WORD = { article: "cardsLinkArticle", video: "cardsLinkVideo", repo: "cardsLinkRepo", product: "cardsLinkProduct" };
28
+ const MAX_LINKS = 10;
29
+ const MAX_DESCRIPTION = 400;
30
+ const HUES = 6;
31
+ const bag = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
32
+ function linkOf(value) {
33
+ const first = Array.isArray(value) ? value[0] : value;
34
+ const o = bag(first);
35
+ return o ? o["url"] ?? o["src"] ?? o["href"] ?? o["secure_url"] ?? o["uri"] : first;
36
+ }
37
+ function address(value, guessScheme = false) {
38
+ let s = str(linkOf(value));
39
+ if (!s) return void 0;
40
+ if (s.startsWith("//")) s = `https:${s}`;
41
+ else if (!/^https?:\/\//i.test(s)) {
42
+ if (!guessScheme || !/^[\w-]+(\.[\w-]+)+(\/|$)/.test(s)) return void 0;
43
+ s = `https://${s}`;
44
+ }
45
+ return webUrl(s) ?? void 0;
46
+ }
47
+ function textOf(value) {
48
+ const first = Array.isArray(value) ? value[0] : value;
49
+ const o = bag(first);
50
+ return o ? str(o["name"] ?? o["title"] ?? o["text"]) : str(first);
51
+ }
52
+ function plain(text) {
53
+ if (!text) return text;
54
+ const entities = { "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#39;": "'", "&#x27;": "'", "&amp;": "&" };
55
+ return text.replace(/<\/?(?:strong|b|em|i|mark)>/gi, "").replace(/&(?:lt|gt|quot|#39|#x27|amp);/gi, (entity) => entities[entity.toLowerCase()]).trim() || void 0;
56
+ }
57
+ function pick(sources, keys) {
58
+ for (const source of sources) {
59
+ if (!source) continue;
60
+ for (const key of keys) {
61
+ const value = source[key];
62
+ if (value !== void 0 && value !== null && value !== "") return value;
63
+ }
64
+ }
65
+ return void 0;
66
+ }
67
+ function dateOf(value) {
68
+ if (value instanceof Date) return toDate(value) ?? void 0;
69
+ const n = num(value);
70
+ if (n !== void 0) return n >= 1e9 ? toDate(n) ?? void 0 : void 0;
71
+ const s = str(value);
72
+ return s && /\d{4}/.test(s) ? toDate(s) ?? void 0 : void 0;
73
+ }
74
+ const TYPE_RULES = [
75
+ ["video", /video|movie|episode|tv[_ .]?show|\bclip\b/],
76
+ ["article", /article|blog|news|post|story/],
77
+ ["repo", /repo|repository|code|software/],
78
+ ["product", /product|offer/]
79
+ ];
80
+ function typeOf(value, url) {
81
+ const s = str(value)?.toLowerCase();
82
+ if (s) {
83
+ for (const [type, rule] of TYPE_RULES) if (rule.test(s)) return type;
84
+ if (s === "website") return "website";
85
+ }
86
+ const host = url.hostname.replace(/^(www|m)\./, "");
87
+ const path = url.pathname.split("/").filter(Boolean);
88
+ if (/^(github\.com|gitlab\.com|codeberg\.org|bitbucket\.org)$/.test(host) && path.length >= 2) return "repo";
89
+ if (host === "youtu.be" || /(^|\.)(youtube\.com|vimeo\.com|twitch\.tv|dailymotion\.com)$/.test(host)) return "video";
90
+ return "website";
91
+ }
92
+ function readLink(raw) {
93
+ const r = bag(typeof raw === "string" ? { url: raw } : raw);
94
+ if (!r) return null;
95
+ const og = bag(r["open_graph"] ?? r["openGraph"] ?? r["og"]);
96
+ const meta = bag(r["meta"]);
97
+ const twitter = bag(r["twitter_card"] ?? r["twitterCard"] ?? r["twitter"]);
98
+ const pagemap = bag(r["pagemap"]);
99
+ const tags = bag(Array.isArray(pagemap?.["metatags"]) ? pagemap["metatags"][0] : void 0);
100
+ const sources = [r, og, meta, tags, twitter];
101
+ const url = address(pick(sources, ["url", "link", "href", "og:url", "ogUrl", "canonical", "uri"])) ?? address(r["displayLink"] ?? r["displayed_link"], true);
102
+ if (!url) return null;
103
+ const parsed = new URL(url);
104
+ const host = parsed.hostname.replace(/^www\./, "");
105
+ const imageCandidates = [
106
+ r["image"],
107
+ r["imageUrl"],
108
+ r["image_url"],
109
+ r["og:image"],
110
+ r["ogImage"],
111
+ og?.["image"],
112
+ og?.["images"],
113
+ r["twitter:image"],
114
+ r["twitterImage"],
115
+ twitter?.["image"],
116
+ twitter?.["images"],
117
+ r["thumbnail"],
118
+ r["thumbnail_url"],
119
+ r["thumbnailUrl"],
120
+ tags?.["og:image"],
121
+ tags?.["twitter:image"],
122
+ pagemap?.["cse_image"],
123
+ pagemap?.["cse_thumbnail"],
124
+ meta?.["image"]
125
+ ];
126
+ const description = plain(str(pick(sources, ["description", "snippet", "summary", "excerpt", "og:description", "ogDescription", "twitter:description", "twitterDescription", "content", "abstract"]))?.replace(/\s+/g, " "));
127
+ const color = str(pick([r, tags], ["themeColor", "theme-color", "theme_color", "color"]));
128
+ return {
129
+ url,
130
+ host,
131
+ title: plain(textOf(pick(sources, ["title", "og:title", "ogTitle", "twitter:title", "twitterTitle", "headline", "name"]))),
132
+ description: description && description.length > MAX_DESCRIPTION ? `${description.slice(0, MAX_DESCRIPTION - 1).trimEnd()}…` : description,
133
+ image: imageCandidates.map((v) => address(v)).find(Boolean),
134
+ siteName: textOf(pick(sources, ["siteName", "site_name", "og:site_name", "ogSiteName", "site", "publisher", "source", "provider_name", "providerName"]) ?? r["profile"]),
135
+ favicon: [r["favicon"], r["icon"], r["logo"], r["faviconUrl"], r["favicon_url"], r["meta_url"]?.["favicon"], r["profile"]?.["img"], meta?.["favicon"]].map((v) => address(v)).find(Boolean),
136
+ author: textOf(pick(sources, ["author", "byline", "article:author", "creator", "authors"])),
137
+ publishedAt: dateOf(pick(sources, ["publishedAt", "published_at", "datePublished", "published_date", "publishedDate", "article:published_time", "publishedTime", "date", "pubDate", "page_age", "createdAt", "created_at"]) ?? og?.["article"]?.["published_time"]),
138
+ type: typeOf(pick(sources, ["type", "og:type", "ogType", "kind"]), parsed),
139
+ themeColor: color && /^#(?:[0-9a-f]{3}){1,2}$/i.test(color) ? color : void 0
140
+ };
141
+ }
142
+ function readLinks(raw) {
143
+ const value = typeof raw === "string" ? [raw] : raw;
144
+ if (!value || typeof value !== "object") return null;
145
+ let title;
146
+ let list;
147
+ if (Array.isArray(value)) {
148
+ list = value;
149
+ } else {
150
+ const r = value;
151
+ const nested = [r["links"], r["results"], r["items"], r["organic_results"], r["sources"], r["search_results"], r["web"]?.["results"]].find(Array.isArray);
152
+ if (nested) {
153
+ list = nested;
154
+ title = str(r["title"]);
155
+ } else {
156
+ list = [value];
157
+ }
158
+ }
159
+ const links = list.map(readLink).filter((l) => l !== null).slice(0, MAX_LINKS);
160
+ return links.length ? { ...title ? { title } : {}, links } : null;
161
+ }
162
+ const titleOf = (link) => link.title ?? link.host;
163
+ const siteOf = (link) => link.siteName ?? link.host;
164
+ function hueOf(host) {
165
+ let sum = 0;
166
+ for (const char of host) sum = (sum * 31 + char.charCodeAt(0)) % 9973;
167
+ return sum % HUES;
168
+ }
169
+ function publishedText(config, date, now = /* @__PURE__ */ new Date()) {
170
+ return new Intl.DateTimeFormat(intlTag(config), {
171
+ month: "short",
172
+ day: "numeric",
173
+ year: date.getFullYear() === now.getFullYear() ? void 0 : "numeric"
174
+ }).format(date);
175
+ }
176
+ const dot = () => el("span", { class: "aparte-link-preview__dot", attrs: { "aria-hidden": "true" }, text: "·" });
177
+ function joined(className, parts) {
178
+ const present = parts.filter((p) => p !== null);
179
+ if (!present.length) return null;
180
+ const line = el("span", { class: className });
181
+ present.forEach((part, i) => {
182
+ if (i) line.append(dot());
183
+ line.append(part);
184
+ });
185
+ return line;
186
+ }
187
+ function dateNode(ctx, link) {
188
+ if (!link.publishedAt) return null;
189
+ const date = link.publishedAt;
190
+ const node = el("time", { class: "aparte-link-preview__date", attrs: { datetime: date.toISOString() } });
191
+ ctx.bind(node, () => publishedText(ctx.config, date));
192
+ return node;
193
+ }
194
+ function iconOf(link, size) {
195
+ const initial = Array.from(siteOf(link))[0].toUpperCase();
196
+ const box = el(
197
+ "span",
198
+ { class: `aparte-link-preview__icon aparte-link-preview__icon--${size}`, attrs: { "aria-hidden": "true", "data-hue": hueOf(link.host) } },
199
+ el("span", { class: "aparte-link-preview__initial", text: initial })
200
+ );
201
+ if (link.favicon) {
202
+ const img = el("img", { class: "aparte-link-preview__favicon", attrs: { src: link.favicon, alt: "", loading: "lazy", decoding: "async" } });
203
+ img.addEventListener("error", () => {
204
+ img.remove();
205
+ box.removeAttribute("data-favicon");
206
+ }, { once: true });
207
+ box.setAttribute("data-favicon", "");
208
+ box.append(img);
209
+ }
210
+ return box;
211
+ }
212
+ function typeBadge(ctx, link) {
213
+ if (link.type === "website") return null;
214
+ const key = TYPE_WORD[link.type];
215
+ const label2 = el("span", { class: "aparte-link-preview__type-label" });
216
+ ctx.word(label2, key, L[key]);
217
+ return el(
218
+ "span",
219
+ { class: "aparte-link-preview__type", attrs: { "data-type": link.type } },
220
+ glyph(typeGlyph(link.type), "aparte-rich-card__glyph aparte-link-preview__type-glyph"),
221
+ label2
222
+ );
223
+ }
224
+ function titleNode(link) {
225
+ return el(
226
+ "div",
227
+ { class: "aparte-link-preview__title" },
228
+ el("a", { class: "aparte-link-preview__anchor", attrs: { href: link.url, target: "_blank", rel: "noopener noreferrer" }, text: titleOf(link) })
229
+ );
230
+ }
231
+ function descriptionOf(link) {
232
+ return link.description ? el("p", { class: "aparte-link-preview__description", text: link.description }) : null;
233
+ }
234
+ function mediaOf(link, className) {
235
+ if (!link.image) return null;
236
+ const box = el("div", { class: className, attrs: { "data-loading": "" } });
237
+ const img = el("img", { class: "aparte-link-preview__img", attrs: { src: link.image, alt: "", loading: "lazy", decoding: "async" } });
238
+ img.addEventListener("load", () => box.removeAttribute("data-loading"), { once: true });
239
+ img.addEventListener("error", () => box.remove(), { once: true });
240
+ box.append(img);
241
+ return box;
242
+ }
243
+ function cardOf(ctx, link) {
244
+ const media = mediaOf(link, "aparte-link-preview__media");
245
+ if (media && link.type === "video") {
246
+ media.append(el("span", { class: "aparte-link-preview__play", attrs: { "aria-hidden": "true" } }, glyph(PLAY, "aparte-rich-card__glyph aparte-link-preview__play-glyph")));
247
+ }
248
+ const site = el(
249
+ "div",
250
+ { class: "aparte-link-preview__site" },
251
+ iconOf(link, "sm"),
252
+ joined("aparte-link-preview__site-text", [
253
+ el("span", { class: "aparte-link-preview__site-name", text: siteOf(link) }),
254
+ link.author ? el("span", { class: "aparte-link-preview__author", text: link.author }) : null,
255
+ dateNode(ctx, link)
256
+ ]),
257
+ typeBadge(ctx, link)
258
+ );
259
+ const body = el("div", { class: "aparte-link-preview__body" }, site, titleNode(link), descriptionOf(link));
260
+ return el("article", { class: "aparte-link-preview aparte-link-preview--card", attrs: { "data-type": link.type } }, media, body);
261
+ }
262
+ function rowOf(ctx, link, tag) {
263
+ const mark = el("span", { class: "aparte-link-preview__mark" }, iconOf(link, "lg"));
264
+ if (link.type !== "website") {
265
+ const corner = el(
266
+ "span",
267
+ { class: "aparte-link-preview__corner", attrs: { role: "img", "data-type": link.type } },
268
+ glyph(typeGlyph(link.type), "aparte-rich-card__glyph aparte-link-preview__corner-glyph")
269
+ );
270
+ ctx.bind(corner, () => word(ctx.config, TYPE_WORD[link.type], L[TYPE_WORD[link.type]]), "aria-label");
271
+ mark.append(corner);
272
+ }
273
+ const meta = joined("aparte-link-preview__meta", [el("span", { class: "aparte-link-preview__site-name", text: siteOf(link) }), dateNode(ctx, link)]);
274
+ const text = el("div", { class: "aparte-link-preview__text" }, titleNode(link), meta);
275
+ return el(tag, { class: "aparte-link-preview aparte-link-preview--row", attrs: { "data-type": link.type } }, mark, text, mediaOf(link, "aparte-link-preview__thumb"));
276
+ }
277
+ function quoteOf(ctx, link, tag) {
278
+ const site = el(
279
+ "div",
280
+ { class: "aparte-link-preview__site" },
281
+ iconOf(link, "sm"),
282
+ el("span", { class: "aparte-link-preview__site-name", text: siteOf(link) }),
283
+ typeBadge(ctx, link)
284
+ );
285
+ const byline = joined("aparte-link-preview__byline", [link.author ? el("span", { class: "aparte-link-preview__author", text: link.author }) : null, dateNode(ctx, link)]);
286
+ const text = el("div", { class: "aparte-link-preview__quote-text" }, site, titleNode(link), descriptionOf(link), byline);
287
+ const node = el(
288
+ tag,
289
+ { class: "aparte-link-preview aparte-link-preview--quote", attrs: { "data-type": link.type, "data-hue": hueOf(link.host) } },
290
+ text,
291
+ mediaOf(link, "aparte-link-preview__quote-media")
292
+ );
293
+ if (link.themeColor) node.style.setProperty("--aparte-link-preview-accent", link.themeColor);
294
+ return node;
295
+ }
296
+ function countLabel(config, data) {
297
+ return data.title ?? word(config, "cardsLinkCount", L.cardsLinkCount, { n: formatNumber(config, data.links?.length ?? 0) });
298
+ }
299
+ function headingOf(ctx, data) {
300
+ const text = el("span", { class: "aparte-link-preview__heading-text" });
301
+ ctx.bind(text, () => countLabel(ctx.config, data));
302
+ return el("div", { class: "aparte-link-preview__heading" }, glyph(CHAIN, "aparte-rich-card__glyph aparte-link-preview__heading-glyph"), text);
303
+ }
304
+ function render(data, ctx) {
305
+ const [first] = data.links;
306
+ if (data.links.length === 1) {
307
+ if (ctx.variant === "compact") return [rowOf(ctx, first, "div")];
308
+ if (ctx.variant === "quote") return [quoteOf(ctx, first, "div")];
309
+ return [cardOf(ctx, first)];
310
+ }
311
+ if (ctx.variant === "quote") {
312
+ const stack = el("ul", { class: "aparte-link-preview__stack" }, ...data.links.map((l) => quoteOf(ctx, l, "li")));
313
+ ctx.bind(stack, () => countLabel(ctx.config, data), "aria-label");
314
+ return [stack];
315
+ }
316
+ const list = el("ul", { class: "aparte-link-preview__list" }, ...data.links.map((l) => rowOf(ctx, l, "li")));
317
+ ctx.bind(list, () => countLabel(ctx.config, data), "aria-label");
318
+ return [headingOf(ctx, data), list];
319
+ }
320
+ function skeleton(partial2, ctx) {
321
+ const block = (cls, kind = "rect") => el("span", { class: `aparte-skeleton aparte-skeleton--${kind} ${cls}`, attrs: { "aria-hidden": "true" } });
322
+ const known = partial2?.links?.[0];
323
+ const title = known?.title ?? partial2?.title;
324
+ const titleLine = title ? el("div", { class: "aparte-link-preview__title", text: title }) : block("aparte-link-preview__sk-title", "text");
325
+ const siteLine = el(
326
+ "div",
327
+ { class: "aparte-link-preview__site" },
328
+ block("aparte-link-preview__sk-icon"),
329
+ known ? el("span", { class: "aparte-link-preview__site-name", text: siteOf(known) }) : block("aparte-link-preview__sk-site", "text")
330
+ );
331
+ if (ctx.variant === "compact") {
332
+ return [el(
333
+ "div",
334
+ { class: "aparte-link-preview aparte-link-preview--row" },
335
+ block("aparte-link-preview__sk-mark"),
336
+ el("div", { class: "aparte-link-preview__text" }, titleLine, known ? el("span", { class: "aparte-link-preview__meta", text: siteOf(known) }) : block("aparte-link-preview__sk-site", "text"))
337
+ )];
338
+ }
339
+ const lines = [block("aparte-link-preview__sk-line", "text"), block("aparte-link-preview__sk-line aparte-link-preview__sk-line--short", "text")];
340
+ if (ctx.variant === "quote") {
341
+ return [el("div", { class: "aparte-link-preview aparte-link-preview--quote" }, el("div", { class: "aparte-link-preview__quote-text" }, siteLine, titleLine, ...lines))];
342
+ }
343
+ return [el(
344
+ "article",
345
+ { class: "aparte-link-preview aparte-link-preview--card" },
346
+ block("aparte-link-preview__media aparte-link-preview__sk-media"),
347
+ el("div", { class: "aparte-link-preview__body" }, siteLine, titleLine, ...lines)
348
+ )];
349
+ }
350
+ function label(data, ctx) {
351
+ const links = data?.links ?? [];
352
+ if (links.length === 1) return titleOf(links[0]);
353
+ if (links.length > 1) return countLabel(ctx.config, data);
354
+ return data?.title ?? word(ctx.config, "cardsLinkTitle", L.cardsLinkTitle);
355
+ }
356
+ function fallback(data) {
357
+ const lines = data.links.map((l) => `${titleOf(l)} — ${siteOf(l)} (${l.url})`);
358
+ return data.title ? `${data.title}:
359
+ ${lines.join("\n")}` : lines.join("\n");
360
+ }
361
+ function partial(input) {
362
+ const one = readLink({ url: input["url"] ?? input["link"] ?? input["href"] });
363
+ if (one) return { links: [one] };
364
+ const query = str(input["query"] ?? input["q"] ?? input["search"] ?? input["title"]);
365
+ return query ? { title: query } : null;
366
+ }
367
+ const linkCard = defineCard({
368
+ name: "link",
369
+ variants: ["card", "compact", "quote"],
370
+ styles: linkStyles,
371
+ icon: CHAIN,
372
+ normalize: readLinks,
373
+ render,
374
+ skeleton,
375
+ label,
376
+ fallback,
377
+ partial
378
+ });
379
+ function setupLinkCard(options = {}) {
380
+ return linkCard.setup(options);
381
+ }
382
+ export {
383
+ linkCard,
384
+ readLinks,
385
+ setupLinkCard
386
+ };
387
+ //# sourceMappingURL=link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.js","sources":["../src/link/icons.ts","../src/link/link.css?raw","../src/link/index.ts"],"sourcesContent":["/**\n * The link card's glyphs: the chain that names the card, one small badge per kind of page\n * (an article, a video, a code repository, a product), and the play disc a video's preview\n * wears.\n *\n * Drawn on a 24 × 24 grid in `currentColor`, so the sheet colours them through `color` and\n * a theme follows. Constants of this package — the only markup the card parses.\n */\nimport type { LinkType } from './index.js';\n\nconst svg = (body: string) =>\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" xmlns=\"http://www.w3.org/2000/svg\">${body}</svg>`; // safe-text: this package's own SVG, built from its glyph constants\n\n/** Two links of a chain: a link, and the card's one-line state glyph. */\nexport const CHAIN = svg('<path d=\"M10 14a4.2 4.2 0 0 0 6 0l3-3a4.2 4.2 0 0 0-6-6l-1 1\"/><path d=\"M14 10a4.2 4.2 0 0 0-6 0l-3 3a4.2 4.2 0 0 0 6 6l1-1\"/>');\n\n/** The play triangle, filled: it sits on a disc over a video's preview. */\nexport const PLAY = '<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.5 5.6v12.8a1 1 0 0 0 1.5.86l10.2-6.4a1 1 0 0 0 0-1.72L10 4.74a1 1 0 0 0-1.5.86z\"/></svg>';\n\nconst TYPE_GLYPHS: Record<Exclude<LinkType, 'website'>, string> = {\n // A page of lines.\n article: svg('<path d=\"M6.5 3.5h8l3.5 3.5v12.5a1 1 0 0 1-1 1H6.5a1 1 0 0 1-1-1v-15a1 1 0 0 1 1-1z\"/><path d=\"M14 3.5V7h4M8.5 11.5h7M8.5 14.5h7M8.5 17.5h4.5\"/>'),\n // A screen with the triangle.\n video: svg('<rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"3\"/><path d=\"m10.2 9.4 4.4 2.6-4.4 2.6z\"/>'),\n // A branch: two commits and the line between them.\n repo: svg('<circle cx=\"7\" cy=\"6\" r=\"2\"/><circle cx=\"7\" cy=\"18\" r=\"2\"/><circle cx=\"17\" cy=\"8\" r=\"2\"/><path d=\"M7 8v8M17 10c0 3.5-4 3.5-8.5 6.5\"/>'),\n // A price tag.\n product: svg('<path d=\"M3.8 12.6 11.4 5a2 2 0 0 1 1.4-.6h5.3a1.5 1.5 0 0 1 1.5 1.5v5.3a2 2 0 0 1-.6 1.4l-7.6 7.6a1.5 1.5 0 0 1-2.1 0l-5.5-5.5a1.5 1.5 0 0 1 0-2.1z\"/><circle cx=\"15.6\" cy=\"8.4\" r=\"1.3\"/>'),\n};\n\n/** The badge glyph for a kind of page; a plain website has none. */\nexport function typeGlyph(type: Exclude<LinkType, 'website'>): string {\n return TYPE_GLYPHS[type];\n}\n","export default \"/* =========================================\\n The link card — @aparte/plugin-cards/link\\n =========================================\\n Three variants of one card, chosen by `data-variant` on its root:\\n card the page's picture on top (16:9), its site line (icon, name, author, date,\\n kind), the title on two lines, the description on three. The whole card is\\n the link. Several links are stacked rows, as in `compact`.\\n compact one row: the site's mark with its kind in the corner, the title, the site and\\n the date, the picture as a small square at the end.\\n quote the block chat apps draw under an unfurled link: a coloured rule down its\\n side, the site, the title, the description, the author and date, the picture\\n beside it when there is room, under it when there is not.\\n Every colour is a core token or a `--aparte-link-preview-*` one here: six hues a site's initial\\n and a quote's rule are drawn in (picked from the host, so one site keeps its colour), and\\n the accent a page's own `theme-color` replaces. The block is `aparte-link-preview`, not\\n `aparte-link`: that name is core's text-link recipe, and wearing it would repaint every\\n card as one underlined link.\\n */\\n\\n.aparte-link-card {\\n --aparte-link-preview-hue-0: #2f6fdf;\\n --aparte-link-preview-hue-1: #d6336c;\\n --aparte-link-preview-hue-2: #2f9e44;\\n --aparte-link-preview-hue-3: #e8710a;\\n --aparte-link-preview-hue-4: #7048e8;\\n --aparte-link-preview-hue-5: #0c9488;\\n --aparte-link-preview-media-ratio: 16 / 9;\\n --aparte-link-preview-thumb-size: 3.25rem;\\n --aparte-link-preview-rule-width: 4px;\\n}\\n\\n.aparte-link-card[data-variant=\\\"card\\\"]:has(> .aparte-link-preview--card) { --aparte-rich-card-max-inline-size: 22rem; }\\n.aparte-link-card[data-variant=\\\"quote\\\"] { --aparte-rich-card-max-inline-size: 30rem; }\\n\\n/* A quote draws its own block, so the card around it steps back. */\\n.aparte-link-card[data-variant=\\\"quote\\\"]:is([data-state=\\\"ready\\\"], [data-state=\\\"loading\\\"]) {\\n overflow: visible;\\n border-color: transparent;\\n background: transparent;\\n}\\n\\n/* Each tint reads its hue once; everything inside says `--aparte-link-preview-hue`. */\\n.aparte-link-card [data-hue=\\\"0\\\"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-0); }\\n.aparte-link-card [data-hue=\\\"1\\\"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-1); }\\n.aparte-link-card [data-hue=\\\"2\\\"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-2); }\\n.aparte-link-card [data-hue=\\\"3\\\"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-3); }\\n.aparte-link-card [data-hue=\\\"4\\\"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-4); }\\n.aparte-link-card [data-hue=\\\"5\\\"] { --aparte-link-preview-hue: var(--aparte-link-preview-hue-5); }\\n\\n/* ── What every variant shares ───────────────────────────────────────── */\\n\\n.aparte-link-preview {\\n position: relative;\\n display: flex;\\n min-inline-size: 0;\\n color: var(--aparte-text);\\n}\\n\\n.aparte-link-preview__dot {\\n flex: none;\\n color: var(--aparte-text-muted);\\n opacity: 0.7;\\n}\\n\\n.aparte-link-preview__title {\\n font-size: var(--aparte-font-size-base);\\n font-weight: var(--aparte-font-weight-semibold);\\n letter-spacing: -0.005em;\\n line-height: 1.3;\\n overflow-wrap: anywhere;\\n}\\n\\n.aparte-link-preview__anchor {\\n color: inherit;\\n text-decoration: none;\\n border-radius: var(--aparte-radius-xs);\\n}\\n\\n/* The title's link covers its whole card, row or block: one target, one name. The ring is\\n then drawn around the target, inset, because the card clips what passes its edge. */\\n.aparte-link-preview__anchor::after {\\n content: \\\"\\\";\\n position: absolute;\\n inset: 0;\\n z-index: 2;\\n border-radius: inherit;\\n}\\n\\n.aparte-link-preview__anchor:focus-visible { outline: none; }\\n\\n.aparte-link-preview__anchor:focus-visible::after {\\n outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);\\n outline-offset: calc(var(--aparte-focus-outline-width) * -1);\\n}\\n\\n.aparte-link-preview:hover .aparte-link-preview__anchor {\\n text-decoration: underline;\\n text-decoration-thickness: 1px;\\n text-underline-offset: 0.18em;\\n}\\n\\n.aparte-link-preview__description {\\n display: -webkit-box;\\n margin: 0;\\n overflow: hidden;\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-md);\\n line-height: 1.45;\\n overflow-wrap: anywhere;\\n -webkit-box-orient: vertical;\\n -webkit-line-clamp: 3;\\n}\\n\\n/* The site line: its mark, its name, who wrote it, when — and the kind of page at the end. */\\n.aparte-link-preview__site {\\n display: flex;\\n align-items: center;\\n gap: var(--aparte-space-3);\\n min-inline-size: 0;\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-sm);\\n}\\n\\n.aparte-link-preview__site-text,\\n.aparte-link-preview__meta,\\n.aparte-link-preview__byline {\\n display: flex;\\n align-items: center;\\n gap: var(--aparte-space-2);\\n min-inline-size: 0;\\n overflow: hidden;\\n white-space: nowrap;\\n}\\n\\n.aparte-link-preview__site-name {\\n overflow: hidden;\\n color: var(--aparte-text);\\n font-weight: var(--aparte-font-weight-medium);\\n text-overflow: ellipsis;\\n}\\n\\n.aparte-link-preview__author {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n}\\n\\n.aparte-link-preview__date { flex: none; }\\n\\n/* ── The site's mark: its icon over its initial on a tint ────────────── */\\n\\n.aparte-link-preview__icon {\\n position: relative;\\n display: inline-grid;\\n flex: none;\\n place-items: center;\\n overflow: hidden;\\n border-radius: var(--aparte-radius-sm);\\n background: color-mix(in oklab, var(--aparte-link-preview-hue) 16%, var(--aparte-surface-1));\\n /* The hue, pulled toward the text: readable on the tint in either theme. */\\n color: color-mix(in oklab, var(--aparte-link-preview-hue) 72%, var(--aparte-text));\\n font-weight: var(--aparte-font-weight-bold, 700);\\n line-height: 1;\\n}\\n\\n.aparte-link-preview__icon--sm {\\n inline-size: 1.125rem;\\n block-size: 1.125rem;\\n font-size: 0.625rem;\\n}\\n\\n.aparte-link-preview__icon--lg {\\n inline-size: 2.5rem;\\n block-size: 2.5rem;\\n border-radius: var(--aparte-radius-md);\\n font-size: var(--aparte-font-size-base);\\n}\\n\\n/* An icon of its own sits on the card's ground, not on the tint; the initial stays behind it. */\\n.aparte-link-preview__icon[data-favicon] {\\n background: var(--aparte-surface-1);\\n box-shadow: inset 0 0 0 var(--aparte-border-width) var(--aparte-border);\\n}\\n\\n.aparte-link-preview__favicon {\\n position: absolute;\\n inset: 0;\\n inline-size: 100%;\\n block-size: 100%;\\n object-fit: contain;\\n background: var(--aparte-surface-1);\\n}\\n\\n.aparte-link-preview__icon--lg > .aparte-link-preview__favicon { padding: 0.4rem; }\\n\\n/* ── The kind of page ────────────────────────────────────────────────── */\\n\\n.aparte-link-preview__type {\\n display: inline-flex;\\n flex: none;\\n align-items: center;\\n gap: var(--aparte-space-1);\\n margin-inline-start: auto;\\n padding: var(--aparte-space-1) var(--aparte-space-3) var(--aparte-space-1) var(--aparte-space-2);\\n border-radius: var(--aparte-radius-full);\\n background: color-mix(in oklab, var(--aparte-text) 6%, transparent);\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-xs);\\n font-weight: var(--aparte-font-weight-medium);\\n line-height: 1.3;\\n}\\n\\n.aparte-link-preview__type-glyph { --aparte-rich-card-glyph-size: 0.85rem; }\\n\\n/* ── card ────────────────────────────────────────────────────────────── */\\n\\n.aparte-link-preview--card {\\n flex-direction: column;\\n transition: background-color var(--aparte-duration-fast, 0.12s) ease;\\n}\\n\\n.aparte-link-preview__media {\\n position: relative;\\n aspect-ratio: var(--aparte-link-preview-media-ratio);\\n overflow: hidden;\\n border-block-end: var(--aparte-border-width) solid var(--aparte-border);\\n background: var(--aparte-surface-2);\\n}\\n\\n.aparte-link-preview__img {\\n display: block;\\n inline-size: 100%;\\n block-size: 100%;\\n object-fit: cover;\\n transition: transform var(--aparte-duration-slower, 0.3s) ease;\\n}\\n\\n.aparte-link-preview--card:hover .aparte-link-preview__media > .aparte-link-preview__img { transform: scale(1.03); }\\n\\n/* A video page: the play disc over its picture. */\\n.aparte-link-preview__play {\\n position: absolute;\\n inset-block-start: 50%;\\n inset-inline-start: 50%;\\n display: grid;\\n place-items: center;\\n inline-size: 3.25rem;\\n block-size: 3.25rem;\\n border-radius: var(--aparte-radius-full);\\n background: rgb(0 0 0 / 0.55);\\n color: #ffffff;\\n box-shadow: 0 0 0 1px rgb(255 255 255 / 0.25) inset, 0 6px 20px -6px rgb(0 0 0 / 0.6);\\n backdrop-filter: blur(6px);\\n translate: -50% -50%;\\n transition: background-color var(--aparte-duration-base, 0.15s) ease;\\n}\\n\\n.aparte-link-preview__play:dir(rtl) { translate: 50% -50%; }\\n.aparte-link-preview__play-glyph { --aparte-rich-card-glyph-size: 1.4rem; margin-inline-start: 0.15rem; }\\n.aparte-link-preview__play:dir(rtl) .aparte-link-preview__play-glyph { margin-inline: 0 0.15rem; transform: scaleX(-1); }\\n.aparte-link-preview--card:hover .aparte-link-preview__play { background: var(--aparte-primary); }\\n\\n.aparte-link-preview__body {\\n display: flex;\\n flex-direction: column;\\n gap: var(--aparte-space-3);\\n min-inline-size: 0;\\n padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-6);\\n}\\n\\n.aparte-link-preview--card .aparte-link-preview__title {\\n display: -webkit-box;\\n overflow: hidden;\\n -webkit-box-orient: vertical;\\n -webkit-line-clamp: 2;\\n}\\n\\n.aparte-link-preview--card:hover { background: color-mix(in oklab, var(--aparte-text) 2.5%, transparent); }\\n\\n/* ── compact: rows ───────────────────────────────────────────────────── */\\n\\n.aparte-link-preview--row {\\n align-items: center;\\n gap: var(--aparte-space-5);\\n padding: var(--aparte-space-4) var(--aparte-space-6);\\n transition: background-color var(--aparte-duration-fast, 0.12s) ease;\\n}\\n\\n.aparte-link-preview--row:hover { background: color-mix(in oklab, var(--aparte-text) 4%, transparent); }\\n\\n.aparte-link-preview__mark {\\n position: relative;\\n flex: none;\\n display: inline-flex;\\n}\\n\\n/* The kind of page, as a small disc on the mark's corner. */\\n.aparte-link-preview__corner {\\n position: absolute;\\n inset-block-end: -0.3rem;\\n inset-inline-end: -0.3rem;\\n display: grid;\\n place-items: center;\\n inline-size: 1.15rem;\\n block-size: 1.15rem;\\n border-radius: var(--aparte-radius-full);\\n background: var(--aparte-surface-1);\\n box-shadow: 0 0 0 var(--aparte-border-width) var(--aparte-border);\\n color: var(--aparte-text-muted);\\n}\\n\\n.aparte-link-preview__corner-glyph { --aparte-rich-card-glyph-size: 0.7rem; }\\n\\n.aparte-link-preview__text {\\n display: flex;\\n flex: 1;\\n flex-direction: column;\\n gap: var(--aparte-space-1);\\n min-inline-size: 0;\\n}\\n\\n.aparte-link-preview--row .aparte-link-preview__title {\\n overflow: hidden;\\n font-size: var(--aparte-font-size-md);\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n\\n.aparte-link-preview--row .aparte-link-preview__meta {\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-sm);\\n}\\n\\n.aparte-link-preview--row .aparte-link-preview__site-name {\\n color: inherit;\\n font-weight: var(--aparte-font-weight-normal);\\n}\\n\\n.aparte-link-preview__thumb {\\n flex: none;\\n inline-size: var(--aparte-link-preview-thumb-size);\\n block-size: var(--aparte-link-preview-thumb-size);\\n overflow: hidden;\\n border-radius: var(--aparte-radius-md);\\n background: var(--aparte-surface-2);\\n box-shadow: inset 0 0 0 var(--aparte-border-width) var(--aparte-border);\\n}\\n\\n.aparte-link-preview__list {\\n margin: 0;\\n padding: 0;\\n list-style: none;\\n}\\n\\n.aparte-link-preview__list > li + li { border-block-start: var(--aparte-border-width) solid var(--aparte-border); }\\n\\n.aparte-link-preview__heading {\\n display: flex;\\n align-items: center;\\n gap: var(--aparte-space-3);\\n min-inline-size: 0;\\n padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-4);\\n border-block-end: var(--aparte-border-width) solid var(--aparte-border);\\n font-size: var(--aparte-font-size-md);\\n font-weight: var(--aparte-font-weight-semibold);\\n}\\n\\n.aparte-link-preview__heading-glyph {\\n --aparte-rich-card-glyph-size: 1rem;\\n color: var(--aparte-text-muted);\\n}\\n\\n.aparte-link-preview__heading-text {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n\\n/* ── quote: the unfurled block ───────────────────────────────────────── */\\n\\n.aparte-link-preview--quote {\\n /* The page's own colour when it gives one, else its host's hue. */\\n --aparte-link-preview-accent-default: var(--aparte-link-preview-hue, var(--aparte-primary));\\n container-type: inline-size;\\n flex-direction: column;\\n gap: var(--aparte-space-5);\\n padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-6);\\n border: var(--aparte-border-width) solid var(--aparte-border);\\n border-inline-start: var(--aparte-link-preview-rule-width) solid var(--aparte-link-preview-accent, var(--aparte-link-preview-accent-default));\\n border-radius: var(--aparte-radius-md);\\n background: color-mix(in oklab, var(--aparte-link-preview-accent, var(--aparte-link-preview-accent-default)) 4%, var(--aparte-surface-1));\\n transition: background-color var(--aparte-duration-fast, 0.12s) ease;\\n}\\n\\n.aparte-link-preview--quote:hover {\\n background: color-mix(in oklab, var(--aparte-link-preview-accent, var(--aparte-link-preview-accent-default)) 8%, var(--aparte-surface-1));\\n}\\n\\n.aparte-link-preview__quote-text {\\n display: flex;\\n flex: 1;\\n flex-direction: column;\\n gap: var(--aparte-space-2);\\n min-inline-size: 0;\\n}\\n\\n.aparte-link-preview--quote .aparte-link-preview__site { margin-block-end: var(--aparte-space-1); }\\n\\n/* The title is a link: core's link colour, the one every link in the chat is written in. */\\n.aparte-link-preview--quote .aparte-link-preview__title {\\n color: var(--aparte-link-color, var(--aparte-primary-ink, var(--aparte-text)));\\n}\\n\\n.aparte-link-preview--quote .aparte-link-preview__description { -webkit-line-clamp: 4; }\\n\\n.aparte-link-preview__byline {\\n margin-block-start: var(--aparte-space-1);\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-sm);\\n}\\n\\n/* Narrow: the picture under the words, wide. */\\n.aparte-link-preview__quote-media {\\n overflow: hidden;\\n aspect-ratio: var(--aparte-link-preview-media-ratio);\\n border-radius: var(--aparte-radius-md);\\n background: var(--aparte-surface-2);\\n}\\n\\n/* With room: the picture beside the words, a square — the shape of a link's thumbnail. */\\n@container (min-width: 24rem) {\\n .aparte-link-preview--quote:has(> .aparte-link-preview__quote-media) {\\n flex-direction: row;\\n align-items: flex-start;\\n }\\n\\n .aparte-link-preview__quote-media {\\n flex: none;\\n inline-size: 6.5rem;\\n aspect-ratio: 1;\\n }\\n}\\n\\n.aparte-link-preview__stack {\\n display: grid;\\n gap: var(--aparte-space-4);\\n margin: 0;\\n padding: 0;\\n list-style: none;\\n}\\n\\n/* ── Loading ─────────────────────────────────────────────────────────── */\\n\\n.aparte-link-card[data-state=\\\"loading\\\"] .aparte-link-preview__sk-media {\\n block-size: auto;\\n border-radius: 0;\\n}\\n\\n.aparte-link-card[data-state=\\\"loading\\\"] .aparte-link-preview__sk-icon {\\n flex: none;\\n inline-size: 1.125rem;\\n block-size: 1.125rem;\\n border-radius: var(--aparte-radius-sm);\\n}\\n\\n.aparte-link-card[data-state=\\\"loading\\\"] .aparte-link-preview__sk-mark {\\n flex: none;\\n inline-size: 2.5rem;\\n block-size: 2.5rem;\\n border-radius: var(--aparte-radius-md);\\n}\\n\\n.aparte-link-card[data-state=\\\"loading\\\"] .aparte-link-preview__sk-site { inline-size: 40%; }\\n.aparte-link-card[data-state=\\\"loading\\\"] .aparte-link-preview__sk-title { inline-size: 75%; block-size: 1rem; }\\n.aparte-link-card[data-state=\\\"loading\\\"] .aparte-link-preview__sk-line { inline-size: 92%; }\\n.aparte-link-card[data-state=\\\"loading\\\"] .aparte-link-preview__sk-line--short { inline-size: 60%; }\\n\\n/* ── Small screens, and stillness ────────────────────────────────────── */\\n\\n@media (max-width: 380px) {\\n .aparte-link-preview__body { padding: var(--aparte-space-4) var(--aparte-space-5) var(--aparte-space-5); }\\n .aparte-link-preview--row { gap: var(--aparte-space-4); padding: var(--aparte-space-4) var(--aparte-space-5); }\\n .aparte-link-preview--quote { padding: var(--aparte-space-4) var(--aparte-space-5) var(--aparte-space-5); }\\n .aparte-link-card { --aparte-link-preview-thumb-size: 2.75rem; }\\n}\\n\\n@media (prefers-reduced-motion: reduce) {\\n .aparte-link-preview--card:hover .aparte-link-preview__media > .aparte-link-preview__img { transform: none; }\\n}\\n\"","/**\n * @aparte/plugin-cards/link — a web page, previewed: its picture, its site, its title and\n * what it says it is about, the whole card one link to it.\n *\n * import { setupLinkCard } from '@aparte/plugin-cards/link';\n *\n * setupLinkCard({ tool: 'unfurl' }); // one page's Open Graph, as it is\n * setupLinkCard({ tool: 'web_search', map: (r) => r.items }); // search results\n *\n * One link, several (`[...]`), or `{ title, links: [...] }`. Three variants of one card:\n * `card` (the picture on top, the site, the title, the description), `compact` (one row: the\n * site's icon, the title, a small picture at the end) and `quote` (the block chat apps draw\n * under an unfurled link, with a coloured rule down its side). Your tool keeps its own\n * fields: `readLinks` reads Open Graph and Twitter keys, unfurlers' nested shapes and search\n * results (Google's Custom Search, SerpAPI, Brave, Tavily, Exa), and `map` says the rest.\n *\n * The card fetches nothing the tool's data does not name: no page, and no address of its\n * own making — no favicon service is asked, no icon guessed at `/favicon.ico`. A picture or an\n * icon the data names is loaded as given, wherever it is hosted (Brave's results name icons\n * on Brave's own proxy). A site without an icon in the data gets its initial on a tinted\n * square, and a page without a picture gets no picture.\n *\n * Every value is text. Search engines highlight the query in their snippets with markup\n * (Brave's `<strong>`): those tags are dropped and the usual entities decoded, and what is\n * left goes in as text — never parsed.\n */\nimport type { AparteConfig } from '@aparte/core';\nimport { defineCard, type CardContext, type CardSetupOptions } from '../kit/define.js';\nimport { el, glyph, num, str, webUrl, type Loose } from '../kit/dom.js';\nimport { formatNumber, toDate } from '../kit/format.js';\nimport { intlTag, word, type CardWord } from '../kit/strings.js';\nimport { CHAIN, PLAY, typeGlyph } from './icons.js';\nimport linkStyles from './link.css?raw';\n\nexport type LinkType = 'article' | 'video' | 'repo' | 'product' | 'website';\nexport type LinkVariant = 'card' | 'compact' | 'quote';\n\n/**\n * A link as `map` returns it. Only `url` is needed; every other field adds a line when it is\n * there and nothing when it is not. `readLinks` also reads the usual aliases (listed on each\n * field).\n */\nexport interface LinkInput {\n /** An `http(s)` page. Also `link`, `href`, `og:url`. */\n url: string;\n /** Falls back to the page's host. Also `og:title`, `twitter:title`, `meta.title`, `name`. */\n title?: string;\n /** Also `og:description`, `snippet`, `summary`, `content`. */\n description?: string;\n /** Also `og:image`, `twitter:image`, `thumbnail`, `open_graph.images[0].url`, `pagemap.cse_image[0].src`. */\n image?: string;\n /** Falls back to the host without `www.`. Also `og:site_name`, `site`, `publisher`, `source`. */\n siteName?: string;\n /** The site's icon, loaded as given. Without it, the site's initial on a tinted square — no icon address is made up. Also `icon`, `logo`, Brave's `meta_url.favicon`. */\n favicon?: string;\n /** Also `byline`, `article:author`, `authors[0]`. */\n author?: string;\n /** An ISO date or an epoch. Also `datePublished`, `published_date`, `date`, `article:published_time`. */\n publishedAt?: string | number | Date;\n /** The badge it wears. Read from `og:type` and the host (GitHub → repo, YouTube → video) when absent. */\n type?: LinkType;\n /** A colour for the quote's side rule: `#rgb` or `#rrggbb`. Also `theme-color`. */\n themeColor?: string;\n}\n\n/** A link as the card draws it, once read. */\nexport interface Link {\n url: string;\n /** The page's host without `www.` — what stands in for a missing title or site name. */\n host: string;\n title?: string;\n description?: string;\n image?: string;\n siteName?: string;\n favicon?: string;\n author?: string;\n publishedAt?: Date;\n type: LinkType;\n themeColor?: string;\n}\n\n/** The card's data: one link or several, and the collection's title. */\nexport interface LinkData {\n title?: string;\n links: Link[];\n}\n\nexport type LinkCardOptions = CardSetupOptions<LinkData, LinkVariant>;\n\nconst L = {\n cardsLinkTitle: { en: 'Link', fr: 'Lien', de: 'Link', es: 'Enlace', it: 'Link', ja: 'リンク', ko: '링크', pt: 'Link', ru: 'Ссылка', zh: '链接' },\n cardsLinkCount: { en: '{n} links', fr: '{n} liens', de: '{n} Links', es: '{n} enlaces', it: '{n} link', ja: 'リンク{n}件', ko: '링크 {n}개', pt: '{n} links', ru: 'Ссылок: {n}', zh: '{n} 个链接' },\n cardsLinkArticle: { en: 'Article', fr: 'Article', de: 'Artikel', es: 'Artículo', it: 'Articolo', ja: '記事', ko: '기사', pt: 'Artigo', ru: 'Статья', zh: '文章' },\n cardsLinkVideo: { en: 'Video', fr: 'Vidéo', de: 'Video', es: 'Vídeo', it: 'Video', ja: '動画', ko: '동영상', pt: 'Vídeo', ru: 'Видео', zh: '视频' },\n cardsLinkRepo: { en: 'Repository', fr: 'Dépôt', de: 'Repository', es: 'Repositorio', it: 'Repository', ja: 'リポジトリ', ko: '저장소', pt: 'Repositório', ru: 'Репозиторий', zh: '代码仓库' },\n cardsLinkProduct: { en: 'Product', fr: 'Produit', de: 'Produkt', es: 'Producto', it: 'Prodotto', ja: '商品', ko: '상품', pt: 'Produto', ru: 'Товар', zh: '商品' },\n} satisfies Record<string, CardWord>;\n\nconst TYPE_WORD = { article: 'cardsLinkArticle', video: 'cardsLinkVideo', repo: 'cardsLinkRepo', product: 'cardsLinkProduct' } as const;\n\n/** The most links one card holds: a result page of fifty is a page, not a card. */\nconst MAX_LINKS = 10;\n/** A description longer than this is cut before it reaches the page; the sheet clamps what is left to its lines. */\nconst MAX_DESCRIPTION = 400;\n/** How many tints a site's initial can stand on: the sheet's six hues. */\nconst HUES = 6;\n\n// ── Reading ──────────────────────────────────────────────────────────────────\n\ntype Bag = Loose;\n\nconst bag = (value: unknown): Bag | undefined =>\n value && typeof value === 'object' && !Array.isArray(value) ? value as Bag : undefined;\n\n/** An address given as a string, as `{ url | src | href | secure_url }`, or as the first of a list of either. */\nfunction linkOf(value: unknown): unknown {\n const first = Array.isArray(value) ? value[0] : value;\n const o = bag(first);\n return o ? o['url'] ?? o['src'] ?? o['href'] ?? o['secure_url'] ?? o['uri'] : first;\n}\n\n/**\n * A page or picture address: `http(s)`, protocol-relative, or a bare host (\"example.com/x\",\n * what a search result's `displayLink` is), read as `https`.\n */\nfunction address(value: unknown, guessScheme = false): string | undefined {\n let s = str(linkOf(value));\n if (!s) return undefined;\n if (s.startsWith('//')) s = `https:${s}`;\n else if (!/^https?:\\/\\//i.test(s)) {\n if (!guessScheme || !/^[\\w-]+(\\.[\\w-]+)+(\\/|$)/.test(s)) return undefined;\n s = `https://${s}`;\n }\n return webUrl(s) ?? undefined;\n}\n\n/** A name given as a string, as `{ name }`, or as the first of a list of either. */\nfunction textOf(value: unknown): string | undefined {\n const first = Array.isArray(value) ? value[0] : value;\n const o = bag(first);\n return o ? str(o['name'] ?? o['title'] ?? o['text']) : str(first);\n}\n\n/**\n * A search engine's snippet as plain text: its highlighting tags dropped (Brave wraps the\n * query's words in `<strong>`, others in `<b>` or `<em>`), then the usual entities decoded —\n * in that order, so `&lt;b&gt;` comes out as the text `<b>`, not as a tag to drop. Only those\n * inline tags: a title that NAMES a tag (\"Why <div> is not a button\") keeps it. The result\n * still goes in as text.\n */\nfunction plain(text: string | undefined): string | undefined {\n if (!text) return text;\n const entities: Record<string, string> = { '&lt;': '<', '&gt;': '>', '&quot;': '\"', '&#39;': \"'\", '&#x27;': \"'\", '&amp;': '&' };\n return text\n .replace(/<\\/?(?:strong|b|em|i|mark)>/gi, '')\n .replace(/&(?:lt|gt|quot|#39|#x27|amp);/gi, (entity) => entities[entity.toLowerCase()]!)\n .trim() || undefined;\n}\n\n/** The first value under any of `keys`, in any of `sources`, in that order — source by source. */\nfunction pick(sources: Array<Bag | undefined>, keys: string[]): unknown {\n for (const source of sources) {\n if (!source) continue;\n for (const key of keys) {\n const value = source[key];\n if (value !== undefined && value !== null && value !== '') return value;\n }\n }\n return undefined;\n}\n\n/** A publication date: a Date, an epoch, or a text with a year in it — \"2 days ago\" is no date. */\nfunction dateOf(value: unknown): Date | undefined {\n if (value instanceof Date) return toDate(value) ?? undefined;\n const n = num(value);\n if (n !== undefined) return n >= 1e9 ? toDate(n) ?? undefined : undefined;\n const s = str(value);\n return s && /\\d{4}/.test(s) ? toDate(s) ?? undefined : undefined;\n}\n\nconst TYPE_RULES: Array<[Exclude<LinkType, 'website'>, RegExp]> = [\n ['video', /video|movie|episode|tv[_ .]?show|\\bclip\\b/],\n ['article', /article|blog|news|post|story/],\n ['repo', /repo|repository|code|software/],\n ['product', /product|offer/],\n];\n\nfunction typeOf(value: unknown, url: URL): LinkType {\n const s = str(value)?.toLowerCase();\n if (s) {\n for (const [type, rule] of TYPE_RULES) if (rule.test(s)) return type;\n if (s === 'website') return 'website';\n }\n const host = url.hostname.replace(/^(www|m)\\./, '');\n const path = url.pathname.split('/').filter(Boolean);\n if (/^(github\\.com|gitlab\\.com|codeberg\\.org|bitbucket\\.org)$/.test(host) && path.length >= 2) return 'repo';\n if (host === 'youtu.be' || /(^|\\.)(youtube\\.com|vimeo\\.com|twitch\\.tv|dailymotion\\.com)$/.test(host)) return 'video';\n return 'website';\n}\n\nfunction readLink(raw: unknown): Link | null {\n const r = bag(typeof raw === 'string' ? { url: raw } : raw);\n if (!r) return null;\n const og = bag(r['open_graph'] ?? r['openGraph'] ?? r['og']);\n const meta = bag(r['meta']);\n const twitter = bag(r['twitter_card'] ?? r['twitterCard'] ?? r['twitter']);\n const pagemap = bag(r['pagemap']);\n const tags = bag(Array.isArray(pagemap?.['metatags']) ? pagemap!['metatags'][0] : undefined);\n const sources = [r, og, meta, tags, twitter];\n\n const url = address(pick(sources, ['url', 'link', 'href', 'og:url', 'ogUrl', 'canonical', 'uri']))\n ?? address(r['displayLink'] ?? r['displayed_link'], true);\n if (!url) return null;\n const parsed = new URL(url);\n const host = parsed.hostname.replace(/^www\\./, '');\n\n const imageCandidates = [\n r['image'], r['imageUrl'], r['image_url'], r['og:image'], r['ogImage'], og?.['image'], og?.['images'],\n r['twitter:image'], r['twitterImage'], twitter?.['image'], twitter?.['images'], r['thumbnail'], r['thumbnail_url'], r['thumbnailUrl'],\n tags?.['og:image'], tags?.['twitter:image'], pagemap?.['cse_image'], pagemap?.['cse_thumbnail'], meta?.['image'],\n ];\n const description = plain(str(pick(sources, ['description', 'snippet', 'summary', 'excerpt', 'og:description', 'ogDescription', 'twitter:description', 'twitterDescription', 'content', 'abstract']))\n ?.replace(/\\s+/g, ' '));\n const color = str(pick([r, tags], ['themeColor', 'theme-color', 'theme_color', 'color']));\n return {\n url,\n host,\n title: plain(textOf(pick(sources, ['title', 'og:title', 'ogTitle', 'twitter:title', 'twitterTitle', 'headline', 'name']))),\n description: description && description.length > MAX_DESCRIPTION ? `${description.slice(0, MAX_DESCRIPTION - 1).trimEnd()}…` : description,\n image: imageCandidates.map((v) => address(v)).find(Boolean),\n siteName: textOf(pick(sources, ['siteName', 'site_name', 'og:site_name', 'ogSiteName', 'site', 'publisher', 'source', 'provider_name', 'providerName']) ?? r['profile']),\n favicon: [r['favicon'], r['icon'], r['logo'], r['faviconUrl'], r['favicon_url'], r['meta_url']?.['favicon'], r['profile']?.['img'], meta?.['favicon']]\n .map((v) => address(v)).find(Boolean),\n author: textOf(pick(sources, ['author', 'byline', 'article:author', 'creator', 'authors'])),\n publishedAt: dateOf(pick(sources, ['publishedAt', 'published_at', 'datePublished', 'published_date', 'publishedDate', 'article:published_time', 'publishedTime', 'date', 'pubDate', 'page_age', 'createdAt', 'created_at'])\n ?? og?.['article']?.['published_time']),\n type: typeOf(pick(sources, ['type', 'og:type', 'ogType', 'kind']), parsed),\n themeColor: color && /^#(?:[0-9a-f]{3}){1,2}$/i.test(color) ? color : undefined,\n };\n}\n\n/**\n * The card's data from what `map` returns: one link (an object, or its address), an array,\n * or a collection (`{ title?, links | results | items | organic_results | sources }`, and\n * Brave's `web.results`). At most ten; `null` when not one has an `http(s)` address.\n */\nexport function readLinks(raw: unknown): LinkData | null {\n const value = typeof raw === 'string' ? [raw] : raw;\n if (!value || typeof value !== 'object') return null;\n let title: string | undefined;\n let list: unknown[];\n if (Array.isArray(value)) {\n list = value;\n } else {\n const r = value as Bag;\n const nested = [r['links'], r['results'], r['items'], r['organic_results'], r['sources'], r['search_results'], r['web']?.['results']].find(Array.isArray);\n if (nested) {\n list = nested;\n title = str(r['title']);\n } else {\n list = [value];\n }\n }\n const links = list.map(readLink).filter((l): l is Link => l !== null).slice(0, MAX_LINKS);\n return links.length ? { ...(title ? { title } : {}), links } : null;\n}\n\n// ── Drawing ──────────────────────────────────────────────────────────────────\n\nconst titleOf = (link: Link) => link.title ?? link.host;\nconst siteOf = (link: Link) => link.siteName ?? link.host;\n\n/** A host's tint, the same every time it is drawn: a sum of its letters, folded onto the six. */\nfunction hueOf(host: string): number {\n let sum = 0;\n for (const char of host) sum = (sum * 31 + char.charCodeAt(0)) % 9973;\n return sum % HUES;\n}\n\n/** \"Mar 3\", or \"Mar 3, 2024\" when it is not this year — as the chat's language writes it. */\nfunction publishedText(config: AparteConfig, date: Date, now = new Date()): string {\n return new Intl.DateTimeFormat(intlTag(config), {\n month: 'short', day: 'numeric', year: date.getFullYear() === now.getFullYear() ? undefined : 'numeric',\n }).format(date);\n}\n\n/** The middle dot between two facts on a line — drawn, not read. */\nconst dot = () => el('span', { class: 'aparte-link-preview__dot', attrs: { 'aria-hidden': 'true' }, text: '·' });\n\nfunction joined(className: string, parts: Array<Node | null>): HTMLElement | null {\n const present = parts.filter((p): p is Node => p !== null);\n if (!present.length) return null;\n const line = el('span', { class: className });\n present.forEach((part, i) => {\n if (i) line.append(dot());\n line.append(part);\n });\n return line;\n}\n\nfunction dateNode(ctx: CardContext, link: Link): HTMLElement | null {\n if (!link.publishedAt) return null;\n const date = link.publishedAt;\n const node = el('time', { class: 'aparte-link-preview__date', attrs: { datetime: date.toISOString() } });\n ctx.bind(node, () => publishedText(ctx.config, date));\n return node;\n}\n\n/**\n * The site's mark: its own icon when the data gives one, over its initial on a tint — the\n * initial is what shows when there is no icon, or when the icon fails to load. Decoration:\n * the site's name is written beside it.\n */\nfunction iconOf(link: Link, size: 'sm' | 'lg'): HTMLElement {\n const initial = Array.from(siteOf(link))[0]!.toUpperCase();\n const box = el('span', { class: `aparte-link-preview__icon aparte-link-preview__icon--${size}`, attrs: { 'aria-hidden': 'true', 'data-hue': hueOf(link.host) } },\n el('span', { class: 'aparte-link-preview__initial', text: initial }));\n if (link.favicon) {\n const img = el('img', { class: 'aparte-link-preview__favicon', attrs: { src: link.favicon, alt: '', loading: 'lazy', decoding: 'async' } });\n img.addEventListener('error', () => {\n img.remove();\n box.removeAttribute('data-favicon');\n }, { once: true });\n box.setAttribute('data-favicon', '');\n box.append(img);\n }\n return box;\n}\n\n/** The kind of page, as a glyph and a word — a plain website wears none. */\nfunction typeBadge(ctx: CardContext, link: Link): HTMLElement | null {\n if (link.type === 'website') return null;\n const key = TYPE_WORD[link.type];\n const label = el('span', { class: 'aparte-link-preview__type-label' });\n ctx.word(label, key, L[key]);\n return el('span', { class: 'aparte-link-preview__type', attrs: { 'data-type': link.type } },\n glyph(typeGlyph(link.type), 'aparte-rich-card__glyph aparte-link-preview__type-glyph'), label);\n}\n\n/**\n * The title, as THE link: its `::after` is stretched over the whole card or row, so every\n * part of it opens the page and a reader hears one link, named by the title.\n */\nfunction titleNode(link: Link): HTMLElement {\n return el('div', { class: 'aparte-link-preview__title' },\n el('a', { class: 'aparte-link-preview__anchor', attrs: { href: link.url, target: '_blank', rel: 'noopener noreferrer' }, text: titleOf(link) }));\n}\n\nfunction descriptionOf(link: Link): HTMLElement | null {\n return link.description ? el('p', { class: 'aparte-link-preview__description', text: link.description }) : null;\n}\n\n/**\n * The page's picture, in a box of its own. Decoration — the title names the link — so its\n * alternative text is empty. A picture that fails to load takes its box with it: a preview\n * without a picture is a preview, a grey hole is not.\n */\nfunction mediaOf(link: Link, className: string): HTMLElement | null {\n if (!link.image) return null;\n const box = el('div', { class: className, attrs: { 'data-loading': '' } });\n const img = el('img', { class: 'aparte-link-preview__img', attrs: { src: link.image, alt: '', loading: 'lazy', decoding: 'async' } });\n img.addEventListener('load', () => box.removeAttribute('data-loading'), { once: true });\n img.addEventListener('error', () => box.remove(), { once: true });\n box.append(img);\n return box;\n}\n\n/** `card`: the picture on top, the site line, the title, the description. */\nfunction cardOf(ctx: CardContext, link: Link): HTMLElement {\n const media = mediaOf(link, 'aparte-link-preview__media');\n if (media && link.type === 'video') {\n media.append(el('span', { class: 'aparte-link-preview__play', attrs: { 'aria-hidden': 'true' } }, glyph(PLAY, 'aparte-rich-card__glyph aparte-link-preview__play-glyph')));\n }\n const site = el('div', { class: 'aparte-link-preview__site' }, iconOf(link, 'sm'),\n joined('aparte-link-preview__site-text', [\n el('span', { class: 'aparte-link-preview__site-name', text: siteOf(link) }),\n link.author ? el('span', { class: 'aparte-link-preview__author', text: link.author }) : null,\n dateNode(ctx, link),\n ]),\n typeBadge(ctx, link));\n const body = el('div', { class: 'aparte-link-preview__body' }, site, titleNode(link), descriptionOf(link));\n return el('article', { class: 'aparte-link-preview aparte-link-preview--card', attrs: { 'data-type': link.type } }, media, body);\n}\n\n/** `compact`: one row — the site's mark (with its kind in the corner), the title, the site, a small picture. */\nfunction rowOf(ctx: CardContext, link: Link, tag: 'div' | 'li'): HTMLElement {\n const mark = el('span', { class: 'aparte-link-preview__mark' }, iconOf(link, 'lg'));\n if (link.type !== 'website') {\n const corner = el('span', { class: 'aparte-link-preview__corner', attrs: { role: 'img', 'data-type': link.type } },\n glyph(typeGlyph(link.type), 'aparte-rich-card__glyph aparte-link-preview__corner-glyph'));\n ctx.bind(corner, () => word(ctx.config, TYPE_WORD[link.type as keyof typeof TYPE_WORD], L[TYPE_WORD[link.type as keyof typeof TYPE_WORD]]), 'aria-label');\n mark.append(corner);\n }\n const meta = joined('aparte-link-preview__meta', [el('span', { class: 'aparte-link-preview__site-name', text: siteOf(link) }), dateNode(ctx, link)]);\n const text = el('div', { class: 'aparte-link-preview__text' }, titleNode(link), meta);\n return el(tag, { class: 'aparte-link-preview aparte-link-preview--row', attrs: { 'data-type': link.type } }, mark, text, mediaOf(link, 'aparte-link-preview__thumb'));\n}\n\n/** `quote`: the block a chat app draws under an unfurled link — a coloured rule, the site, the title, the words, the picture beside. */\nfunction quoteOf(ctx: CardContext, link: Link, tag: 'div' | 'li'): HTMLElement {\n const site = el('div', { class: 'aparte-link-preview__site' }, iconOf(link, 'sm'),\n el('span', { class: 'aparte-link-preview__site-name', text: siteOf(link) }), typeBadge(ctx, link));\n const byline = joined('aparte-link-preview__byline', [link.author ? el('span', { class: 'aparte-link-preview__author', text: link.author }) : null, dateNode(ctx, link)]);\n const text = el('div', { class: 'aparte-link-preview__quote-text' }, site, titleNode(link), descriptionOf(link), byline);\n const node = el(tag, { class: 'aparte-link-preview aparte-link-preview--quote', attrs: { 'data-type': link.type, 'data-hue': hueOf(link.host) } },\n text, mediaOf(link, 'aparte-link-preview__quote-media'));\n // A colour the reader checked (#rgb, #rrggbb), set as a property — never a style string.\n if (link.themeColor) node.style.setProperty('--aparte-link-preview-accent', link.themeColor);\n return node;\n}\n\nfunction countLabel(config: AparteConfig, data: Partial<LinkData>): string {\n return data.title ?? word(config, 'cardsLinkCount', L.cardsLinkCount, { n: formatNumber(config, data.links?.length ?? 0) });\n}\n\nfunction headingOf(ctx: CardContext, data: LinkData): HTMLElement {\n const text = el('span', { class: 'aparte-link-preview__heading-text' });\n ctx.bind(text, () => countLabel(ctx.config, data));\n return el('div', { class: 'aparte-link-preview__heading' }, glyph(CHAIN, 'aparte-rich-card__glyph aparte-link-preview__heading-glyph'), text);\n}\n\nfunction render(data: LinkData, ctx: CardContext<LinkVariant>): Node[] {\n const [first] = data.links as [Link];\n if (data.links.length === 1) {\n if (ctx.variant === 'compact') return [rowOf(ctx, first, 'div')];\n if (ctx.variant === 'quote') return [quoteOf(ctx, first, 'div')];\n return [cardOf(ctx, first)];\n }\n if (ctx.variant === 'quote') {\n const stack = el('ul', { class: 'aparte-link-preview__stack' }, ...data.links.map((l) => quoteOf(ctx, l, 'li')));\n ctx.bind(stack, () => countLabel(ctx.config, data), 'aria-label');\n return [stack];\n }\n // Several cards would be a wall of pictures in a chat: several links are rows, stacked,\n // in `card` as in `compact`.\n const list = el('ul', { class: 'aparte-link-preview__list' }, ...data.links.map((l) => rowOf(ctx, l, 'li')));\n ctx.bind(list, () => countLabel(ctx.config, data), 'aria-label');\n return [headingOf(ctx, data), list];\n}\n\nfunction skeleton(partial: Partial<LinkData> | null, ctx: CardContext<LinkVariant>): Node[] {\n const block = (cls: string, kind: 'text' | 'rect' = 'rect') =>\n el('span', { class: `aparte-skeleton aparte-skeleton--${kind} ${cls}`, attrs: { 'aria-hidden': 'true' } });\n const known = partial?.links?.[0];\n const title = known?.title ?? partial?.title;\n const titleLine = title ? el('div', { class: 'aparte-link-preview__title', text: title }) : block('aparte-link-preview__sk-title', 'text');\n const siteLine = el('div', { class: 'aparte-link-preview__site' }, block('aparte-link-preview__sk-icon'),\n known ? el('span', { class: 'aparte-link-preview__site-name', text: siteOf(known) }) : block('aparte-link-preview__sk-site', 'text'));\n if (ctx.variant === 'compact') {\n return [el('div', { class: 'aparte-link-preview aparte-link-preview--row' }, block('aparte-link-preview__sk-mark'),\n el('div', { class: 'aparte-link-preview__text' }, titleLine, known ? el('span', { class: 'aparte-link-preview__meta', text: siteOf(known) }) : block('aparte-link-preview__sk-site', 'text')))];\n }\n const lines = [block('aparte-link-preview__sk-line', 'text'), block('aparte-link-preview__sk-line aparte-link-preview__sk-line--short', 'text')];\n if (ctx.variant === 'quote') {\n return [el('div', { class: 'aparte-link-preview aparte-link-preview--quote' }, el('div', { class: 'aparte-link-preview__quote-text' }, siteLine, titleLine, ...lines))];\n }\n return [el('article', { class: 'aparte-link-preview aparte-link-preview--card' }, block('aparte-link-preview__media aparte-link-preview__sk-media'),\n el('div', { class: 'aparte-link-preview__body' }, siteLine, titleLine, ...lines))];\n}\n\nfunction label(data: Partial<LinkData> | null, ctx: CardContext): string {\n const links = data?.links ?? [];\n if (links.length === 1) return titleOf(links[0]!);\n if (links.length > 1) return countLabel(ctx.config, data!);\n return data?.title ?? word(ctx.config, 'cardsLinkTitle', L.cardsLinkTitle);\n}\n\n/** One line a link: \"Title — Site (https://…)\". */\nfunction fallback(data: LinkData): string {\n const lines = data.links.map((l) => `${titleOf(l)} — ${siteOf(l)} (${l.url})`);\n return data.title ? `${data.title}:\\n${lines.join('\\n')}` : lines.join('\\n');\n}\n\n/** What a running call already says: the page it unfurls, else what it searches for. */\nfunction partial(input: Record<string, unknown>): Partial<LinkData> | null {\n const one = readLink({ url: input['url'] ?? input['link'] ?? input['href'] });\n if (one) return { links: [one] };\n const query = str(input['query'] ?? input['q'] ?? input['search'] ?? input['title']);\n return query ? { title: query } : null;\n}\n\n/** The link card — for `linkCard.segment(data)`. Plug it in with `setupLinkCard`. */\nexport const linkCard = defineCard<LinkData, LinkVariant>({\n name: 'link',\n variants: ['card', 'compact', 'quote'],\n styles: linkStyles,\n icon: CHAIN,\n normalize: readLinks,\n render,\n skeleton,\n label,\n fallback,\n partial,\n});\n\n/** Draw a tool's links as this card — returns the function that stops it. */\nexport function setupLinkCard(options: LinkCardOptions = {}): () => void {\n return linkCard.setup(options);\n}\n"],"names":["label","partial"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The media card's glyphs: one line drawing per kind of medium (for the placeholder a
3
+ * tile shows before, or instead of, its picture), the play triangle a video wears, the
4
+ * stack of frames a gallery is named with, and the small marks of a caption line.
5
+ *
6
+ * Drawn on a 24 × 24 grid in `currentColor`, so the sheet colours them through `color`
7
+ * and a theme follows. Constants of this package — the only markup the card parses.
8
+ */
9
+ import type { MediaKind } from './index.js';
10
+ /** A framed landscape: an image. */
11
+ export declare const IMAGE: string;
12
+ /** A strip of film: a video. */
13
+ export declare const FILM: string;
14
+ /** A note: a sound. */
15
+ export declare const NOTE: string;
16
+ /** Two frames, one behind the other: a gallery, and the card's one-line state glyph. */
17
+ export declare const FRAMES: string;
18
+ /** The play triangle, filled: it sits on a disc over a picture. */
19
+ export declare const PLAY = "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.5 5.6v12.8a1 1 0 0 0 1.5.86l10.2-6.4a1 1 0 0 0 0-1.72L10 4.74a1 1 0 0 0-1.5.86z\"/></svg>";
20
+ /** Four corners pulled out: "see it larger", in the corner of a picture you can open. */
21
+ export declare const EXPAND: string;
22
+ /** A box with an arrow leaving it: a page that opens elsewhere. */
23
+ export declare const EXTERNAL: string;
24
+ /** A camera body: who took or made the picture. */
25
+ export declare const CAMERA: string;
26
+ /** The glyph for a kind of medium. */
27
+ export declare function kindGlyph(kind: MediaKind): string;
28
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/media/icons.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAK5C,oCAAoC;AACpC,eAAO,MAAM,KAAK,QAAyL,CAAC;AAE5M,gCAAgC;AAChC,eAAO,MAAM,IAAI,QAA0I,CAAC;AAE5J,uBAAuB;AACvB,eAAO,MAAM,IAAI,QAAgH,CAAC;AAElI,wFAAwF;AACxF,eAAO,MAAM,MAAM,QAAsL,CAAC;AAE1M,mEAAmE;AACnE,eAAO,MAAM,IAAI,iMAAyL,CAAC;AAE3M,yFAAyF;AACzF,eAAO,MAAM,MAAM,QAA6E,CAAC;AAEjG,mEAAmE;AACnE,eAAO,MAAM,QAAQ,QAA2H,CAAC;AAEjJ,mDAAmD;AACnD,eAAO,MAAM,MAAM,QAAqJ,CAAC;AAEzK,sCAAsC;AACtC,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAEjD"}