@heyramzi/ui 3.3.1 → 3.5.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 (273) hide show
  1. package/COMPONENTS.md +1 -0
  2. package/dist/components/copy/clipboard.js +3 -3
  3. package/dist/components/docs/index.js +1 -0
  4. package/dist/components/flag/codes.js +13 -0
  5. package/dist/components/flag/index.js +2 -0
  6. package/dist/slides/export/pdf.js +7 -0
  7. package/dist/vite/flags.js +46 -0
  8. package/package.json +7 -2
  9. package/src/charts/chart-plot.svelte +21 -18
  10. package/src/components/copy/clipboard.test.ts +17 -1
  11. package/src/components/copy/clipboard.ts +3 -3
  12. package/src/components/docs/document-view.svelte +232 -0
  13. package/src/components/docs/document-view.svelte.d.ts +29 -0
  14. package/src/components/docs/index.ts +1 -0
  15. package/src/components/flag/codes.test.ts +30 -0
  16. package/src/components/flag/codes.ts +25 -0
  17. package/src/components/flag/flag.svelte +51 -0
  18. package/src/components/flag/flag.svelte.d.ts +12 -0
  19. package/src/components/flag/flags/AD.svg +1 -0
  20. package/src/components/flag/flags/AE.svg +1 -0
  21. package/src/components/flag/flags/AF.svg +1 -0
  22. package/src/components/flag/flags/AG.svg +1 -0
  23. package/src/components/flag/flags/AI.svg +1 -0
  24. package/src/components/flag/flags/AL.svg +1 -0
  25. package/src/components/flag/flags/AM.svg +1 -0
  26. package/src/components/flag/flags/AO.svg +1 -0
  27. package/src/components/flag/flags/AQ.svg +1 -0
  28. package/src/components/flag/flags/AR.svg +1 -0
  29. package/src/components/flag/flags/AS.svg +1 -0
  30. package/src/components/flag/flags/AT.svg +1 -0
  31. package/src/components/flag/flags/AU.svg +1 -0
  32. package/src/components/flag/flags/AW.svg +1 -0
  33. package/src/components/flag/flags/AX.svg +1 -0
  34. package/src/components/flag/flags/AZ.svg +1 -0
  35. package/src/components/flag/flags/BA.svg +1 -0
  36. package/src/components/flag/flags/BB.svg +1 -0
  37. package/src/components/flag/flags/BD.svg +1 -0
  38. package/src/components/flag/flags/BE.svg +1 -0
  39. package/src/components/flag/flags/BF.svg +1 -0
  40. package/src/components/flag/flags/BG.svg +1 -0
  41. package/src/components/flag/flags/BH.svg +1 -0
  42. package/src/components/flag/flags/BI.svg +1 -0
  43. package/src/components/flag/flags/BJ.svg +1 -0
  44. package/src/components/flag/flags/BL.svg +1 -0
  45. package/src/components/flag/flags/BM.svg +1 -0
  46. package/src/components/flag/flags/BN.svg +1 -0
  47. package/src/components/flag/flags/BO.svg +1 -0
  48. package/src/components/flag/flags/BQ.svg +1 -0
  49. package/src/components/flag/flags/BR.svg +1 -0
  50. package/src/components/flag/flags/BS.svg +1 -0
  51. package/src/components/flag/flags/BT.svg +1 -0
  52. package/src/components/flag/flags/BV.svg +1 -0
  53. package/src/components/flag/flags/BW.svg +1 -0
  54. package/src/components/flag/flags/BY.svg +1 -0
  55. package/src/components/flag/flags/BZ.svg +1 -0
  56. package/src/components/flag/flags/CA.svg +1 -0
  57. package/src/components/flag/flags/CC.svg +1 -0
  58. package/src/components/flag/flags/CD.svg +1 -0
  59. package/src/components/flag/flags/CF.svg +1 -0
  60. package/src/components/flag/flags/CG.svg +1 -0
  61. package/src/components/flag/flags/CH.svg +1 -0
  62. package/src/components/flag/flags/CI.svg +1 -0
  63. package/src/components/flag/flags/CK.svg +1 -0
  64. package/src/components/flag/flags/CL.svg +1 -0
  65. package/src/components/flag/flags/CM.svg +1 -0
  66. package/src/components/flag/flags/CN.svg +1 -0
  67. package/src/components/flag/flags/CO.svg +1 -0
  68. package/src/components/flag/flags/CR.svg +1 -0
  69. package/src/components/flag/flags/CU.svg +1 -0
  70. package/src/components/flag/flags/CV.svg +1 -0
  71. package/src/components/flag/flags/CW.svg +1 -0
  72. package/src/components/flag/flags/CX.svg +1 -0
  73. package/src/components/flag/flags/CY.svg +1 -0
  74. package/src/components/flag/flags/CZ.svg +1 -0
  75. package/src/components/flag/flags/DE.svg +1 -0
  76. package/src/components/flag/flags/DJ.svg +1 -0
  77. package/src/components/flag/flags/DK.svg +1 -0
  78. package/src/components/flag/flags/DM.svg +1 -0
  79. package/src/components/flag/flags/DO.svg +1 -0
  80. package/src/components/flag/flags/DZ.svg +1 -0
  81. package/src/components/flag/flags/EC.svg +1 -0
  82. package/src/components/flag/flags/EE.svg +1 -0
  83. package/src/components/flag/flags/EG.svg +1 -0
  84. package/src/components/flag/flags/EH.svg +1 -0
  85. package/src/components/flag/flags/ER.svg +1 -0
  86. package/src/components/flag/flags/ES.svg +1 -0
  87. package/src/components/flag/flags/ET.svg +1 -0
  88. package/src/components/flag/flags/FI.svg +1 -0
  89. package/src/components/flag/flags/FJ.svg +1 -0
  90. package/src/components/flag/flags/FK.svg +1 -0
  91. package/src/components/flag/flags/FM.svg +1 -0
  92. package/src/components/flag/flags/FO.svg +1 -0
  93. package/src/components/flag/flags/FR.svg +1 -0
  94. package/src/components/flag/flags/GA.svg +1 -0
  95. package/src/components/flag/flags/GB.svg +1 -0
  96. package/src/components/flag/flags/GD.svg +1 -0
  97. package/src/components/flag/flags/GE.svg +1 -0
  98. package/src/components/flag/flags/GF.svg +1 -0
  99. package/src/components/flag/flags/GG.svg +1 -0
  100. package/src/components/flag/flags/GH.svg +1 -0
  101. package/src/components/flag/flags/GI.svg +1 -0
  102. package/src/components/flag/flags/GL.svg +1 -0
  103. package/src/components/flag/flags/GM.svg +1 -0
  104. package/src/components/flag/flags/GN.svg +1 -0
  105. package/src/components/flag/flags/GP.svg +1 -0
  106. package/src/components/flag/flags/GQ.svg +1 -0
  107. package/src/components/flag/flags/GR.svg +1 -0
  108. package/src/components/flag/flags/GS.svg +1 -0
  109. package/src/components/flag/flags/GT.svg +1 -0
  110. package/src/components/flag/flags/GU.svg +1 -0
  111. package/src/components/flag/flags/GW.svg +1 -0
  112. package/src/components/flag/flags/GY.svg +1 -0
  113. package/src/components/flag/flags/HK.svg +1 -0
  114. package/src/components/flag/flags/HM.svg +1 -0
  115. package/src/components/flag/flags/HN.svg +1 -0
  116. package/src/components/flag/flags/HR.svg +1 -0
  117. package/src/components/flag/flags/HT.svg +1 -0
  118. package/src/components/flag/flags/HU.svg +1 -0
  119. package/src/components/flag/flags/ID.svg +1 -0
  120. package/src/components/flag/flags/IE.svg +1 -0
  121. package/src/components/flag/flags/IL.svg +1 -0
  122. package/src/components/flag/flags/IM.svg +1 -0
  123. package/src/components/flag/flags/IN.svg +1 -0
  124. package/src/components/flag/flags/IO.svg +1 -0
  125. package/src/components/flag/flags/IQ.svg +1 -0
  126. package/src/components/flag/flags/IR.svg +1 -0
  127. package/src/components/flag/flags/IS.svg +1 -0
  128. package/src/components/flag/flags/IT.svg +1 -0
  129. package/src/components/flag/flags/JE.svg +1 -0
  130. package/src/components/flag/flags/JM.svg +1 -0
  131. package/src/components/flag/flags/JO.svg +1 -0
  132. package/src/components/flag/flags/JP.svg +1 -0
  133. package/src/components/flag/flags/KE.svg +1 -0
  134. package/src/components/flag/flags/KG.svg +1 -0
  135. package/src/components/flag/flags/KH.svg +1 -0
  136. package/src/components/flag/flags/KI.svg +1 -0
  137. package/src/components/flag/flags/KM.svg +1 -0
  138. package/src/components/flag/flags/KN.svg +1 -0
  139. package/src/components/flag/flags/KP.svg +1 -0
  140. package/src/components/flag/flags/KR.svg +1 -0
  141. package/src/components/flag/flags/KW.svg +1 -0
  142. package/src/components/flag/flags/KY.svg +1 -0
  143. package/src/components/flag/flags/KZ.svg +1 -0
  144. package/src/components/flag/flags/LA.svg +1 -0
  145. package/src/components/flag/flags/LB.svg +1 -0
  146. package/src/components/flag/flags/LC.svg +1 -0
  147. package/src/components/flag/flags/LI.svg +1 -0
  148. package/src/components/flag/flags/LICENSE +22 -0
  149. package/src/components/flag/flags/LK.svg +1 -0
  150. package/src/components/flag/flags/LR.svg +1 -0
  151. package/src/components/flag/flags/LS.svg +1 -0
  152. package/src/components/flag/flags/LT.svg +1 -0
  153. package/src/components/flag/flags/LU.svg +1 -0
  154. package/src/components/flag/flags/LV.svg +1 -0
  155. package/src/components/flag/flags/LY.svg +1 -0
  156. package/src/components/flag/flags/MA.svg +1 -0
  157. package/src/components/flag/flags/MC.svg +1 -0
  158. package/src/components/flag/flags/MD.svg +1 -0
  159. package/src/components/flag/flags/ME.svg +1 -0
  160. package/src/components/flag/flags/MF.svg +1 -0
  161. package/src/components/flag/flags/MG.svg +1 -0
  162. package/src/components/flag/flags/MH.svg +1 -0
  163. package/src/components/flag/flags/MK.svg +1 -0
  164. package/src/components/flag/flags/ML.svg +1 -0
  165. package/src/components/flag/flags/MM.svg +1 -0
  166. package/src/components/flag/flags/MN.svg +1 -0
  167. package/src/components/flag/flags/MO.svg +1 -0
  168. package/src/components/flag/flags/MP.svg +1 -0
  169. package/src/components/flag/flags/MQ.svg +1 -0
  170. package/src/components/flag/flags/MR.svg +1 -0
  171. package/src/components/flag/flags/MS.svg +1 -0
  172. package/src/components/flag/flags/MT.svg +1 -0
  173. package/src/components/flag/flags/MU.svg +1 -0
  174. package/src/components/flag/flags/MV.svg +1 -0
  175. package/src/components/flag/flags/MW.svg +1 -0
  176. package/src/components/flag/flags/MX.svg +1 -0
  177. package/src/components/flag/flags/MY.svg +1 -0
  178. package/src/components/flag/flags/MZ.svg +1 -0
  179. package/src/components/flag/flags/NA.svg +1 -0
  180. package/src/components/flag/flags/NC.svg +1 -0
  181. package/src/components/flag/flags/NE.svg +1 -0
  182. package/src/components/flag/flags/NF.svg +1 -0
  183. package/src/components/flag/flags/NG.svg +1 -0
  184. package/src/components/flag/flags/NI.svg +1 -0
  185. package/src/components/flag/flags/NL.svg +1 -0
  186. package/src/components/flag/flags/NO.svg +1 -0
  187. package/src/components/flag/flags/NP.svg +1 -0
  188. package/src/components/flag/flags/NR.svg +1 -0
  189. package/src/components/flag/flags/NU.svg +1 -0
  190. package/src/components/flag/flags/NZ.svg +1 -0
  191. package/src/components/flag/flags/OM.svg +1 -0
  192. package/src/components/flag/flags/PA.svg +1 -0
  193. package/src/components/flag/flags/PE.svg +1 -0
  194. package/src/components/flag/flags/PF.svg +1 -0
  195. package/src/components/flag/flags/PG.svg +1 -0
  196. package/src/components/flag/flags/PH.svg +1 -0
  197. package/src/components/flag/flags/PK.svg +1 -0
  198. package/src/components/flag/flags/PL.svg +1 -0
  199. package/src/components/flag/flags/PM.svg +1 -0
  200. package/src/components/flag/flags/PN.svg +1 -0
  201. package/src/components/flag/flags/PR.svg +1 -0
  202. package/src/components/flag/flags/PS.svg +1 -0
  203. package/src/components/flag/flags/PT.svg +1 -0
  204. package/src/components/flag/flags/PW.svg +1 -0
  205. package/src/components/flag/flags/PY.svg +1 -0
  206. package/src/components/flag/flags/QA.svg +1 -0
  207. package/src/components/flag/flags/RE.svg +1 -0
  208. package/src/components/flag/flags/RO.svg +1 -0
  209. package/src/components/flag/flags/RS.svg +1 -0
  210. package/src/components/flag/flags/RU.svg +1 -0
  211. package/src/components/flag/flags/RW.svg +1 -0
  212. package/src/components/flag/flags/SA.svg +1 -0
  213. package/src/components/flag/flags/SB.svg +1 -0
  214. package/src/components/flag/flags/SC.svg +1 -0
  215. package/src/components/flag/flags/SD.svg +1 -0
  216. package/src/components/flag/flags/SE.svg +1 -0
  217. package/src/components/flag/flags/SG.svg +1 -0
  218. package/src/components/flag/flags/SH.svg +1 -0
  219. package/src/components/flag/flags/SI.svg +1 -0
  220. package/src/components/flag/flags/SJ.svg +1 -0
  221. package/src/components/flag/flags/SK.svg +1 -0
  222. package/src/components/flag/flags/SL.svg +1 -0
  223. package/src/components/flag/flags/SM.svg +1 -0
  224. package/src/components/flag/flags/SN.svg +1 -0
  225. package/src/components/flag/flags/SO.svg +1 -0
  226. package/src/components/flag/flags/SR.svg +1 -0
  227. package/src/components/flag/flags/SS.svg +1 -0
  228. package/src/components/flag/flags/ST.svg +1 -0
  229. package/src/components/flag/flags/SV.svg +1 -0
  230. package/src/components/flag/flags/SX.svg +1 -0
  231. package/src/components/flag/flags/SY.svg +1 -0
  232. package/src/components/flag/flags/SZ.svg +1 -0
  233. package/src/components/flag/flags/TC.svg +1 -0
  234. package/src/components/flag/flags/TD.svg +1 -0
  235. package/src/components/flag/flags/TF.svg +1 -0
  236. package/src/components/flag/flags/TG.svg +1 -0
  237. package/src/components/flag/flags/TH.svg +1 -0
  238. package/src/components/flag/flags/TJ.svg +1 -0
  239. package/src/components/flag/flags/TK.svg +1 -0
  240. package/src/components/flag/flags/TL.svg +1 -0
  241. package/src/components/flag/flags/TM.svg +1 -0
  242. package/src/components/flag/flags/TN.svg +1 -0
  243. package/src/components/flag/flags/TO.svg +1 -0
  244. package/src/components/flag/flags/TR.svg +1 -0
  245. package/src/components/flag/flags/TT.svg +1 -0
  246. package/src/components/flag/flags/TV.svg +1 -0
  247. package/src/components/flag/flags/TW.svg +1 -0
  248. package/src/components/flag/flags/TZ.svg +1 -0
  249. package/src/components/flag/flags/UA.svg +1 -0
  250. package/src/components/flag/flags/UG.svg +1 -0
  251. package/src/components/flag/flags/UM.svg +1 -0
  252. package/src/components/flag/flags/US.svg +1 -0
  253. package/src/components/flag/flags/UY.svg +1 -0
  254. package/src/components/flag/flags/UZ.svg +1 -0
  255. package/src/components/flag/flags/VA.svg +1 -0
  256. package/src/components/flag/flags/VC.svg +1 -0
  257. package/src/components/flag/flags/VE.svg +1 -0
  258. package/src/components/flag/flags/VG.svg +1 -0
  259. package/src/components/flag/flags/VI.svg +1 -0
  260. package/src/components/flag/flags/VN.svg +1 -0
  261. package/src/components/flag/flags/VU.svg +1 -0
  262. package/src/components/flag/flags/WF.svg +1 -0
  263. package/src/components/flag/flags/WS.svg +1 -0
  264. package/src/components/flag/flags/XK.svg +1 -0
  265. package/src/components/flag/flags/YE.svg +1 -0
  266. package/src/components/flag/flags/YT.svg +1 -0
  267. package/src/components/flag/flags/ZA.svg +1 -0
  268. package/src/components/flag/flags/ZM.svg +1 -0
  269. package/src/components/flag/flags/ZW.svg +1 -0
  270. package/src/components/flag/index.ts +2 -0
  271. package/src/slides/export/pdf.ts +11 -0
  272. package/src/vite/flags.test.ts +38 -0
  273. package/src/vite/flags.ts +63 -0
