@aparte/plugin-cards 0.0.0-stage → 0.29.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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -2
  3. package/dist/carousel-CPy-0JGN.js +63 -0
  4. package/dist/carousel-CPy-0JGN.js.map +1 -0
  5. package/dist/controls-BVX2u7sK.js +62 -0
  6. package/dist/controls-BVX2u7sK.js.map +1 -0
  7. package/dist/format-CchcJRum.js +720 -0
  8. package/dist/format-CchcJRum.js.map +1 -0
  9. package/dist/index.d.ts +28 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +35 -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 +194 -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/format.d.ts +49 -0
  28. package/dist/kit/format.d.ts.map +1 -0
  29. package/dist/kit/rating.d.ts +19 -0
  30. package/dist/kit/rating.d.ts.map +1 -0
  31. package/dist/kit/sparkline.d.ts +19 -0
  32. package/dist/kit/sparkline.d.ts.map +1 -0
  33. package/dist/kit/strings.d.ts +139 -0
  34. package/dist/kit/strings.d.ts.map +1 -0
  35. package/dist/link/icons.d.ts +16 -0
  36. package/dist/link/icons.d.ts.map +1 -0
  37. package/dist/link/index.d.ts +98 -0
  38. package/dist/link/index.d.ts.map +1 -0
  39. package/dist/link.js +387 -0
  40. package/dist/link.js.map +1 -0
  41. package/dist/media/icons.d.ts +28 -0
  42. package/dist/media/icons.d.ts.map +1 -0
  43. package/dist/media/index.d.ts +107 -0
  44. package/dist/media/index.d.ts.map +1 -0
  45. package/dist/media.js +610 -0
  46. package/dist/media.js.map +1 -0
  47. package/dist/order/icons.d.ts +49 -0
  48. package/dist/order/icons.d.ts.map +1 -0
  49. package/dist/order/index.d.ts +204 -0
  50. package/dist/order/index.d.ts.map +1 -0
  51. package/dist/order.js +1843 -0
  52. package/dist/order.js.map +1 -0
  53. package/dist/place/icons.d.ts +18 -0
  54. package/dist/place/icons.d.ts.map +1 -0
  55. package/dist/place/index.d.ts +207 -0
  56. package/dist/place/index.d.ts.map +1 -0
  57. package/dist/place.js +1456 -0
  58. package/dist/place.js.map +1 -0
  59. package/dist/plan/icons.d.ts +25 -0
  60. package/dist/plan/icons.d.ts.map +1 -0
  61. package/dist/plan/index.d.ts +153 -0
  62. package/dist/plan/index.d.ts.map +1 -0
  63. package/dist/plan.js +1191 -0
  64. package/dist/plan.js.map +1 -0
  65. package/dist/product/icons.d.ts +16 -0
  66. package/dist/product/icons.d.ts.map +1 -0
  67. package/dist/product/index.d.ts +133 -0
  68. package/dist/product/index.d.ts.map +1 -0
  69. package/dist/product.js +1245 -0
  70. package/dist/product.js.map +1 -0
  71. package/dist/rating-B8Mdx_b_.js +35 -0
  72. package/dist/rating-B8Mdx_b_.js.map +1 -0
  73. package/dist/score/icons.d.ts +35 -0
  74. package/dist/score/icons.d.ts.map +1 -0
  75. package/dist/score/index.d.ts +185 -0
  76. package/dist/score/index.d.ts.map +1 -0
  77. package/dist/score.js +1843 -0
  78. package/dist/score.js.map +1 -0
  79. package/dist/sparkline-C-cA9obz.js +56 -0
  80. package/dist/sparkline-C-cA9obz.js.map +1 -0
  81. package/dist/stats/icons.d.ts +19 -0
  82. package/dist/stats/icons.d.ts.map +1 -0
  83. package/dist/stats/index.d.ts +137 -0
  84. package/dist/stats/index.d.ts.map +1 -0
  85. package/dist/stats.js +1085 -0
  86. package/dist/stats.js.map +1 -0
  87. package/dist/stock/chart.d.ts +135 -0
  88. package/dist/stock/chart.d.ts.map +1 -0
  89. package/dist/stock/icons.d.ts +15 -0
  90. package/dist/stock/icons.d.ts.map +1 -0
  91. package/dist/stock/index.d.ts +195 -0
  92. package/dist/stock/index.d.ts.map +1 -0
  93. package/dist/stock.js +1013 -0
  94. package/dist/stock.js.map +1 -0
  95. package/dist/weather/conditions.d.ts +25 -0
  96. package/dist/weather/conditions.d.ts.map +1 -0
  97. package/dist/weather/icons.d.ts +12 -0
  98. package/dist/weather/icons.d.ts.map +1 -0
  99. package/dist/weather/index.d.ts +146 -0
  100. package/dist/weather/index.d.ts.map +1 -0
  101. package/dist/weather.js +1162 -0
  102. package/dist/weather.js.map +1 -0
  103. package/package.json +137 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product.js","sources":["../src/product/icons.ts","../src/product/product.css?raw","../src/product/index.ts"],"sourcesContent":["/**\n * The product card's glyphs: the bag a tile shows when it has no photo, the van beside a\n * delivery line, the loupe before a search that is still running.\n *\n * Drawn on a 24 × 24 grid in `currentColor` strokes, so the sheet colours them through\n * `color` and a theme follows. Constants of this package — the only markup the card parses.\n */\n\nconst svg = (body: string) =>\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" 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/** A shopping bag: the placeholder where a photo is missing or failed to load. */\nexport const BAG = svg('<path d=\"M5.5 8h13l-1 11.2a1.8 1.8 0 0 1-1.8 1.6H8.3a1.8 1.8 0 0 1-1.8-1.6z\"/><path d=\"M9 10.5V6.8a3 3 0 0 1 6 0v3.7\"/>');\n\n/** A delivery van, before the shipping line. */\nexport const TRUCK = svg('<path d=\"M2.8 6.5h10.4v9.3H2.8z\"/><path d=\"M13.2 9.5h4.1l3 3.2v3.1h-7.1\"/><circle cx=\"7\" cy=\"17.3\" r=\"1.7\"/><circle cx=\"17\" cy=\"17.3\" r=\"1.7\"/>');\n\n/** A loupe, before the query a running search was given. */\nexport const SEARCH = svg('<circle cx=\"10.8\" cy=\"10.8\" r=\"6.3\"/><path d=\"M15.5 15.5l4.7 4.7\"/>');\n\n/** The card's own glyph, for its one-line states (waiting, refused, failed). */\nexport const TAG = svg('<path d=\"M3.5 12.6V4.8a1.3 1.3 0 0 1 1.3-1.3h7.8l8 8a1.3 1.3 0 0 1 0 1.8l-7 7a1.3 1.3 0 0 1-1.8 0z\"/><circle cx=\"8.3\" cy=\"8.3\" r=\"1.5\"/>');\n","export default \"/* =========================================\\n The product card — @aparte/plugin-cards/product\\n =========================================\\n Three variants of one card, chosen by `data-variant` on its root:\\n card a tile: the photo, the shop, the name, the stars, the price. Several\\n products are a row of tiles that scrolls, and the card drops its own\\n frame so the tiles are not boxes inside a box.\\n compact a row per product — thumbnail, name, stars, price — a list to scan.\\n detail one product at length: the photo beside the facts on a wide card, above\\n them on a narrow one; the others in a small row under it.\\n Every colour is a theme token or derived from one, so light and dark follow the chat.\\n */\\n\\n.aparte-product-card {\\n --aparte-product-tile-size: 12.5rem;\\n --aparte-product-thumb-size: 3.5rem;\\n --aparte-product-media-radius: var(--aparte-radius-lg);\\n /* A product photo is shot on white. The plate it sits on is near-white in BOTH schemes\\n (derived from the ink, so it is a hair off white in light and a hair off cream in\\n dark) and the photo is multiplied into it: its white background melts into the plate\\n instead of sitting as a white square on a grey one. A photo on a dark plate in dark\\n mode keeps its white box whatever we do — the shops' own apps show it light too. */\\n --aparte-product-media-bg: color-mix(in oklab, var(--aparte-text) 5%, #fff);\\n --aparte-product-image-fit: contain;\\n --aparte-product-image-inset: 7%;\\n /* Where there is no photo: the theme's own surfaces, warmed by its accent. */\\n --aparte-product-placeholder: linear-gradient(\\n 145deg,\\n color-mix(in oklab, var(--aparte-primary) 16%, var(--aparte-surface-2)) 0%,\\n var(--aparte-surface-2) 55%,\\n color-mix(in oklab, var(--aparte-text) 5%, var(--aparte-surface-2)) 100%\\n );\\n --aparte-product-placeholder-ink: color-mix(in oklab, var(--aparte-text) 30%, transparent);\\n --aparte-product-price-color: var(--aparte-text);\\n --aparte-product-hover-border: color-mix(in oklab, var(--aparte-text) 22%, var(--aparte-border));\\n --aparte-product-hover-shadow: 0 8px 18px -12px rgb(0 0 0 / 0.38);\\n}\\n\\n/* ── Widths: each variant asks for its own; the bubble's limit caps them all ── */\\n\\n.aparte-product-card[data-variant=\\\"card\\\"] { --aparte-rich-card-max-inline-size: 16.5rem; }\\n.aparte-product-card[data-variant=\\\"compact\\\"] { --aparte-rich-card-max-inline-size: 28rem; }\\n.aparte-product-card[data-variant=\\\"detail\\\"] { --aparte-rich-card-max-inline-size: 36rem; }\\n.aparte-product-card:has(> .aparte-product__collection) { --aparte-rich-card-max-inline-size: 42rem; }\\n\\n/* Several tiles: each is framed, so the card is not. The kit's buttons under it lose\\n their band, which would otherwise float as a strip under nothing. */\\n.aparte-product-card:has(> .aparte-product__collection) {\\n overflow: visible;\\n border-color: transparent;\\n border-radius: 0;\\n background: transparent;\\n}\\n\\n.aparte-product-card:has(> .aparte-product__collection) > .aparte-rich-card__actions {\\n padding-inline: 0;\\n border-block-start: 0;\\n background: none;\\n}\\n\\n/* ── Headings: the collection's title, the query of a running search ─── */\\n\\n.aparte-product__heading {\\n margin-block-end: var(--aparte-space-4);\\n font-size: var(--aparte-font-size-md);\\n font-weight: var(--aparte-font-weight-semibold);\\n overflow-wrap: anywhere;\\n}\\n\\n.aparte-product-card[data-variant=\\\"compact\\\"] > .aparte-product__heading {\\n margin: 0;\\n padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-2);\\n}\\n\\n.aparte-product__query {\\n display: flex;\\n align-items: center;\\n gap: var(--aparte-space-3);\\n min-inline-size: 0;\\n margin-block-end: var(--aparte-space-4);\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-md);\\n --aparte-rich-card-glyph-size: 1rem;\\n}\\n\\n.aparte-product__query-text {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n\\n.aparte-product-card[data-variant=\\\"compact\\\"] > .aparte-product__query,\\n.aparte-product-card[data-variant=\\\"detail\\\"] > .aparte-product__query {\\n margin: 0;\\n padding: var(--aparte-space-5) var(--aparte-space-6) 0;\\n}\\n\\n/* ── The carousel, in product sizes ──────────────────────────────────── */\\n\\n.aparte-product-card .aparte-rich-card__carousel {\\n --aparte-rich-card-slide-size: var(--aparte-product-tile-size);\\n}\\n\\n/* ── A product, whatever its shape ───────────────────────────────────── */\\n\\n.aparte-product {\\n position: relative;\\n min-inline-size: 0;\\n color: var(--aparte-text);\\n}\\n\\n/* The title's link covers the tile or the row: one link per product, read once. */\\n.aparte-product__link {\\n color: inherit;\\n text-decoration: none;\\n}\\n\\n.aparte-product--tile .aparte-product__link,\\n.aparte-product--row .aparte-product__link {\\n outline: none;\\n}\\n\\n.aparte-product--tile .aparte-product__link::after,\\n.aparte-product--row .aparte-product__link::after {\\n content: \\\"\\\";\\n position: absolute;\\n inset: 0;\\n border-radius: inherit;\\n}\\n\\n.aparte-product[data-linked]:hover .aparte-product__title {\\n text-decoration: underline;\\n text-decoration-thickness: 1px;\\n text-underline-offset: 0.18em;\\n}\\n\\n/* The ring is the product's, since the link is all of it. */\\n.aparte-product--tile:has(.aparte-product__link:focus-visible) {\\n outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);\\n outline-offset: var(--aparte-focus-outline-offset);\\n}\\n\\n.aparte-product--row:has(.aparte-product__link:focus-visible) {\\n /* A row reaches the card's clipped edges: its ring is drawn inside. */\\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/* ── The photo ───────────────────────────────────────────────────────── */\\n\\n.aparte-product__media {\\n position: relative;\\n display: grid;\\n place-items: center;\\n aspect-ratio: 1;\\n overflow: hidden;\\n border-radius: var(--aparte-product-media-radius);\\n background: var(--aparte-product-media-bg);\\n isolation: isolate;\\n}\\n\\n.aparte-product__media[data-empty] {\\n background: var(--aparte-product-placeholder);\\n}\\n\\n.aparte-product__placeholder {\\n grid-area: 1 / 1;\\n color: var(--aparte-product-placeholder-ink);\\n --aparte-rich-card-glyph-size: clamp(1.25rem, 30%, 3.5rem);\\n}\\n\\n.aparte-product__media:not([data-empty]) .aparte-product__placeholder { visibility: hidden; }\\n\\n.aparte-product__image {\\n grid-area: 1 / 1;\\n box-sizing: border-box;\\n inline-size: 100%;\\n block-size: 100%;\\n padding: var(--aparte-product-image-inset);\\n object-fit: var(--aparte-product-image-fit);\\n mix-blend-mode: multiply;\\n transition: scale var(--aparte-duration-slower) ease, opacity var(--aparte-duration-base) ease;\\n}\\n\\n.aparte-product__image[hidden] { display: none; }\\n\\n.aparte-product[data-linked]:hover .aparte-product__image,\\n.aparte-product__media-link:hover .aparte-product__image {\\n scale: 1.04;\\n}\\n\\n/* Out of stock: the photo steps back, the line under it says why. */\\n.aparte-product[data-availability=\\\"out-of-stock\\\"] .aparte-product__image {\\n opacity: 0.55;\\n filter: grayscale(0.5);\\n}\\n\\n.aparte-product__flags {\\n position: absolute;\\n z-index: 1;\\n inset-block-start: var(--aparte-space-3);\\n inset-inline-start: var(--aparte-space-3);\\n display: flex;\\n flex-wrap: wrap;\\n gap: var(--aparte-space-2);\\n max-inline-size: calc(100% - var(--aparte-space-3) * 2);\\n}\\n\\n/* The shop's word: a pill of the card's own surface over the plate, in either scheme. */\\n.aparte-product__flags .aparte-product__badge {\\n max-inline-size: 100%;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n background: color-mix(in oklab, var(--aparte-surface-1) 90%, transparent);\\n color: var(--aparte-text);\\n box-shadow: 0 1px 3px rgb(0 0 0 / 0.14);\\n backdrop-filter: blur(6px);\\n}\\n\\n.aparte-product__discount {\\n font-variant-numeric: tabular-nums;\\n}\\n\\n/* ── The words beside the photo ──────────────────────────────────────── */\\n\\n.aparte-product__body {\\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-product__merchant {\\n display: flex;\\n align-items: center;\\n gap: var(--aparte-space-2);\\n min-inline-size: 0;\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-xs);\\n}\\n\\n.aparte-product__merchant-name {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n\\n.aparte-product__logo {\\n flex: none;\\n inline-size: 1rem;\\n block-size: 1rem;\\n border-radius: var(--aparte-radius-xs);\\n object-fit: cover;\\n}\\n\\n.aparte-product__logo[hidden] { display: none; }\\n\\n.aparte-product__title {\\n display: -webkit-box;\\n overflow: hidden;\\n -webkit-box-orient: vertical;\\n -webkit-line-clamp: 2;\\n line-clamp: 2;\\n font-size: var(--aparte-font-size-md);\\n font-weight: var(--aparte-font-weight-medium);\\n line-height: 1.3;\\n overflow-wrap: anywhere;\\n}\\n\\n.aparte-product__price {\\n display: flex;\\n flex-wrap: wrap;\\n align-items: baseline;\\n gap: var(--aparte-space-1) var(--aparte-space-3);\\n font-variant-numeric: tabular-nums;\\n}\\n\\n.aparte-product__now {\\n color: var(--aparte-product-price-color);\\n font-size: var(--aparte-font-size-lg);\\n font-weight: var(--aparte-font-weight-bold);\\n letter-spacing: -0.01em;\\n white-space: nowrap;\\n}\\n\\n.aparte-product__was {\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-sm);\\n text-decoration-thickness: 1px;\\n white-space: nowrap;\\n}\\n\\n.aparte-product__price .aparte-product__discount { align-self: center; }\\n\\n/* Stock: a dot in the state's colour, the word in its readable ink. */\\n.aparte-product__stock {\\n --aparte-product-stock-tone: var(--aparte-neutral);\\n --aparte-product-stock-ink: var(--aparte-text-muted);\\n display: inline-flex;\\n align-items: center;\\n gap: var(--aparte-space-2);\\n min-inline-size: 0;\\n color: var(--aparte-product-stock-ink);\\n font-size: var(--aparte-font-size-xs);\\n font-weight: var(--aparte-font-weight-medium);\\n}\\n\\n.aparte-product__stock[data-availability=\\\"in-stock\\\"] { --aparte-product-stock-tone: var(--aparte-success); --aparte-product-stock-ink: var(--aparte-success-ink); }\\n.aparte-product__stock[data-availability=\\\"low-stock\\\"] { --aparte-product-stock-tone: var(--aparte-warning); --aparte-product-stock-ink: var(--aparte-warning-ink); }\\n.aparte-product__stock[data-availability=\\\"out-of-stock\\\"] { --aparte-product-stock-tone: var(--aparte-error); --aparte-product-stock-ink: var(--aparte-error-ink); }\\n.aparte-product__stock[data-availability=\\\"preorder\\\"] { --aparte-product-stock-tone: var(--aparte-info); --aparte-product-stock-ink: var(--aparte-info-ink); }\\n\\n.aparte-product__dot {\\n flex: none;\\n inline-size: 0.4375rem;\\n block-size: 0.4375rem;\\n border-radius: var(--aparte-radius-full);\\n background: var(--aparte-product-stock-tone);\\n box-shadow: 0 0 0 3px color-mix(in oklab, var(--aparte-product-stock-tone) 20%, transparent);\\n}\\n\\n.aparte-product__stock-text {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n\\n.aparte-product__shipping {\\n display: inline-flex;\\n align-items: center;\\n gap: var(--aparte-space-2);\\n min-inline-size: 0;\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-xs);\\n --aparte-rich-card-glyph-size: 1rem;\\n}\\n\\n.aparte-product__shipping[data-free] {\\n color: var(--aparte-success-ink);\\n font-weight: var(--aparte-font-weight-medium);\\n}\\n\\n.aparte-product__shipping-icon:dir(rtl) { transform: scaleX(-1); }\\n\\n/* Buttons sit above the link that covers the product. */\\n.aparte-product__actions {\\n position: relative;\\n z-index: 1;\\n display: flex;\\n flex-wrap: wrap;\\n gap: var(--aparte-space-2);\\n}\\n\\n/* ── card: the tile ──────────────────────────────────────────────────── */\\n\\n.aparte-product--tile {\\n display: flex;\\n flex-direction: column;\\n gap: var(--aparte-space-4);\\n padding: var(--aparte-space-3) var(--aparte-space-3) var(--aparte-space-4);\\n border-radius: var(--aparte-card-radius);\\n}\\n\\n.aparte-product--tile .aparte-product__body { padding-inline: var(--aparte-space-2); }\\n\\n/* Two lines held even when the name takes one, so the prices in a row line up. */\\n.aparte-product--tile .aparte-product__title { min-block-size: 2lh; }\\n\\n/* The price sits at the foot of the tile, level with its neighbours'. */\\n.aparte-product--tile .aparte-product__price {\\n margin-block-start: auto;\\n padding-block-start: var(--aparte-space-1);\\n}\\n\\n.aparte-product--tile .aparte-product__actions { padding-inline: var(--aparte-space-2); }\\n.aparte-product--tile .aparte-product__action { flex: 1 1 auto; justify-content: center; }\\n\\n.aparte-product__collection .aparte-product--tile,\\n.aparte-product__more .aparte-product--tile {\\n border: var(--aparte-border-width) solid var(--aparte-border);\\n background: var(--aparte-surface-1);\\n transition:\\n border-color var(--aparte-duration-base) ease,\\n box-shadow var(--aparte-duration-base) ease,\\n translate var(--aparte-duration-base) ease;\\n}\\n\\n.aparte-product__collection .aparte-product--tile[data-linked]:hover,\\n.aparte-product__more .aparte-product--tile[data-linked]:hover {\\n border-color: var(--aparte-product-hover-border);\\n box-shadow: var(--aparte-product-hover-shadow);\\n translate: 0 -2px;\\n}\\n\\n/* A mini tile: the others under a detail. */\\n.aparte-product--mini {\\n gap: var(--aparte-space-3);\\n padding: var(--aparte-space-2) var(--aparte-space-2) var(--aparte-space-3);\\n}\\n\\n.aparte-product--mini .aparte-product__body { gap: var(--aparte-space-1); }\\n.aparte-product--mini .aparte-product__title { font-size: var(--aparte-font-size-sm); }\\n.aparte-product--mini .aparte-product__now { font-size: var(--aparte-font-size-md); }\\n.aparte-product--mini .aparte-product__was { font-size: var(--aparte-font-size-xs); }\\n\\n/* ── compact: the rows ───────────────────────────────────────────────── */\\n\\n/* On the drawn card only: a one-line state shrinks to its words, and a size container\\n cannot. */\\n.aparte-product-card[data-variant=\\\"compact\\\"][data-state=\\\"ready\\\"] {\\n container: aparte-product-rows / inline-size;\\n}\\n\\n.aparte-product__rows {\\n margin: 0;\\n padding: var(--aparte-space-2) 0;\\n list-style: none;\\n}\\n\\n/* A divider starting after the thumbnail, the way a settings list draws it. */\\n.aparte-product__item { position: relative; }\\n\\n.aparte-product__item + .aparte-product__item::before {\\n content: \\\"\\\";\\n position: absolute;\\n inset-block-start: 0;\\n inset-inline: calc(var(--aparte-space-6) + var(--aparte-product-thumb-size) + var(--aparte-space-5)) var(--aparte-space-6);\\n border-block-start: var(--aparte-border-width) solid var(--aparte-border);\\n}\\n\\n.aparte-product--row {\\n display: grid;\\n grid-template-columns: var(--aparte-product-thumb-size) minmax(0, 1fr) auto;\\n align-items: center;\\n column-gap: var(--aparte-space-5);\\n padding: var(--aparte-space-4) var(--aparte-space-6);\\n transition: background-color var(--aparte-duration-fast) ease;\\n}\\n\\n.aparte-product--row:has(> .aparte-product__actions) {\\n grid-template-columns: var(--aparte-product-thumb-size) minmax(0, 1fr) auto auto;\\n}\\n\\n.aparte-product--row[data-linked]:hover { background: var(--aparte-surface-2); }\\n\\n.aparte-product__thumb {\\n inline-size: var(--aparte-product-thumb-size);\\n --aparte-product-media-radius: var(--aparte-radius-md);\\n --aparte-product-image-inset: 4%;\\n}\\n\\n.aparte-product__thumb .aparte-product__placeholder { --aparte-rich-card-glyph-size: 45%; }\\n\\n.aparte-product__main {\\n display: flex;\\n flex-direction: column;\\n gap: var(--aparte-space-1);\\n min-inline-size: 0;\\n}\\n\\n.aparte-product__meta {\\n display: flex;\\n flex-wrap: wrap;\\n align-items: center;\\n gap: var(--aparte-space-1) var(--aparte-space-3);\\n min-inline-size: 0;\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-xs);\\n}\\n\\n.aparte-product__meta .aparte-rich-card__rating { font-size: var(--aparte-font-size-xs); }\\n\\n.aparte-product--row .aparte-product__price {\\n flex-direction: column;\\n align-items: flex-end;\\n gap: 0;\\n text-align: end;\\n}\\n\\n.aparte-product--row .aparte-product__now { font-size: var(--aparte-font-size-base); }\\n.aparte-product--row .aparte-product__was { font-size: var(--aparte-font-size-xs); }\\n\\n/* On a narrow card a row's button goes under its name rather than squeeze it. */\\n@container aparte-product-rows (max-width: 24rem) {\\n .aparte-product--row:has(> .aparte-product__actions) {\\n grid-template-columns: var(--aparte-product-thumb-size) minmax(0, 1fr) auto;\\n }\\n\\n .aparte-product--row > .aparte-product__actions {\\n grid-column: 2 / -1;\\n margin-block-start: var(--aparte-space-3);\\n }\\n}\\n\\n/* ── detail: one product at length ───────────────────────────────────── */\\n\\n.aparte-product__detail {\\n container: aparte-product-detail / inline-size;\\n}\\n\\n.aparte-product--detail {\\n display: grid;\\n gap: var(--aparte-space-6);\\n padding: var(--aparte-space-6);\\n}\\n\\n.aparte-product--detail .aparte-product__media,\\n.aparte-product--detail .aparte-product__sk-media {\\n aspect-ratio: 4 / 3;\\n}\\n\\n.aparte-product__media-link {\\n display: block;\\n border-radius: var(--aparte-product-media-radius);\\n}\\n\\n.aparte-product--detail .aparte-product__body { gap: var(--aparte-space-3); }\\n\\n/* The clamp clips what overflows it, the focus ring included: the padding gives the ring\\n room inside, and the margin takes that room back from the layout. */\\n.aparte-product--detail .aparte-product__title {\\n -webkit-line-clamp: 4;\\n line-clamp: 4;\\n margin: calc(var(--aparte-space-2) * -1);\\n padding: var(--aparte-space-2);\\n font-size: var(--aparte-font-size-lg);\\n font-weight: var(--aparte-font-weight-semibold);\\n}\\n\\n.aparte-product--detail .aparte-product__link:hover { text-decoration: underline; text-underline-offset: 0.18em; }\\n\\n.aparte-product--detail .aparte-product__link:focus-visible {\\n border-radius: var(--aparte-radius-xs);\\n outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);\\n outline-offset: var(--aparte-focus-outline-offset);\\n}\\n\\n.aparte-product--detail .aparte-product__now { font-size: var(--aparte-font-size-2xl); }\\n.aparte-product--detail .aparte-product__was { font-size: var(--aparte-font-size-md); }\\n\\n.aparte-product__facts {\\n display: flex;\\n flex-wrap: wrap;\\n align-items: center;\\n gap: var(--aparte-space-2) var(--aparte-space-6);\\n}\\n\\n.aparte-product__description {\\n display: -webkit-box;\\n margin: 0;\\n overflow: hidden;\\n -webkit-box-orient: vertical;\\n -webkit-line-clamp: 3;\\n line-clamp: 3;\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-sm);\\n line-height: 1.5;\\n overflow-wrap: anywhere;\\n}\\n\\n.aparte-product__options {\\n display: flex;\\n flex-wrap: wrap;\\n gap: var(--aparte-space-2);\\n margin: 0;\\n padding: 0;\\n list-style: none;\\n}\\n\\n.aparte-product__option--more { color: var(--aparte-text-muted); }\\n\\n.aparte-product--detail .aparte-product__actions { margin-block-start: var(--aparte-space-2); }\\n.aparte-product--detail .aparte-product__action { flex: 1 1 8rem; justify-content: center; }\\n\\n@container aparte-product-detail (min-width: 30rem) {\\n .aparte-product--detail {\\n grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);\\n align-items: start;\\n gap: var(--aparte-space-7);\\n }\\n\\n .aparte-product--detail .aparte-product__media,\\n .aparte-product--detail .aparte-product__sk-media {\\n aspect-ratio: 1;\\n }\\n}\\n\\n/* The others, under the detail. */\\n.aparte-product__more {\\n --aparte-product-tile-size: 9.5rem;\\n padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-6);\\n border-block-start: var(--aparte-border-width) solid var(--aparte-border);\\n background: color-mix(in oklab, var(--aparte-surface-2) 45%, var(--aparte-surface-1));\\n}\\n\\n.aparte-product__more-title {\\n margin-block-end: var(--aparte-space-4);\\n color: var(--aparte-text-muted);\\n font-size: var(--aparte-font-size-sm);\\n font-weight: var(--aparte-font-weight-semibold);\\n}\\n\\n/* ── Loading ─────────────────────────────────────────────────────────── */\\n\\n.aparte-product__sk-track {\\n display: grid;\\n grid-auto-flow: column;\\n grid-auto-columns: var(--aparte-product-tile-size);\\n column-gap: var(--aparte-space-4);\\n overflow: hidden;\\n}\\n\\n.aparte-product-card .aparte-product__sk-media {\\n block-size: auto;\\n aspect-ratio: 1;\\n border-radius: var(--aparte-product-media-radius);\\n}\\n\\n.aparte-product-card .aparte-product__sk-thumb {\\n inline-size: var(--aparte-product-thumb-size);\\n block-size: auto;\\n aspect-ratio: 1;\\n border-radius: var(--aparte-radius-md);\\n}\\n\\n.aparte-product-card .aparte-product__sk-line--short { inline-size: 60%; }\\n\\n.aparte-product-card .aparte-product__sk-price {\\n inline-size: 4.5rem;\\n block-size: 1.125rem;\\n margin-block-start: auto;\\n}\\n\\n.aparte-product--row .aparte-product__sk-price { margin-block-start: 0; }\\n\\n/* ── Small screens, motion ───────────────────────────────────────────── */\\n\\n@media (max-width: 380px) {\\n .aparte-product-card { --aparte-product-tile-size: 10.75rem; --aparte-product-thumb-size: 3rem; }\\n .aparte-product--row { column-gap: var(--aparte-space-4); padding-inline: var(--aparte-space-5); }\\n .aparte-product__item + .aparte-product__item::before {\\n inset-inline: calc(var(--aparte-space-5) + var(--aparte-product-thumb-size) + var(--aparte-space-4)) var(--aparte-space-5);\\n }\\n .aparte-product--detail { padding: var(--aparte-space-5); }\\n .aparte-product__more { padding-inline: var(--aparte-space-5); }\\n}\\n\\n/* The kit stops every transition under reduced motion; the hover's lift and zoom are\\n end states, not transitions, so they are taken out here. */\\n@media (prefers-reduced-motion: reduce) {\\n .aparte-product__collection .aparte-product--tile[data-linked]:hover,\\n .aparte-product__more .aparte-product--tile[data-linked]:hover { translate: none; }\\n\\n .aparte-product[data-linked]:hover .aparte-product__image,\\n .aparte-product__media-link:hover .aparte-product__image { scale: none; }\\n}\\n\"","/**\n * @aparte/plugin-cards/product — a tool's products, drawn as cards.\n *\n * import { setupProductCard } from '@aparte/plugin-cards/product';\n *\n * setupProductCard({\n * tool: 'search_products',\n * map: (result) => ({ title: result.query, products: result.items }),\n * });\n *\n * Three variants of one card: `card` (a tile — the photo, the shop, the name, the stars,\n * the price; several products are a row of tiles that scrolls), `compact` (a row per\n * product, to scan) and `detail` (one product at length, the others in a small row under\n * it). Your tool keeps its own fields: `readProducts` already reads the usual spellings —\n * `name` or `title`, a price as `49.9`, `\"49.90\"` or `\"49,90 € TTC\"`, schema.org's `offers`\n * and `aggregateRating`, a Google Shopping result's `extracted_price` and `source`, a Shopify\n * product from the Admin API (`variants`, `body_html`) or the Storefront API (`priceRange`,\n * `onlineStoreUrl`) — and `map` covers the rest.\n */\nimport type { AparteConfig } from '@aparte/core';\nimport { defineCard, type CardAction, type CardContext, type CardSetupOptions } from '../kit/define.js';\nimport { el, glyph, num, str, webUrl } from '../kit/dom.js';\nimport { carousel } from '../kit/carousel.js';\nimport { currencyCode, formatNumber, formatPercent, formatPrice, readPriceText } from '../kit/format.js';\nimport { rating } from '../kit/rating.js';\nimport { word, type CardWord } from '../kit/strings.js';\nimport { BAG, SEARCH, TAG, TRUCK } from './icons.js';\nimport productStyles from './product.css?raw';\n\nexport type ProductVariant = 'card' | 'compact' | 'detail';\n\n/** Where a product stands; anything else a tool writes is shown as it wrote it. */\nexport type ProductAvailability = 'in-stock' | 'low-stock' | 'out-of-stock' | 'preorder';\n\n/**\n * One product as `map` returns it. Only `title` is needed; every other field adds a line\n * when it is there and nothing when it is not.\n */\nexport interface ProductInput {\n title: string;\n /** An `http(s)` URL, or `{ src, alt }`. Anything else is no photo. */\n image?: string | { src: string; alt?: string };\n /** `49.9`, or a string a shop writes: `\"49.90\"`, `\"$49.90\"`, `\"49,90 €\"`. */\n price?: number | string;\n /** An ISO 4217 code (`EUR`) or a usual symbol (`€`). */\n currency?: string;\n /** The price before the sale: struck through, and the discount computed from it. */\n compareAtPrice?: number | string;\n /** 0–5. */\n rating?: number;\n reviewCount?: number;\n availability?: ProductAvailability | string;\n merchant?: string;\n /** The shop's logo, small, before its name. */\n merchantLogo?: string;\n /** A word over the photo: \"Bestseller\", \"New\". */\n badge?: string;\n /** Sizes, colours: drawn as chips in `detail`. */\n options?: string[];\n /** A line of your own (\"Delivered tomorrow\"), or a cost — `0` reads \"Free delivery\". */\n shipping?: string | number;\n /** A few lines, in `detail`. Tags are stripped: it is shown as text. */\n description?: string;\n /** Where the product's name and photo link to; `http(s)` only. */\n url?: string;\n /** Your own id, handed back with the product in `aparte-card-action`. */\n id?: string;\n /**\n * At most two buttons per product, each a link (`http(s)`) or an `id`, reporting the\n * product as its `data`. A `send` is dropped: a result never writes the person's next\n * message — send one from your `aparte-card-action` listener, or the card's `actions`.\n */\n actions?: CardAction[];\n}\n\n/** What `map` returns: one product, a list, or a list with a heading. */\nexport type ProductsInput = ProductInput | ProductInput[] | { title?: string; products: ProductInput[] };\n\n/** One product as the card draws it, once read. */\nexport interface Product {\n id?: string;\n title: string;\n image?: { src: string; alt?: string };\n price?: number;\n /** An ISO 4217 code. */\n currency?: string;\n /** Kept only when it is above `price`: a \"was\" price lower than the price is no sale. */\n compareAtPrice?: number;\n rating?: number;\n reviewCount?: number;\n /** One of the four, or the tool's own words. */\n availability?: { status: ProductAvailability } | { text: string };\n merchant?: string;\n merchantLogo?: string;\n badge?: string;\n options: string[];\n shipping?: { text: string } | { cost: number };\n description?: string;\n url?: string;\n actions: CardAction[];\n}\n\n/** The data as the card draws it. */\nexport interface ProductData {\n /** The heading over several products (\"Trail shoes under €100\"). */\n title?: string;\n products: Product[];\n}\n\nconst P = {\n cardsProductTitle: { en: 'Products', fr: 'Produits', de: 'Produkte', es: 'Productos', it: 'Prodotti', ja: '商品', ko: '상품', pt: 'Produtos', ru: 'Товары', zh: '商品' },\n cardsProductCount: { en: '{n} products', fr: '{n} produits', de: '{n} Produkte', es: '{n} productos', it: '{n} prodotti', ja: '{n}件の商品', ko: '상품 {n}개', pt: '{n} produtos', ru: 'Товаров: {n}', zh: '{n} 件商品' },\n cardsProductInStock: { en: 'In stock', fr: 'En stock', de: 'Auf Lager', es: 'En stock', it: 'Disponibile', ja: '在庫あり', ko: '재고 있음', pt: 'Em estoque', ru: 'В наличии', zh: '有货' },\n cardsProductLowStock: { en: 'Only a few left', fr: 'Plus que quelques-uns', de: 'Nur noch wenige', es: 'Quedan pocas unidades', it: 'Ultimi pezzi', ja: '残りわずか', ko: '품절 임박', pt: 'Últimas unidades', ru: 'Осталось мало', zh: '仅剩少量' },\n cardsProductOutOfStock: { en: 'Out of stock', fr: 'Rupture de stock', de: 'Ausverkauft', es: 'Agotado', it: 'Esaurito', ja: '在庫切れ', ko: '품절', pt: 'Esgotado', ru: 'Нет в наличии', zh: '缺货' },\n cardsProductPreorder: { en: 'Pre-order', fr: 'Précommande', de: 'Vorbestellbar', es: 'Preventa', it: 'Preordine', ja: '予約受付中', ko: '예약 판매', pt: 'Pré-venda', ru: 'Предзаказ', zh: '预售' },\n cardsProductWas: { en: 'Was', fr: 'Au lieu de', de: 'Statt', es: 'Antes', it: 'Prima', ja: '通常価格', ko: '정가', pt: 'De', ru: 'Было', zh: '原价' },\n cardsProductFreeShipping: { en: 'Free delivery', fr: 'Livraison gratuite', de: 'Kostenloser Versand', es: 'Envío gratis', it: 'Spedizione gratuita', ja: '送料無料', ko: '무료 배송', pt: 'Frete grátis', ru: 'Бесплатная доставка', zh: '免运费' },\n cardsProductShippingCost: { en: '{price} delivery', fr: 'Livraison {price}', de: 'Versand {price}', es: 'Envío {price}', it: 'Spedizione {price}', ja: '送料 {price}', ko: '배송비 {price}', pt: 'Frete {price}', ru: 'Доставка {price}', zh: '运费 {price}' },\n cardsProductOptions: { en: 'Options', fr: 'Options', de: 'Optionen', es: 'Opciones', it: 'Opzioni', ja: 'オプション', ko: '옵션', pt: 'Opções', ru: 'Варианты', zh: '可选项' },\n cardsProductMoreOptions: { en: '{n} more', fr: '{n} de plus', de: '{n} weitere', es: '{n} más', it: 'altri {n}', ja: 'ほか{n}件', ko: '{n}개 더', pt: 'mais {n}', ru: 'ещё {n}', zh: '还有 {n} 个' },\n cardsProductMore: { en: 'More options', fr: 'Autres choix', de: 'Weitere Angebote', es: 'Más opciones', it: 'Altre proposte', ja: 'その他の候補', ko: '다른 상품', pt: 'Mais opções', ru: 'Другие варианты', zh: '更多选择' },\n} satisfies Record<string, CardWord>;\n\nconst STOCK_WORD: Record<ProductAvailability, keyof typeof P> = {\n 'in-stock': 'cardsProductInStock',\n 'low-stock': 'cardsProductLowStock',\n 'out-of-stock': 'cardsProductOutOfStock',\n preorder: 'cardsProductPreorder',\n};\n\n/** Ten: past that a row of tiles is a catalogue, not an answer (3–8 is the comfortable range). */\nconst PRODUCTS_SHOWN = 10;\nconst OPTIONS_SHOWN = 6;\nconst ACTIONS_PER_PRODUCT = 2;\n\n// ── Reading ──────────────────────────────────────────────────────────────────\n\ntype Raw = Record<string, unknown>;\n\nconst isObject = (value: unknown): value is Raw => !!value && typeof value === 'object' && !Array.isArray(value);\n\n/** The first of `keys` that `r` has a value for. */\nfunction pick(r: Raw | undefined, keys: readonly string[]): unknown {\n if (!r) return undefined;\n for (const key of keys) if (r[key] !== undefined && r[key] !== null) return r[key];\n return undefined;\n}\n\n/** A list from an array or a GraphQL connection (`{ nodes }`, `{ edges }`) — Shopify's Storefront API returns the latter. */\nfunction listOf(value: unknown): unknown[] {\n if (Array.isArray(value)) return value;\n if (!isObject(value)) return [];\n if (Array.isArray(value['nodes'])) return value['nodes'];\n if (Array.isArray(value['edges'])) return value['edges'].map((e) => (isObject(e) ? e['node'] : undefined));\n return [];\n}\n\n/** An amount and its currency, from a number, a shop's text, or `{ amount, currencyCode }`. */\nfunction readMoney(value: unknown): { amount?: number; currency?: string } {\n if (typeof value === 'number') return { amount: Number.isFinite(value) && value >= 0 ? value : undefined };\n if (typeof value === 'string') {\n // A price is not negative: a minus in a shop's text is not this price's.\n const { amount, currency } = readPriceText(value);\n return { amount: amount === undefined ? undefined : Math.abs(amount), currency };\n }\n if (isObject(value)) {\n return {\n amount: readMoney(pick(value, ['amount', 'value', 'price'])).amount,\n currency: currencyCode(pick(value, ['currencyCode', 'currency_code', 'currency'])),\n };\n }\n return {};\n}\n\nfunction readImage(value: unknown): Product['image'] {\n if (Array.isArray(value)) {\n for (const item of value) {\n const image = readImage(item);\n if (image) return image;\n }\n return undefined;\n }\n if (isObject(value)) {\n // schema.org's ImageObject keeps its address in `contentUrl`.\n const src = webUrl(pick(value, ['src', 'url', 'contentUrl', 'href', 'large', 'medium', 'thumbnail']));\n const alt = str(pick(value, ['alt', 'altText', 'alt_text']));\n return src ? { src, ...(alt ? { alt } : {}) } : undefined;\n }\n const src = webUrl(value);\n return src ? { src } : undefined;\n}\n\n/** schema.org's ItemAvailability names and the usual shop words, squeezed to letters. */\nconst AVAILABILITY: Record<string, ProductAvailability> = {\n instock: 'in-stock', available: 'in-stock', instoreonly: 'in-stock', onlineonly: 'in-stock',\n lowstock: 'low-stock', limitedavailability: 'low-stock', fewleft: 'low-stock',\n outofstock: 'out-of-stock', soldout: 'out-of-stock', unavailable: 'out-of-stock', discontinued: 'out-of-stock',\n preorder: 'preorder', presale: 'preorder', backorder: 'preorder',\n};\n\nfunction readAvailability(r: Raw, offer: Raw | undefined): Product['availability'] {\n const value = pick(r, ['availability', 'stockStatus', 'stock_status']) ?? pick(offer, ['availability']);\n if (typeof value === 'string' && value.trim()) {\n // `https://schema.org/InStock`, `schema:InStock` (a JSON-LD prefix), `InStock`: one key.\n const key = value.trim().toLowerCase().replace(/^(?:https?:\\/\\/schema\\.org\\/|schema:)/, '').replace(/[^a-z]/g, '');\n const status = AVAILABILITY[key];\n return status ? { status } : { text: value.trim() };\n }\n const flag = typeof value === 'boolean' ? value : pick(r, ['inStock', 'in_stock', 'available']);\n if (typeof flag === 'boolean') return { status: flag ? 'in-stock' : 'out-of-stock' };\n return undefined;\n}\n\nfunction readRating(r: Raw): { rating?: number; reviewCount?: number } {\n const aggregate = isObject(r['aggregateRating']) ? r['aggregateRating'] : undefined;\n let value = pick(r, ['rating', 'stars', 'ratingValue', 'rating_value', 'averageRating', 'average_rating']) ?? pick(aggregate, ['ratingValue']);\n let count = pick(r, ['reviewCount', 'review_count', 'reviews', 'reviewsCount', 'reviews_count', 'ratingCount', 'rating_count', 'numReviews'])\n ?? pick(aggregate, ['reviewCount', 'ratingCount']);\n // `{ rate, count }`, as the Fake Store API and others nest it.\n if (isObject(value)) {\n count ??= pick(value, ['count', 'reviews']);\n value = pick(value, ['rate', 'value', 'average']);\n }\n if (Array.isArray(count)) count = count.length;\n // schema.org names the scale with `bestRating` (5 when missing): 4 out of 10 is two stars, not four.\n const best = num(pick(r, ['bestRating', 'best_rating']) ?? pick(aggregate, ['bestRating']));\n const given = num(value);\n const stars = given !== undefined && best !== undefined && best > 0 && given <= best ? (given * 5) / best : given;\n const reviews = num(count);\n return {\n // A value off the five-star scale is another scale, not a five: drawn as none.\n rating: stars !== undefined && stars >= 0 && stars <= 5 ? stars : undefined,\n reviewCount: reviews !== undefined && reviews >= 0 ? Math.floor(reviews) : undefined,\n };\n}\n\n/** Shopify's name for the one variant of a product that has none. */\nconst NO_VARIANT = 'Default Title';\n\nfunction readOptions(r: Raw): string[] {\n const out: string[] = [];\n const add = (value: unknown) => {\n const s = str(value);\n if (s && s !== NO_VARIANT && !out.includes(s)) out.push(s);\n };\n // A shop's variants are its options combined (\"Pink / M\"): beside the options they would\n // say each value a second time, in another chip.\n const keys = Array.isArray(r['options']) ? ['options', 'sizes', 'colors', 'colours'] : ['sizes', 'colors', 'colours', 'variants'];\n for (const key of keys) {\n for (const item of listOf(r[key])) {\n if (!isObject(item)) add(item);\n // Shopify's `{ name: 'Size', values: ['S', 'M'] }`: the values are the choices.\n else if (Array.isArray(item['values'])) item['values'].forEach(add);\n else add(pick(item, ['title', 'name', 'value', 'label']));\n }\n }\n return out.slice(0, 50);\n}\n\nfunction readShipping(r: Raw): Product['shipping'] {\n if (r['freeShipping'] === true || r['free_shipping'] === true) return { cost: 0 };\n let value = pick(r, ['shipping', 'delivery', 'shippingInfo', 'shipping_info']);\n if (isObject(value)) value = pick(value, ['text', 'label', 'description']) ?? pick(value, ['cost', 'price', 'amount']);\n if (typeof value === 'number') return Number.isFinite(value) && value >= 0 ? { cost: value } : undefined;\n const text = str(value);\n if (!text) return undefined;\n const cost = num(text);\n return cost !== undefined && cost >= 0 ? { cost } : { text };\n}\n\n/**\n * A product's own buttons, as the result gave them: a link (`http(s)`) or an `id` your\n * `aparte-card-action` listener handles. Never a `send`: the result is the tool's — a shop's\n * page, a feed, a model — and a button that posts its text as the person's next message lets\n * that text speak for them, a lever for prompt injection. A message is the app's to write: in\n * its listener, from the product the event carries, or in the card's `actions` option.\n */\nfunction readActions(value: unknown): CardAction[] {\n if (!Array.isArray(value)) return [];\n return value\n .map((a): CardAction | null => {\n const label = isObject(a) ? str(a['label']) : undefined;\n if (!label) return null;\n const url = a['url'] !== undefined ? webUrl(a['url']) : undefined;\n if (url === null) return null;\n const id = str(a['id']);\n if (!url && !id) return null;\n return { label, ...(url ? { url } : {}), ...(id ? { id } : {}), ...(a['primary'] === true ? { primary: true } : {}) };\n })\n .filter((a): a is CardAction => a !== null)\n .slice(0, ACTIONS_PER_PRODUCT);\n}\n\n/** HTML's usual entities, once its tags are out: `&amp;` is `&`. One pass, so `&amp;lt;` stays `&lt;`. */\nconst ENTITIES: Record<string, string> = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: '\\'', nbsp: ' ' };\nfunction decodeEntities(text: string): string {\n return text.replace(/&(#x[0-9a-f]+|#\\d+|[a-z]+);/gi, (whole, name: string) => {\n if (name.startsWith('#')) {\n const code = /^#x/i.test(name) ? parseInt(name.slice(2), 16) : Number(name.slice(1));\n return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : whole;\n }\n return ENTITIES[name.toLowerCase()] ?? whole;\n });\n}\n\n/** The markup a shop writes a description in (Shopify's `body_html`), as the words a reader sees. */\nfunction plainText(html: string): string {\n return decodeEntities(html.replace(/<\\/?(?:br|p|li|div|h\\d)(?=[\\s/>])[^>]*>/gi, ' ').replace(/<[^>]*>/g, '')).replace(/\\s+/g, ' ').trim();\n}\n\ntype Money = ReturnType<typeof readMoney>;\n\n/**\n * The cheapest of a product's variants, with the price it replaces — where Shopify's Admin\n * API keeps a product's price (`variants[].price`, `compare_at_price`).\n */\nfunction cheapestVariant(r: Raw): { price: Money; compare: Money } | undefined {\n let best: { price: Money; compare: Money } | undefined;\n for (const v of listOf(r['variants'])) {\n if (!isObject(v)) continue;\n const price = readMoney(pick(v, ['price', 'amount']));\n if (price.amount === undefined) continue;\n if (!best || price.amount < best.price.amount!) best = { price, compare: readMoney(pick(v, ['compare_at_price', 'compareAtPrice'])) };\n }\n return best;\n}\n\n/** The low end of Shopify's Storefront range, `{ minVariantPrice: { amount, currencyCode } }`. */\nconst lowEnd = (range: unknown) => (isObject(range) ? range['minVariantPrice'] : undefined);\n\n/** One product from whatever a tool wrote, or `null` without a name to show. */\nfunction readProduct(raw: unknown): Product | null {\n if (!isObject(raw)) return null;\n const r = raw;\n const title = str(pick(r, ['title', 'name', 'productName', 'product_title']));\n if (!title) return null;\n // schema.org puts the price, the currency, the stock and the link on an offer.\n const offers = r['offers'];\n const offer = Array.isArray(offers) ? (isObject(offers[0]) ? offers[0] : undefined) : isObject(offers) ? offers : undefined;\n\n // A price of its own, an offer's, Shopify Storefront's range — else its cheapest variant's.\n const own = pick(r, ['price', 'extracted_price', 'salePrice', 'sale_price', 'currentPrice', 'current_price'])\n ?? pick(offer, ['price', 'lowPrice']) ?? lowEnd(r['priceRange']);\n const variant = own === undefined ? cheapestVariant(r) : undefined;\n const price = variant ? variant.price : readMoney(own);\n const compareOwn = pick(r, [\n 'compareAtPrice', 'compare_at_price', 'originalPrice', 'original_price', 'listPrice', 'list_price',\n 'regularPrice', 'regular_price', 'wasPrice', 'was_price', 'extracted_old_price', 'oldPrice', 'old_price', 'msrp',\n ]) ?? lowEnd(r['compareAtPriceRange']);\n const compare = compareOwn === undefined && variant ? variant.compare : readMoney(compareOwn);\n const currency = currencyCode(pick(r, ['currency', 'currency_code', 'currencyCode', 'priceCurrency']) ?? pick(offer, ['priceCurrency']))\n ?? price.currency ?? compare.currency;\n\n const merchantRaw = pick(r, ['merchant', 'seller', 'store', 'shop', 'source', 'retailer', 'vendor']) ?? pick(r, ['brand']);\n const merchant = isObject(merchantRaw) ? str(pick(merchantRaw, ['name', 'title'])) : str(merchantRaw);\n const merchantLogo = webUrl(pick(r, ['merchantLogo', 'merchant_logo', 'source_icon', 'storeLogo', 'logo'])\n ?? (isObject(merchantRaw) ? pick(merchantRaw, ['logo', 'icon', 'image']) : undefined));\n\n const description = str(pick(r, ['description', 'summary', 'snippet', 'subtitle', 'body_html', 'descriptionHtml']));\n const { rating: stars, reviewCount } = readRating(r);\n const availability = readAvailability(r, offer);\n const shipping = readShipping(r);\n const image = readImage(pick(r, ['image', 'images', 'thumbnail', 'image_url', 'imageUrl', 'featuredImage', 'photo']));\n const url = webUrl(pick(r, ['url', 'link', 'permalink', 'product_link', 'productUrl', 'product_url', 'onlineStoreUrl', 'href']) ?? pick(offer, ['url']));\n const id = str(pick(r, ['id', 'sku', 'product_id', 'productId']));\n const badge = str(pick(r, ['badge', 'tag', 'ribbon']));\n\n return {\n title,\n ...(id ? { id } : {}),\n ...(image ? { image } : {}),\n ...(price.amount !== undefined ? { price: price.amount } : {}),\n ...(currency ? { currency } : {}),\n ...(price.amount !== undefined && compare.amount !== undefined && compare.amount > price.amount ? { compareAtPrice: compare.amount } : {}),\n ...(stars !== undefined ? { rating: stars } : {}),\n ...(reviewCount !== undefined ? { reviewCount } : {}),\n ...(availability ? { availability } : {}),\n ...(merchant ? { merchant } : {}),\n ...(merchant && merchantLogo ? { merchantLogo } : {}),\n ...(badge ? { badge } : {}),\n options: readOptions(r),\n ...(shipping ? { shipping } : {}),\n // Shown as text, so a tag would show as `<p>`: take the words out of the markup.\n ...(description ? { description: plainText(description) } : {}),\n ...(url ? { url } : {}),\n actions: readActions(r['actions']),\n };\n}\n\n/**\n * The card's data from the loose shape `map` returns — one product, a list, or\n * `{ title, products }` (also `results`, `items`, Google Shopping's `shopping_results`) —\n * or `null` when not one product has a name. At most ten are kept.\n */\nexport function readProducts(raw: unknown): ProductData | null {\n if (!raw || typeof raw !== 'object') return null;\n let list: unknown[];\n let title: string | undefined;\n if (Array.isArray(raw)) {\n list = raw;\n } else {\n const r = raw as Raw;\n const many = ['products', 'results', 'shopping_results', 'items'].map((key) => r[key]).find(Array.isArray);\n if (many) {\n list = many;\n title = str(pick(r, ['title', 'heading']));\n } else {\n list = [r];\n }\n }\n const products = list.map(readProduct).filter((p): p is Product => p !== null).slice(0, PRODUCTS_SHOWN);\n return products.length ? { ...(title ? { title } : {}), products } : null;\n}\n\n/** The share taken off, from the two prices — never read from a tool's \"-20%\". `undefined` under one percent. */\nexport function discountOf(product: Pick<Product, 'price' | 'compareAtPrice'>): number | undefined {\n const { price, compareAtPrice } = product;\n if (price === undefined || compareAtPrice === undefined || compareAtPrice <= price || compareAtPrice <= 0) return undefined;\n const ratio = (compareAtPrice - price) / compareAtPrice;\n return Math.round(ratio * 100) >= 1 ? ratio : undefined;\n}\n\n// ── Drawing ──────────────────────────────────────────────────────────────────\n\nconst money = (config: AparteConfig, product: Product, amount: number) => formatPrice(config, amount, product.currency);\nconst notNull = <T>(value: T | null | undefined): value is T => value !== null && value !== undefined;\n\n/**\n * The photo in its square, over a bag glyph that stays underneath: a missing photo shows\n * the bag, and one that fails to load is hidden to show it — no node comes or goes.\n */\nfunction media(product: Product, className = ''): HTMLElement {\n const frame = el('div', { class: `aparte-product__media${className ? ` ${className}` : ''}` },\n glyph(BAG, 'aparte-rich-card__glyph aparte-product__placeholder'));\n if (!product.image) {\n frame.setAttribute('data-empty', '');\n return frame;\n }\n const image = el('img', {\n class: 'aparte-product__image',\n attrs: {\n src: product.image.src,\n alt: product.image.alt ?? product.title,\n loading: 'lazy',\n decoding: 'async',\n // A shop's CDN learns nothing about the chat page that showed its photo.\n referrerpolicy: 'no-referrer',\n },\n });\n image.addEventListener('error', () => {\n image.hidden = true;\n frame.setAttribute('data-empty', '');\n }, { once: true });\n frame.append(image);\n return frame;\n}\n\n/** The product's name, a link out when it has a URL — the one link a tile or a row has. */\nfunction titleOf(product: Product): HTMLElement {\n const title = el('div', { class: 'aparte-product__title' });\n if (product.url) {\n title.append(el('a', {\n class: 'aparte-product__link',\n attrs: { href: product.url, target: '_blank', rel: 'noopener noreferrer' },\n text: product.title,\n }));\n } else {\n title.textContent = product.title;\n }\n return title;\n}\n\nfunction merchantOf(product: Product): HTMLElement | null {\n if (!product.merchant) return null;\n const node = el('div', { class: 'aparte-product__merchant' });\n if (product.merchantLogo) {\n // Empty alt: the shop's name is written right beside it.\n const logo = el('img', { class: 'aparte-product__logo', attrs: { src: product.merchantLogo, alt: '', loading: 'lazy', decoding: 'async', referrerpolicy: 'no-referrer' } });\n logo.addEventListener('error', () => { logo.hidden = true; }, { once: true });\n node.append(logo);\n }\n node.append(el('span', { class: 'aparte-product__merchant-name', text: product.merchant }));\n return node;\n}\n\nfunction discountBadge(ctx: CardContext, product: Product): HTMLElement | null {\n const ratio = discountOf(product);\n if (ratio === undefined) return null;\n const badge = el('span', { class: 'aparte-badge aparte-badge--danger aparte-badge--solid aparte-badge--sm aparte-product__discount' });\n ctx.bind(badge, () => `\\u2212${formatPercent(ctx.config, ratio)}`);\n return badge;\n}\n\n/** The price, the \"was\" price struck through after it, and — in `detail` — the discount beside them. */\nfunction priceOf(ctx: CardContext, product: Product, withDiscount = false): HTMLElement | null {\n if (product.price === undefined) return null;\n const row = el('div', { class: 'aparte-product__price' });\n const now = el('span', { class: 'aparte-product__now' });\n ctx.bind(now, () => money(ctx.config, product, product.price!));\n row.append(now);\n if (product.compareAtPrice !== undefined) {\n // A struck-through number is silent to a screen reader: it is told which one this is.\n const said = el('span', { class: 'aparte-sr-only' });\n ctx.bind(said, () => `${word(ctx.config, 'cardsProductWas', P.cardsProductWas)} `);\n const value = el('span', { class: 'aparte-product__was-value' });\n ctx.bind(value, () => money(ctx.config, product, product.compareAtPrice!));\n row.append(el('del', { class: 'aparte-product__was' }, said, value));\n const discount = withDiscount ? discountBadge(ctx, product) : null;\n if (discount) row.append(discount);\n }\n return row;\n}\n\nfunction availabilityOf(ctx: CardContext, product: Product): HTMLElement | null {\n const availability = product.availability;\n if (!availability) return null;\n const status = 'status' in availability ? availability.status : undefined;\n const text = el('span', { class: 'aparte-product__stock-text' });\n if (status) ctx.word(text, STOCK_WORD[status], P[STOCK_WORD[status]]);\n else text.textContent = (availability as { text: string }).text;\n return el('div', { class: 'aparte-product__stock', attrs: { 'data-availability': status ?? 'other' } },\n el('span', { class: 'aparte-product__dot', attrs: { 'aria-hidden': 'true' } }),\n text);\n}\n\nfunction shippingOf(ctx: CardContext, product: Product): HTMLElement | null {\n const shipping = product.shipping;\n if (!shipping) return null;\n const text = el('span', { class: 'aparte-product__shipping-text' });\n if ('text' in shipping) text.textContent = shipping.text;\n else if (shipping.cost === 0) ctx.word(text, 'cardsProductFreeShipping', P.cardsProductFreeShipping);\n else ctx.bind(text, () => word(ctx.config, 'cardsProductShippingCost', P.cardsProductShippingCost, { price: money(ctx.config, product, shipping.cost) }));\n return el('div', { class: 'aparte-product__shipping', attrs: { 'data-free': 'cost' in shipping && shipping.cost === 0 } },\n glyph(TRUCK, 'aparte-rich-card__glyph aparte-product__shipping-icon'),\n text);\n}\n\n/** What sits over the photo's corner: the shop's badge, and the discount where the variant puts it there. */\nfunction flags(ctx: CardContext, product: Product, withDiscount: boolean): HTMLElement | null {\n const items = [\n product.badge ? el('span', { class: 'aparte-badge aparte-badge--sm aparte-product__badge', text: product.badge }) : null,\n withDiscount ? discountBadge(ctx, product) : null,\n ].filter(notNull);\n return items.length ? el('div', { class: 'aparte-product__flags' }, ...items) : null;\n}\n\n/** Sizes, colours: six chips, then \"+n\" for the rest. */\nfunction optionsOf(ctx: CardContext, product: Product): HTMLElement | null {\n if (!product.options.length) return null;\n const list = el('ul', { class: 'aparte-product__options', attrs: { role: 'list' } });\n ctx.bind(list, () => word(ctx.config, 'cardsProductOptions', P.cardsProductOptions), 'aria-label');\n for (const option of product.options.slice(0, OPTIONS_SHOWN)) {\n list.append(el('li', { class: 'aparte-tag aparte-tag--sm aparte-product__option' }, el('span', { class: 'aparte-tag__label', text: option })));\n }\n const rest = product.options.length - OPTIONS_SHOWN;\n if (rest > 0) {\n const shown = el('span', { class: 'aparte-tag__label', attrs: { 'aria-hidden': 'true' } });\n ctx.bind(shown, () => `+${formatNumber(ctx.config, rest)}`);\n const said = el('span', { class: 'aparte-sr-only' });\n ctx.bind(said, () => word(ctx.config, 'cardsProductMoreOptions', P.cardsProductMoreOptions, { n: formatNumber(ctx.config, rest) }));\n list.append(el('li', { class: 'aparte-tag aparte-tag--sm aparte-product__option aparte-product__option--more' }, shown, said));\n }\n return list;\n}\n\n/** A product's own buttons, each handing the product back as the event's `data`. */\nfunction actionsOf(ctx: CardContext, product: Product, max: number): HTMLElement | null {\n const buttons = product.actions.slice(0, max).map((action) => ctx.action(action, product, 'aparte-product__action')).filter(notNull);\n return buttons.length ? el('div', { class: 'aparte-product__actions' }, ...buttons) : null;\n}\n\nfunction productRoot(kind: 'tile' | 'row' | 'detail', product: Product, extra = ''): HTMLElement {\n return el('div', {\n class: `aparte-product aparte-product--${kind}${extra ? ` ${extra}` : ''}`,\n attrs: {\n 'data-availability': product.availability && 'status' in product.availability ? product.availability.status : undefined,\n 'data-linked': product.url !== undefined,\n },\n });\n}\n\n/**\n * The `card` variant's tile. The title's link stretches over the whole tile (the sheet's\n * `::after`), so the tile is one link, read once, with its buttons lifted above it.\n * A `mini` tile — the others under a `detail` — keeps the photo, the name and the price.\n */\nfunction tile(ctx: CardContext, product: Product, { actions, mini = false }: { actions: number; mini?: boolean }): HTMLElement {\n const node = productRoot('tile', product, mini ? 'aparte-product--mini' : '');\n const photo = media(product);\n const corner = mini ? null : flags(ctx, product, true);\n if (corner) photo.append(corner);\n const body = el('div', { class: 'aparte-product__body' },\n mini ? null : merchantOf(product),\n titleOf(product),\n mini ? null : rating(ctx, product.rating, { count: product.reviewCount }),\n priceOf(ctx, product),\n mini ? null : availabilityOf(ctx, product));\n node.append(photo, body);\n const buttons = actionsOf(ctx, product, actions);\n if (buttons) node.append(buttons);\n return node;\n}\n\n/**\n * The `compact` variant's row: thumbnail, name, shop and stars, price. \"In stock\" is left\n * out — it is what a reader assumes — so the line only speaks when the stock changes the choice.\n */\nfunction row(ctx: CardContext, product: Product): HTMLElement {\n const node = productRoot('row', product);\n const meta = el('div', { class: 'aparte-product__meta' },\n product.merchant ? el('span', { class: 'aparte-product__merchant-name', text: product.merchant }) : null,\n rating(ctx, product.rating, { starsOnly: true }),\n product.availability && !('status' in product.availability && product.availability.status === 'in-stock') ? availabilityOf(ctx, product) : null);\n const main = el('div', { class: 'aparte-product__main' }, titleOf(product), meta.childNodes.length ? meta : null);\n node.append(media(product, 'aparte-product__thumb'), main);\n const amount = priceOf(ctx, product);\n if (amount) node.append(amount);\n const buttons = actionsOf(ctx, product, 1);\n if (buttons) node.append(buttons);\n return node;\n}\n\n/** The `detail` variant: everything the card knows, the photo beside it on a wide card. */\nfunction detail(ctx: CardContext, product: Product): HTMLElement {\n const node = productRoot('detail', product);\n const photo = media(product);\n const corner = flags(ctx, product, false);\n if (corner) photo.append(corner);\n // The photo links where the name does; the name's link is the one a keyboard and a\n // screen reader meet, so this copy is out of both.\n const shown = product.url\n ? el('a', { class: 'aparte-product__media-link', attrs: { href: product.url, target: '_blank', rel: 'noopener noreferrer', tabindex: -1, 'aria-hidden': 'true' } }, photo)\n : photo;\n const facts = el('div', { class: 'aparte-product__facts' }, availabilityOf(ctx, product), shippingOf(ctx, product));\n const body = el('div', { class: 'aparte-product__body' },\n merchantOf(product),\n titleOf(product),\n rating(ctx, product.rating, { count: product.reviewCount }),\n priceOf(ctx, product, true),\n facts.childNodes.length ? facts : null,\n product.description ? el('p', { class: 'aparte-product__description', text: product.description }) : null,\n optionsOf(ctx, product),\n actionsOf(ctx, product, ACTIONS_PER_PRODUCT));\n node.append(shown, body);\n return el('div', { class: 'aparte-product__detail' }, node);\n}\n\nfunction heading(data: ProductData): HTMLElement | null {\n return data.title ? el('div', { class: 'aparte-product__heading', text: data.title }) : null;\n}\n\nfunction labelOf(data: Partial<ProductData> | null, ctx: CardContext): string {\n const products = data?.products ?? [];\n if (products.length === 1) return products[0]!.title;\n if (data?.title) return data.title;\n if (products.length > 1) return word(ctx.config, 'cardsProductCount', P.cardsProductCount, { n: formatNumber(ctx.config, products.length) });\n return word(ctx.config, 'cardsProductTitle', P.cardsProductTitle);\n}\n\nfunction render(data: ProductData, ctx: CardContext<ProductVariant>): Node[] {\n const { products } = data;\n const first = products[0]!;\n\n if (ctx.variant === 'detail') {\n // One product looked at closely; the others stay one swipe away, small, rather\n // than a second row of full tiles competing with it. Rows were the other choice,\n // but nine rows under a detail make a page, and a row of mini tiles stays one line.\n const out: Node[] = [detail(ctx, first)];\n if (products.length > 1) {\n const title = el('div', { class: 'aparte-product__more-title' });\n ctx.word(title, 'cardsProductMore', P.cardsProductMore);\n const slides = products.slice(1).map((p) => tile(ctx, p, { actions: 0, mini: true }));\n out.push(el('div', { class: 'aparte-product__more' }, title,\n carousel(ctx, slides, { label: () => word(ctx.config, 'cardsProductMore', P.cardsProductMore) })));\n }\n return out;\n }\n\n if (ctx.variant === 'compact') {\n if (products.length === 1) return [row(ctx, first)];\n const list = el('ul', { class: 'aparte-product__rows', attrs: { role: 'list' } },\n ...products.map((p) => el('li', { class: 'aparte-product__item' }, row(ctx, p))));\n return [heading(data), list].filter(notNull);\n }\n\n if (products.length === 1) return [tile(ctx, first, { actions: ACTIONS_PER_PRODUCT })];\n // Several: tiles in a row that scrolls, one button each — a row of tiles reads as a\n // choice when each offers one thing to do.\n const slides = products.map((p) => tile(ctx, p, { actions: 1 }));\n return [el('div', { class: 'aparte-product__collection' },\n heading(data),\n carousel(ctx, slides, { label: () => labelOf(data, ctx) }))];\n}\n\nfunction skeleton(partial: Partial<ProductData> | null, ctx: CardContext<ProductVariant>): 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 lines = () => [block('aparte-product__sk-line', 'text'), block('aparte-product__sk-line aparte-product__sk-line--short', 'text')];\n // What the search was for, before anything is found: the call's input already says it.\n const query = partial?.title\n ? el('div', { class: 'aparte-product__query' }, glyph(SEARCH, 'aparte-rich-card__glyph aparte-product__query-icon'), el('span', { class: 'aparte-product__query-text', text: partial.title }))\n : null;\n\n if (ctx.variant === 'compact') {\n const rows = [0, 1, 2].map(() => el('div', { class: 'aparte-product__item' },\n el('div', { class: 'aparte-product aparte-product--row aparte-product--skeleton' },\n block('aparte-product__sk-thumb'),\n el('div', { class: 'aparte-product__main' }, ...lines()),\n block('aparte-product__sk-price', 'text'))));\n return [query, el('div', { class: 'aparte-product__rows' }, ...rows)].filter(notNull);\n }\n if (ctx.variant === 'detail') {\n return [query, el('div', { class: 'aparte-product__detail' },\n el('div', { class: 'aparte-product aparte-product--detail aparte-product--skeleton' },\n block('aparte-product__sk-media'),\n el('div', { class: 'aparte-product__body' }, ...lines(), block('aparte-product__sk-price', 'text'), block('aparte-product__sk-line', 'text'))))].filter(notNull);\n }\n // Three tiles, the third cut by the edge: the shape a row of results will take.\n const tiles = [0, 1, 2].map(() => el('div', { class: 'aparte-product aparte-product--tile aparte-product--skeleton' },\n block('aparte-product__sk-media'),\n el('div', { class: 'aparte-product__body' }, ...lines(), block('aparte-product__sk-price', 'text'))));\n return [el('div', { class: 'aparte-product__collection' }, query, el('div', { class: 'aparte-product__sk-track' }, ...tiles))];\n}\n\nexport const productCard = defineCard<ProductData, ProductVariant>({\n name: 'product',\n variants: ['card', 'compact', 'detail'],\n styles: productStyles,\n icon: TAG,\n normalize: readProducts,\n render,\n skeleton,\n label: labelOf,\n fallback: (data, ctx) => {\n // One line per product, \"Title — price (4.6/5)\", the way a model or a text-only\n // reader would want them listed.\n const lines = data.products.map((p) => {\n const amount = p.price !== undefined ? ` — ${money(ctx.config, p, p.price)}` : '';\n const stars = p.rating !== undefined ? ` (${formatNumber(ctx.config, p.rating, { maximumFractionDigits: 1 })}/5)` : '';\n return `${p.title}${amount}${stars}`;\n });\n return [data.title, ...lines].filter(notNull).join('\\n');\n },\n partial: (input) => {\n const query = str(input['query'] ?? input['q'] ?? input['search'] ?? input['keywords'] ?? input['term'] ?? input['product']);\n return query ? { title: query } : null;\n },\n});\n\n/** Draw a tool's products as this card — returns the function that stops it. */\nexport function setupProductCard(options: CardSetupOptions<ProductData, ProductVariant> = {}): () => void {\n return productCard.setup(options);\n}\n"],"names":["src","row","slides"],"mappings":";;;AAQA,MAAM,MAAM,CAAC,SACT,mKAAmK,IAAI;AAGpK,MAAM,MAAM,IAAI,yHAAyH;AAGzI,MAAM,QAAQ,IAAI,iJAAiJ;AAGnK,MAAM,SAAS,IAAI,qEAAqE;AAGxF,MAAM,MAAM,IAAI,0IAA0I;ACrBjK,MAAA,gBAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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"}