package/COMPONENTS.md CHANGED
@@ -63,6 +63,7 @@ Generic pieces built on the primitives, imported from `@heyramzi/ui/components/<
63
63
 
64
64
  - `copy` — CopyButton, copyText, copyRichText, htmlToPlainText, Copied
65
65
  - `data-table` — SortableHead, Pagination, InfiniteScroll, RowContextMenu, TableSkeleton, and the sort, page and filter helpers
66
+ - `flag` — Flag, FLAG_CODES, flagCode: a country flag from an ISO 3166-1 alpha-2 code (plus XK for Kosovo), 1em tall at 3:2, one lazy `<img>` of `/flags/<CODE>.svg` each. Add `flagAssets()` from `@heyramzi/ui/vite` to the app's Vite plugins to serve the 250 SVGs (country-flag-icons, MIT, licence beside them). Decorative unless `label` is set. An unknown code renders nothing; to keep a row's layout, wrap it in `<span class="inline-block aspect-[3/2] h-[1em]">`
66
67
  - `hold-to-confirm` — HoldToConfirm, press and hold to confirm
67
68
  - `language-flag` — LanguageFlag, LANGUAGE_FLAGS (code to `/flags/*.svg`, also a SegmentedFilter option's `image`)
68
69
  - `list-page` — ListPageShell, MasterDetail, GroupedList, RailRow and the rest of the master-detail page kit, DetailPanel, ToolbarSearch, toolbarTriggerClass, NoticeBar, EmptyState, FilterPopover, setSectionHost
@@ -32,7 +32,7 @@ export function htmlToPlainText(html) {
32
32
  if (typeof document === "undefined") {
33
33
  return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/li>/gi, "\n").replace(/<li>/gi, "• ").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").trim();
34
34
  }
35
- const div = document.createElement("div");
36
- div.innerHTML = html;
37
- return div.textContent || div.innerText || "";
35
+ // A DOMParser document has no browsing context, so nothing in the markup loads or runs.
36
+ // An element from document.createElement belongs to the live page: an <img onerror> fires.
37
+ return new DOMParser().parseFromString(html, "text/html").body.textContent ?? "";
38
38
  }
@@ -3,5 +3,6 @@
3
3
  * A surface supplies a title and a body, and never lays a document out itself.
4
4
  */
5
5
  export { default as DocPage } from "../../../src/components/docs/doc-page.svelte";
6
+ export { default as DocumentView } from "../../../src/components/docs/document-view.svelte";
6
7
  export { default as OnThisPage } from "../../../src/components/docs/on-this-page.svelte";
7
8
  export { ScrollSpy, READ_LINE } from "./scroll-spy.svelte.js";
@@ -0,0 +1,13 @@
1
+ /** Where the host serves the flag files. `flagAssets()` from `@heyramzi/ui/vite` serves them there. */
2
+ export const FLAG_BASE = "/flags";
3
+ /**
4
+ * The 249 ISO 3166-1 alpha-2 codes plus XK (Kosovo, user-assigned but in common use), each with
5
+ * a file in `./flags`, from country-flag-icons 1.6.20 (MIT).
6
+ */
7
+ export const FLAG_CODES = ("AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR " + "BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ " + "EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW " + "GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY " + "KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV " + "MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY " + "QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG " + "TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS XK YE YT ZA ZM ZW").split(" ");
8
+ const KNOWN = new Set(FLAG_CODES);
9
+ /** The code in capitals when a flag exists for it, else null. */
10
+ export function flagCode(code) {
11
+ const upper = code?.trim().toUpperCase() ?? "";
12
+ return KNOWN.has(upper) ? upper : null;
13
+ }
@@ -0,0 +1,2 @@
1
+ export { default as Flag } from "../../../src/components/flag/flag.svelte";
2
+ export { FLAG_BASE, FLAG_CODES, flagCode } from "./codes.js";
@@ -38,6 +38,13 @@ export async function printPage(page, options = {}) {
38
38
  }