@@ -0,0 +1,35 @@
1
+ import { e as el, k as glyph, b as formatNumber, p as word, K as KIT_WORDS } from "./format-CchcJRum.js";
2
+ const STAR = "M12 2.6l2.9 5.9 6.5.95-4.7 4.6 1.1 6.5L12 17.5l-5.8 3.05 1.1-6.5-4.7-4.6 6.5-.95z";
3
+ const FIVE = `<svg viewBox="0 0 120 24" xmlns="http://www.w3.org/2000/svg">${[0, 24, 48, 72, 96].map((x) => `<path transform="translate(${x} 0)" d="${STAR}"/>`).join("")}</svg>`;
4
+ function rating(ctx, value, { max = 5, count, starsOnly = false } = {}) {
5
+ if (value === void 0 || !Number.isFinite(value) || max <= 0) return null;
6
+ const clamped = Math.max(0, Math.min(max, value));
7
+ const stars = el(
8
+ "span",
9
+ { class: "aparte-rich-card__stars", attrs: { "aria-hidden": "true" } },
10
+ glyph(FIVE, "aparte-rich-card__stars-base"),
11
+ glyph(FIVE, "aparte-rich-card__stars-fill")
12
+ );
13
+ stars.style.setProperty("--aparte-rich-card-rating", `${clamped / max * 100}%`);
14
+ const node = el("span", { class: "aparte-rich-card__rating", attrs: { role: "img" } }, stars);
15
+ const shown = (n) => formatNumber(ctx.config, n, { maximumFractionDigits: 1 });
16
+ if (!starsOnly) {
17
+ const number = el("span", { class: "aparte-rich-card__rating-value", attrs: { "aria-hidden": "true" } });
18
+ ctx.bind(number, () => shown(clamped));
19
+ node.append(number);
20
+ }
21
+ if (count !== void 0 && Number.isFinite(count)) {
22
+ const reviews = el("span", { class: "aparte-rich-card__rating-count", attrs: { "aria-hidden": "true" } });
23
+ ctx.bind(reviews, () => `(${formatNumber(ctx.config, count, { notation: count >= 1e4 ? "compact" : "standard" })})`);
24
+ node.append(reviews);
25
+ }
26
+ ctx.bind(node, () => {
27
+ const sentence = word(ctx.config, "cardsRating", KIT_WORDS.cardsRating, { value: shown(clamped), max: shown(max) });
28
+ return count !== void 0 && Number.isFinite(count) ? `${sentence}, ${word(ctx.config, "cardsReviews", KIT_WORDS.cardsReviews, { count: formatNumber(ctx.config, count) })}` : sentence;
29
+ }, "aria-label");
30
+ return node;
31
+ }
32
+ export {
33
+ rating as r
34
+ };
35
+ //# sourceMappingURL=rating-B8Mdx_b_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rating-B8Mdx_b_.js","sources":["../src/kit/rating.ts"],"sourcesContent":["/**\n * A rating: stars filled to the value, the value, and how many reviews it averages.\n *\n * Two rows of the same five stars, the coloured one clipped to the value's width, so\n * 4.6 shows six tenths of the fifth star. Read aloud as one sentence (\"Rated 4.6 out\n * of 5, 1,284 reviews\"); the stars themselves are hidden from a screen reader.\n */\nimport type { CardContext } from './define.js';\nimport { el, glyph } from './dom.js';\nimport { formatNumber } from './format.js';\nimport { KIT_WORDS, word } from './strings.js';\n\nconst STAR = 'M12 2.6l2.9 5.9 6.5.95-4.7 4.6 1.1 6.5L12 17.5l-5.8 3.05 1.1-6.5-4.7-4.6 6.5-.95z';\nconst FIVE = `<svg viewBox=\"0 0 120 24\" xmlns=\"http://www.w3.org/2000/svg\">${[0, 24, 48, 72, 96].map((x) => `<path transform=\"translate(${x} 0)\" d=\"${STAR}\"/>`).join('')}</svg>`; // safe-attr: this package's own glyph constants and loop numbers, never data // safe-text: this package's own SVG, built from its glyph constants\n\nexport interface RatingOptions {\n /** The scale's top (5 by default). */\n max?: number;\n /** How many reviews the value averages. */\n count?: number;\n /** Leave the number out and keep the stars. */\n starsOnly?: boolean;\n}\n\n/** The rating as an element, or `null` for no value. */\nexport function rating(ctx: CardContext, value: number | undefined, { max = 5, count, starsOnly = false }: RatingOptions = {}): HTMLElement | null {\n if (value === undefined || !Number.isFinite(value) || max <= 0) return null;\n const clamped = Math.max(0, Math.min(max, value));\n const stars = el('span', { class: 'aparte-rich-card__stars', attrs: { 'aria-hidden': 'true' } },\n glyph(FIVE, 'aparte-rich-card__stars-base'),\n glyph(FIVE, 'aparte-rich-card__stars-fill'));\n stars.style.setProperty('--aparte-rich-card-rating', `${(clamped / max) * 100}%`);\n const node = el('span', { class: 'aparte-rich-card__rating', attrs: { role: 'img' } }, stars);\n const shown = (n: number) => formatNumber(ctx.config, n, { maximumFractionDigits: 1 });\n if (!starsOnly) {\n const number = el('span', { class: 'aparte-rich-card__rating-value', attrs: { 'aria-hidden': 'true' } });\n ctx.bind(number, () => shown(clamped));\n node.append(number);\n }\n if (count !== undefined && Number.isFinite(count)) {\n const reviews = el('span', { class: 'aparte-rich-card__rating-count', attrs: { 'aria-hidden': 'true' } });\n ctx.bind(reviews, () => `(${formatNumber(ctx.config, count, { notation: count >= 10_000 ? 'compact' : 'standard' })})`);\n node.append(reviews);\n }\n ctx.bind(node, () => {\n const sentence = word(ctx.config, 'cardsRating', KIT_WORDS.cardsRating, { value: shown(clamped), max: shown(max) });\n return count !== undefined && Number.isFinite(count)\n ? `${sentence}, ${word(ctx.config, 'cardsReviews', KIT_WORDS.cardsReviews, { count: formatNumber(ctx.config, count) })}`\n : sentence;\n }, 'aria-label');\n return node;\n}\n"],"names":[],"mappings":";AAYA,MAAM,OAAO;AACb,MAAM,OAAO,gEAAgE,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,MAAM,8BAA8B,CAAC,WAAW,IAAI,KAAK,EAAE,KAAK,EAAE,CAAC;AAYlK,SAAS,OAAO,KAAkB,OAA2B,EAAE,MAAM,GAAG,OAAO,YAAY,MAAA,IAAyB,IAAwB;AAC/I,MAAI,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,KAAK,OAAO,EAAG,QAAO;AACvE,QAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC;AAChD,QAAM,QAAQ;AAAA,IAAG;AAAA,IAAQ,EAAE,OAAO,2BAA2B,OAAO,EAAE,eAAe,SAAO;AAAA,IACxF,MAAM,MAAM,8BAA8B;AAAA,IAC1C,MAAM,MAAM,8BAA8B;AAAA,EAAA;AAC9C,QAAM,MAAM,YAAY,6BAA6B,GAAI,UAAU,MAAO,GAAG,GAAG;AAChF,QAAM,OAAO,GAAG,QAAQ,EAAE,OAAO,4BAA4B,OAAO,EAAE,MAAM,MAAA,EAAM,GAAK,KAAK;AAC5F,QAAM,QAAQ,CAAC,MAAc,aAAa,IAAI,QAAQ,GAAG,EAAE,uBAAuB,GAAG;AACrF,MAAI,CAAC,WAAW;AACZ,UAAM,SAAS,GAAG,QAAQ,EAAE,OAAO,kCAAkC,OAAO,EAAE,eAAe,OAAA,GAAU;AACvG,QAAI,KAAK,QAAQ,MAAM,MAAM,OAAO,CAAC;AACrC,SAAK,OAAO,MAAM;AAAA,EACtB;AACA,MAAI,UAAU,UAAa,OAAO,SAAS,KAAK,GAAG;AAC/C,UAAM,UAAU,GAAG,QAAQ,EAAE,OAAO,kCAAkC,OAAO,EAAE,eAAe,OAAA,GAAU;AACxG,QAAI,KAAK,SAAS,MAAM,IAAI,aAAa,IAAI,QAAQ,OAAO,EAAE,UAAU,SAAS,MAAS,YAAY,WAAA,CAAY,CAAC,GAAG;AACtH,SAAK,OAAO,OAAO;AAAA,EACvB;AACA,MAAI,KAAK,MAAM,MAAM;AACjB,UAAM,WAAW,KAAK,IAAI,QAAQ,eAAe,UAAU,aAAa,EAAE,OAAO,MAAM,OAAO,GAAG,KAAK,MAAM,GAAG,GAAG;AAClH,WAAO,UAAU,UAAa,OAAO,SAAS,KAAK,IAC7C,GAAG,QAAQ,KAAK,KAAK,IAAI,QAAQ,gBAAgB,UAAU,cAAc,EAAE,OAAO,aAAa,IAAI,QAAQ,KAAK,GAAG,CAAC,KACpH;AAAA,EACV,GAAG,YAAY;AACf,SAAO;AACX;"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The score card's glyphs: one per sport, one per match event, and the few the footer
3
+ * wears (the venue, the broadcast, the way to the match page).
4
+ *
5
+ * Drawn on a 24 × 24 grid in `currentColor`, so the sheet colours them through `color`
6
+ * and a theme follows — the cards are the exception, filled, because a yellow card is a
7
+ * filled rectangle and nothing else reads as one. Constants of this package: the only
8
+ * markup the card parses.
9
+ */
10
+ /** A cup: any other sport, and the card's own glyph for its one-line states. */
11
+ export declare const TROPHY: string;
12
+ /** The glyph a match wears beside its competition, by sport. */
13
+ export declare const SPORT_GLYPHS: {
14
+ readonly football: string;
15
+ readonly basketball: string;
16
+ readonly tennis: string;
17
+ readonly rugby: string;
18
+ readonly hockey: string;
19
+ readonly baseball: string;
20
+ readonly 'american-football': string;
21
+ readonly generic: string;
22
+ };
23
+ /** A goal is the ball; a penalty and an own goal also say their word beside it. */
24
+ export declare const EVENT_GLYPHS: {
25
+ readonly goal: string;
26
+ readonly penalty: string;
27
+ readonly 'own-goal': string;
28
+ readonly yellow: "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><rect x=\"7\" y=\"3.5\" width=\"10\" height=\"16\" rx=\"1.6\" fill=\"currentColor\" transform=\"rotate(8 12 11.5)\"/></svg>";
29
+ readonly red: "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><rect x=\"7\" y=\"3.5\" width=\"10\" height=\"16\" rx=\"1.6\" fill=\"currentColor\" transform=\"rotate(8 12 11.5)\"/></svg>";
30
+ readonly sub: string;
31
+ };
32
+ export declare const PIN: string;
33
+ export declare const TV: string;
34
+ export declare const CHEVRON: string;
35
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/score/icons.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsBH,gFAAgF;AAChF,eAAO,MAAM,MAAM,QAAkK,CAAC;AAEtL,gEAAgE;AAChE,eAAO,MAAM,YAAY;;;;;;;;;CASf,CAAC;AAQX,mFAAmF;AACnF,eAAO,MAAM,YAAY;;;;;;;CAOf,CAAC;AAEX,eAAO,MAAM,GAAG,QAAqH,CAAC;AAEtI,eAAO,MAAM,EAAE,QAAiG,CAAC;AAEjH,eAAO,MAAM,OAAO,QAAoC,CAAC"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * @aparte/plugin-cards/score — a tool's matches, drawn as a scoreboard: football,
3
+ * basketball, tennis, rugby, hockey, baseball, American football, any other sport.
4
+ *
5
+ * import { setupScoreCard } from '@aparte/plugin-cards/score';
6
+ *
7
+ * setupScoreCard({ tool: 'get_match', map: (result) => result.response });
8
+ *
9
+ * One match, a list of them (a round, a scoreboard), or `{ title, matches: [...] }`. Three
10
+ * variants of one card: `card` (the two teams facing each other, the score big between
11
+ * them, the state of play under it), `compact` (one row per match, the teams stacked with
12
+ * their scores at the end) and `detail` (the card's board, then the score by period and
13
+ * the match's events, each on the side of the team it belongs to).
14
+ *
15
+ * Several matches are always rows, whatever the variant: a scoreboard is read down a
16
+ * column, one glance per match, and a carousel would hide all but one score behind a swipe.
17
+ *
18
+ * Your tool keeps its own fields: `readMatches` reads the usual shapes — API-Football
19
+ * (`fixture`, `teams`, `goals`, the `FT`/`HT`/`1H`/`NS` codes), football-data.org
20
+ * (`homeTeam`, `score.fullTime`, `utcDate`, `goals`/`bookings`), ESPN (`competitions[0]
21
+ * .competitors[]` with `homeAway`, `linescores`, `status.type`), TheSportsDB (`strHomeTeam`,
22
+ * `intHomeScore`) and what a model writes by hand — and `map` says the rest.
23
+ */
24
+ import type { AparteConfig } from '@aparte/core';
25
+ import { type CardSetupOptions } from '../kit/define.js';
26
+ import { EVENT_GLYPHS, SPORT_GLYPHS } from './icons.js';
27
+ export type ScoreVariant = 'card' | 'compact' | 'detail';
28
+ /** Where a match stands. A status a tool writes that is none of these is kept as its text. */
29
+ export type MatchStatus = 'scheduled' | 'live' | 'halftime' | 'final' | 'postponed' | 'cancelled';
30
+ /** The sport, which picks the match's glyph and how its end is said ("Full time", "Final"). */
31
+ export type Sport = keyof typeof SPORT_GLYPHS;
32
+ export type MatchSide = 'home' | 'away';
33
+ export type MatchEventType = keyof typeof EVENT_GLYPHS;
34
+ /** A team as `map` returns it. Only `name` is needed. */
35
+ export interface TeamInput {
36
+ name: string;
37
+ /** "LOSC", "LIL" — the monogram's letters when there is no logo, and the name on a narrow row. */
38
+ shortName?: string;
39
+ /** An `http(s)` image; anything else is ignored and the monogram is drawn. */
40
+ logo?: string;
41
+ score?: number | string;
42
+ /** The team's record, as the sport writes it: "12-4", "W8 D2 L1". */
43
+ record?: string;
44
+ /** A CSS hex colour the team wears (`#c8102e`, `c8102e`, `#c12`) — anything else is ignored. */
45
+ color?: string;
46
+ }
47
+ /** One period of play: a quarter, a half, a set, an inning. */
48
+ export interface MatchPeriodInput {
49
+ label?: string;
50
+ home?: number | string;
51
+ away?: number | string;
52
+ }
53
+ export interface MatchEventInput {
54
+ /** "23'", "90+2'", or a number of minutes. */
55
+ minute?: string | number;
56
+ team: MatchSide;
57
+ type: MatchEventType;
58
+ player?: string;
59
+ /** A line beside the player: the assist, the player going off. */
60
+ detail?: string;
61
+ }
62
+ /** A match as `map` returns it. Only the two teams' names are needed. */
63
+ export interface MatchInput {
64
+ home: TeamInput | string;
65
+ away: TeamInput | string;
66
+ /** One of `MatchStatus`, a feed's code (`FT`, `HT`, `1H`, `NS`, `PST`, `IN_PLAY`…), or any text. */
67
+ status?: MatchStatus | string;
68
+ /** The match clock while it is live: "67'", "Q3 4:12". A number is minutes. */
69
+ clock?: string | number;
70
+ /** The period being played, when the clock does not already say it. */
71
+ period?: string | number;
72
+ /** Kick-off: an ISO string, an epoch, or a Date. */
73
+ startTime?: string | number | Date;
74
+ competition?: string;
75
+ /** Another name for `competition`. */
76
+ league?: string;
77
+ round?: string;
78
+ venue?: string;
79
+ sport?: Sport | string;
80
+ periods?: MatchPeriodInput[];
81
+ events?: MatchEventInput[];
82
+ /** Who won; worked out from a finished match's score when it is not given. */
83
+ winner?: MatchSide;
84
+ /** Where to watch it: "Canal+", or several. */
85
+ broadcast?: string | string[];
86
+ /** The match's own page. */
87
+ url?: string;
88
+ }
89
+ /** What `map` returns: one match, a scoreboard of them, or a scoreboard with a heading. */
90
+ export type ScoreInput = MatchInput | MatchInput[] | {
91
+ title?: string;
92
+ matches: MatchInput[];
93
+ };
94
+ /** A team as the card draws it, once read. */
95
+ export interface Team {
96
+ name: string;
97
+ shortName?: string;
98
+ logo?: string;
99
+ score?: number;
100
+ record?: string;
101
+ /** `#rrggbb`, always. */
102
+ color?: string;
103
+ }
104
+ export interface MatchPeriod {
105
+ label: string;
106
+ home?: number;
107
+ away?: number;
108
+ }
109
+ export interface MatchEvent {
110
+ /** As drawn: "23'", "90+2'". */
111
+ minute?: string;
112
+ team: MatchSide;
113
+ type: MatchEventType;
114
+ player?: string;
115
+ detail?: string;
116
+ }
117
+ /** A match as the card draws it, once read. */
118
+ export interface Match {
119
+ home: Team;
120
+ away: Team;
121
+ /** Absent when the tool said nothing, or something that is none of these (see `statusText`). */
122
+ status?: MatchStatus;
123
+ /** A status the card has no word for, as the tool wrote it: "Suspended", "Abandoned". */
124
+ statusText?: string;
125
+ clock?: string;
126
+ period?: string;
127
+ startTime?: Date;
128
+ competition?: string;
129
+ round?: string;
130
+ /** A league's matchday, said in the chat's language ("Matchday 12", "12e journée"). */
131
+ matchday?: number;
132
+ venue?: string;
133
+ sport: Sport;
134
+ periods?: MatchPeriod[];
135
+ events?: MatchEvent[];
136
+ winner?: MatchSide;
137
+ broadcast?: string;
138
+ url?: string;
139
+ }
140
+ /** The card's data: one match or several, and the scoreboard's title. */
141
+ export interface ScoreData {
142
+ title?: string;
143
+ matches: Match[];
144
+ }
145
+ /** How an app plugs the score card in: the kit's options, and the clock "Today" is read against. */
146
+ export interface ScoreCardOptions extends CardSetupOptions<ScoreData, ScoreVariant> {
147
+ /** The time "Today" and "Tomorrow" are worked out from (the device's clock by default). */
148
+ now?: () => Date;
149
+ }
150
+ /** `#c8102e`, `c8102e` (ESPN writes it bare) or `#c12` → `#c8102e`; anything else is no colour. */
151
+ export declare function teamColor(value: unknown): string | undefined;
152
+ /**
153
+ * The card's data from the loose shape `map` returns — one match, a list, or an envelope
154
+ * (`matches`, `fixtures`, `games`, `events` of matches, API-Football's `response`) with its
155
+ * `title` (or the competition's name) — or `null` when not one match has two named teams.
156
+ * At most twelve are kept.
157
+ */
158
+ export declare function readMatches(raw: unknown): ScoreData | null;
159
+ /**
160
+ * A team's letters for its monogram: its short name when it is one (`PSG`), else the
161
+ * initials of its name's words (`Paris Saint-Germain` → `PSG`, `Real Madrid` → `RM`), else
162
+ * the first three letters of its one word (`Lille` → `LIL`), as broadcasters write them.
163
+ * A club's `FC` or `AC` is not a word of its name. Two characters for Chinese, Japanese and
164
+ * Korean names, which a disc fits.
165
+ */
166
+ export declare function monogram(team: Pick<Team, 'name' | 'shortName'>): string;
167
+ /** Light or dark text on a team's colour: whichever reads better, by the colour's luminance. */
168
+ export declare function inkOn(hex: string): 'light' | 'dark';
169
+ /**
170
+ * The score card on the device's clock — for `scoreCard.segment(data)`. Plug it in with
171
+ * `setupScoreCard`, which also hears the card's own option.
172
+ */
173
+ export declare const scoreCard: {
174
+ name: string;
175
+ variants: readonly ScoreVariant[];
176
+ setup: (options?: CardSetupOptions<ScoreData, ScoreVariant>) => () => void;
177
+ segment: (raw: unknown, options?: {
178
+ id?: string;
179
+ variant?: ScoreVariant | undefined;
180
+ config?: AparteConfig;
181
+ }) => import("../index.js").AparteCardSegment;
182
+ };
183
+ /** Draw a tool's matches as this card — returns the function that stops it. */
184
+ export declare function setupScoreCard(options?: ScoreCardOptions): () => void;
185
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/score/index.ts"],"names":[],"mappings":"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"}