39
39
  await page.evaluate(() => document.fonts.ready.then(() => undefined));
40
40
  if (options.settle) await page.waitForTimeout(options.settle);
41
+ // WHY: a lazy or still-loading image read as broken while the PDF printed it (testimonial photos, 11 Nov 2026).
42
+ await page.evaluate(() => Promise.all([...document.images].map((img) => {
43
+ img.loading = "eager";
44
+ if (img.complete) return undefined;
45
+ const loaded = img.decode().catch(() => undefined);
46
+ return Promise.race([loaded, new Promise((resolve) => setTimeout(resolve, 15e3))]);
47
+ })).then(() => undefined));
41
48
  const brokenImages = await page.evaluate(() => [...document.images].filter((img) => !img.complete || img.naturalWidth === 0).map((img) => img.src));
42
49
  const { size } = options;
43
50
  const pdf = await page.pdf({
@@ -0,0 +1,46 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ // From src/vite in the repo, or dist/vite in the published package.
5
+ const here = dirname(fileURLToPath(import.meta.url));
6
+ const FLAGS_DIR = [join(here, "../components/flag/flags"), join(here, "../../src/components/flag/flags")].find((dir) => existsSync(dir));
7
+ /**
8
+ * Serves the `Flag` SVGs at `/flags/<CODE>.svg`: from the package in dev and preview, and as
9
+ * one static file each in the client build. Add it to `plugins` in `vite.config.ts`.
10
+ */
11
+ export function flagAssets(options = {}) {
12
+ const base = (options.base ?? "/flags").replace(/\/$/, "");
13
+ if (!FLAGS_DIR) throw new Error("@heyramzi/ui: the flag SVGs are missing from the package");
14
+ const dir = FLAGS_DIR;
15
+ const files = readdirSync(dir).filter((name) => /^[A-Z]{2}\.svg$/.test(name));
16
+ const known = new Set(files);
17
+ let ssrBuild = false;
18
+ // Returns nothing: Vite calls a function returned from configureServer as a post hook.
19
+ const serve = (server) => {
20
+ server.middlewares.use((req, res, next) => {
21
+ const path = req.url?.split("?")[0] ?? "";
22
+ const name = path.startsWith(`${base}/`) ? path.slice(base.length + 1) : "";
23
+ if (!known.has(name)) return next();
24
+ res.setHeader("Content-Type", "image/svg+xml");
25
+ res.setHeader("Cache-Control", "public, max-age=86400");
26
+ res.end(readFileSync(join(dir, name)));
27
+ });
28
+ };
29
+ return {
30
+ name: "heyramzi-ui:flags",
31
+ configResolved(config) {
32
+ ssrBuild = Boolean(config.build.ssr);
33
+ },
34
+ configureServer: serve,
35
+ configurePreviewServer: serve,
36
+ generateBundle() {
37
+ const consumer = this.environment?.config?.consumer;
38
+ if (consumer ? consumer !== "client" : ssrBuild) return;
39
+ for (const name of files) this.emitFile({
40
+ type: "asset",
41
+ fileName: `${base.replace(/^\//, "")}/${name}`,
42
+ source: readFileSync(join(dir, name))
43
+ });
44
+ }
45
+ };
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyramzi/ui",
3
- "version": "3.3.1",
3
+ "version": "3.5.0",
4
4
  "description": "Svelte 5 UI for apps: primitives, effects and animations, plus work (tasks and docs), charts, slides, an editor, onboarding and a canvas as subpaths.",
5
5
  "homepage": "https://ui.heyramzi.com",
6
6
  "license": "MIT",
@@ -115,6 +115,11 @@
115
115
  "node": "./dist/effects/magnetic/magnetic.js",
116
116
  "default": "./src/effects/magnetic/magnetic.ts"
117
117
  },
118
+ "./vite": {
119
+ "types": "./src/vite/flags.ts",
120
+ "node": "./dist/vite/flags.js",
121
+ "default": "./src/vite/flags.ts"
122
+ },
118
123
  "./icons/sf-symbols": {
119
124
  "svelte": "./src/icons/sf-symbols.ts",
120
125
  "types": "./src/icons/sf-symbols.ts",
@@ -389,6 +394,6 @@
389
394
  "sync": "pnpm sync:transitions && pnpm sync:shadcn",
390
395
  "sync:check": "node scripts/sync-transitions.mjs --check && node scripts/sync-shadcn.mjs --check",
391
396
  "check": "svelte-check --tsconfig ./tsconfig.json",
392
- "build": "node ../../../vibe-kit/scripts/build-dist.mjs"
397
+ "build": "node ../../../upsys/kit/scripts/build-dist.mjs"
393
398
  }
394
399
  }
@@ -82,25 +82,28 @@
82
82
  {#if count > 0}
83
83
  <p class="hr-sr-only" aria-live="polite">{announce}</p>
84
84
  {/if}
85
- <table class="hr-sr-only">
86
- <caption>{label}</caption>
87
- <thead>
88
- <tr>
89
- <th scope="col">{table.xLabel}</th>
90
- {#each table.columns as column, j (j)}
91
- <th scope="col">{column}</th>
92
- {/each}
93
- </tr>
94
- </thead>
95
- <tbody>
96
- {#each table.rows as row, i (i)}
85
+ <!-- A table ignores width: 1px and spills past the page, so the clipping box is a div. -->
86
+ <div class="hr-sr-only">
87
+ <table>
88
+ <caption>{label}</caption>
89
+ <thead>
97
90
  <tr>
98
- <th scope="row">{row.x}</th>
99
- {#each row.cells as cell, j (j)}
100
- <td>{cell}</td>
91
+ <th scope="col">{table.xLabel}</th>
92
+ {#each table.columns as column, j (j)}
93
+ <th scope="col">{column}</th>
101
94
  {/each}
102
95
  </tr>
103
- {/each}
104
- </tbody>
105
- </table>
96
+ </thead>
97
+ <tbody>
98
+ {#each table.rows as row, i (i)}
99
+ <tr>
100
+ <th scope="row">{row.x}</th>
101
+ {#each row.cells as cell, j (j)}
102
+ <td>{cell}</td>
103
+ {/each}
104
+ </tr>
105
+ {/each}
106
+ </tbody>
107
+ </table>
108
+ </div>
106
109
  {/if}
@@ -1,5 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
2
- import { copyRichText, copyText } from "./clipboard.js";
2
+ import { copyRichText, copyText, htmlToPlainText } from "./clipboard.js";
3
3
 
4
4
  // Mock navigator.clipboard
5
5
  const mockClipboard = {
@@ -204,3 +204,19 @@ describe("htmlToPlainText (server-side)", () => {
204
204
  expect(mockWriteText).toHaveBeenCalledWith("Hello World & Friends <3>");
205
205
  });
206
206
  });
207
+
208
+ describe("htmlToPlainText (browser)", () => {
209
+ afterEach(() => vi.restoreAllMocks());
210
+
211
+ it("parses into an inert document, so an onerror in the markup never runs", async () => {
212
+ const win = window as Window & { __fired?: boolean };
213
+ win.__fired = false;
214
+ const createElement = vi.spyOn(document, "createElement");
215
+ expect(htmlToPlainText('<img src="x" onerror="window.__fired=true">hi')).toBe("hi");
216
+ await new Promise((done) => setTimeout(done, 10));
217
+ expect(win.__fired).toBe(false);
218
+ // jsdom loads no images, so the onerror can't fire here either way: the live-document
219
+ // element the old code built is what a real browser runs the handler on.
220
+ expect(createElement).not.toHaveBeenCalled();
221
+ });
222
+ });
@@ -47,7 +47,7 @@ export function htmlToPlainText(html: string): string {
47
47
  .replace(/&gt;/g, ">")
48
48
  .trim();
49
49
  }
50
- const div = document.createElement("div");
51
- div.innerHTML = html;
52
- return div.textContent || div.innerText || "";
50
+ // A DOMParser document has no browsing context, so nothing in the markup loads or runs.
51
+ // An element from document.createElement belongs to the live page: an <img onerror> fires.
52
+ return new DOMParser().parseFromString(html, "text/html").body.textContent ?? "";
53
53
  }
@@ -0,0 +1,232 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Component, Snippet } from "svelte";
6
+ import { untrack } from "svelte";
7
+ import DocPage from "./doc-page.svelte";
8
+ import type { DocHeading } from "./scroll-spy.svelte.js";
9
+
10
+ // =========================
11
+ // TYPES
12
+ // =========================
13
+ interface Props {
14
+ title: string;
15
+ /** Breadcrumb, as a reader would say it: "Marketing / Reports". */
16
+ section?: string;
17
+ /** Already worded and localised: "Updated 30 Jul 2026". */
18
+ updated?: string;
19
+ icon?: Component<{
20
+ class?: string;
21
+ weight?: "bold";
22
+ "aria-hidden"?: boolean | "true" | "false";
23
+ }>;
24
+ actions?: Snippet;
25
+ /** The body's h2s, anchored by id, for the "On this page" rail. */
26
+ headings?: DocHeading[];
27
+ contentsLabel: string;
28
+ /** The rendered body. Trusted HTML from the consumer's own parser, never user input. */
29
+ html: string;
30
+ /** Changes when another document is shown: the pane returns to the top and the body re-enters. */
31
+ resetKey: string;
32
+ measure?: "prose" | "wide";
33
+ /** Runs on the body after every swap (icons, diagrams). Returns its cleanup. */
34
+ enhance?: (root: HTMLElement) => void | (() => void);
35
+ class?: string;
36
+ }
37
+
38
+ // =========================
39
+ // PROPS
40
+ // =========================
41
+ let {
42
+ title,
43
+ section,
44
+ updated,
45
+ icon,
46
+ actions,
47
+ headings = [],
48
+ contentsLabel,
49
+ html,
50
+ resetKey,
51
+ measure = "prose",
52
+ enhance,
53
+ class: className = "",
54
+ }: Props = $props();
55
+
56
+ // =========================
57
+ // STATE
58
+ // =========================
59
+ let body = $state<HTMLElement | null>(null);
60
+
61
+ // =========================
62
+ // EFFECTS
63
+ // =========================
64
+ $effect(() => {
65
+ void html;
66
+ const root = body;
67
+ if (!root || !enhance) return;
68
+ return untrack(() => enhance(root)) ?? undefined;
69
+ });
70
+ </script>
71
+
72
+ <!-- =========================== -->
73
+ <!-- MARKUP -->
74
+ <!-- =========================== -->
75
+ <!-- Inline, not framed: a link is an ordinary anchor, so the router swaps only this body. -->
76
+ <DocPage
77
+ {title}
78
+ {section}
79
+ {updated}
80
+ {icon}
81
+ {actions}
82
+ {headings}
83
+ {contentsLabel}
84
+ {resetKey}
85
+ {measure}
86
+ >
87
+ {#key resetKey}
88
+ <div bind:this={body} class="document-view t-enter {className}">
89
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- trusted, parsed server-side -->
90
+ {@html html}
91
+ </div>
92
+ {/key}
93
+ </DocPage>
94
+
95
+ <!-- =========================== -->
96
+ <!-- STYLES -->
97
+ <!-- =========================== -->
98
+ <!-- In the components layer at zero specificity, so a consumer's own document stylesheet wins without !important. -->
99
+ <style>
100
+ @layer theme, base, components, utilities;
101
+
102
+ @layer components {
103
+ :global {
104
+ :where(.document-view) {
105
+ color: var(--color-foreground);
106
+ font-size: 0.9375rem;
107
+ line-height: 1.65;
108
+ overflow-wrap: anywhere;
109
+ }
110
+ :where(.document-view) :where(h1, h2, h3, h4) {
111
+ font-family: var(--font-heading);
112
+ color: var(--color-foreground);
113
+ line-height: 1.25;
114
+ text-wrap: balance;
115
+ scroll-margin-top: 6rem;
116
+ }
117
+ :where(.document-view) :where(h1:first-child) {
118
+ display: none;
119
+ }
120
+ :where(.document-view) :where(h2) {
121
+ margin: 2.5rem 0 0.75rem;
122
+ font-size: 1.25rem;
123
+ font-weight: 700;
124
+ letter-spacing: -0.01em;
125
+ }
126
+ :where(.document-view) :where(h3) {
127
+ margin: 1.75rem 0 0.5rem;
128
+ font-size: 1rem;
129
+ font-weight: 600;
130
+ }
131
+ :where(.document-view) > :where(:first-child, h1:first-child + *) {
132
+ margin-top: 0;
133
+ }
134
+ :where(.document-view) :where(p, ul, ol, blockquote, pre, table) {
135
+ margin: 0 0 1rem;
136
+ }
137
+ :where(.document-view) :where(ul, ol) {
138
+ padding-left: 1.25rem;
139
+ }
140
+ :where(.document-view) :where(li + li) {
141
+ margin-top: 0.25rem;
142
+ }
143
+ :where(.document-view) :where(a) {
144
+ color: var(--color-brand);
145
+ text-decoration: underline;
146
+ text-decoration-color: color-mix(in oklch, currentColor 35%, transparent);
147
+ text-underline-offset: 0.16em;
148
+ transition: text-decoration-color var(--duration-fast) ease-out;
149
+ }
150
+ @media (hover: hover) and (pointer: fine) {
151
+ :where(.document-view) :where(a:hover) {
152
+ text-decoration-color: currentColor;
153
+ }
154
+ }
155
+ /* A wikilink to a page that does not exist yet reads as text, not as a dead link. */
156
+ :where(.document-view) :where(.wikilink.missing, a.missing) {
157
+ color: var(--color-muted-foreground);
158
+ text-decoration-style: dashed;
159
+ }
160
+ :where(.document-view) :where(code) {
161
+ font-family: var(--font-mono);
162
+ font-size: 0.85em;
163
+ padding: 0.1em 0.35em;
164
+ border-radius: var(--radius-sm, 0.375rem);
165
+ background: var(--color-muted);
166
+ }
167
+ :where(.document-view) :where(pre) {
168
+ overflow-x: auto;
169
+ padding: 0.875rem 1rem;
170
+ border: 1px solid var(--color-border);
171
+ border-radius: var(--radius-lg);
172
+ background: var(--color-muted);
173
+ font-size: 0.8125rem;
174
+ line-height: 1.55;
175
+ }
176
+ :where(.document-view) :where(pre code) {
177
+ padding: 0;
178
+ background: none;
179
+ font-size: inherit;
180
+ }
181
+ :where(.document-view) :where(blockquote) {
182
+ padding: 0.75rem 1rem;
183
+ border-radius: var(--radius-md);
184
+ background: var(--color-muted);
185
+ color: var(--color-muted-foreground);
186
+ }
187
+ :where(.document-view) :where(blockquote > :last-child) {
188
+ margin-bottom: 0;
189
+ }
190
+ :where(.document-view) :where(.callout) {
191
+ margin: 0 0 1rem;
192
+ padding: 0.75rem 1rem;
193
+ border: 1px solid var(--color-border);
194
+ border-radius: var(--radius-lg);
195
+ background: var(--color-card);
196
+ }
197
+ :where(.document-view) :where(table) {
198
+ display: block;
199
+ max-width: 100%;
200
+ overflow-x: auto;
201
+ border-collapse: collapse;
202
+ font-size: 0.875rem;
203
+ font-variant-numeric: tabular-nums;
204
+ }
205
+ :where(.document-view) :where(th, td) {
206
+ padding: 0.5rem 0.75rem;
207
+ border-bottom: 1px solid var(--color-border);
208
+ text-align: left;
209
+ vertical-align: top;
210
+ }
211
+ :where(.document-view) :where(th) {
212
+ font-weight: 600;
213
+ color: var(--color-muted-foreground);
214
+ }
215
+ :where(.document-view) :where(hr) {
216
+ margin: 2rem 0;
217
+ border: 0;
218
+ border-top: 1px solid var(--color-border);
219
+ }
220
+ :where(.document-view) :where(img) {
221
+ max-width: 100%;
222
+ height: auto;
223
+ border-radius: var(--radius-md);
224
+ }
225
+ @media (prefers-reduced-motion: reduce) {
226
+ :where(.document-view) :where(a) {
227
+ transition: none;
228
+ }
229
+ }
230
+ }
231
+ }
232
+ </style>
@@ -0,0 +1,29 @@
1
+ import type { Component, Snippet } from "svelte";
2
+ import type { DocHeading } from "./scroll-spy.svelte.js";
3
+ interface Props {
4
+ title: string;
5
+ /** Breadcrumb, as a reader would say it: "Marketing / Reports". */
6
+ section?: string;
7
+ /** Already worded and localised: "Updated 30 Jul 2026". */
8
+ updated?: string;
9
+ icon?: Component<{
10
+ class?: string;
11
+ weight?: "bold";
12
+ "aria-hidden"?: boolean | "true" | "false";
13
+ }>;
14
+ actions?: Snippet;
15
+ /** The body's h2s, anchored by id, for the "On this page" rail. */
16
+ headings?: DocHeading[];
17
+ contentsLabel: string;
18
+ /** The rendered body. Trusted HTML from the consumer's own parser, never user input. */
19
+ html: string;
20
+ /** Changes when another document is shown: the pane returns to the top and the body re-enters. */
21
+ resetKey: string;
22
+ measure?: "prose" | "wide";
23
+ /** Runs on the body after every swap (icons, diagrams). Returns its cleanup. */
24
+ enhance?: (root: HTMLElement) => void | (() => void);
25
+ class?: string;
26
+ }
27
+ declare const $$Component: Component<Props, {}, "">;
28
+ type $$Component = ReturnType<typeof $$Component>;
29
+ export default $$Component;
@@ -3,6 +3,7 @@
3
3
  * A surface supplies a title and a body, and never lays a document out itself.
4
4
  */
5
5
  export { default as DocPage } from "./doc-page.svelte";
6
+ export { default as DocumentView } from "./document-view.svelte";
6
7
  export { default as OnThisPage } from "./on-this-page.svelte";
7
8
  export {
8
9
  ScrollSpy,
@@ -0,0 +1,30 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it } from "vite-plus/test";
5
+ import { FLAG_CODES, flagCode } from "./codes.js";
6
+
7
+ const dir = join(dirname(fileURLToPath(import.meta.url)), "flags");
8
+
9
+ describe("flag codes", () => {
10
+ it("lists exactly the SVG files shipped", () => {
11
+ const files = readdirSync(dir)
12
+ .filter((name) => name.endsWith(".svg"))
13
+ .map((name) => name.slice(0, -4));
14
+ expect(FLAG_CODES.toSorted()).toEqual(files.toSorted());
15
+ expect(FLAG_CODES).toHaveLength(250);
16
+ });
17
+
18
+ it("knows Kosovo, which ISO 3166-1 leaves user-assigned", () => {
19
+ expect(flagCode("xk")).toBe("XK");
20
+ });
21
+
22
+ it("takes any case and refuses what it has no file for", () => {
23
+ expect(flagCode("jp")).toBe("JP");
24
+ expect(flagCode(" Pl ")).toBe("PL");
25
+ expect(flagCode("")).toBeNull();
26
+ expect(flagCode(undefined)).toBeNull();
27
+ expect(flagCode("ZZ")).toBeNull();
28
+ expect(flagCode("../FR")).toBeNull();
29
+ });
30
+ });
@@ -0,0 +1,25 @@
1
+ /** Where the host serves the flag files. `flagAssets()` from `@heyramzi/ui/vite` serves them there. */
2
+ export const FLAG_BASE = "/flags";
3
+
4
+ /**
5
+ * The 249 ISO 3166-1 alpha-2 codes plus XK (Kosovo, user-assigned but in common use), each with
6
+ * a file in `./flags`, from country-flag-icons 1.6.20 (MIT).
7
+ */
8
+ export const FLAG_CODES: readonly string[] = (
9
+ "AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR " +
10
+ "BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ " +
11
+ "EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW " +
12
+ "GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY " +
13
+ "KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV " +
14
+ "MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY " +
15
+ "QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG " +
16
+ "TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS XK YE YT ZA ZM ZW"
17
+ ).split(" ");
18
+
19
+ const KNOWN = new Set(FLAG_CODES);
20
+
21
+ /** The code in capitals when a flag exists for it, else null. */
22
+ export function flagCode(code: string | null | undefined): string | null {
23
+ const upper = code?.trim().toUpperCase() ?? "";
24
+ return KNOWN.has(upper) ? upper : null;
25
+ }
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { cn } from "../../lib/utils.js";
6
+ import { FLAG_BASE, flagCode } from "./codes.js";
7
+
8
+ // =========================
9
+ // PROPS
10
+ // =========================
11
+ interface Props {
12
+ /** ISO 3166-1 alpha-2 code, or XK for Kosovo, any case. Unknown or empty renders nothing. */
13
+ code: string | null | undefined;
14
+ /** Names the flag for a reader. Leave it out when the country name sits beside it. */
15
+ label?: string;
16
+ /** Where the host serves `<CODE>.svg`. */
17
+ base?: string;
18
+ class?: string;
19
+ }
20
+
21
+ let { code, label, base = FLAG_BASE, class: className }: Props = $props();
22
+
23
+ // =========================
24
+ // STATE
25
+ // =========================
26
+ let file = $derived(flagCode(code));
27
+ // A file the host doesn't serve hides the flag instead of showing a broken image.
28
+ let failedFor = $state<string | null>(null);
29
+ </script>
30
+
31
+ <!-- =========================== -->
32
+ <!-- MARKUP -->
33
+ <!-- =========================== -->
34
+ {#if file && failedFor !== file}
35
+ <img
36
+ src="{base}/{file}.svg"
37
+ alt={label ?? ""}
38
+ aria-hidden={label ? undefined : "true"}
39
+ width="3"
40
+ height="2"
41
+ loading="lazy"
42
+ decoding="async"
43
+ draggable="false"
44
+ onerror={() => (failedFor = file)}
45
+ class={cn(
46
+ "inline-block aspect-[3/2] h-[1em] w-auto max-w-none shrink-0 object-cover align-[-0.125em]",
47
+ "rounded-[min(var(--radius-sm),0.15em)] outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/15",
48
+ className,
49
+ )}
50
+ />
51
+ {/if}
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ /** ISO 3166-1 alpha-2 code, or XK for Kosovo, any case. Unknown or empty renders nothing. */
3
+ code: string | null | undefined;
4
+ /** Names the flag for a reader. Leave it out when the country name sits beside it. */
5
+ label?: string;
6
+ /** Where the host serves `<CODE>.svg`. */
7
+ base?: string;
8
+ class?: string;
9
+ }
10
+ declare const $$Component: import("svelte").Component<Props, {}, "">;
11
+ type $$Component = ReturnType<typeof $$Component>;
12
+ export default $$Component;
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 342"><path fill="#fedd00" d="M0 0h513v342H0z"/><path fill="#10069f" d="M0 0h171v342H0z"/><path fill="#d50032" d="M342 0h171v342H342z"/><path fill="#D80027" d="M198.9 113.6H256v64.8h-57.1z"/><g stroke="#D80027" stroke-width="7"><path d="M267.6 113.6v64.7M284.2 113.6v64.7M300.8 113.6v64.7"/></g><g stroke="#D80027" stroke-width="5"><path d="M247.4 178.4v64.7M234.1 178.4v64.7M220.8 178.5v53.9M207.1 178.5v47.1"/></g><path fill="#FFDA44" d="m199.9 146 27.6-32.4 28.5 32.3-28.1 32.4z"/><path fill="#BC8B00" d="M182.2 95.9v92.2c0 34.3 27.3 54.2 48.6 64.5-.8 1.4 25.1 8.3 25.1 8.3s25.9-6.9 25.1-8.3c21.4-10.3 48.6-30.1 48.6-64.5V95.9H182.2zm129.9 92.2c0 16.9-10 29.4-32.8 43.9-8.6 5.5-17.5 9-23.3 11-5.8-2-14.7-5.5-23.3-11-22.8-14.5-32.8-28-32.8-43.9v-74.5h112.2v74.5z"/><path fill="#D80027" d="M264.4 188.9h29.5v8.6h-29.5zM264.4 206.5h29.5v8.6h-29.5z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 342"><path fill="#FFF" d="M0 0h513v342H0z"/><path fill="#00843d" d="M0 0h513v114H0z"/><path d="M0 228h513v114H0z"/><path fill="#c8102e" d="M0 0h171v342H0z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 342"><path fill="#D32011" d="M0 0h513v342H0z"/><path fill="#007A36" d="M342 0h171v342H342z"/><path d="M0 0h171v342H0z"/><path fill="#FFF" d="M256.5 131.3c-17.5 0-31.8 14.3-31.8 31.8v47.7h63.6v-47.7c0-17.7-14.3-31.8-31.8-31.8z"/><path fill="#FFF" d="M256.5 93c-43.1 0-78 34.9-78 78s34.9 77.9 78 77.9 78-34.9 78-78-34.9-77.9-78-77.9zm0 142.4c-35.6 0-64.5-28.9-64.5-64.5s28.9-64.5 64.5-64.5 64.5 29 64.5 64.6c0 35.6-28.9 64.4-64.5 64.4z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 342"><path d="M0 0h513v342H0z"/><path fill="#0072c6" d="M88.8 136.5c-2.2 12.9-3.4 26.2-3.4 39.8 0 13.6 1.2 27 3.4 39.8L256 228.3l167.2-12.1c2.2-12.9 3.4-26.2 3.4-39.8s-1.2-27-3.4-39.8"/><path fill="#FFF" d="M423.2 219H88.8c15.8 69.8 84.7 122.3 167.2 122.3S407.4 288.8 423.2 219z"/><path fill="#FFDA44" d="M365.9 136.5H146.1l44.9-21.1-23.9-43.5 48.8 9.4L222 32l34 36.2L290 32l6.1 49.3 48.8-9.4-23.9 43.5z"/><g fill="#ce1126"><path d="M256.5 342 0 0v342z"/><path d="M513 342V0L256 342z"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 342"><path fill="#012169" d="M0 0h513v342H0z"/><path fill="#FFF" d="M454.8 265.38c7.94-10.93 13.24-24.27 13.24-40.42V104.89a59.754 59.754 0 0 1-36.02 12.01c-19.64 0-37.07-9.43-48.03-24.01-10.95 14.58-28.39 24.01-48.03 24.01-13.52 0-25.99-4.47-36.02-12v120.06c0 16.16 5.3 29.5 13.24 40.42H454.8z"/><path fill="#9cf" d="M310.23 260.98C332.65 296.96 384 309 384 309s51.35-12.04 73.77-48.02H310.23z"/><g fill="#f90"><path d="M396.66 172.21c.32 1.42 8.72 10.17 17.56 20.15 1.39 1.57-8.74 12.63-6.91 12.59 4.75-.12 19.27-17.26 19.34 6.65.05 15.94-30 27.51-30 27.51h17.47l.65 14.96s4.97-13.34 7.59-16.57c8.1-9.97 20.18-30.07 34.59-27.1 14.41 2.97-13.66-13.92-13.66-13.92s-5.2-15.19-16.63-16.97c-10.06-1.57-14.29-2.51-26.64-7.3-1.68-.65-3.88-2.32-3.36 0z"/><path d="M359.69 198.69c1.07-.99 4.46-12.63 8.69-25.28.67-1.99 15.31 1.27 14.35-.3-2.47-4.06-24.58-8.08-3.9-20.08 13.79-8 38.82 12.27 38.82 12.27l-8.72-15.14 12.64-8.03s-14.05 2.35-18.14 1.69c-12.68-2.04-36.13-2.48-40.75-16.45-4.62-13.97-5.25 18.79-5.25 18.79s-10.56 12.08-6.4 22.88c3.66 9.5 4.96 13.64 6.97 26.73.29 1.79-.06 4.53 1.69 2.92z"/><path d="M395.67 219.87c-1.39-.43-13.16 2.49-26.22 5.18-2.06.42-6.59-13.88-7.47-12.26-2.27 4.17 5.36 25.31-15.41 13.45-13.84-7.91-8.89-39.73-8.89-39.73l-8.71 15.14-13.29-6.9s9.09 10.96 10.57 14.84c4.6 11.99 16 32.49 6.24 43.49-9.76 11.01 18.88-4.9 18.88-4.9s15.76 3.07 23-5.96c6.38-7.94 9.3-11.14 19.61-19.45 1.43-1.12 3.97-2.19 1.69-2.9z"/></g><path fill="#012169" d="M0 0h256.5v171H0z"/><g fill="#FFF"><path d="M256.5 0v30.6l-45.3 25.2h45.3v59.4h-59.2l59.2 32.9V171h-26.7l-73.7-40.9V171h-55.7v-48.7L12.8 171H0v-30.6l45.3-25.2H0V55.8h59.2L0 22.9V0h26.7l73.7 40.9V0h55.7v48.7L243.7 0z"/><path d="M156.1 115.2 256.5 171v-15.8l-72-40zM100.4 55.8 0 0v15.8l72 40z"/></g><g fill="#D80027"><path d="M144.3 0h-32.1v69.5H0v32h112.2V171h32.1v-69.5h112.2v-32H144.3z"/><path d="M156.1 115.2 256.5 171v-15.8l-72-40zM72 115.2l-72 40V171l100.4-55.8zM100.4 55.8 0 0v15.8l72 40zM184.5 55.8l72-40V0L156.1 55.8z"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 60"><path fill="#ED2024" d="M0 0h90v60H0z"/><path fill="#212121" d="M32.5 12.2v16l7.1 2.5-5 4.9 2.4 3 5.3-5.5 1.2 2.3-3 4.9 3.6 5.6-1.8 2.6 2.8 3.6 2.7-3.7-1.5-2.6 3-5.8-2.7-4.6 1.2-2.3 5.2 5.5 2.5-2.9-5.2-5.1 7.2-2.6V12.2l-5.2 1.9-.1 3.8-3.3.3v-2.6l1.7-2.3 5.6-2.2-2.3-.4 1.4-1.2.9.4-.8-1.4L54 9l-1-1.1L47.5 9l1.4 1.1-3.8 4.9-3.8-4.8 1.3-1.1-5-1.2L36.2 9l-1.5-.4-.8 1.4.9-.6 1.6 1.1-2.3.5 5.5 2 1.6 2.3v2.9l-3.3-.3v-3.6z"/><g fill="#212121"><path d="M26.2 25h6.4v2.5h-6.4zM26.2 20.9h6.4v2.7h-6.4zM26.2 16.7h6.4v2.6h-6.4zM26.2 12.7h6.4v2.5h-6.4z"/></g><g fill="#212121"><path d="M57.4 25h6.4v2.5h-6.4zM57.4 20.9h6.4v2.7h-6.4zM57.4 16.7h6.4v2.6h-6.4zM57.4 12.7h6.4v2.5h-6.4z"/></g><path fill="#212121" d="M53.4 36.1h6.4V38h-6.4zM30.2 36.1h6.4V38h-6.4z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 342"><path fill="#0052B4" d="M0 0h513v342H0z"/><path fill="#D80027" d="M0 0h513v114H0z"/><path fill="#FF9811" d="M0 228h513v114H0z"/></svg>