diff --git a/.changeset/olive-rules-repeat.md b/.changeset/olive-rules-repeat.md new file mode 100644 index 000000000..d9865cbb2 --- /dev/null +++ b/.changeset/olive-rules-repeat.md @@ -0,0 +1,43 @@ +--- +"@hyperbook/markdown": minor +"hyperbook": minor +"hyperbook-studio": minor +--- + +Rework how bookmarks store their label, so bookmarks show what the page shows. + +A bookmark label used to be baked into an `onclick` attribute of the bookmark +button and rendered with `innerHTML`. Labels are now read from the rendered +heading when a bookmark is saved, and stored as parts instead of markup: + +```js +[{ text: "Getting started " }, { text: "🐧", emoji: "1f427" }]; +``` + +The bookmark list builds its entries from those parts with the DOM, so nothing +that was stored is parsed as HTML, and an emoji that is rendered as an image +stays an image in the bookmark list. Emojis are stored by id, never by URL, so +bookmarks survive a change of the `basePath`. + +This also fixes bookmarking a heading that contains a quote or a backslash. +Those characters ended up inside a JavaScript string in the `onclick` +attribute and made the button throw a syntax error. + +The bookmark indicator is no longer the 🔖 emoji. Both the button on a heading +and the marker in the bookmark list are drawn from the stylesheet with a mask, +so the control looks the same on every platform, takes the color of the +heading it belongs to in light and dark mode, and does not change when the +emoji style changes. A bookmarked heading now shows a filled icon instead of only a less +transparent one. + +Breaking: + +- `hyperbook.ui.toggleBookmark(key, label)` no longer takes a label: + `hyperbook.ui.toggleBookmark(key)`. The label comes from the heading. +- Bookmark buttons no longer carry an inline `onclick`. They are handled by a + delegated listener and carry `data-key`, `data-label`, `aria-label` and + `aria-pressed`. +- Bookmark buttons are empty. The icon comes from `.bookmark` in the + stylesheet, so a custom style that replaced the emoji has to be updated. +- The store moves to version 6. Labels of existing bookmarks are migrated to + the new shape, and a plain label is still rendered, so nothing is lost. diff --git a/.changeset/tricky-donuts-smile.md b/.changeset/tricky-donuts-smile.md new file mode 100644 index 000000000..817c950a8 --- /dev/null +++ b/.changeset/tricky-donuts-smile.md @@ -0,0 +1,10 @@ +--- +"@hyperbook/types": patch +"hyperbook": patch +"hyperbook-studio": patch +--- + +Fix `mailto:` and `tel:` links in markdown. `makeUrl` only passed through URLs +containing `://` or starting with `data:`, so `[Write us](mailto:a@b.c)` was +resolved against the base path and became `href="/mailto:a@b.c"`. Any URL with +a scheme is now passed through untouched. diff --git a/.changeset/wild-pugs-invite.md b/.changeset/wild-pugs-invite.md new file mode 100644 index 000000000..f57eeec6e --- /dev/null +++ b/.changeset/wild-pugs-invite.md @@ -0,0 +1,13 @@ +--- +"@hyperbook/markdown": patch +"@hyperbook/types": patch +"hyperbook": patch +"hyperbook-studio": patch +--- + +Add `elements.emoji.style`. Emojis are drawn by the reader's operating system, +so the same emoji looks different on Windows, macOS, Android and Linux. Setting +the style to `twemoji` replaces them with Twemoji images at build time, so a +hyperbook looks the same on every platform. This covers emojis in the content +as well as icons from the config, leaves code untouched, and only copies the +emojis a book actually uses into its output. The default stays `native`. diff --git a/packages/hyperbook/build.ts b/packages/hyperbook/build.ts index 4d9b0bbbc..5d28b4688 100644 --- a/packages/hyperbook/build.ts +++ b/packages/hyperbook/build.ts @@ -21,6 +21,7 @@ import { HyperbookPage, HyperbookSection, Navigation, + isExternalUrl, } from "@hyperbook/types"; import lunr from "lunr"; import { process as hyperbookProcess } from "@hyperbook/markdown"; @@ -100,6 +101,8 @@ export type PageResultSink = (href: string, result: SinglePageResult) => void; export interface SinglePageResult { searchDocuments: any[]; directives: string[]; + /** Twemoji file names used on this page, without the .svg extension. */ + emojis: string[]; /** Absolute paths whose contents were inlined into this page. */ dependencies: string[]; } @@ -134,6 +137,7 @@ export async function buildSingleBookPage( const result = await hyperbookProcess(file.markdown.content, ctx); const searchDocuments = [...(result.data.searchDocuments || [])]; const directives = Object.keys(result.data.directives || {}); + const emojis = [...(result.data.emojis || [])]; for (const generated of (result.data.generatedFiles as any[]) || []) { let genDir; @@ -182,7 +186,12 @@ export async function buildSingleBookPage( } await fs.writeFile(fileOut, result.value); - return { searchDocuments, directives, dependencies: [...dependencies] }; + return { + searchDocuments, + directives, + emojis, + dependencies: [...dependencies], + }; } export async function buildSingleGlossaryPage( @@ -232,6 +241,7 @@ export async function buildSingleGlossaryPage( const result = await hyperbookProcess(file.markdown.content, ctx); const searchDocuments = [...(result.data.searchDocuments || [])]; const directives = Object.keys(result.data.directives || {}); + const emojis = [...(result.data.emojis || [])]; for (const generated of (result.data.generatedFiles as any[]) || []) { let genDir; @@ -261,7 +271,12 @@ export async function buildSingleGlossaryPage( } await fs.writeFile(fileOut, result.value); - return { searchDocuments, directives, dependencies: [...dependencies] }; + return { + searchDocuments, + directives, + emojis, + dependencies: [...dependencies], + }; } /** @@ -456,7 +471,7 @@ export function makeBaseCtx( version: packageJson.version, makeUrl: (p, base, page, options = { versioned: true }) => { if (typeof p === "string") { - if (p.includes("://") || p.startsWith("data:")) { + if (isExternalUrl(p)) { return p; } if (p.endsWith(".md.hbs")) { @@ -561,6 +576,7 @@ async function runBuild( const baseCtx = makeBaseCtx(root, hyperbookJson, basePath, rootProject); const directives = new Set([]); + const emojis = new Set([]); const pagesAndSections = await hyperbook.getPagesAndSections(root); const pageList = hyperbook.getPageList( pagesAndSections.sections, @@ -589,6 +605,9 @@ async function runBuild( for (let directive of pageResult.directives) { directives.add(directive); } + for (let emoji of pageResult.emojis) { + emojis.add(emoji); + } onPage?.(file.path.href || file.path.absolute, pageResult); if (!process.env.CI) { @@ -626,6 +645,9 @@ async function runBuild( for (let directive of pageResult.directives) { directives.add(directive); } + for (let emoji of pageResult.emojis) { + emojis.add(emoji); + } onPage?.(file.path.href || file.path.absolute, pageResult); if (!process.env.CI) { @@ -839,12 +861,44 @@ async function runBuild( } process.stdout.write("\n"); + if (emojis.size > 0) { + const emojiPath = path.join(assetsPath, "emoji"); + const emojiOut = path.join(assetsOut, "emoji"); + await mkdir(emojiOut, { recursive: true }); + i = 1; + for (let emoji of emojis) { + if (!process.env.CI) { + readline.clearLine(process.stdout, 0); + readline.cursorTo(process.stdout, 0); + } + process.stdout.write( + `${chalk.blue(`[${prefix}]`)} Copying emojis: [${i++}/${emojis.size}]`, + ); + if (process.env.CI) { + process.stdout.write("\n"); + } + try { + await cp( + path.join(emojiPath, `${emoji}.svg`), + path.join(emojiOut, `${emoji}.svg`), + ); + } catch (e) { + process.stdout.write("\n"); + process.stdout.write( + `${chalk.red(`[${prefix}]`)} Failed copying emoji: ${emoji}`, + ); + process.stdout.write("\n"); + } + } + process.stdout.write("\n"); + } + const mainAssets = await fs.readdir(assetsPath); i = 1; for (let asset of mainAssets) { const assetPath = path.join(assetsPath, asset); const assetOut = path.join(assetsOut, asset); - if (!asset.startsWith("directive-")) { + if (!asset.startsWith("directive-") && asset !== "emoji") { await cp(assetPath, assetOut, { recursive: true, filter: (src) => { diff --git a/packages/hyperbook/incremental.ts b/packages/hyperbook/incremental.ts index db1a25729..48699e8ff 100644 --- a/packages/hyperbook/incremental.ts +++ b/packages/hyperbook/incremental.ts @@ -353,6 +353,7 @@ export class IncrementalBuilder { // Copy any new directive assets await this.copyDirectiveAssets(result.directives); + await this.copyEmojiAssets(result.emojis); const changedHref = changedFile.path.href || "/"; @@ -423,6 +424,7 @@ export class IncrementalBuilder { } await this.copyDirectiveAssets(result.directives); + await this.copyEmojiAssets(result.emojis); const changedHref = changedFile.path.href || "/glossary"; await this.refreshSearchIndex(); @@ -487,6 +489,7 @@ export class IncrementalBuilder { this.directives.add(directive); } await this.copyDirectiveAssets(result.directives); + await this.copyEmojiAssets(result.emojis); return href; } @@ -507,6 +510,7 @@ export class IncrementalBuilder { this.directives.add(directive); } await this.copyDirectiveAssets(result.directives); + await this.copyEmojiAssets(result.emojis); return href; } @@ -529,6 +533,27 @@ export class IncrementalBuilder { ); } + /** + * Emojis are copied one file at a time, so a book only ships the Twemoji + * images it actually uses. + */ + private async copyEmojiAssets(newEmojis: string[]): Promise { + if (newEmojis.length === 0) return; + const emojiPath = path.join(__dirname, "assets", "emoji"); + const emojiOut = path.join(this.assetsOut, "emoji"); + await fs.mkdir(emojiOut, { recursive: true }); + for (const emoji of newEmojis) { + try { + await cp( + path.join(emojiPath, `${emoji}.svg`), + path.join(emojiOut, `${emoji}.svg`), + ); + } catch { + // Emoji has no asset + } + } + } + private async copyDirectiveAssets(newDirectives: string[]): Promise { const assetsPath = path.join(__dirname, "assets"); for (const directive of newDirectives) { diff --git a/packages/markdown/assets/bootstrap.js b/packages/markdown/assets/bootstrap.js index f76016bd6..198848935 100644 --- a/packages/markdown/assets/bootstrap.js +++ b/packages/markdown/assets/bootstrap.js @@ -98,11 +98,25 @@ hyperbook.bootstrap = (function () { hyperbook.store.db.bookmarks.get(key).then((bookmark) => { if (bookmark) { bookmarkEl.classList.add("active"); + bookmarkEl.setAttribute("aria-pressed", "true"); } }); } } + /** + * Bookmark buttons are handled by one delegated listener, so headings that + * are added later work too and no heading has to carry its label in an + * inline script. + */ + document.addEventListener("click", (event) => { + const bookmarkEl = event.target.closest?.("button.bookmark"); + const key = bookmarkEl?.getAttribute("data-key"); + if (key) { + hyperbook.ui.toggleBookmark(key); + } + }); + /** * Initialize all hyperbook elements within a root. * @param {HTMLElement} root diff --git a/packages/markdown/assets/content.css b/packages/markdown/assets/content.css index 2cf1b2976..cda02636a 100644 --- a/packages/markdown/assets/content.css +++ b/packages/markdown/assets/content.css @@ -32,6 +32,16 @@ figure { max-width: 100%; } +img.emoji { + height: 1em; + width: 1em; + min-width: 1em; + margin: 0 0.05em 0 0.1em; + vertical-align: -0.1em; + display: inline-block; + max-width: none; +} + .hyperbook-markdown figure.align-left { display: table; float: left; @@ -321,12 +331,33 @@ figure { align-items: center; } +/* The bookmark icon is drawn from the stylesheet, so it looks the same on + every platform and takes the color of its heading. */ +:root { + --bookmark-icon: url('data:image/svg+xml,'); + --bookmark-icon-active: url('data:image/svg+xml,'); +} + .hyperbook-markdown .bookmark { margin-left: 10px; - background: none; border: none; - opacity: 0.5; + padding: 0; + /* The heading text is a link, and the button is its sibling, so inheriting + would pick up the color of the h1 rather than the color the heading + appears in. Take the same color the heading link takes. */ + color: var(--color-brand); + cursor: pointer; + display: inline-block; + vertical-align: middle; + width: 1em; + height: 1em; font-size: 1rem; + opacity: 0.5; + background-color: currentColor; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: var(--bookmark-icon); } .hyperbook-markdown .bookmark:hover { @@ -335,6 +366,7 @@ figure { .hyperbook-markdown .bookmark.active { opacity: 1; + mask-image: var(--bookmark-icon-active); } .hyperbook-markdown ul.bookmarks { diff --git a/packages/markdown/assets/directive-bookmarks/client.js b/packages/markdown/assets/directive-bookmarks/client.js index cf066a913..5fe350aed 100644 --- a/packages/markdown/assets/directive-bookmarks/client.js +++ b/packages/markdown/assets/directive-bookmarks/client.js @@ -7,6 +7,32 @@ * @see hyperbook.store */ hyperbook.bookmarks = (function () { + /** + * Render a stored label into a link. Labels are built from parts instead of + * markup, so nothing that was persisted is ever parsed as HTML. + * @param {Element} link + * @param {HyperbookBookmarkLabel | string} label + */ + function renderLabel(link, label) { + // Bookmarks saved before labels became parts. + if (typeof label === "string") { + link.textContent = label; + return; + } + for (let part of label || []) { + if (part.emoji && hyperbook.emoji?.base) { + const img = document.createElement("img"); + img.className = "emoji"; + img.src = `${hyperbook.emoji.base}/${part.emoji}.svg`; + img.alt = part.text || ""; + img.draggable = false; + link.append(img); + } else if (part.text) { + link.append(document.createTextNode(part.text)); + } + } + } + function update(root = document) { hyperbook.store.db.bookmarks .toArray() @@ -15,7 +41,7 @@ hyperbook.bookmarks = (function () { const bookmarkEl = root.createElement("li"); const link = root.createElement("a"); link.href = bookmark.path; - link.innerHTML = bookmark.label; + renderLabel(link, bookmark.label); bookmarkEl.append(link); return bookmarkEl; }); diff --git a/packages/markdown/assets/directive-bookmarks/style.css b/packages/markdown/assets/directive-bookmarks/style.css index 547a6cc92..6acd539b4 100644 --- a/packages/markdown/assets/directive-bookmarks/style.css +++ b/packages/markdown/assets/directive-bookmarks/style.css @@ -34,12 +34,18 @@ ul.directive-bookmarks li:last-of-type { } ul.directive-bookmarks a::before { - content: "🔖"; + content: ""; position: absolute; - font-size: 0.75rem; + width: 0.75rem; + height: 0.75rem; left: 12px; top: 50%; transform: translateY(-50%); + background-color: currentColor; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: var(--bookmark-icon-active); } ul.directive-bookmarks li { diff --git a/packages/markdown/assets/hyperbook.types.js b/packages/markdown/assets/hyperbook.types.js index 4529c2537..3812630e2 100644 --- a/packages/markdown/assets/hyperbook.types.js +++ b/packages/markdown/assets/hyperbook.types.js @@ -55,11 +55,32 @@ * @property {(id: string) => void} togglePlayPause - Toggle play/pause for an audio element. */ +/** + * One piece of a bookmark label. `text` is what the heading reads, `emoji` is + * the id of a Twemoji image, so the label can be rendered without storing + * markup or a URL. + * @typedef {{ text: string, emoji?: string }} HyperbookBookmarkLabelPart + */ + +/** + * A bookmark label, built from parts. A plain string comes from a bookmark + * that was stored before labels became parts. + * @typedef {HyperbookBookmarkLabelPart[]} HyperbookBookmarkLabel + */ + /** * @typedef {Object} HyperbookBookmarks * @property {(root?: Document) => void} update - Refresh the bookmarks list in the DOM. */ +/** + * Emoji rendering of the hyperbook. Only set when the emojis are rendered as + * images. + * @typedef {Object} HyperbookEmoji + * @property {"twemoji"} style - How emojis are rendered. + * @property {string} base - URL of the folder that holds the emoji images. + */ + /** * @typedef {Object} HyperbookDownload * @property {(root: HTMLElement) => void} init - Initialize download status indicators. @@ -150,7 +171,7 @@ * store: HyperbookStore, * cloud?: HyperbookCloud, * toggleLightbox: (el: HTMLElement) => void, - * toggleBookmark: (key: string, label: string) => void, + * toggleBookmark: (key: string) => void, * navToggle: () => void, * tocToggle: () => void, * searchToggle: () => void, @@ -166,6 +187,7 @@ * archive?: HyperbookArchive, * audio?: HyperbookAudio, * bookmarks?: HyperbookBookmarks, + * emoji?: HyperbookEmoji, * download?: HyperbookDownload, * excalidraw?: HyperbookExcalidraw, * geogebra?: HyperbookGeogebra, diff --git a/packages/markdown/assets/shell.css b/packages/markdown/assets/shell.css index 1d9cd791e..5d39024d9 100644 --- a/packages/markdown/assets/shell.css +++ b/packages/markdown/assets/shell.css @@ -1267,3 +1267,13 @@ nav.toc li.level-3 { .hidden { display: none !important; } + +img.emoji { + height: 1em; + width: 1em; + min-width: 1em; + margin: 0 0.05em 0 0.1em; + vertical-align: -0.1em; + display: inline-block; + max-width: none; +} diff --git a/packages/markdown/assets/store.js b/packages/markdown/assets/store.js index 5a761898a..02c4cd207 100644 --- a/packages/markdown/assets/store.js +++ b/packages/markdown/assets/store.js @@ -48,6 +48,22 @@ hyperbook.store = (function () { typst: `id,code`, openscad: `id,code,params`, }); + // Bookmark labels became a list of parts, so they can carry emojis without + // storing markup. The label is content, not a key, so it is not indexed. + db.version(6) + .stores({ + bookmarks: `path`, + }) + .upgrade((tx) => + tx + .table("bookmarks") + .toCollection() + .modify((bookmark) => { + if (typeof bookmark.label === "string") { + bookmark.label = [{ text: bookmark.label }]; + } + }), + ); /** @returns {Promise} */ const init = async () => { diff --git a/packages/markdown/assets/ui.js b/packages/markdown/assets/ui.js index 2ef8df924..79db66f27 100644 --- a/packages/markdown/assets/ui.js +++ b/packages/markdown/assets/ui.js @@ -48,22 +48,56 @@ hyperbook.ui = (function () { overlay.style.display = "flex"; } + /** + * Read the label of a heading as bookmark parts. Reading it from the + * rendered heading instead of the markup keeps whatever the page shows, + * so an emoji that is drawn as an image stays an image in the bookmark + * list. Emojis are stored by id, never by URL, so bookmarks survive a + * change of the basePath. + * @param {Element} [buttonEl] - The bookmark button of the heading. + * @returns {HyperbookBookmarkLabel} The label parts. + */ + function readBookmarkLabel(buttonEl) { + const labelEl = buttonEl?.parentElement?.querySelector("a.heading > span"); + const label = []; + for (let node of labelEl ? labelEl.childNodes : []) { + if (node.nodeName === "IMG" && node.dataset.emoji) { + label.push({ text: node.alt || "", emoji: node.dataset.emoji }); + } else if (node.textContent) { + label.push({ text: node.textContent }); + } + } + if (label.length === 0) { + const fallback = buttonEl?.getAttribute("data-label"); + if (fallback) { + label.push({ text: fallback }); + } + } + return label; + } + /** * Toggle a bookmark on/off. * @param {string} key - The bookmark path key. - * @param {string} label - The bookmark label. */ - function toggleBookmark(key, label) { + function toggleBookmark(key) { const el = document.querySelectorAll(`.bookmark[data-key="${key}"]`); hyperbook.store.db.bookmarks.get(key).then((bookmark) => { if (!bookmark) { + const label = readBookmarkLabel(el[0]); hyperbook.store.db.bookmarks.add({ path: key, label }).then(() => { - el.forEach((e) => e.classList.add("active")); + el.forEach((e) => { + e.classList.add("active"); + e.setAttribute("aria-pressed", "true"); + }); hyperbook.bookmarks.update(); }); } else { hyperbook.store.db.bookmarks.delete(key).then(() => { - el.forEach((e) => e.classList.remove("active")); + el.forEach((e) => { + e.classList.remove("active"); + e.setAttribute("aria-pressed", "false"); + }); hyperbook.bookmarks.update(); }); } diff --git a/packages/markdown/devBuild.mjs b/packages/markdown/devBuild.mjs index 5dc585502..9c0081a33 100644 --- a/packages/markdown/devBuild.mjs +++ b/packages/markdown/devBuild.mjs @@ -1,6 +1,7 @@ import fs from "fs/promises"; import path from "path"; import { process } from "./dist/index.js"; +import { isExternalUrl } from "@hyperbook/types"; const markdown = await fs.readFile("dev.md", "utf8"); @@ -54,7 +55,7 @@ const result = await process(markdown, { }, }, makeUrl: (p, base) => { - if (p.includes("://")) return p; + if (typeof p === "string" && isExternalUrl(p)) return p; if (typeof p === "string") { p = [p]; } diff --git a/packages/markdown/package.json b/packages/markdown/package.json index 012ff8cfb..587d22763 100644 --- a/packages/markdown/package.json +++ b/packages/markdown/package.json @@ -35,7 +35,7 @@ "build": "rimraf dist && pnpm build:pkg && pnpm build:types && node postbuild.mjs", "build:pkg": "node ../../scripts/build.mjs && ncp assets dist/assets", "build:types": "tsc --project tsconfig.build.json --declaration --emitDeclarationOnly", - "emojis:update": "node updateEmojis.mjs" + "emojis:update": "node updateEmojis.mjs && node updateEmojiAssets.mjs" }, "bundle": "all", "dependencies": { @@ -92,6 +92,7 @@ "@learningmap/web-component": "^0.3.7", "@raspberrypifoundation/python-friendly-error-messages": "^0.1.6", "@shikijs/types": "^3.21.0", + "@twemoji/svg": "15.0.0", "@types/hast": "3.0.4", "@types/js-yaml": "^4.0.9", "@types/mdast": "^4.0.4", diff --git a/packages/markdown/postbuild.mjs b/packages/markdown/postbuild.mjs index 2cb31ba26..0ceabff6a 100644 --- a/packages/markdown/postbuild.mjs +++ b/packages/markdown/postbuild.mjs @@ -6,6 +6,7 @@ import { Extract } from "unzipper"; import { createWriteStream, createReadStream } from "fs"; import { createHash } from "crypto"; import { build as esbuild } from "esbuild"; +import { createRequire } from "module"; const CACHE_DIR = path.join(".cache", "downloads"); @@ -381,6 +382,22 @@ async function postbuild() { }); } + // Copy the Twemoji SVGs, which back the "twemoji" emoji style. A build only + // copies the emojis a book actually uses into its output, so shipping the + // full set here costs nothing for the reader. + const twemojiDir = path.dirname( + createRequire(import.meta.url).resolve("@twemoji/svg/package.json"), + ); + const emojiOut = path.join("./dist", "assets", "emoji"); + await mkdir(emojiOut, { recursive: true }); + const emojiFiles = (await readdir(twemojiDir)).filter((f) => + f.endsWith(".svg"), + ); + for (const file of emojiFiles) { + await cp(path.join(twemojiDir, file), path.join(emojiOut, file)); + } + console.log(`Copied ${emojiFiles.length} emojis → ${emojiOut}`); + // Bundle CodeMirror 6 + language packages into a single IIFE for browser use. await mkdir("./dist/assets/codemirror", { recursive: true }); await esbuild({ diff --git a/packages/markdown/src/emoji-assets.json b/packages/markdown/src/emoji-assets.json new file mode 100644 index 000000000..899773924 --- /dev/null +++ b/packages/markdown/src/emoji-assets.json @@ -0,0 +1,3722 @@ +[ + "1f004", + "1f0cf", + "1f170", + "1f171", + "1f17e", + "1f17f", + "1f18e", + "1f191", + "1f192", + "1f193", + "1f194", + "1f195", + "1f196", + "1f197", + "1f198", + "1f199", + "1f19a", + "1f1e6", + "1f1e6-1f1e8", + "1f1e6-1f1e9", + "1f1e6-1f1ea", + "1f1e6-1f1eb", + "1f1e6-1f1ec", + "1f1e6-1f1ee", + "1f1e6-1f1f1", + "1f1e6-1f1f2", + "1f1e6-1f1f4", + "1f1e6-1f1f6", + "1f1e6-1f1f7", + "1f1e6-1f1f8", + "1f1e6-1f1f9", + "1f1e6-1f1fa", + "1f1e6-1f1fc", + "1f1e6-1f1fd", + "1f1e6-1f1ff", + "1f1e7", + "1f1e7-1f1e6", + "1f1e7-1f1e7", + "1f1e7-1f1e9", + "1f1e7-1f1ea", + "1f1e7-1f1eb", + "1f1e7-1f1ec", + "1f1e7-1f1ed", + "1f1e7-1f1ee", + "1f1e7-1f1ef", + "1f1e7-1f1f1", + "1f1e7-1f1f2", + "1f1e7-1f1f3", + "1f1e7-1f1f4", + "1f1e7-1f1f6", + "1f1e7-1f1f7", + "1f1e7-1f1f8", + "1f1e7-1f1f9", + "1f1e7-1f1fb", + "1f1e7-1f1fc", + "1f1e7-1f1fe", + "1f1e7-1f1ff", + "1f1e8", + "1f1e8-1f1e6", + "1f1e8-1f1e8", + "1f1e8-1f1e9", + "1f1e8-1f1eb", + "1f1e8-1f1ec", + "1f1e8-1f1ed", + "1f1e8-1f1ee", + "1f1e8-1f1f0", + "1f1e8-1f1f1", + "1f1e8-1f1f2", + "1f1e8-1f1f3", + "1f1e8-1f1f4", + "1f1e8-1f1f5", + "1f1e8-1f1f7", + "1f1e8-1f1fa", + "1f1e8-1f1fb", + "1f1e8-1f1fc", + "1f1e8-1f1fd", + "1f1e8-1f1fe", + "1f1e8-1f1ff", + "1f1e9", + "1f1e9-1f1ea", + "1f1e9-1f1ec", + "1f1e9-1f1ef", + "1f1e9-1f1f0", + "1f1e9-1f1f2", + "1f1e9-1f1f4", + "1f1e9-1f1ff", + "1f1ea", + "1f1ea-1f1e6", + "1f1ea-1f1e8", + "1f1ea-1f1ea", + "1f1ea-1f1ec", + "1f1ea-1f1ed", + "1f1ea-1f1f7", + "1f1ea-1f1f8", + "1f1ea-1f1f9", + "1f1ea-1f1fa", + "1f1eb", + "1f1eb-1f1ee", + "1f1eb-1f1ef", + "1f1eb-1f1f0", + "1f1eb-1f1f2", + "1f1eb-1f1f4", + "1f1eb-1f1f7", + "1f1ec", + "1f1ec-1f1e6", + "1f1ec-1f1e7", + "1f1ec-1f1e9", + "1f1ec-1f1ea", + "1f1ec-1f1eb", + "1f1ec-1f1ec", + "1f1ec-1f1ed", + "1f1ec-1f1ee", + "1f1ec-1f1f1", + "1f1ec-1f1f2", + "1f1ec-1f1f3", + "1f1ec-1f1f5", + "1f1ec-1f1f6", + "1f1ec-1f1f7", + "1f1ec-1f1f8", + "1f1ec-1f1f9", + "1f1ec-1f1fa", + "1f1ec-1f1fc", + "1f1ec-1f1fe", + "1f1ed", + "1f1ed-1f1f0", + "1f1ed-1f1f2", + "1f1ed-1f1f3", + "1f1ed-1f1f7", + "1f1ed-1f1f9", + "1f1ed-1f1fa", + "1f1ee", + "1f1ee-1f1e8", + "1f1ee-1f1e9", + "1f1ee-1f1ea", + "1f1ee-1f1f1", + "1f1ee-1f1f2", + "1f1ee-1f1f3", + "1f1ee-1f1f4", + "1f1ee-1f1f6", + "1f1ee-1f1f7", + "1f1ee-1f1f8", + "1f1ee-1f1f9", + "1f1ef", + "1f1ef-1f1ea", + "1f1ef-1f1f2", + "1f1ef-1f1f4", + "1f1ef-1f1f5", + "1f1f0", + "1f1f0-1f1ea", + "1f1f0-1f1ec", + "1f1f0-1f1ed", + "1f1f0-1f1ee", + "1f1f0-1f1f2", + "1f1f0-1f1f3", + "1f1f0-1f1f5", + "1f1f0-1f1f7", + "1f1f0-1f1fc", + "1f1f0-1f1fe", + "1f1f0-1f1ff", + "1f1f1", + "1f1f1-1f1e6", + "1f1f1-1f1e7", + "1f1f1-1f1e8", + "1f1f1-1f1ee", + "1f1f1-1f1f0", + "1f1f1-1f1f7", + "1f1f1-1f1f8", + "1f1f1-1f1f9", + "1f1f1-1f1fa", + "1f1f1-1f1fb", + "1f1f1-1f1fe", + "1f1f2", + "1f1f2-1f1e6", + "1f1f2-1f1e8", + "1f1f2-1f1e9", + "1f1f2-1f1ea", + "1f1f2-1f1eb", + "1f1f2-1f1ec", + "1f1f2-1f1ed", + "1f1f2-1f1f0", + "1f1f2-1f1f1", + "1f1f2-1f1f2", + "1f1f2-1f1f3", + "1f1f2-1f1f4", + "1f1f2-1f1f5", + "1f1f2-1f1f6", + "1f1f2-1f1f7", + "1f1f2-1f1f8", + "1f1f2-1f1f9", + "1f1f2-1f1fa", + "1f1f2-1f1fb", + "1f1f2-1f1fc", + "1f1f2-1f1fd", + "1f1f2-1f1fe", + "1f1f2-1f1ff", + "1f1f3", + "1f1f3-1f1e6", + "1f1f3-1f1e8", + "1f1f3-1f1ea", + "1f1f3-1f1eb", + "1f1f3-1f1ec", + "1f1f3-1f1ee", + "1f1f3-1f1f1", + "1f1f3-1f1f4", + "1f1f3-1f1f5", + "1f1f3-1f1f7", + "1f1f3-1f1fa", + "1f1f3-1f1ff", + "1f1f4", + "1f1f4-1f1f2", + "1f1f5", + "1f1f5-1f1e6", + "1f1f5-1f1ea", + "1f1f5-1f1eb", + "1f1f5-1f1ec", + "1f1f5-1f1ed", + "1f1f5-1f1f0", + "1f1f5-1f1f1", + "1f1f5-1f1f2", + "1f1f5-1f1f3", + "1f1f5-1f1f7", + "1f1f5-1f1f8", + "1f1f5-1f1f9", + "1f1f5-1f1fc", + "1f1f5-1f1fe", + "1f1f6", + "1f1f6-1f1e6", + "1f1f7", + "1f1f7-1f1ea", + "1f1f7-1f1f4", + "1f1f7-1f1f8", + "1f1f7-1f1fa", + "1f1f7-1f1fc", + "1f1f8", + "1f1f8-1f1e6", + "1f1f8-1f1e7", + "1f1f8-1f1e8", + "1f1f8-1f1e9", + "1f1f8-1f1ea", + "1f1f8-1f1ec", + "1f1f8-1f1ed", + "1f1f8-1f1ee", + "1f1f8-1f1ef", + "1f1f8-1f1f0", + "1f1f8-1f1f1", + "1f1f8-1f1f2", + "1f1f8-1f1f3", + "1f1f8-1f1f4", + "1f1f8-1f1f7", + "1f1f8-1f1f8", + "1f1f8-1f1f9", + "1f1f8-1f1fb", + "1f1f8-1f1fd", + "1f1f8-1f1fe", + "1f1f8-1f1ff", + "1f1f9", + "1f1f9-1f1e6", + "1f1f9-1f1e8", + "1f1f9-1f1e9", + "1f1f9-1f1eb", + "1f1f9-1f1ec", + "1f1f9-1f1ed", + "1f1f9-1f1ef", + "1f1f9-1f1f0", + "1f1f9-1f1f1", + "1f1f9-1f1f2", + "1f1f9-1f1f3", + "1f1f9-1f1f4", + "1f1f9-1f1f7", + "1f1f9-1f1f9", + "1f1f9-1f1fb", + "1f1f9-1f1fc", + "1f1f9-1f1ff", + "1f1fa", + "1f1fa-1f1e6", + "1f1fa-1f1ec", + "1f1fa-1f1f2", + "1f1fa-1f1f3", + "1f1fa-1f1f8", + "1f1fa-1f1fe", + "1f1fa-1f1ff", + "1f1fb", + "1f1fb-1f1e6", + "1f1fb-1f1e8", + "1f1fb-1f1ea", + "1f1fb-1f1ec", + "1f1fb-1f1ee", + "1f1fb-1f1f3", + "1f1fb-1f1fa", + "1f1fc", + "1f1fc-1f1eb", + "1f1fc-1f1f8", + "1f1fd", + "1f1fd-1f1f0", + "1f1fe", + "1f1fe-1f1ea", + "1f1fe-1f1f9", + "1f1ff", + "1f1ff-1f1e6", + "1f1ff-1f1f2", + "1f1ff-1f1fc", + "1f201", + "1f202", + "1f21a", + "1f22f", + "1f232", + "1f233", + "1f234", + "1f235", + "1f236", + "1f237", + "1f238", + "1f239", + "1f23a", + "1f250", + "1f251", + "1f300", + "1f301", + "1f302", + "1f303", + "1f304", + "1f305", + "1f306", + "1f307", + "1f308", + "1f309", + "1f30a", + "1f30b", + "1f30c", + "1f30d", + "1f30e", + "1f30f", + "1f310", + "1f311", + "1f312", + "1f313", + "1f314", + "1f315", + "1f316", + "1f317", + "1f318", + "1f319", + "1f31a", + "1f31b", + "1f31c", + "1f31d", + "1f31e", + "1f31f", + "1f320", + "1f321", + "1f324", + "1f325", + "1f326", + "1f327", + "1f328", + "1f329", + "1f32a", + "1f32b", + "1f32c", + "1f32d", + "1f32e", + "1f32f", + "1f330", + "1f331", + "1f332", + "1f333", + "1f334", + "1f335", + "1f336", + "1f337", + "1f338", + "1f339", + "1f33a", + "1f33b", + "1f33c", + "1f33d", + "1f33e", + "1f33f", + "1f340", + "1f341", + "1f342", + "1f343", + "1f344", + "1f345", + "1f346", + "1f347", + "1f348", + "1f349", + "1f34a", + "1f34b", + "1f34c", + "1f34d", + "1f34e", + "1f34f", + "1f350", + "1f351", + "1f352", + "1f353", + "1f354", + "1f355", + "1f356", + "1f357", + "1f358", + "1f359", + "1f35a", + "1f35b", + "1f35c", + "1f35d", + "1f35e", + "1f35f", + "1f360", + "1f361", + "1f362", + "1f363", + "1f364", + "1f365", + "1f366", + "1f367", + "1f368", + "1f369", + "1f36a", + "1f36b", + "1f36c", + "1f36d", + "1f36e", + "1f36f", + "1f370", + "1f371", + "1f372", + "1f373", + "1f374", + "1f375", + "1f376", + "1f377", + "1f378", + "1f379", + "1f37a", + "1f37b", + "1f37c", + "1f37d", + "1f37e", + "1f37f", + "1f380", + "1f381", + "1f382", + "1f383", + "1f384", + "1f385", + "1f385-1f3fb", + "1f385-1f3fc", + "1f385-1f3fd", + "1f385-1f3fe", + "1f385-1f3ff", + "1f386", + "1f387", + "1f388", + "1f389", + "1f38a", + "1f38b", + "1f38c", + "1f38d", + "1f38e", + "1f38f", + "1f390", + "1f391", + "1f392", + "1f393", + "1f396", + "1f397", + "1f399", + "1f39a", + "1f39b", + "1f39e", + "1f39f", + "1f3a0", + "1f3a1", + "1f3a2", + "1f3a3", + "1f3a4", + "1f3a5", + "1f3a6", + "1f3a7", + "1f3a8", + "1f3a9", + "1f3aa", + "1f3ab", + "1f3ac", + "1f3ad", + "1f3ae", + "1f3af", + "1f3b0", + "1f3b1", + "1f3b2", + "1f3b3", + "1f3b4", + "1f3b5", + "1f3b6", + "1f3b7", + "1f3b8", + "1f3b9", + "1f3ba", + "1f3bb", + "1f3bc", + "1f3bd", + "1f3be", + "1f3bf", + "1f3c0", + "1f3c1", + "1f3c2", + "1f3c2-1f3fb", + "1f3c2-1f3fc", + "1f3c2-1f3fd", + "1f3c2-1f3fe", + "1f3c2-1f3ff", + "1f3c3", + "1f3c3-1f3fb", + "1f3c3-1f3fb-200d-2640-fe0f", + "1f3c3-1f3fb-200d-2642-fe0f", + "1f3c3-1f3fc", + "1f3c3-1f3fc-200d-2640-fe0f", + "1f3c3-1f3fc-200d-2642-fe0f", + "1f3c3-1f3fd", + "1f3c3-1f3fd-200d-2640-fe0f", + "1f3c3-1f3fd-200d-2642-fe0f", + "1f3c3-1f3fe", + "1f3c3-1f3fe-200d-2640-fe0f", + "1f3c3-1f3fe-200d-2642-fe0f", + "1f3c3-1f3ff", + "1f3c3-1f3ff-200d-2640-fe0f", + "1f3c3-1f3ff-200d-2642-fe0f", + "1f3c3-200d-2640-fe0f", + "1f3c3-200d-2642-fe0f", + "1f3c4", + "1f3c4-1f3fb", + "1f3c4-1f3fb-200d-2640-fe0f", + "1f3c4-1f3fb-200d-2642-fe0f", + "1f3c4-1f3fc", + "1f3c4-1f3fc-200d-2640-fe0f", + "1f3c4-1f3fc-200d-2642-fe0f", + "1f3c4-1f3fd", + "1f3c4-1f3fd-200d-2640-fe0f", + "1f3c4-1f3fd-200d-2642-fe0f", + "1f3c4-1f3fe", + "1f3c4-1f3fe-200d-2640-fe0f", + "1f3c4-1f3fe-200d-2642-fe0f", + "1f3c4-1f3ff", + "1f3c4-1f3ff-200d-2640-fe0f", + "1f3c4-1f3ff-200d-2642-fe0f", + "1f3c4-200d-2640-fe0f", + "1f3c4-200d-2642-fe0f", + "1f3c5", + "1f3c6", + "1f3c7", + "1f3c7-1f3fb", + "1f3c7-1f3fc", + "1f3c7-1f3fd", + "1f3c7-1f3fe", + "1f3c7-1f3ff", + "1f3c8", + "1f3c9", + "1f3ca", + "1f3ca-1f3fb", + "1f3ca-1f3fb-200d-2640-fe0f", + "1f3ca-1f3fb-200d-2642-fe0f", + "1f3ca-1f3fc", + "1f3ca-1f3fc-200d-2640-fe0f", + "1f3ca-1f3fc-200d-2642-fe0f", + "1f3ca-1f3fd", + "1f3ca-1f3fd-200d-2640-fe0f", + "1f3ca-1f3fd-200d-2642-fe0f", + "1f3ca-1f3fe", + "1f3ca-1f3fe-200d-2640-fe0f", + "1f3ca-1f3fe-200d-2642-fe0f", + "1f3ca-1f3ff", + "1f3ca-1f3ff-200d-2640-fe0f", + "1f3ca-1f3ff-200d-2642-fe0f", + "1f3ca-200d-2640-fe0f", + "1f3ca-200d-2642-fe0f", + "1f3cb", + "1f3cb-1f3fb", + "1f3cb-1f3fb-200d-2640-fe0f", + "1f3cb-1f3fb-200d-2642-fe0f", + "1f3cb-1f3fc", + "1f3cb-1f3fc-200d-2640-fe0f", + "1f3cb-1f3fc-200d-2642-fe0f", + "1f3cb-1f3fd", + "1f3cb-1f3fd-200d-2640-fe0f", + "1f3cb-1f3fd-200d-2642-fe0f", + "1f3cb-1f3fe", + "1f3cb-1f3fe-200d-2640-fe0f", + "1f3cb-1f3fe-200d-2642-fe0f", + "1f3cb-1f3ff", + "1f3cb-1f3ff-200d-2640-fe0f", + "1f3cb-1f3ff-200d-2642-fe0f", + "1f3cb-fe0f-200d-2640-fe0f", + "1f3cb-fe0f-200d-2642-fe0f", + "1f3cc", + "1f3cc-1f3fb", + "1f3cc-1f3fb-200d-2640-fe0f", + "1f3cc-1f3fb-200d-2642-fe0f", + "1f3cc-1f3fc", + "1f3cc-1f3fc-200d-2640-fe0f", + "1f3cc-1f3fc-200d-2642-fe0f", + "1f3cc-1f3fd", + "1f3cc-1f3fd-200d-2640-fe0f", + "1f3cc-1f3fd-200d-2642-fe0f", + "1f3cc-1f3fe", + "1f3cc-1f3fe-200d-2640-fe0f", + "1f3cc-1f3fe-200d-2642-fe0f", + "1f3cc-1f3ff", + "1f3cc-1f3ff-200d-2640-fe0f", + "1f3cc-1f3ff-200d-2642-fe0f", + "1f3cc-fe0f-200d-2640-fe0f", + "1f3cc-fe0f-200d-2642-fe0f", + "1f3cd", + "1f3ce", + "1f3cf", + "1f3d0", + "1f3d1", + "1f3d2", + "1f3d3", + "1f3d4", + "1f3d5", + "1f3d6", + "1f3d7", + "1f3d8", + "1f3d9", + "1f3da", + "1f3db", + "1f3dc", + "1f3dd", + "1f3de", + "1f3df", + "1f3e0", + "1f3e1", + "1f3e2", + "1f3e3", + "1f3e4", + "1f3e5", + "1f3e6", + "1f3e7", + "1f3e8", + "1f3e9", + "1f3ea", + "1f3eb", + "1f3ec", + "1f3ed", + "1f3ee", + "1f3ef", + "1f3f0", + "1f3f3", + "1f3f3-fe0f-200d-1f308", + "1f3f3-fe0f-200d-26a7-fe0f", + "1f3f4", + "1f3f4-200d-2620-fe0f", + "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", + "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", + "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", + "1f3f5", + "1f3f7", + "1f3f8", + "1f3f9", + "1f3fa", + "1f3fb", + "1f3fc", + "1f3fd", + "1f3fe", + "1f3ff", + "1f400", + "1f401", + "1f402", + "1f403", + "1f404", + "1f405", + "1f406", + "1f407", + "1f408", + "1f408-200d-2b1b", + "1f409", + "1f40a", + "1f40b", + "1f40c", + "1f40d", + "1f40e", + "1f40f", + "1f410", + "1f411", + "1f412", + "1f413", + "1f414", + "1f415", + "1f415-200d-1f9ba", + "1f416", + "1f417", + "1f418", + "1f419", + "1f41a", + "1f41b", + "1f41c", + "1f41d", + "1f41e", + "1f41f", + "1f420", + "1f421", + "1f422", + "1f423", + "1f424", + "1f425", + "1f426", + "1f426-200d-2b1b", + "1f427", + "1f428", + "1f429", + "1f42a", + "1f42b", + "1f42c", + "1f42d", + "1f42e", + "1f42f", + "1f430", + "1f431", + "1f432", + "1f433", + "1f434", + "1f435", + "1f436", + "1f437", + "1f438", + "1f439", + "1f43a", + "1f43b", + "1f43b-200d-2744-fe0f", + "1f43c", + "1f43d", + "1f43e", + "1f43f", + "1f440", + "1f441", + "1f441-200d-1f5e8", + "1f442", + "1f442-1f3fb", + "1f442-1f3fc", + "1f442-1f3fd", + "1f442-1f3fe", + "1f442-1f3ff", + "1f443", + "1f443-1f3fb", + "1f443-1f3fc", + "1f443-1f3fd", + "1f443-1f3fe", + "1f443-1f3ff", + "1f444", + "1f445", + "1f446", + "1f446-1f3fb", + "1f446-1f3fc", + "1f446-1f3fd", + "1f446-1f3fe", + "1f446-1f3ff", + "1f447", + "1f447-1f3fb", + "1f447-1f3fc", + "1f447-1f3fd", + "1f447-1f3fe", + "1f447-1f3ff", + "1f448", + "1f448-1f3fb", + "1f448-1f3fc", + "1f448-1f3fd", + "1f448-1f3fe", + "1f448-1f3ff", + "1f449", + "1f449-1f3fb", + "1f449-1f3fc", + "1f449-1f3fd", + "1f449-1f3fe", + "1f449-1f3ff", + "1f44a", + "1f44a-1f3fb", + "1f44a-1f3fc", + "1f44a-1f3fd", + "1f44a-1f3fe", + "1f44a-1f3ff", + "1f44b", + "1f44b-1f3fb", + "1f44b-1f3fc", + "1f44b-1f3fd", + "1f44b-1f3fe", + "1f44b-1f3ff", + "1f44c", + "1f44c-1f3fb", + "1f44c-1f3fc", + "1f44c-1f3fd", + "1f44c-1f3fe", + "1f44c-1f3ff", + "1f44d", + "1f44d-1f3fb", + "1f44d-1f3fc", + "1f44d-1f3fd", + "1f44d-1f3fe", + "1f44d-1f3ff", + "1f44e", + "1f44e-1f3fb", + "1f44e-1f3fc", + "1f44e-1f3fd", + "1f44e-1f3fe", + "1f44e-1f3ff", + "1f44f", + "1f44f-1f3fb", + "1f44f-1f3fc", + "1f44f-1f3fd", + "1f44f-1f3fe", + "1f44f-1f3ff", + "1f450", + "1f450-1f3fb", + "1f450-1f3fc", + "1f450-1f3fd", + "1f450-1f3fe", + "1f450-1f3ff", + "1f451", + "1f452", + "1f453", + "1f454", + "1f455", + "1f456", + "1f457", + "1f458", + "1f459", + "1f45a", + "1f45b", + "1f45c", + "1f45d", + "1f45e", + "1f45f", + "1f460", + "1f461", + "1f462", + "1f463", + "1f464", + "1f465", + "1f466", + "1f466-1f3fb", + "1f466-1f3fc", + "1f466-1f3fd", + "1f466-1f3fe", + "1f466-1f3ff", + "1f467", + "1f467-1f3fb", + "1f467-1f3fc", + "1f467-1f3fd", + "1f467-1f3fe", + "1f467-1f3ff", + "1f468", + "1f468-1f3fb", + "1f468-1f3fb-200d-1f33e", + "1f468-1f3fb-200d-1f373", + "1f468-1f3fb-200d-1f37c", + "1f468-1f3fb-200d-1f384", + "1f468-1f3fb-200d-1f393", + "1f468-1f3fb-200d-1f3a4", + "1f468-1f3fb-200d-1f3a8", + "1f468-1f3fb-200d-1f3eb", + "1f468-1f3fb-200d-1f3ed", + "1f468-1f3fb-200d-1f4bb", + "1f468-1f3fb-200d-1f4bc", + "1f468-1f3fb-200d-1f527", + "1f468-1f3fb-200d-1f52c", + "1f468-1f3fb-200d-1f680", + "1f468-1f3fb-200d-1f692", + "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc", + "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd", + "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe", + "1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff", + "1f468-1f3fb-200d-1f9af", + "1f468-1f3fb-200d-1f9b0", + "1f468-1f3fb-200d-1f9b1", + "1f468-1f3fb-200d-1f9b2", + "1f468-1f3fb-200d-1f9b3", + "1f468-1f3fb-200d-1f9bc", + "1f468-1f3fb-200d-1f9bd", + "1f468-1f3fb-200d-2695-fe0f", + "1f468-1f3fb-200d-2696-fe0f", + "1f468-1f3fb-200d-2708-fe0f", + "1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb", + "1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc", + "1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd", + "1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe", + "1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff", + "1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f468-1f3fc", + "1f468-1f3fc-200d-1f33e", + "1f468-1f3fc-200d-1f373", + "1f468-1f3fc-200d-1f37c", + "1f468-1f3fc-200d-1f384", + "1f468-1f3fc-200d-1f393", + "1f468-1f3fc-200d-1f3a4", + "1f468-1f3fc-200d-1f3a8", + "1f468-1f3fc-200d-1f3eb", + "1f468-1f3fc-200d-1f3ed", + "1f468-1f3fc-200d-1f4bb", + "1f468-1f3fc-200d-1f4bc", + "1f468-1f3fc-200d-1f527", + "1f468-1f3fc-200d-1f52c", + "1f468-1f3fc-200d-1f680", + "1f468-1f3fc-200d-1f692", + "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb", + "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd", + "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe", + "1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff", + "1f468-1f3fc-200d-1f9af", + "1f468-1f3fc-200d-1f9b0", + "1f468-1f3fc-200d-1f9b1", + "1f468-1f3fc-200d-1f9b2", + "1f468-1f3fc-200d-1f9b3", + "1f468-1f3fc-200d-1f9bc", + "1f468-1f3fc-200d-1f9bd", + "1f468-1f3fc-200d-2695-fe0f", + "1f468-1f3fc-200d-2696-fe0f", + "1f468-1f3fc-200d-2708-fe0f", + "1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb", + "1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc", + "1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd", + "1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe", + "1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff", + "1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f468-1f3fd", + "1f468-1f3fd-200d-1f33e", + "1f468-1f3fd-200d-1f373", + "1f468-1f3fd-200d-1f37c", + "1f468-1f3fd-200d-1f384", + "1f468-1f3fd-200d-1f393", + "1f468-1f3fd-200d-1f3a4", + "1f468-1f3fd-200d-1f3a8", + "1f468-1f3fd-200d-1f3eb", + "1f468-1f3fd-200d-1f3ed", + "1f468-1f3fd-200d-1f4bb", + "1f468-1f3fd-200d-1f4bc", + "1f468-1f3fd-200d-1f527", + "1f468-1f3fd-200d-1f52c", + "1f468-1f3fd-200d-1f680", + "1f468-1f3fd-200d-1f692", + "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb", + "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc", + "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe", + "1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff", + "1f468-1f3fd-200d-1f9af", + "1f468-1f3fd-200d-1f9b0", + "1f468-1f3fd-200d-1f9b1", + "1f468-1f3fd-200d-1f9b2", + "1f468-1f3fd-200d-1f9b3", + "1f468-1f3fd-200d-1f9bc", + "1f468-1f3fd-200d-1f9bd", + "1f468-1f3fd-200d-2695-fe0f", + "1f468-1f3fd-200d-2696-fe0f", + "1f468-1f3fd-200d-2708-fe0f", + "1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb", + "1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc", + "1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd", + "1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe", + "1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff", + "1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f468-1f3fe", + "1f468-1f3fe-200d-1f33e", + "1f468-1f3fe-200d-1f373", + "1f468-1f3fe-200d-1f37c", + "1f468-1f3fe-200d-1f384", + "1f468-1f3fe-200d-1f393", + "1f468-1f3fe-200d-1f3a4", + "1f468-1f3fe-200d-1f3a8", + "1f468-1f3fe-200d-1f3eb", + "1f468-1f3fe-200d-1f3ed", + "1f468-1f3fe-200d-1f4bb", + "1f468-1f3fe-200d-1f4bc", + "1f468-1f3fe-200d-1f527", + "1f468-1f3fe-200d-1f52c", + "1f468-1f3fe-200d-1f680", + "1f468-1f3fe-200d-1f692", + "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb", + "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc", + "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd", + "1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff", + "1f468-1f3fe-200d-1f9af", + "1f468-1f3fe-200d-1f9b0", + "1f468-1f3fe-200d-1f9b1", + "1f468-1f3fe-200d-1f9b2", + "1f468-1f3fe-200d-1f9b3", + "1f468-1f3fe-200d-1f9bc", + "1f468-1f3fe-200d-1f9bd", + "1f468-1f3fe-200d-2695-fe0f", + "1f468-1f3fe-200d-2696-fe0f", + "1f468-1f3fe-200d-2708-fe0f", + "1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb", + "1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc", + "1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd", + "1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe", + "1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff", + "1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f468-1f3ff", + "1f468-1f3ff-200d-1f33e", + "1f468-1f3ff-200d-1f373", + "1f468-1f3ff-200d-1f37c", + "1f468-1f3ff-200d-1f384", + "1f468-1f3ff-200d-1f393", + "1f468-1f3ff-200d-1f3a4", + "1f468-1f3ff-200d-1f3a8", + "1f468-1f3ff-200d-1f3eb", + "1f468-1f3ff-200d-1f3ed", + "1f468-1f3ff-200d-1f4bb", + "1f468-1f3ff-200d-1f4bc", + "1f468-1f3ff-200d-1f527", + "1f468-1f3ff-200d-1f52c", + "1f468-1f3ff-200d-1f680", + "1f468-1f3ff-200d-1f692", + "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb", + "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc", + "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd", + "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe", + "1f468-1f3ff-200d-1f9af", + "1f468-1f3ff-200d-1f9b0", + "1f468-1f3ff-200d-1f9b1", + "1f468-1f3ff-200d-1f9b2", + "1f468-1f3ff-200d-1f9b3", + "1f468-1f3ff-200d-1f9bc", + "1f468-1f3ff-200d-1f9bd", + "1f468-1f3ff-200d-2695-fe0f", + "1f468-1f3ff-200d-2696-fe0f", + "1f468-1f3ff-200d-2708-fe0f", + "1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb", + "1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc", + "1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd", + "1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe", + "1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff", + "1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f468-200d-1f33e", + "1f468-200d-1f373", + "1f468-200d-1f37c", + "1f468-200d-1f384", + "1f468-200d-1f393", + "1f468-200d-1f3a4", + "1f468-200d-1f3a8", + "1f468-200d-1f3eb", + "1f468-200d-1f3ed", + "1f468-200d-1f466", + "1f468-200d-1f466-200d-1f466", + "1f468-200d-1f467", + "1f468-200d-1f467-200d-1f466", + "1f468-200d-1f467-200d-1f467", + "1f468-200d-1f468-200d-1f466", + "1f468-200d-1f468-200d-1f466-200d-1f466", + "1f468-200d-1f468-200d-1f467", + "1f468-200d-1f468-200d-1f467-200d-1f466", + "1f468-200d-1f468-200d-1f467-200d-1f467", + "1f468-200d-1f469-200d-1f466", + "1f468-200d-1f469-200d-1f466-200d-1f466", + "1f468-200d-1f469-200d-1f467", + "1f468-200d-1f469-200d-1f467-200d-1f466", + "1f468-200d-1f469-200d-1f467-200d-1f467", + "1f468-200d-1f4bb", + "1f468-200d-1f4bc", + "1f468-200d-1f527", + "1f468-200d-1f52c", + "1f468-200d-1f680", + "1f468-200d-1f692", + "1f468-200d-1f9af", + "1f468-200d-1f9b0", + "1f468-200d-1f9b1", + "1f468-200d-1f9b2", + "1f468-200d-1f9b3", + "1f468-200d-1f9bc", + "1f468-200d-1f9bd", + "1f468-200d-2695-fe0f", + "1f468-200d-2696-fe0f", + "1f468-200d-2708-fe0f", + "1f468-200d-2764-fe0f-200d-1f468", + "1f468-200d-2764-fe0f-200d-1f48b-200d-1f468", + "1f469", + "1f469-1f3fb", + "1f469-1f3fb-200d-1f33e", + "1f469-1f3fb-200d-1f373", + "1f469-1f3fb-200d-1f37c", + "1f469-1f3fb-200d-1f384", + "1f469-1f3fb-200d-1f393", + "1f469-1f3fb-200d-1f3a4", + "1f469-1f3fb-200d-1f3a8", + "1f469-1f3fb-200d-1f3eb", + "1f469-1f3fb-200d-1f3ed", + "1f469-1f3fb-200d-1f4bb", + "1f469-1f3fb-200d-1f4bc", + "1f469-1f3fb-200d-1f527", + "1f469-1f3fb-200d-1f52c", + "1f469-1f3fb-200d-1f680", + "1f469-1f3fb-200d-1f692", + "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc", + "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd", + "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe", + "1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff", + "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc", + "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd", + "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe", + "1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff", + "1f469-1f3fb-200d-1f9af", + "1f469-1f3fb-200d-1f9b0", + "1f469-1f3fb-200d-1f9b1", + "1f469-1f3fb-200d-1f9b2", + "1f469-1f3fb-200d-1f9b3", + "1f469-1f3fb-200d-1f9bc", + "1f469-1f3fb-200d-1f9bd", + "1f469-1f3fb-200d-2695-fe0f", + "1f469-1f3fb-200d-2696-fe0f", + "1f469-1f3fb-200d-2708-fe0f", + "1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb", + "1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc", + "1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd", + "1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe", + "1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff", + "1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb", + "1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc", + "1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd", + "1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe", + "1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe", + "1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff", + "1f469-1f3fc", + "1f469-1f3fc-200d-1f33e", + "1f469-1f3fc-200d-1f373", + "1f469-1f3fc-200d-1f37c", + "1f469-1f3fc-200d-1f384", + "1f469-1f3fc-200d-1f393", + "1f469-1f3fc-200d-1f3a4", + "1f469-1f3fc-200d-1f3a8", + "1f469-1f3fc-200d-1f3eb", + "1f469-1f3fc-200d-1f3ed", + "1f469-1f3fc-200d-1f4bb", + "1f469-1f3fc-200d-1f4bc", + "1f469-1f3fc-200d-1f527", + "1f469-1f3fc-200d-1f52c", + "1f469-1f3fc-200d-1f680", + "1f469-1f3fc-200d-1f692", + "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb", + "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd", + "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe", + "1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff", + "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb", + "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd", + "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe", + "1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff", + "1f469-1f3fc-200d-1f9af", + "1f469-1f3fc-200d-1f9b0", + "1f469-1f3fc-200d-1f9b1", + "1f469-1f3fc-200d-1f9b2", + "1f469-1f3fc-200d-1f9b3", + "1f469-1f3fc-200d-1f9bc", + "1f469-1f3fc-200d-1f9bd", + "1f469-1f3fc-200d-2695-fe0f", + "1f469-1f3fc-200d-2696-fe0f", + "1f469-1f3fc-200d-2708-fe0f", + "1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb", + "1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc", + "1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd", + "1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe", + "1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff", + "1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb", + "1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc", + "1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd", + "1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe", + "1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe", + "1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff", + "1f469-1f3fd", + "1f469-1f3fd-200d-1f33e", + "1f469-1f3fd-200d-1f373", + "1f469-1f3fd-200d-1f37c", + "1f469-1f3fd-200d-1f384", + "1f469-1f3fd-200d-1f393", + "1f469-1f3fd-200d-1f3a4", + "1f469-1f3fd-200d-1f3a8", + "1f469-1f3fd-200d-1f3eb", + "1f469-1f3fd-200d-1f3ed", + "1f469-1f3fd-200d-1f4bb", + "1f469-1f3fd-200d-1f4bc", + "1f469-1f3fd-200d-1f527", + "1f469-1f3fd-200d-1f52c", + "1f469-1f3fd-200d-1f680", + "1f469-1f3fd-200d-1f692", + "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb", + "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc", + "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe", + "1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff", + "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb", + "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc", + "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe", + "1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff", + "1f469-1f3fd-200d-1f9af", + "1f469-1f3fd-200d-1f9b0", + "1f469-1f3fd-200d-1f9b1", + "1f469-1f3fd-200d-1f9b2", + "1f469-1f3fd-200d-1f9b3", + "1f469-1f3fd-200d-1f9bc", + "1f469-1f3fd-200d-1f9bd", + "1f469-1f3fd-200d-2695-fe0f", + "1f469-1f3fd-200d-2696-fe0f", + "1f469-1f3fd-200d-2708-fe0f", + "1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb", + "1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc", + "1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd", + "1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe", + "1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff", + "1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb", + "1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc", + "1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd", + "1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe", + "1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe", + "1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff", + "1f469-1f3fe", + "1f469-1f3fe-200d-1f33e", + "1f469-1f3fe-200d-1f373", + "1f469-1f3fe-200d-1f37c", + "1f469-1f3fe-200d-1f384", + "1f469-1f3fe-200d-1f393", + "1f469-1f3fe-200d-1f3a4", + "1f469-1f3fe-200d-1f3a8", + "1f469-1f3fe-200d-1f3eb", + "1f469-1f3fe-200d-1f3ed", + "1f469-1f3fe-200d-1f4bb", + "1f469-1f3fe-200d-1f4bc", + "1f469-1f3fe-200d-1f527", + "1f469-1f3fe-200d-1f52c", + "1f469-1f3fe-200d-1f680", + "1f469-1f3fe-200d-1f692", + "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb", + "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc", + "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd", + "1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff", + "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb", + "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc", + "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd", + "1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff", + "1f469-1f3fe-200d-1f9af", + "1f469-1f3fe-200d-1f9b0", + "1f469-1f3fe-200d-1f9b1", + "1f469-1f3fe-200d-1f9b2", + "1f469-1f3fe-200d-1f9b3", + "1f469-1f3fe-200d-1f9bc", + "1f469-1f3fe-200d-1f9bd", + "1f469-1f3fe-200d-2695-fe0f", + "1f469-1f3fe-200d-2696-fe0f", + "1f469-1f3fe-200d-2708-fe0f", + "1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb", + "1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc", + "1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd", + "1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe", + "1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff", + "1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb", + "1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc", + "1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd", + "1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe", + "1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe", + "1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff", + "1f469-1f3ff", + "1f469-1f3ff-200d-1f33e", + "1f469-1f3ff-200d-1f373", + "1f469-1f3ff-200d-1f37c", + "1f469-1f3ff-200d-1f384", + "1f469-1f3ff-200d-1f393", + "1f469-1f3ff-200d-1f3a4", + "1f469-1f3ff-200d-1f3a8", + "1f469-1f3ff-200d-1f3eb", + "1f469-1f3ff-200d-1f3ed", + "1f469-1f3ff-200d-1f4bb", + "1f469-1f3ff-200d-1f4bc", + "1f469-1f3ff-200d-1f527", + "1f469-1f3ff-200d-1f52c", + "1f469-1f3ff-200d-1f680", + "1f469-1f3ff-200d-1f692", + "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb", + "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc", + "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd", + "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe", + "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb", + "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc", + "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd", + "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe", + "1f469-1f3ff-200d-1f9af", + "1f469-1f3ff-200d-1f9b0", + "1f469-1f3ff-200d-1f9b1", + "1f469-1f3ff-200d-1f9b2", + "1f469-1f3ff-200d-1f9b3", + "1f469-1f3ff-200d-1f9bc", + "1f469-1f3ff-200d-1f9bd", + "1f469-1f3ff-200d-2695-fe0f", + "1f469-1f3ff-200d-2696-fe0f", + "1f469-1f3ff-200d-2708-fe0f", + "1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb", + "1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc", + "1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd", + "1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe", + "1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff", + "1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb", + "1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc", + "1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd", + "1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe", + "1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe", + "1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff", + "1f469-200d-1f33e", + "1f469-200d-1f373", + "1f469-200d-1f37c", + "1f469-200d-1f384", + "1f469-200d-1f393", + "1f469-200d-1f3a4", + "1f469-200d-1f3a8", + "1f469-200d-1f3eb", + "1f469-200d-1f3ed", + "1f469-200d-1f466", + "1f469-200d-1f466-200d-1f466", + "1f469-200d-1f467", + "1f469-200d-1f467-200d-1f466", + "1f469-200d-1f467-200d-1f467", + "1f469-200d-1f469-200d-1f466", + "1f469-200d-1f469-200d-1f466-200d-1f466", + "1f469-200d-1f469-200d-1f467", + "1f469-200d-1f469-200d-1f467-200d-1f466", + "1f469-200d-1f469-200d-1f467-200d-1f467", + "1f469-200d-1f4bb", + "1f469-200d-1f4bc", + "1f469-200d-1f527", + "1f469-200d-1f52c", + "1f469-200d-1f680", + "1f469-200d-1f692", + "1f469-200d-1f9af", + "1f469-200d-1f9b0", + "1f469-200d-1f9b1", + "1f469-200d-1f9b2", + "1f469-200d-1f9b3", + "1f469-200d-1f9bc", + "1f469-200d-1f9bd", + "1f469-200d-2695-fe0f", + "1f469-200d-2696-fe0f", + "1f469-200d-2708-fe0f", + "1f469-200d-2764-fe0f-200d-1f468", + "1f469-200d-2764-fe0f-200d-1f469", + "1f469-200d-2764-fe0f-200d-1f48b-200d-1f468", + "1f469-200d-2764-fe0f-200d-1f48b-200d-1f469", + "1f46a", + "1f46b", + "1f46b-1f3fb", + "1f46b-1f3fc", + "1f46b-1f3fd", + "1f46b-1f3fe", + "1f46b-1f3ff", + "1f46c", + "1f46c-1f3fb", + "1f46c-1f3fc", + "1f46c-1f3fd", + "1f46c-1f3fe", + "1f46c-1f3ff", + "1f46d", + "1f46d-1f3fb", + "1f46d-1f3fc", + "1f46d-1f3fd", + "1f46d-1f3fe", + "1f46d-1f3ff", + "1f46e", + "1f46e-1f3fb", + "1f46e-1f3fb-200d-2640-fe0f", + "1f46e-1f3fb-200d-2642-fe0f", + "1f46e-1f3fc", + "1f46e-1f3fc-200d-2640-fe0f", + "1f46e-1f3fc-200d-2642-fe0f", + "1f46e-1f3fd", + "1f46e-1f3fd-200d-2640-fe0f", + "1f46e-1f3fd-200d-2642-fe0f", + "1f46e-1f3fe", + "1f46e-1f3fe-200d-2640-fe0f", + "1f46e-1f3fe-200d-2642-fe0f", + "1f46e-1f3ff", + "1f46e-1f3ff-200d-2640-fe0f", + "1f46e-1f3ff-200d-2642-fe0f", + "1f46e-200d-2640-fe0f", + "1f46e-200d-2642-fe0f", + "1f46f", + "1f46f-200d-2640-fe0f", + "1f46f-200d-2642-fe0f", + "1f470", + "1f470-1f3fb", + "1f470-1f3fb-200d-2640-fe0f", + "1f470-1f3fb-200d-2642-fe0f", + "1f470-1f3fc", + "1f470-1f3fc-200d-2640-fe0f", + "1f470-1f3fc-200d-2642-fe0f", + "1f470-1f3fd", + "1f470-1f3fd-200d-2640-fe0f", + "1f470-1f3fd-200d-2642-fe0f", + "1f470-1f3fe", + "1f470-1f3fe-200d-2640-fe0f", + "1f470-1f3fe-200d-2642-fe0f", + "1f470-1f3ff", + "1f470-1f3ff-200d-2640-fe0f", + "1f470-1f3ff-200d-2642-fe0f", + "1f470-200d-2640-fe0f", + "1f470-200d-2642-fe0f", + "1f471", + "1f471-1f3fb", + "1f471-1f3fb-200d-2640-fe0f", + "1f471-1f3fb-200d-2642-fe0f", + "1f471-1f3fc", + "1f471-1f3fc-200d-2640-fe0f", + "1f471-1f3fc-200d-2642-fe0f", + "1f471-1f3fd", + "1f471-1f3fd-200d-2640-fe0f", + "1f471-1f3fd-200d-2642-fe0f", + "1f471-1f3fe", + "1f471-1f3fe-200d-2640-fe0f", + "1f471-1f3fe-200d-2642-fe0f", + "1f471-1f3ff", + "1f471-1f3ff-200d-2640-fe0f", + "1f471-1f3ff-200d-2642-fe0f", + "1f471-200d-2640-fe0f", + "1f471-200d-2642-fe0f", + "1f472", + "1f472-1f3fb", + "1f472-1f3fc", + "1f472-1f3fd", + "1f472-1f3fe", + "1f472-1f3ff", + "1f473", + "1f473-1f3fb", + "1f473-1f3fb-200d-2640-fe0f", + "1f473-1f3fb-200d-2642-fe0f", + "1f473-1f3fc", + "1f473-1f3fc-200d-2640-fe0f", + "1f473-1f3fc-200d-2642-fe0f", + "1f473-1f3fd", + "1f473-1f3fd-200d-2640-fe0f", + "1f473-1f3fd-200d-2642-fe0f", + "1f473-1f3fe", + "1f473-1f3fe-200d-2640-fe0f", + "1f473-1f3fe-200d-2642-fe0f", + "1f473-1f3ff", + "1f473-1f3ff-200d-2640-fe0f", + "1f473-1f3ff-200d-2642-fe0f", + "1f473-200d-2640-fe0f", + "1f473-200d-2642-fe0f", + "1f474", + "1f474-1f3fb", + "1f474-1f3fc", + "1f474-1f3fd", + "1f474-1f3fe", + "1f474-1f3ff", + "1f475", + "1f475-1f3fb", + "1f475-1f3fc", + "1f475-1f3fd", + "1f475-1f3fe", + "1f475-1f3ff", + "1f476", + "1f476-1f3fb", + "1f476-1f3fc", + "1f476-1f3fd", + "1f476-1f3fe", + "1f476-1f3ff", + "1f477", + "1f477-1f3fb", + "1f477-1f3fb-200d-2640-fe0f", + "1f477-1f3fb-200d-2642-fe0f", + "1f477-1f3fc", + "1f477-1f3fc-200d-2640-fe0f", + "1f477-1f3fc-200d-2642-fe0f", + "1f477-1f3fd", + "1f477-1f3fd-200d-2640-fe0f", + "1f477-1f3fd-200d-2642-fe0f", + "1f477-1f3fe", + "1f477-1f3fe-200d-2640-fe0f", + "1f477-1f3fe-200d-2642-fe0f", + "1f477-1f3ff", + "1f477-1f3ff-200d-2640-fe0f", + "1f477-1f3ff-200d-2642-fe0f", + "1f477-200d-2640-fe0f", + "1f477-200d-2642-fe0f", + "1f478", + "1f478-1f3fb", + "1f478-1f3fc", + "1f478-1f3fd", + "1f478-1f3fe", + "1f478-1f3ff", + "1f479", + "1f47a", + "1f47b", + "1f47c", + "1f47c-1f3fb", + "1f47c-1f3fc", + "1f47c-1f3fd", + "1f47c-1f3fe", + "1f47c-1f3ff", + "1f47d", + "1f47e", + "1f47f", + "1f480", + "1f481", + "1f481-1f3fb", + "1f481-1f3fb-200d-2640-fe0f", + "1f481-1f3fb-200d-2642-fe0f", + "1f481-1f3fc", + "1f481-1f3fc-200d-2640-fe0f", + "1f481-1f3fc-200d-2642-fe0f", + "1f481-1f3fd", + "1f481-1f3fd-200d-2640-fe0f", + "1f481-1f3fd-200d-2642-fe0f", + "1f481-1f3fe", + "1f481-1f3fe-200d-2640-fe0f", + "1f481-1f3fe-200d-2642-fe0f", + "1f481-1f3ff", + "1f481-1f3ff-200d-2640-fe0f", + "1f481-1f3ff-200d-2642-fe0f", + "1f481-200d-2640-fe0f", + "1f481-200d-2642-fe0f", + "1f482", + "1f482-1f3fb", + "1f482-1f3fb-200d-2640-fe0f", + "1f482-1f3fb-200d-2642-fe0f", + "1f482-1f3fc", + "1f482-1f3fc-200d-2640-fe0f", + "1f482-1f3fc-200d-2642-fe0f", + "1f482-1f3fd", + "1f482-1f3fd-200d-2640-fe0f", + "1f482-1f3fd-200d-2642-fe0f", + "1f482-1f3fe", + "1f482-1f3fe-200d-2640-fe0f", + "1f482-1f3fe-200d-2642-fe0f", + "1f482-1f3ff", + "1f482-1f3ff-200d-2640-fe0f", + "1f482-1f3ff-200d-2642-fe0f", + "1f482-200d-2640-fe0f", + "1f482-200d-2642-fe0f", + "1f483", + "1f483-1f3fb", + "1f483-1f3fc", + "1f483-1f3fd", + "1f483-1f3fe", + "1f483-1f3ff", + "1f484", + "1f485", + "1f485-1f3fb", + "1f485-1f3fc", + "1f485-1f3fd", + "1f485-1f3fe", + "1f485-1f3ff", + "1f486", + "1f486-1f3fb", + "1f486-1f3fb-200d-2640-fe0f", + "1f486-1f3fb-200d-2642-fe0f", + "1f486-1f3fc", + "1f486-1f3fc-200d-2640-fe0f", + "1f486-1f3fc-200d-2642-fe0f", + "1f486-1f3fd", + "1f486-1f3fd-200d-2640-fe0f", + "1f486-1f3fd-200d-2642-fe0f", + "1f486-1f3fe", + "1f486-1f3fe-200d-2640-fe0f", + "1f486-1f3fe-200d-2642-fe0f", + "1f486-1f3ff", + "1f486-1f3ff-200d-2640-fe0f", + "1f486-1f3ff-200d-2642-fe0f", + "1f486-200d-2640-fe0f", + "1f486-200d-2642-fe0f", + "1f487", + "1f487-1f3fb", + "1f487-1f3fb-200d-2640-fe0f", + "1f487-1f3fb-200d-2642-fe0f", + "1f487-1f3fc", + "1f487-1f3fc-200d-2640-fe0f", + "1f487-1f3fc-200d-2642-fe0f", + "1f487-1f3fd", + "1f487-1f3fd-200d-2640-fe0f", + "1f487-1f3fd-200d-2642-fe0f", + "1f487-1f3fe", + "1f487-1f3fe-200d-2640-fe0f", + "1f487-1f3fe-200d-2642-fe0f", + "1f487-1f3ff", + "1f487-1f3ff-200d-2640-fe0f", + "1f487-1f3ff-200d-2642-fe0f", + "1f487-200d-2640-fe0f", + "1f487-200d-2642-fe0f", + "1f488", + "1f489", + "1f48a", + "1f48b", + "1f48c", + "1f48d", + "1f48e", + "1f48f", + "1f48f-1f3fb", + "1f48f-1f3fc", + "1f48f-1f3fd", + "1f48f-1f3fe", + "1f48f-1f3ff", + "1f490", + "1f491", + "1f491-1f3fb", + "1f491-1f3fc", + "1f491-1f3fd", + "1f491-1f3fe", + "1f491-1f3ff", + "1f492", + "1f493", + "1f494", + "1f495", + "1f496", + "1f497", + "1f498", + "1f499", + "1f49a", + "1f49b", + "1f49c", + "1f49d", + "1f49e", + "1f49f", + "1f4a0", + "1f4a1", + "1f4a2", + "1f4a3", + "1f4a4", + "1f4a5", + "1f4a6", + "1f4a7", + "1f4a8", + "1f4a9", + "1f4aa", + "1f4aa-1f3fb", + "1f4aa-1f3fc", + "1f4aa-1f3fd", + "1f4aa-1f3fe", + "1f4aa-1f3ff", + "1f4ab", + "1f4ac", + "1f4ad", + "1f4ae", + "1f4af", + "1f4b0", + "1f4b1", + "1f4b2", + "1f4b3", + "1f4b4", + "1f4b5", + "1f4b6", + "1f4b7", + "1f4b8", + "1f4b9", + "1f4ba", + "1f4bb", + "1f4bc", + "1f4bd", + "1f4be", + "1f4bf", + "1f4c0", + "1f4c1", + "1f4c2", + "1f4c3", + "1f4c4", + "1f4c5", + "1f4c6", + "1f4c7", + "1f4c8", + "1f4c9", + "1f4ca", + "1f4cb", + "1f4cc", + "1f4cd", + "1f4ce", + "1f4cf", + "1f4d0", + "1f4d1", + "1f4d2", + "1f4d3", + "1f4d4", + "1f4d5", + "1f4d6", + "1f4d7", + "1f4d8", + "1f4d9", + "1f4da", + "1f4db", + "1f4dc", + "1f4dd", + "1f4de", + "1f4df", + "1f4e0", + "1f4e1", + "1f4e2", + "1f4e3", + "1f4e4", + "1f4e5", + "1f4e6", + "1f4e7", + "1f4e8", + "1f4e9", + "1f4ea", + "1f4eb", + "1f4ec", + "1f4ed", + "1f4ee", + "1f4ef", + "1f4f0", + "1f4f1", + "1f4f2", + "1f4f3", + "1f4f4", + "1f4f5", + "1f4f6", + "1f4f7", + "1f4f8", + "1f4f9", + "1f4fa", + "1f4fb", + "1f4fc", + "1f4fd", + "1f4ff", + "1f500", + "1f501", + "1f502", + "1f503", + "1f504", + "1f505", + "1f506", + "1f507", + "1f508", + "1f509", + "1f50a", + "1f50b", + "1f50c", + "1f50d", + "1f50e", + "1f50f", + "1f510", + "1f511", + "1f512", + "1f513", + "1f514", + "1f515", + "1f516", + "1f517", + "1f518", + "1f519", + "1f51a", + "1f51b", + "1f51c", + "1f51d", + "1f51e", + "1f51f", + "1f520", + "1f521", + "1f522", + "1f523", + "1f524", + "1f525", + "1f526", + "1f527", + "1f528", + "1f529", + "1f52a", + "1f52b", + "1f52c", + "1f52d", + "1f52e", + "1f52f", + "1f530", + "1f531", + "1f532", + "1f533", + "1f534", + "1f535", + "1f536", + "1f537", + "1f538", + "1f539", + "1f53a", + "1f53b", + "1f53c", + "1f53d", + "1f549", + "1f54a", + "1f54b", + "1f54c", + "1f54d", + "1f54e", + "1f550", + "1f551", + "1f552", + "1f553", + "1f554", + "1f555", + "1f556", + "1f557", + "1f558", + "1f559", + "1f55a", + "1f55b", + "1f55c", + "1f55d", + "1f55e", + "1f55f", + "1f560", + "1f561", + "1f562", + "1f563", + "1f564", + "1f565", + "1f566", + "1f567", + "1f56f", + "1f570", + "1f573", + "1f574", + "1f574-1f3fb", + "1f574-1f3fb-200d-2640-fe0f", + "1f574-1f3fb-200d-2642-fe0f", + "1f574-1f3fc", + "1f574-1f3fc-200d-2640-fe0f", + "1f574-1f3fc-200d-2642-fe0f", + "1f574-1f3fd", + "1f574-1f3fd-200d-2640-fe0f", + "1f574-1f3fd-200d-2642-fe0f", + "1f574-1f3fe", + "1f574-1f3fe-200d-2640-fe0f", + "1f574-1f3fe-200d-2642-fe0f", + "1f574-1f3ff", + "1f574-1f3ff-200d-2640-fe0f", + "1f574-1f3ff-200d-2642-fe0f", + "1f574-fe0f-200d-2640-fe0f", + "1f574-fe0f-200d-2642-fe0f", + "1f575", + "1f575-1f3fb", + "1f575-1f3fb-200d-2640-fe0f", + "1f575-1f3fb-200d-2642-fe0f", + "1f575-1f3fc", + "1f575-1f3fc-200d-2640-fe0f", + "1f575-1f3fc-200d-2642-fe0f", + "1f575-1f3fd", + "1f575-1f3fd-200d-2640-fe0f", + "1f575-1f3fd-200d-2642-fe0f", + "1f575-1f3fe", + "1f575-1f3fe-200d-2640-fe0f", + "1f575-1f3fe-200d-2642-fe0f", + "1f575-1f3ff", + "1f575-1f3ff-200d-2640-fe0f", + "1f575-1f3ff-200d-2642-fe0f", + "1f575-fe0f-200d-2640-fe0f", + "1f575-fe0f-200d-2642-fe0f", + "1f576", + "1f577", + "1f578", + "1f579", + "1f57a", + "1f57a-1f3fb", + "1f57a-1f3fc", + "1f57a-1f3fd", + "1f57a-1f3fe", + "1f57a-1f3ff", + "1f587", + "1f58a", + "1f58b", + "1f58c", + "1f58d", + "1f590", + "1f590-1f3fb", + "1f590-1f3fc", + "1f590-1f3fd", + "1f590-1f3fe", + "1f590-1f3ff", + "1f595", + "1f595-1f3fb", + "1f595-1f3fc", + "1f595-1f3fd", + "1f595-1f3fe", + "1f595-1f3ff", + "1f596", + "1f596-1f3fb", + "1f596-1f3fc", + "1f596-1f3fd", + "1f596-1f3fe", + "1f596-1f3ff", + "1f5a4", + "1f5a5", + "1f5a8", + "1f5b1", + "1f5b2", + "1f5bc", + "1f5c2", + "1f5c3", + "1f5c4", + "1f5d1", + "1f5d2", + "1f5d3", + "1f5dc", + "1f5dd", + "1f5de", + "1f5e1", + "1f5e3", + "1f5e8", + "1f5ef", + "1f5f3", + "1f5fa", + "1f5fb", + "1f5fc", + "1f5fd", + "1f5fe", + "1f5ff", + "1f600", + "1f601", + "1f602", + "1f603", + "1f604", + "1f605", + "1f606", + "1f607", + "1f608", + "1f609", + "1f60a", + "1f60b", + "1f60c", + "1f60d", + "1f60e", + "1f60f", + "1f610", + "1f611", + "1f612", + "1f613", + "1f614", + "1f615", + "1f616", + "1f617", + "1f618", + "1f619", + "1f61a", + "1f61b", + "1f61c", + "1f61d", + "1f61e", + "1f61f", + "1f620", + "1f621", + "1f622", + "1f623", + "1f624", + "1f625", + "1f626", + "1f627", + "1f628", + "1f629", + "1f62a", + "1f62b", + "1f62c", + "1f62d", + "1f62e", + "1f62e-200d-1f4a8", + "1f62f", + "1f630", + "1f631", + "1f632", + "1f633", + "1f634", + "1f635", + "1f635-200d-1f4ab", + "1f636", + "1f636-200d-1f32b-fe0f", + "1f637", + "1f638", + "1f639", + "1f63a", + "1f63b", + "1f63c", + "1f63d", + "1f63e", + "1f63f", + "1f640", + "1f641", + "1f642", + "1f643", + "1f644", + "1f645", + "1f645-1f3fb", + "1f645-1f3fb-200d-2640-fe0f", + "1f645-1f3fb-200d-2642-fe0f", + "1f645-1f3fc", + "1f645-1f3fc-200d-2640-fe0f", + "1f645-1f3fc-200d-2642-fe0f", + "1f645-1f3fd", + "1f645-1f3fd-200d-2640-fe0f", + "1f645-1f3fd-200d-2642-fe0f", + "1f645-1f3fe", + "1f645-1f3fe-200d-2640-fe0f", + "1f645-1f3fe-200d-2642-fe0f", + "1f645-1f3ff", + "1f645-1f3ff-200d-2640-fe0f", + "1f645-1f3ff-200d-2642-fe0f", + "1f645-200d-2640-fe0f", + "1f645-200d-2642-fe0f", + "1f646", + "1f646-1f3fb", + "1f646-1f3fb-200d-2640-fe0f", + "1f646-1f3fb-200d-2642-fe0f", + "1f646-1f3fc", + "1f646-1f3fc-200d-2640-fe0f", + "1f646-1f3fc-200d-2642-fe0f", + "1f646-1f3fd", + "1f646-1f3fd-200d-2640-fe0f", + "1f646-1f3fd-200d-2642-fe0f", + "1f646-1f3fe", + "1f646-1f3fe-200d-2640-fe0f", + "1f646-1f3fe-200d-2642-fe0f", + "1f646-1f3ff", + "1f646-1f3ff-200d-2640-fe0f", + "1f646-1f3ff-200d-2642-fe0f", + "1f646-200d-2640-fe0f", + "1f646-200d-2642-fe0f", + "1f647", + "1f647-1f3fb", + "1f647-1f3fb-200d-2640-fe0f", + "1f647-1f3fb-200d-2642-fe0f", + "1f647-1f3fc", + "1f647-1f3fc-200d-2640-fe0f", + "1f647-1f3fc-200d-2642-fe0f", + "1f647-1f3fd", + "1f647-1f3fd-200d-2640-fe0f", + "1f647-1f3fd-200d-2642-fe0f", + "1f647-1f3fe", + "1f647-1f3fe-200d-2640-fe0f", + "1f647-1f3fe-200d-2642-fe0f", + "1f647-1f3ff", + "1f647-1f3ff-200d-2640-fe0f", + "1f647-1f3ff-200d-2642-fe0f", + "1f647-200d-2640-fe0f", + "1f647-200d-2642-fe0f", + "1f648", + "1f649", + "1f64a", + "1f64b", + "1f64b-1f3fb", + "1f64b-1f3fb-200d-2640-fe0f", + "1f64b-1f3fb-200d-2642-fe0f", + "1f64b-1f3fc", + "1f64b-1f3fc-200d-2640-fe0f", + "1f64b-1f3fc-200d-2642-fe0f", + "1f64b-1f3fd", + "1f64b-1f3fd-200d-2640-fe0f", + "1f64b-1f3fd-200d-2642-fe0f", + "1f64b-1f3fe", + "1f64b-1f3fe-200d-2640-fe0f", + "1f64b-1f3fe-200d-2642-fe0f", + "1f64b-1f3ff", + "1f64b-1f3ff-200d-2640-fe0f", + "1f64b-1f3ff-200d-2642-fe0f", + "1f64b-200d-2640-fe0f", + "1f64b-200d-2642-fe0f", + "1f64c", + "1f64c-1f3fb", + "1f64c-1f3fc", + "1f64c-1f3fd", + "1f64c-1f3fe", + "1f64c-1f3ff", + "1f64d", + "1f64d-1f3fb", + "1f64d-1f3fb-200d-2640-fe0f", + "1f64d-1f3fb-200d-2642-fe0f", + "1f64d-1f3fc", + "1f64d-1f3fc-200d-2640-fe0f", + "1f64d-1f3fc-200d-2642-fe0f", + "1f64d-1f3fd", + "1f64d-1f3fd-200d-2640-fe0f", + "1f64d-1f3fd-200d-2642-fe0f", + "1f64d-1f3fe", + "1f64d-1f3fe-200d-2640-fe0f", + "1f64d-1f3fe-200d-2642-fe0f", + "1f64d-1f3ff", + "1f64d-1f3ff-200d-2640-fe0f", + "1f64d-1f3ff-200d-2642-fe0f", + "1f64d-200d-2640-fe0f", + "1f64d-200d-2642-fe0f", + "1f64e", + "1f64e-1f3fb", + "1f64e-1f3fb-200d-2640-fe0f", + "1f64e-1f3fb-200d-2642-fe0f", + "1f64e-1f3fc", + "1f64e-1f3fc-200d-2640-fe0f", + "1f64e-1f3fc-200d-2642-fe0f", + "1f64e-1f3fd", + "1f64e-1f3fd-200d-2640-fe0f", + "1f64e-1f3fd-200d-2642-fe0f", + "1f64e-1f3fe", + "1f64e-1f3fe-200d-2640-fe0f", + "1f64e-1f3fe-200d-2642-fe0f", + "1f64e-1f3ff", + "1f64e-1f3ff-200d-2640-fe0f", + "1f64e-1f3ff-200d-2642-fe0f", + "1f64e-200d-2640-fe0f", + "1f64e-200d-2642-fe0f", + "1f64f", + "1f64f-1f3fb", + "1f64f-1f3fc", + "1f64f-1f3fd", + "1f64f-1f3fe", + "1f64f-1f3ff", + "1f680", + "1f681", + "1f682", + "1f683", + "1f684", + "1f685", + "1f686", + "1f687", + "1f688", + "1f689", + "1f68a", + "1f68b", + "1f68c", + "1f68d", + "1f68e", + "1f68f", + "1f690", + "1f691", + "1f692", + "1f693", + "1f694", + "1f695", + "1f696", + "1f697", + "1f698", + "1f699", + "1f69a", + "1f69b", + "1f69c", + "1f69d", + "1f69e", + "1f69f", + "1f6a0", + "1f6a1", + "1f6a2", + "1f6a3", + "1f6a3-1f3fb", + "1f6a3-1f3fb-200d-2640-fe0f", + "1f6a3-1f3fb-200d-2642-fe0f", + "1f6a3-1f3fc", + "1f6a3-1f3fc-200d-2640-fe0f", + "1f6a3-1f3fc-200d-2642-fe0f", + "1f6a3-1f3fd", + "1f6a3-1f3fd-200d-2640-fe0f", + "1f6a3-1f3fd-200d-2642-fe0f", + "1f6a3-1f3fe", + "1f6a3-1f3fe-200d-2640-fe0f", + "1f6a3-1f3fe-200d-2642-fe0f", + "1f6a3-1f3ff", + "1f6a3-1f3ff-200d-2640-fe0f", + "1f6a3-1f3ff-200d-2642-fe0f", + "1f6a3-200d-2640-fe0f", + "1f6a3-200d-2642-fe0f", + "1f6a4", + "1f6a5", + "1f6a6", + "1f6a7", + "1f6a8", + "1f6a9", + "1f6aa", + "1f6ab", + "1f6ac", + "1f6ad", + "1f6ae", + "1f6af", + "1f6b0", + "1f6b1", + "1f6b2", + "1f6b3", + "1f6b4", + "1f6b4-1f3fb", + "1f6b4-1f3fb-200d-2640-fe0f", + "1f6b4-1f3fb-200d-2642-fe0f", + "1f6b4-1f3fc", + "1f6b4-1f3fc-200d-2640-fe0f", + "1f6b4-1f3fc-200d-2642-fe0f", + "1f6b4-1f3fd", + "1f6b4-1f3fd-200d-2640-fe0f", + "1f6b4-1f3fd-200d-2642-fe0f", + "1f6b4-1f3fe", + "1f6b4-1f3fe-200d-2640-fe0f", + "1f6b4-1f3fe-200d-2642-fe0f", + "1f6b4-1f3ff", + "1f6b4-1f3ff-200d-2640-fe0f", + "1f6b4-1f3ff-200d-2642-fe0f", + "1f6b4-200d-2640-fe0f", + "1f6b4-200d-2642-fe0f", + "1f6b5", + "1f6b5-1f3fb", + "1f6b5-1f3fb-200d-2640-fe0f", + "1f6b5-1f3fb-200d-2642-fe0f", + "1f6b5-1f3fc", + "1f6b5-1f3fc-200d-2640-fe0f", + "1f6b5-1f3fc-200d-2642-fe0f", + "1f6b5-1f3fd", + "1f6b5-1f3fd-200d-2640-fe0f", + "1f6b5-1f3fd-200d-2642-fe0f", + "1f6b5-1f3fe", + "1f6b5-1f3fe-200d-2640-fe0f", + "1f6b5-1f3fe-200d-2642-fe0f", + "1f6b5-1f3ff", + "1f6b5-1f3ff-200d-2640-fe0f", + "1f6b5-1f3ff-200d-2642-fe0f", + "1f6b5-200d-2640-fe0f", + "1f6b5-200d-2642-fe0f", + "1f6b6", + "1f6b6-1f3fb", + "1f6b6-1f3fb-200d-2640-fe0f", + "1f6b6-1f3fb-200d-2642-fe0f", + "1f6b6-1f3fc", + "1f6b6-1f3fc-200d-2640-fe0f", + "1f6b6-1f3fc-200d-2642-fe0f", + "1f6b6-1f3fd", + "1f6b6-1f3fd-200d-2640-fe0f", + "1f6b6-1f3fd-200d-2642-fe0f", + "1f6b6-1f3fe", + "1f6b6-1f3fe-200d-2640-fe0f", + "1f6b6-1f3fe-200d-2642-fe0f", + "1f6b6-1f3ff", + "1f6b6-1f3ff-200d-2640-fe0f", + "1f6b6-1f3ff-200d-2642-fe0f", + "1f6b6-200d-2640-fe0f", + "1f6b6-200d-2642-fe0f", + "1f6b7", + "1f6b8", + "1f6b9", + "1f6ba", + "1f6bb", + "1f6bc", + "1f6bd", + "1f6be", + "1f6bf", + "1f6c0", + "1f6c0-1f3fb", + "1f6c0-1f3fc", + "1f6c0-1f3fd", + "1f6c0-1f3fe", + "1f6c0-1f3ff", + "1f6c1", + "1f6c2", + "1f6c3", + "1f6c4", + "1f6c5", + "1f6cb", + "1f6cc", + "1f6cc-1f3fb", + "1f6cc-1f3fc", + "1f6cc-1f3fd", + "1f6cc-1f3fe", + "1f6cc-1f3ff", + "1f6cd", + "1f6ce", + "1f6cf", + "1f6d0", + "1f6d1", + "1f6d2", + "1f6d5", + "1f6d6", + "1f6d7", + "1f6dc", + "1f6dd", + "1f6de", + "1f6df", + "1f6e0", + "1f6e1", + "1f6e2", + "1f6e3", + "1f6e4", + "1f6e5", + "1f6e9", + "1f6eb", + "1f6ec", + "1f6f0", + "1f6f3", + "1f6f4", + "1f6f5", + "1f6f6", + "1f6f7", + "1f6f8", + "1f6f9", + "1f6fa", + "1f6fb", + "1f6fc", + "1f7e0", + "1f7e1", + "1f7e2", + "1f7e3", + "1f7e4", + "1f7e5", + "1f7e6", + "1f7e7", + "1f7e8", + "1f7e9", + "1f7ea", + "1f7eb", + "1f7f0", + "1f90c", + "1f90c-1f3fb", + "1f90c-1f3fc", + "1f90c-1f3fd", + "1f90c-1f3fe", + "1f90c-1f3ff", + "1f90d", + "1f90e", + "1f90f", + "1f90f-1f3fb", + "1f90f-1f3fc", + "1f90f-1f3fd", + "1f90f-1f3fe", + "1f90f-1f3ff", + "1f910", + "1f911", + "1f912", + "1f913", + "1f914", + "1f915", + "1f916", + "1f917", + "1f918", + "1f918-1f3fb", + "1f918-1f3fc", + "1f918-1f3fd", + "1f918-1f3fe", + "1f918-1f3ff", + "1f919", + "1f919-1f3fb", + "1f919-1f3fc", + "1f919-1f3fd", + "1f919-1f3fe", + "1f919-1f3ff", + "1f91a", + "1f91a-1f3fb", + "1f91a-1f3fc", + "1f91a-1f3fd", + "1f91a-1f3fe", + "1f91a-1f3ff", + "1f91b", + "1f91b-1f3fb", + "1f91b-1f3fc", + "1f91b-1f3fd", + "1f91b-1f3fe", + "1f91b-1f3ff", + "1f91c", + "1f91c-1f3fb", + "1f91c-1f3fc", + "1f91c-1f3fd", + "1f91c-1f3fe", + "1f91c-1f3ff", + "1f91d", + "1f91d-1f3fb", + "1f91d-1f3fc", + "1f91d-1f3fd", + "1f91d-1f3fe", + "1f91d-1f3ff", + "1f91e", + "1f91e-1f3fb", + "1f91e-1f3fc", + "1f91e-1f3fd", + "1f91e-1f3fe", + "1f91e-1f3ff", + "1f91f", + "1f91f-1f3fb", + "1f91f-1f3fc", + "1f91f-1f3fd", + "1f91f-1f3fe", + "1f91f-1f3ff", + "1f920", + "1f921", + "1f922", + "1f923", + "1f924", + "1f925", + "1f926", + "1f926-1f3fb", + "1f926-1f3fb-200d-2640-fe0f", + "1f926-1f3fb-200d-2642-fe0f", + "1f926-1f3fc", + "1f926-1f3fc-200d-2640-fe0f", + "1f926-1f3fc-200d-2642-fe0f", + "1f926-1f3fd", + "1f926-1f3fd-200d-2640-fe0f", + "1f926-1f3fd-200d-2642-fe0f", + "1f926-1f3fe", + "1f926-1f3fe-200d-2640-fe0f", + "1f926-1f3fe-200d-2642-fe0f", + "1f926-1f3ff", + "1f926-1f3ff-200d-2640-fe0f", + "1f926-1f3ff-200d-2642-fe0f", + "1f926-200d-2640-fe0f", + "1f926-200d-2642-fe0f", + "1f927", + "1f928", + "1f929", + "1f92a", + "1f92b", + "1f92c", + "1f92d", + "1f92e", + "1f92f", + "1f930", + "1f930-1f3fb", + "1f930-1f3fc", + "1f930-1f3fd", + "1f930-1f3fe", + "1f930-1f3ff", + "1f931", + "1f931-1f3fb", + "1f931-1f3fc", + "1f931-1f3fd", + "1f931-1f3fe", + "1f931-1f3ff", + "1f932", + "1f932-1f3fb", + "1f932-1f3fc", + "1f932-1f3fd", + "1f932-1f3fe", + "1f932-1f3ff", + "1f933", + "1f933-1f3fb", + "1f933-1f3fc", + "1f933-1f3fd", + "1f933-1f3fe", + "1f933-1f3ff", + "1f934", + "1f934-1f3fb", + "1f934-1f3fc", + "1f934-1f3fd", + "1f934-1f3fe", + "1f934-1f3ff", + "1f935", + "1f935-1f3fb", + "1f935-1f3fb-200d-2640-fe0f", + "1f935-1f3fb-200d-2642-fe0f", + "1f935-1f3fc", + "1f935-1f3fc-200d-2640-fe0f", + "1f935-1f3fc-200d-2642-fe0f", + "1f935-1f3fd", + "1f935-1f3fd-200d-2640-fe0f", + "1f935-1f3fd-200d-2642-fe0f", + "1f935-1f3fe", + "1f935-1f3fe-200d-2640-fe0f", + "1f935-1f3fe-200d-2642-fe0f", + "1f935-1f3ff", + "1f935-1f3ff-200d-2640-fe0f", + "1f935-1f3ff-200d-2642-fe0f", + "1f935-200d-2640-fe0f", + "1f935-200d-2642-fe0f", + "1f936", + "1f936-1f3fb", + "1f936-1f3fc", + "1f936-1f3fd", + "1f936-1f3fe", + "1f936-1f3ff", + "1f937", + "1f937-1f3fb", + "1f937-1f3fb-200d-2640-fe0f", + "1f937-1f3fb-200d-2642-fe0f", + "1f937-1f3fc", + "1f937-1f3fc-200d-2640-fe0f", + "1f937-1f3fc-200d-2642-fe0f", + "1f937-1f3fd", + "1f937-1f3fd-200d-2640-fe0f", + "1f937-1f3fd-200d-2642-fe0f", + "1f937-1f3fe", + "1f937-1f3fe-200d-2640-fe0f", + "1f937-1f3fe-200d-2642-fe0f", + "1f937-1f3ff", + "1f937-1f3ff-200d-2640-fe0f", + "1f937-1f3ff-200d-2642-fe0f", + "1f937-200d-2640-fe0f", + "1f937-200d-2642-fe0f", + "1f938", + "1f938-1f3fb", + "1f938-1f3fb-200d-2640-fe0f", + "1f938-1f3fb-200d-2642-fe0f", + "1f938-1f3fc", + "1f938-1f3fc-200d-2640-fe0f", + "1f938-1f3fc-200d-2642-fe0f", + "1f938-1f3fd", + "1f938-1f3fd-200d-2640-fe0f", + "1f938-1f3fd-200d-2642-fe0f", + "1f938-1f3fe", + "1f938-1f3fe-200d-2640-fe0f", + "1f938-1f3fe-200d-2642-fe0f", + "1f938-1f3ff", + "1f938-1f3ff-200d-2640-fe0f", + "1f938-1f3ff-200d-2642-fe0f", + "1f938-200d-2640-fe0f", + "1f938-200d-2642-fe0f", + "1f939", + "1f939-1f3fb", + "1f939-1f3fb-200d-2640-fe0f", + "1f939-1f3fb-200d-2642-fe0f", + "1f939-1f3fc", + "1f939-1f3fc-200d-2640-fe0f", + "1f939-1f3fc-200d-2642-fe0f", + "1f939-1f3fd", + "1f939-1f3fd-200d-2640-fe0f", + "1f939-1f3fd-200d-2642-fe0f", + "1f939-1f3fe", + "1f939-1f3fe-200d-2640-fe0f", + "1f939-1f3fe-200d-2642-fe0f", + "1f939-1f3ff", + "1f939-1f3ff-200d-2640-fe0f", + "1f939-1f3ff-200d-2642-fe0f", + "1f939-200d-2640-fe0f", + "1f939-200d-2642-fe0f", + "1f93a", + "1f93c", + "1f93c-200d-2640-fe0f", + "1f93c-200d-2642-fe0f", + "1f93d", + "1f93d-1f3fb", + "1f93d-1f3fb-200d-2640-fe0f", + "1f93d-1f3fb-200d-2642-fe0f", + "1f93d-1f3fc", + "1f93d-1f3fc-200d-2640-fe0f", + "1f93d-1f3fc-200d-2642-fe0f", + "1f93d-1f3fd", + "1f93d-1f3fd-200d-2640-fe0f", + "1f93d-1f3fd-200d-2642-fe0f", + "1f93d-1f3fe", + "1f93d-1f3fe-200d-2640-fe0f", + "1f93d-1f3fe-200d-2642-fe0f", + "1f93d-1f3ff", + "1f93d-1f3ff-200d-2640-fe0f", + "1f93d-1f3ff-200d-2642-fe0f", + "1f93d-200d-2640-fe0f", + "1f93d-200d-2642-fe0f", + "1f93e", + "1f93e-1f3fb", + "1f93e-1f3fb-200d-2640-fe0f", + "1f93e-1f3fb-200d-2642-fe0f", + "1f93e-1f3fc", + "1f93e-1f3fc-200d-2640-fe0f", + "1f93e-1f3fc-200d-2642-fe0f", + "1f93e-1f3fd", + "1f93e-1f3fd-200d-2640-fe0f", + "1f93e-1f3fd-200d-2642-fe0f", + "1f93e-1f3fe", + "1f93e-1f3fe-200d-2640-fe0f", + "1f93e-1f3fe-200d-2642-fe0f", + "1f93e-1f3ff", + "1f93e-1f3ff-200d-2640-fe0f", + "1f93e-1f3ff-200d-2642-fe0f", + "1f93e-200d-2640-fe0f", + "1f93e-200d-2642-fe0f", + "1f93f", + "1f940", + "1f941", + "1f942", + "1f943", + "1f944", + "1f945", + "1f947", + "1f948", + "1f949", + "1f94a", + "1f94b", + "1f94c", + "1f94d", + "1f94e", + "1f94f", + "1f950", + "1f951", + "1f952", + "1f953", + "1f954", + "1f955", + "1f956", + "1f957", + "1f958", + "1f959", + "1f95a", + "1f95b", + "1f95c", + "1f95d", + "1f95e", + "1f95f", + "1f960", + "1f961", + "1f962", + "1f963", + "1f964", + "1f965", + "1f966", + "1f967", + "1f968", + "1f969", + "1f96a", + "1f96b", + "1f96c", + "1f96d", + "1f96e", + "1f96f", + "1f970", + "1f971", + "1f972", + "1f973", + "1f974", + "1f975", + "1f976", + "1f977", + "1f977-1f3fb", + "1f977-1f3fc", + "1f977-1f3fd", + "1f977-1f3fe", + "1f977-1f3ff", + "1f978", + "1f979", + "1f97a", + "1f97b", + "1f97c", + "1f97d", + "1f97e", + "1f97f", + "1f980", + "1f981", + "1f982", + "1f983", + "1f984", + "1f985", + "1f986", + "1f987", + "1f988", + "1f989", + "1f98a", + "1f98b", + "1f98c", + "1f98d", + "1f98e", + "1f98f", + "1f990", + "1f991", + "1f992", + "1f993", + "1f994", + "1f995", + "1f996", + "1f997", + "1f998", + "1f999", + "1f99a", + "1f99b", + "1f99c", + "1f99d", + "1f99e", + "1f99f", + "1f9a0", + "1f9a1", + "1f9a2", + "1f9a3", + "1f9a4", + "1f9a5", + "1f9a6", + "1f9a7", + "1f9a8", + "1f9a9", + "1f9aa", + "1f9ab", + "1f9ac", + "1f9ad", + "1f9ae", + "1f9af", + "1f9b0", + "1f9b1", + "1f9b2", + "1f9b3", + "1f9b4", + "1f9b5", + "1f9b5-1f3fb", + "1f9b5-1f3fc", + "1f9b5-1f3fd", + "1f9b5-1f3fe", + "1f9b5-1f3ff", + "1f9b6", + "1f9b6-1f3fb", + "1f9b6-1f3fc", + "1f9b6-1f3fd", + "1f9b6-1f3fe", + "1f9b6-1f3ff", + "1f9b7", + "1f9b8", + "1f9b8-1f3fb", + "1f9b8-1f3fb-200d-2640-fe0f", + "1f9b8-1f3fb-200d-2642-fe0f", + "1f9b8-1f3fc", + "1f9b8-1f3fc-200d-2640-fe0f", + "1f9b8-1f3fc-200d-2642-fe0f", + "1f9b8-1f3fd", + "1f9b8-1f3fd-200d-2640-fe0f", + "1f9b8-1f3fd-200d-2642-fe0f", + "1f9b8-1f3fe", + "1f9b8-1f3fe-200d-2640-fe0f", + "1f9b8-1f3fe-200d-2642-fe0f", + "1f9b8-1f3ff", + "1f9b8-1f3ff-200d-2640-fe0f", + "1f9b8-1f3ff-200d-2642-fe0f", + "1f9b8-200d-2640-fe0f", + "1f9b8-200d-2642-fe0f", + "1f9b9", + "1f9b9-1f3fb", + "1f9b9-1f3fb-200d-2640-fe0f", + "1f9b9-1f3fb-200d-2642-fe0f", + "1f9b9-1f3fc", + "1f9b9-1f3fc-200d-2640-fe0f", + "1f9b9-1f3fc-200d-2642-fe0f", + "1f9b9-1f3fd", + "1f9b9-1f3fd-200d-2640-fe0f", + "1f9b9-1f3fd-200d-2642-fe0f", + "1f9b9-1f3fe", + "1f9b9-1f3fe-200d-2640-fe0f", + "1f9b9-1f3fe-200d-2642-fe0f", + "1f9b9-1f3ff", + "1f9b9-1f3ff-200d-2640-fe0f", + "1f9b9-1f3ff-200d-2642-fe0f", + "1f9b9-200d-2640-fe0f", + "1f9b9-200d-2642-fe0f", + "1f9ba", + "1f9bb", + "1f9bb-1f3fb", + "1f9bb-1f3fc", + "1f9bb-1f3fd", + "1f9bb-1f3fe", + "1f9bb-1f3ff", + "1f9bc", + "1f9bd", + "1f9be", + "1f9bf", + "1f9c0", + "1f9c1", + "1f9c2", + "1f9c3", + "1f9c4", + "1f9c5", + "1f9c6", + "1f9c7", + "1f9c8", + "1f9c9", + "1f9ca", + "1f9cb", + "1f9cc", + "1f9cd", + "1f9cd-1f3fb", + "1f9cd-1f3fb-200d-2640-fe0f", + "1f9cd-1f3fb-200d-2642-fe0f", + "1f9cd-1f3fc", + "1f9cd-1f3fc-200d-2640-fe0f", + "1f9cd-1f3fc-200d-2642-fe0f", + "1f9cd-1f3fd", + "1f9cd-1f3fd-200d-2640-fe0f", + "1f9cd-1f3fd-200d-2642-fe0f", + "1f9cd-1f3fe", + "1f9cd-1f3fe-200d-2640-fe0f", + "1f9cd-1f3fe-200d-2642-fe0f", + "1f9cd-1f3ff", + "1f9cd-1f3ff-200d-2640-fe0f", + "1f9cd-1f3ff-200d-2642-fe0f", + "1f9cd-200d-2640-fe0f", + "1f9cd-200d-2642-fe0f", + "1f9ce", + "1f9ce-1f3fb", + "1f9ce-1f3fb-200d-2640-fe0f", + "1f9ce-1f3fb-200d-2642-fe0f", + "1f9ce-1f3fc", + "1f9ce-1f3fc-200d-2640-fe0f", + "1f9ce-1f3fc-200d-2642-fe0f", + "1f9ce-1f3fd", + "1f9ce-1f3fd-200d-2640-fe0f", + "1f9ce-1f3fd-200d-2642-fe0f", + "1f9ce-1f3fe", + "1f9ce-1f3fe-200d-2640-fe0f", + "1f9ce-1f3fe-200d-2642-fe0f", + "1f9ce-1f3ff", + "1f9ce-1f3ff-200d-2640-fe0f", + "1f9ce-1f3ff-200d-2642-fe0f", + "1f9ce-200d-2640-fe0f", + "1f9ce-200d-2642-fe0f", + "1f9cf", + "1f9cf-1f3fb", + "1f9cf-1f3fb-200d-2640-fe0f", + "1f9cf-1f3fb-200d-2642-fe0f", + "1f9cf-1f3fc", + "1f9cf-1f3fc-200d-2640-fe0f", + "1f9cf-1f3fc-200d-2642-fe0f", + "1f9cf-1f3fd", + "1f9cf-1f3fd-200d-2640-fe0f", + "1f9cf-1f3fd-200d-2642-fe0f", + "1f9cf-1f3fe", + "1f9cf-1f3fe-200d-2640-fe0f", + "1f9cf-1f3fe-200d-2642-fe0f", + "1f9cf-1f3ff", + "1f9cf-1f3ff-200d-2640-fe0f", + "1f9cf-1f3ff-200d-2642-fe0f", + "1f9cf-200d-2640-fe0f", + "1f9cf-200d-2642-fe0f", + "1f9d0", + "1f9d1", + "1f9d1-1f3fb", + "1f9d1-1f3fb-200d-1f33e", + "1f9d1-1f3fb-200d-1f373", + "1f9d1-1f3fb-200d-1f37c", + "1f9d1-1f3fb-200d-1f384", + "1f9d1-1f3fb-200d-1f393", + "1f9d1-1f3fb-200d-1f3a4", + "1f9d1-1f3fb-200d-1f3a8", + "1f9d1-1f3fb-200d-1f3eb", + "1f9d1-1f3fb-200d-1f3ed", + "1f9d1-1f3fb-200d-1f4bb", + "1f9d1-1f3fb-200d-1f4bc", + "1f9d1-1f3fb-200d-1f527", + "1f9d1-1f3fb-200d-1f52c", + "1f9d1-1f3fb-200d-1f680", + "1f9d1-1f3fb-200d-1f692", + "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb", + "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc", + "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd", + "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe", + "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff", + "1f9d1-1f3fb-200d-1f9af", + "1f9d1-1f3fb-200d-1f9b0", + "1f9d1-1f3fb-200d-1f9b1", + "1f9d1-1f3fb-200d-1f9b2", + "1f9d1-1f3fb-200d-1f9b3", + "1f9d1-1f3fb-200d-1f9bc", + "1f9d1-1f3fb-200d-1f9bd", + "1f9d1-1f3fb-200d-2695-fe0f", + "1f9d1-1f3fb-200d-2696-fe0f", + "1f9d1-1f3fb-200d-2708-fe0f", + "1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc", + "1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd", + "1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe", + "1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff", + "1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc", + "1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd", + "1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe", + "1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff", + "1f9d1-1f3fc", + "1f9d1-1f3fc-200d-1f33e", + "1f9d1-1f3fc-200d-1f373", + "1f9d1-1f3fc-200d-1f37c", + "1f9d1-1f3fc-200d-1f384", + "1f9d1-1f3fc-200d-1f393", + "1f9d1-1f3fc-200d-1f3a4", + "1f9d1-1f3fc-200d-1f3a8", + "1f9d1-1f3fc-200d-1f3eb", + "1f9d1-1f3fc-200d-1f3ed", + "1f9d1-1f3fc-200d-1f4bb", + "1f9d1-1f3fc-200d-1f4bc", + "1f9d1-1f3fc-200d-1f527", + "1f9d1-1f3fc-200d-1f52c", + "1f9d1-1f3fc-200d-1f680", + "1f9d1-1f3fc-200d-1f692", + "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb", + "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc", + "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd", + "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe", + "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff", + "1f9d1-1f3fc-200d-1f9af", + "1f9d1-1f3fc-200d-1f9b0", + "1f9d1-1f3fc-200d-1f9b1", + "1f9d1-1f3fc-200d-1f9b2", + "1f9d1-1f3fc-200d-1f9b3", + "1f9d1-1f3fc-200d-1f9bc", + "1f9d1-1f3fc-200d-1f9bd", + "1f9d1-1f3fc-200d-2695-fe0f", + "1f9d1-1f3fc-200d-2696-fe0f", + "1f9d1-1f3fc-200d-2708-fe0f", + "1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb", + "1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd", + "1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe", + "1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff", + "1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb", + "1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd", + "1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe", + "1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff", + "1f9d1-1f3fd", + "1f9d1-1f3fd-200d-1f33e", + "1f9d1-1f3fd-200d-1f373", + "1f9d1-1f3fd-200d-1f37c", + "1f9d1-1f3fd-200d-1f384", + "1f9d1-1f3fd-200d-1f393", + "1f9d1-1f3fd-200d-1f3a4", + "1f9d1-1f3fd-200d-1f3a8", + "1f9d1-1f3fd-200d-1f3eb", + "1f9d1-1f3fd-200d-1f3ed", + "1f9d1-1f3fd-200d-1f4bb", + "1f9d1-1f3fd-200d-1f4bc", + "1f9d1-1f3fd-200d-1f527", + "1f9d1-1f3fd-200d-1f52c", + "1f9d1-1f3fd-200d-1f680", + "1f9d1-1f3fd-200d-1f692", + "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb", + "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc", + "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd", + "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe", + "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff", + "1f9d1-1f3fd-200d-1f9af", + "1f9d1-1f3fd-200d-1f9b0", + "1f9d1-1f3fd-200d-1f9b1", + "1f9d1-1f3fd-200d-1f9b2", + "1f9d1-1f3fd-200d-1f9b3", + "1f9d1-1f3fd-200d-1f9bc", + "1f9d1-1f3fd-200d-1f9bd", + "1f9d1-1f3fd-200d-2695-fe0f", + "1f9d1-1f3fd-200d-2696-fe0f", + "1f9d1-1f3fd-200d-2708-fe0f", + "1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb", + "1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc", + "1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe", + "1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff", + "1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb", + "1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc", + "1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe", + "1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff", + "1f9d1-1f3fe", + "1f9d1-1f3fe-200d-1f33e", + "1f9d1-1f3fe-200d-1f373", + "1f9d1-1f3fe-200d-1f37c", + "1f9d1-1f3fe-200d-1f384", + "1f9d1-1f3fe-200d-1f393", + "1f9d1-1f3fe-200d-1f3a4", + "1f9d1-1f3fe-200d-1f3a8", + "1f9d1-1f3fe-200d-1f3eb", + "1f9d1-1f3fe-200d-1f3ed", + "1f9d1-1f3fe-200d-1f4bb", + "1f9d1-1f3fe-200d-1f4bc", + "1f9d1-1f3fe-200d-1f527", + "1f9d1-1f3fe-200d-1f52c", + "1f9d1-1f3fe-200d-1f680", + "1f9d1-1f3fe-200d-1f692", + "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb", + "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc", + "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd", + "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe", + "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff", + "1f9d1-1f3fe-200d-1f9af", + "1f9d1-1f3fe-200d-1f9b0", + "1f9d1-1f3fe-200d-1f9b1", + "1f9d1-1f3fe-200d-1f9b2", + "1f9d1-1f3fe-200d-1f9b3", + "1f9d1-1f3fe-200d-1f9bc", + "1f9d1-1f3fe-200d-1f9bd", + "1f9d1-1f3fe-200d-2695-fe0f", + "1f9d1-1f3fe-200d-2696-fe0f", + "1f9d1-1f3fe-200d-2708-fe0f", + "1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb", + "1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc", + "1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd", + "1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff", + "1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb", + "1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc", + "1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd", + "1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff", + "1f9d1-1f3ff", + "1f9d1-1f3ff-200d-1f33e", + "1f9d1-1f3ff-200d-1f373", + "1f9d1-1f3ff-200d-1f37c", + "1f9d1-1f3ff-200d-1f384", + "1f9d1-1f3ff-200d-1f393", + "1f9d1-1f3ff-200d-1f3a4", + "1f9d1-1f3ff-200d-1f3a8", + "1f9d1-1f3ff-200d-1f3eb", + "1f9d1-1f3ff-200d-1f3ed", + "1f9d1-1f3ff-200d-1f4bb", + "1f9d1-1f3ff-200d-1f4bc", + "1f9d1-1f3ff-200d-1f527", + "1f9d1-1f3ff-200d-1f52c", + "1f9d1-1f3ff-200d-1f680", + "1f9d1-1f3ff-200d-1f692", + "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb", + "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc", + "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd", + "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe", + "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff", + "1f9d1-1f3ff-200d-1f9af", + "1f9d1-1f3ff-200d-1f9b0", + "1f9d1-1f3ff-200d-1f9b1", + "1f9d1-1f3ff-200d-1f9b2", + "1f9d1-1f3ff-200d-1f9b3", + "1f9d1-1f3ff-200d-1f9bc", + "1f9d1-1f3ff-200d-1f9bd", + "1f9d1-1f3ff-200d-2695-fe0f", + "1f9d1-1f3ff-200d-2696-fe0f", + "1f9d1-1f3ff-200d-2708-fe0f", + "1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb", + "1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc", + "1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd", + "1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe", + "1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb", + "1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc", + "1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd", + "1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe", + "1f9d1-200d-1f33e", + "1f9d1-200d-1f373", + "1f9d1-200d-1f37c", + "1f9d1-200d-1f384", + "1f9d1-200d-1f393", + "1f9d1-200d-1f3a4", + "1f9d1-200d-1f3a8", + "1f9d1-200d-1f3eb", + "1f9d1-200d-1f3ed", + "1f9d1-200d-1f4bb", + "1f9d1-200d-1f4bc", + "1f9d1-200d-1f527", + "1f9d1-200d-1f52c", + "1f9d1-200d-1f680", + "1f9d1-200d-1f692", + "1f9d1-200d-1f91d-200d-1f9d1", + "1f9d1-200d-1f9af", + "1f9d1-200d-1f9b0", + "1f9d1-200d-1f9b1", + "1f9d1-200d-1f9b2", + "1f9d1-200d-1f9b3", + "1f9d1-200d-1f9bc", + "1f9d1-200d-1f9bd", + "1f9d1-200d-2695-fe0f", + "1f9d1-200d-2696-fe0f", + "1f9d1-200d-2708-fe0f", + "1f9d2", + "1f9d2-1f3fb", + "1f9d2-1f3fc", + "1f9d2-1f3fd", + "1f9d2-1f3fe", + "1f9d2-1f3ff", + "1f9d3", + "1f9d3-1f3fb", + "1f9d3-1f3fc", + "1f9d3-1f3fd", + "1f9d3-1f3fe", + "1f9d3-1f3ff", + "1f9d4", + "1f9d4-1f3fb", + "1f9d4-1f3fb-200d-2640-fe0f", + "1f9d4-1f3fb-200d-2642-fe0f", + "1f9d4-1f3fc", + "1f9d4-1f3fc-200d-2640-fe0f", + "1f9d4-1f3fc-200d-2642-fe0f", + "1f9d4-1f3fd", + "1f9d4-1f3fd-200d-2640-fe0f", + "1f9d4-1f3fd-200d-2642-fe0f", + "1f9d4-1f3fe", + "1f9d4-1f3fe-200d-2640-fe0f", + "1f9d4-1f3fe-200d-2642-fe0f", + "1f9d4-1f3ff", + "1f9d4-1f3ff-200d-2640-fe0f", + "1f9d4-1f3ff-200d-2642-fe0f", + "1f9d4-200d-2640-fe0f", + "1f9d4-200d-2642-fe0f", + "1f9d5", + "1f9d5-1f3fb", + "1f9d5-1f3fc", + "1f9d5-1f3fd", + "1f9d5-1f3fe", + "1f9d5-1f3ff", + "1f9d6", + "1f9d6-1f3fb", + "1f9d6-1f3fb-200d-2640-fe0f", + "1f9d6-1f3fb-200d-2642-fe0f", + "1f9d6-1f3fc", + "1f9d6-1f3fc-200d-2640-fe0f", + "1f9d6-1f3fc-200d-2642-fe0f", + "1f9d6-1f3fd", + "1f9d6-1f3fd-200d-2640-fe0f", + "1f9d6-1f3fd-200d-2642-fe0f", + "1f9d6-1f3fe", + "1f9d6-1f3fe-200d-2640-fe0f", + "1f9d6-1f3fe-200d-2642-fe0f", + "1f9d6-1f3ff", + "1f9d6-1f3ff-200d-2640-fe0f", + "1f9d6-1f3ff-200d-2642-fe0f", + "1f9d6-200d-2640-fe0f", + "1f9d6-200d-2642-fe0f", + "1f9d7", + "1f9d7-1f3fb", + "1f9d7-1f3fb-200d-2640-fe0f", + "1f9d7-1f3fb-200d-2642-fe0f", + "1f9d7-1f3fc", + "1f9d7-1f3fc-200d-2640-fe0f", + "1f9d7-1f3fc-200d-2642-fe0f", + "1f9d7-1f3fd", + "1f9d7-1f3fd-200d-2640-fe0f", + "1f9d7-1f3fd-200d-2642-fe0f", + "1f9d7-1f3fe", + "1f9d7-1f3fe-200d-2640-fe0f", + "1f9d7-1f3fe-200d-2642-fe0f", + "1f9d7-1f3ff", + "1f9d7-1f3ff-200d-2640-fe0f", + "1f9d7-1f3ff-200d-2642-fe0f", + "1f9d7-200d-2640-fe0f", + "1f9d7-200d-2642-fe0f", + "1f9d8", + "1f9d8-1f3fb", + "1f9d8-1f3fb-200d-2640-fe0f", + "1f9d8-1f3fb-200d-2642-fe0f", + "1f9d8-1f3fc", + "1f9d8-1f3fc-200d-2640-fe0f", + "1f9d8-1f3fc-200d-2642-fe0f", + "1f9d8-1f3fd", + "1f9d8-1f3fd-200d-2640-fe0f", + "1f9d8-1f3fd-200d-2642-fe0f", + "1f9d8-1f3fe", + "1f9d8-1f3fe-200d-2640-fe0f", + "1f9d8-1f3fe-200d-2642-fe0f", + "1f9d8-1f3ff", + "1f9d8-1f3ff-200d-2640-fe0f", + "1f9d8-1f3ff-200d-2642-fe0f", + "1f9d8-200d-2640-fe0f", + "1f9d8-200d-2642-fe0f", + "1f9d9", + "1f9d9-1f3fb", + "1f9d9-1f3fb-200d-2640-fe0f", + "1f9d9-1f3fb-200d-2642-fe0f", + "1f9d9-1f3fc", + "1f9d9-1f3fc-200d-2640-fe0f", + "1f9d9-1f3fc-200d-2642-fe0f", + "1f9d9-1f3fd", + "1f9d9-1f3fd-200d-2640-fe0f", + "1f9d9-1f3fd-200d-2642-fe0f", + "1f9d9-1f3fe", + "1f9d9-1f3fe-200d-2640-fe0f", + "1f9d9-1f3fe-200d-2642-fe0f", + "1f9d9-1f3ff", + "1f9d9-1f3ff-200d-2640-fe0f", + "1f9d9-1f3ff-200d-2642-fe0f", + "1f9d9-200d-2640-fe0f", + "1f9d9-200d-2642-fe0f", + "1f9da", + "1f9da-1f3fb", + "1f9da-1f3fb-200d-2640-fe0f", + "1f9da-1f3fb-200d-2642-fe0f", + "1f9da-1f3fc", + "1f9da-1f3fc-200d-2640-fe0f", + "1f9da-1f3fc-200d-2642-fe0f", + "1f9da-1f3fd", + "1f9da-1f3fd-200d-2640-fe0f", + "1f9da-1f3fd-200d-2642-fe0f", + "1f9da-1f3fe", + "1f9da-1f3fe-200d-2640-fe0f", + "1f9da-1f3fe-200d-2642-fe0f", + "1f9da-1f3ff", + "1f9da-1f3ff-200d-2640-fe0f", + "1f9da-1f3ff-200d-2642-fe0f", + "1f9da-200d-2640-fe0f", + "1f9da-200d-2642-fe0f", + "1f9db", + "1f9db-1f3fb", + "1f9db-1f3fb-200d-2640-fe0f", + "1f9db-1f3fb-200d-2642-fe0f", + "1f9db-1f3fc", + "1f9db-1f3fc-200d-2640-fe0f", + "1f9db-1f3fc-200d-2642-fe0f", + "1f9db-1f3fd", + "1f9db-1f3fd-200d-2640-fe0f", + "1f9db-1f3fd-200d-2642-fe0f", + "1f9db-1f3fe", + "1f9db-1f3fe-200d-2640-fe0f", + "1f9db-1f3fe-200d-2642-fe0f", + "1f9db-1f3ff", + "1f9db-1f3ff-200d-2640-fe0f", + "1f9db-1f3ff-200d-2642-fe0f", + "1f9db-200d-2640-fe0f", + "1f9db-200d-2642-fe0f", + "1f9dc", + "1f9dc-1f3fb", + "1f9dc-1f3fb-200d-2640-fe0f", + "1f9dc-1f3fb-200d-2642-fe0f", + "1f9dc-1f3fc", + "1f9dc-1f3fc-200d-2640-fe0f", + "1f9dc-1f3fc-200d-2642-fe0f", + "1f9dc-1f3fd", + "1f9dc-1f3fd-200d-2640-fe0f", + "1f9dc-1f3fd-200d-2642-fe0f", + "1f9dc-1f3fe", + "1f9dc-1f3fe-200d-2640-fe0f", + "1f9dc-1f3fe-200d-2642-fe0f", + "1f9dc-1f3ff", + "1f9dc-1f3ff-200d-2640-fe0f", + "1f9dc-1f3ff-200d-2642-fe0f", + "1f9dc-200d-2640-fe0f", + "1f9dc-200d-2642-fe0f", + "1f9dd", + "1f9dd-1f3fb", + "1f9dd-1f3fb-200d-2640-fe0f", + "1f9dd-1f3fb-200d-2642-fe0f", + "1f9dd-1f3fc", + "1f9dd-1f3fc-200d-2640-fe0f", + "1f9dd-1f3fc-200d-2642-fe0f", + "1f9dd-1f3fd", + "1f9dd-1f3fd-200d-2640-fe0f", + "1f9dd-1f3fd-200d-2642-fe0f", + "1f9dd-1f3fe", + "1f9dd-1f3fe-200d-2640-fe0f", + "1f9dd-1f3fe-200d-2642-fe0f", + "1f9dd-1f3ff", + "1f9dd-1f3ff-200d-2640-fe0f", + "1f9dd-1f3ff-200d-2642-fe0f", + "1f9dd-200d-2640-fe0f", + "1f9dd-200d-2642-fe0f", + "1f9de", + "1f9de-200d-2640-fe0f", + "1f9de-200d-2642-fe0f", + "1f9df", + "1f9df-200d-2640-fe0f", + "1f9df-200d-2642-fe0f", + "1f9e0", + "1f9e1", + "1f9e2", + "1f9e3", + "1f9e4", + "1f9e5", + "1f9e6", + "1f9e7", + "1f9e8", + "1f9e9", + "1f9ea", + "1f9eb", + "1f9ec", + "1f9ed", + "1f9ee", + "1f9ef", + "1f9f0", + "1f9f1", + "1f9f2", + "1f9f3", + "1f9f4", + "1f9f5", + "1f9f6", + "1f9f7", + "1f9f8", + "1f9f9", + "1f9fa", + "1f9fb", + "1f9fc", + "1f9fd", + "1f9fe", + "1f9ff", + "1fa70", + "1fa71", + "1fa72", + "1fa73", + "1fa74", + "1fa75", + "1fa76", + "1fa77", + "1fa78", + "1fa79", + "1fa7a", + "1fa7b", + "1fa7c", + "1fa80", + "1fa81", + "1fa82", + "1fa83", + "1fa84", + "1fa85", + "1fa86", + "1fa87", + "1fa88", + "1fa90", + "1fa91", + "1fa92", + "1fa93", + "1fa94", + "1fa95", + "1fa96", + "1fa97", + "1fa98", + "1fa99", + "1fa9a", + "1fa9b", + "1fa9c", + "1fa9d", + "1fa9e", + "1fa9f", + "1faa0", + "1faa1", + "1faa2", + "1faa3", + "1faa4", + "1faa5", + "1faa6", + "1faa7", + "1faa8", + "1faa9", + "1faaa", + "1faab", + "1faac", + "1faad", + "1faae", + "1faaf", + "1fab0", + "1fab1", + "1fab2", + "1fab3", + "1fab4", + "1fab5", + "1fab6", + "1fab7", + "1fab8", + "1fab9", + "1faba", + "1fabb", + "1fabc", + "1fabd", + "1fabf", + "1fac0", + "1fac1", + "1fac2", + "1fac3", + "1fac3-1f3fb", + "1fac3-1f3fc", + "1fac3-1f3fd", + "1fac3-1f3fe", + "1fac3-1f3ff", + "1fac4", + "1fac4-1f3fb", + "1fac4-1f3fc", + "1fac4-1f3fd", + "1fac4-1f3fe", + "1fac4-1f3ff", + "1fac5", + "1fac5-1f3fb", + "1fac5-1f3fc", + "1fac5-1f3fd", + "1fac5-1f3fe", + "1fac5-1f3ff", + "1face", + "1facf", + "1fad0", + "1fad1", + "1fad2", + "1fad3", + "1fad4", + "1fad5", + "1fad6", + "1fad7", + "1fad8", + "1fad9", + "1fada", + "1fadb", + "1fae0", + "1fae1", + "1fae2", + "1fae3", + "1fae4", + "1fae5", + "1fae6", + "1fae7", + "1fae8", + "1faf0", + "1faf0-1f3fb", + "1faf0-1f3fc", + "1faf0-1f3fd", + "1faf0-1f3fe", + "1faf0-1f3ff", + "1faf1", + "1faf1-1f3fb", + "1faf1-1f3fb-200d-1faf2-1f3fc", + "1faf1-1f3fb-200d-1faf2-1f3fd", + "1faf1-1f3fb-200d-1faf2-1f3fe", + "1faf1-1f3fb-200d-1faf2-1f3ff", + "1faf1-1f3fc", + "1faf1-1f3fc-200d-1faf2-1f3fb", + "1faf1-1f3fc-200d-1faf2-1f3fd", + "1faf1-1f3fc-200d-1faf2-1f3fe", + "1faf1-1f3fc-200d-1faf2-1f3ff", + "1faf1-1f3fd", + "1faf1-1f3fd-200d-1faf2-1f3fb", + "1faf1-1f3fd-200d-1faf2-1f3fc", + "1faf1-1f3fd-200d-1faf2-1f3fe", + "1faf1-1f3fd-200d-1faf2-1f3ff", + "1faf1-1f3fe", + "1faf1-1f3fe-200d-1faf2-1f3fb", + "1faf1-1f3fe-200d-1faf2-1f3fc", + "1faf1-1f3fe-200d-1faf2-1f3fd", + "1faf1-1f3fe-200d-1faf2-1f3ff", + "1faf1-1f3ff", + "1faf1-1f3ff-200d-1faf2-1f3fb", + "1faf1-1f3ff-200d-1faf2-1f3fc", + "1faf1-1f3ff-200d-1faf2-1f3fd", + "1faf1-1f3ff-200d-1faf2-1f3fe", + "1faf2", + "1faf2-1f3fb", + "1faf2-1f3fc", + "1faf2-1f3fd", + "1faf2-1f3fe", + "1faf2-1f3ff", + "1faf3", + "1faf3-1f3fb", + "1faf3-1f3fc", + "1faf3-1f3fd", + "1faf3-1f3fe", + "1faf3-1f3ff", + "1faf4", + "1faf4-1f3fb", + "1faf4-1f3fc", + "1faf4-1f3fd", + "1faf4-1f3fe", + "1faf4-1f3ff", + "1faf5", + "1faf5-1f3fb", + "1faf5-1f3fc", + "1faf5-1f3fd", + "1faf5-1f3fe", + "1faf5-1f3ff", + "1faf6", + "1faf6-1f3fb", + "1faf6-1f3fc", + "1faf6-1f3fd", + "1faf6-1f3fe", + "1faf6-1f3ff", + "1faf7", + "1faf7-1f3fb", + "1faf7-1f3fc", + "1faf7-1f3fd", + "1faf7-1f3fe", + "1faf7-1f3ff", + "1faf8", + "1faf8-1f3fb", + "1faf8-1f3fc", + "1faf8-1f3fd", + "1faf8-1f3fe", + "1faf8-1f3ff", + "203c", + "2049", + "2122", + "2139", + "2194", + "2195", + "2196", + "2197", + "2198", + "2199", + "21a9", + "21aa", + "23-20e3", + "231a", + "231b", + "2328", + "23cf", + "23e9", + "23ea", + "23eb", + "23ec", + "23ed", + "23ee", + "23ef", + "23f0", + "23f1", + "23f2", + "23f3", + "23f8", + "23f9", + "23fa", + "24c2", + "25aa", + "25ab", + "25b6", + "25c0", + "25fb", + "25fc", + "25fd", + "25fe", + "2600", + "2601", + "2602", + "2603", + "2604", + "260e", + "2611", + "2614", + "2615", + "2618", + "261d", + "261d-1f3fb", + "261d-1f3fc", + "261d-1f3fd", + "261d-1f3fe", + "261d-1f3ff", + "2620", + "2622", + "2623", + "2626", + "262a", + "262e", + "262f", + "2638", + "2639", + "263a", + "2640", + "2642", + "2648", + "2649", + "264a", + "264b", + "264c", + "264d", + "264e", + "264f", + "2650", + "2651", + "2652", + "2653", + "265f", + "2660", + "2663", + "2665", + "2666", + "2668", + "267b", + "267e", + "267f", + "2692", + "2693", + "2694", + "2695", + "2696", + "2697", + "2699", + "269b", + "269c", + "26a0", + "26a1", + "26a7", + "26aa", + "26ab", + "26b0", + "26b1", + "26bd", + "26be", + "26c4", + "26c5", + "26c8", + "26ce", + "26cf", + "26d1", + "26d3", + "26d4", + "26e9", + "26ea", + "26f0", + "26f1", + "26f2", + "26f3", + "26f4", + "26f5", + "26f7", + "26f7-1f3fb", + "26f7-1f3fc", + "26f7-1f3fd", + "26f7-1f3fe", + "26f7-1f3ff", + "26f8", + "26f9", + "26f9-1f3fb", + "26f9-1f3fb-200d-2640-fe0f", + "26f9-1f3fb-200d-2642-fe0f", + "26f9-1f3fc", + "26f9-1f3fc-200d-2640-fe0f", + "26f9-1f3fc-200d-2642-fe0f", + "26f9-1f3fd", + "26f9-1f3fd-200d-2640-fe0f", + "26f9-1f3fd-200d-2642-fe0f", + "26f9-1f3fe", + "26f9-1f3fe-200d-2640-fe0f", + "26f9-1f3fe-200d-2642-fe0f", + "26f9-1f3ff", + "26f9-1f3ff-200d-2640-fe0f", + "26f9-1f3ff-200d-2642-fe0f", + "26f9-fe0f-200d-2640-fe0f", + "26f9-fe0f-200d-2642-fe0f", + "26fa", + "26fd", + "2702", + "2705", + "2708", + "2709", + "270a", + "270a-1f3fb", + "270a-1f3fc", + "270a-1f3fd", + "270a-1f3fe", + "270a-1f3ff", + "270b", + "270b-1f3fb", + "270b-1f3fc", + "270b-1f3fd", + "270b-1f3fe", + "270b-1f3ff", + "270c", + "270c-1f3fb", + "270c-1f3fc", + "270c-1f3fd", + "270c-1f3fe", + "270c-1f3ff", + "270d", + "270d-1f3fb", + "270d-1f3fc", + "270d-1f3fd", + "270d-1f3fe", + "270d-1f3ff", + "270f", + "2712", + "2714", + "2716", + "271d", + "2721", + "2728", + "2733", + "2734", + "2744", + "2747", + "274c", + "274e", + "2753", + "2754", + "2755", + "2757", + "2763", + "2764", + "2764-fe0f-200d-1f525", + "2764-fe0f-200d-1fa79", + "2795", + "2796", + "2797", + "27a1", + "27b0", + "27bf", + "2934", + "2935", + "2a-20e3", + "2b05", + "2b06", + "2b07", + "2b1b", + "2b1c", + "2b50", + "2b55", + "30-20e3", + "3030", + "303d", + "31-20e3", + "32-20e3", + "3297", + "3299", + "33-20e3", + "34-20e3", + "35-20e3", + "36-20e3", + "37-20e3", + "38-20e3", + "39-20e3", + "a9", + "ae", + "e50a" +] diff --git a/packages/markdown/src/index.ts b/packages/markdown/src/index.ts index f5c7f3684..8542d3344 100644 --- a/packages/markdown/src/index.ts +++ b/packages/markdown/src/index.ts @@ -18,6 +18,8 @@ declare module "vfile" { anchor: string; label: string; }[]; + /** Twemoji file names used on the page, without the .svg extension. */ + emojis: string[]; searchDocuments: { description: string; keywords: string[]; diff --git a/packages/markdown/src/process.ts b/packages/markdown/src/process.ts index 2d33a03ee..acf909bdb 100644 --- a/packages/markdown/src/process.ts +++ b/packages/markdown/src/process.ts @@ -59,6 +59,7 @@ import remarkDirectiveMultievent from "./remarkDirectiveMultievent"; import remarkDirectiveUnpack from "./remarkDirectiveUnpack"; import { makeTransformerCopyButton } from "./rehypePrettyCodeCopyButton"; import { remarkGithubEmoji } from "./remarkGithubEmoji"; +import { rehypeEmoji } from "./rehypeEmoji"; import remarkParse from "./remarkParse"; import remarkSubSup from "./remarkSubSup"; import remarkImageAttrs from "./remarkImageAttrs"; @@ -200,6 +201,8 @@ export const process = (md: string, ctx: HyperbookContext) => { return remark(ctx) .use(rehypePlugins) .use(rehypeShell(ctx)) + /* needs to be after the shell, so icons from the config are covered too */ + .use(rehypeEmoji, ctx) .use(rehypeHtmlStructure(ctx)) .use(rehypeStringify, { allowDangerousCharacters: true, diff --git a/packages/markdown/src/rehypeEmoji.ts b/packages/markdown/src/rehypeEmoji.ts new file mode 100644 index 000000000..57f544b74 --- /dev/null +++ b/packages/markdown/src/rehypeEmoji.ts @@ -0,0 +1,169 @@ +import { Plugin } from "unified"; +import { Element, ElementContent, Parent, Root, Text } from "hast"; +import { HyperbookContext } from "@hyperbook/types"; +import { VFile } from "vfile"; + +import emojiAssets from "./emoji-assets.json"; + +const availableEmojis = new Set(emojiAssets); + +/** + * Elements whose text is never emoji: replacing a character inside them would + * either break the code that is being shown or the script that is running. + */ +const skippedTags = new Set([ + "pre", + "code", + "script", + "style", + "textarea", + "title", +]); + +const VS16 = "\\uFE0F"; +const ZWJ = "\\u200D"; +const MODIFIER = "\\p{Emoji_Modifier}"; +const PICTOGRAPHIC = "\\p{Extended_Pictographic}"; +/** Tag sequence, used by the subdivision flags (England, Scotland, Wales). */ +const TAG = "(?:[\\u{E0020}-\\u{E007E}]+\\u{E007F})"; +/** A single emoji, which may be a flag, or a base with a modifier or a tag. */ +const ELEMENT = `(?:\\p{RI}\\p{RI}|${PICTOGRAPHIC}(?:${MODIFIER}|${VS16})?${TAG}?)`; +/** Keycaps are built from plain digits, so they need their own rule. */ +const KEYCAP = `(?:[0-9#*]${VS16}?\\u20E3)`; + +const emojiPattern = new RegExp( + `${KEYCAP}|(?:${ELEMENT}(?:${ZWJ}${ELEMENT})*)`, + "gu", +); + +/** + * Extended_Pictographic also covers characters that are text by default, like + * © or ™. Those should only become an image when the author asked for the + * emoji presentation, which is what a variation selector, a keycap or a + * regional indicator does. + */ +const hasEmojiPresentation = (emoji: string): boolean => + emoji.includes("\uFE0F") || + emoji.includes("\u20E3") || + /^\p{Emoji_Presentation}/u.test(emoji) || + /^\p{RI}/u.test(emoji); + +/** + * Builds the Twemoji file name of an emoji. Twemoji drops the variation + * selector, unless the emoji is a sequence joined by a zero width joiner. + */ +export const toEmojiId = (emoji: string): string => { + const codePoints = Array.from(emoji).map((c) => c.codePointAt(0) as number); + const isSequence = codePoints.includes(0x200d); + return codePoints + .filter((codePoint) => isSequence || codePoint !== 0xfe0f) + .map((codePoint) => codePoint.toString(16)) + .join("-"); +}; + +const makeImage = ( + ctx: HyperbookContext, + emoji: string, + id: string, +): Element => ({ + type: "element", + tagName: "img", + properties: { + class: "emoji", + src: ctx.makeUrl(["emoji", `${id}.svg`], "assets"), + alt: emoji, + "data-emoji": id, + draggable: "false", + loading: "lazy", + decoding: "async", + width: 20, + height: 20, + }, + children: [], +}); + +/** + * Replaces emoji characters with Twemoji images, so a hyperbook looks the same + * on every platform instead of picking up the emoji font of the reader's + * operating system. + */ +export const rehypeEmoji: Plugin<[HyperbookContext], Root> = + (ctx: HyperbookContext) => (tree: Root, file: VFile) => { + if (ctx.config.elements?.emoji?.style !== "twemoji") { + return; + } + + const used = new Set(file.data.emojis || []); + + const splitText = (node: Text): ElementContent[] | null => { + let children: ElementContent[] | null = null; + let lastIndex = 0; + + for (const match of node.value.matchAll(emojiPattern)) { + const emoji = match[0]; + const id = toEmojiId(emoji); + if (!hasEmojiPresentation(emoji) || !availableEmojis.has(id)) { + continue; + } + + if (!children) children = []; + if (match.index > lastIndex) { + children.push({ + type: "text", + value: node.value.slice(lastIndex, match.index), + }); + } + children.push(makeImage(ctx, emoji, id)); + used.add(id); + lastIndex = match.index + emoji.length; + } + + if (!children) { + return null; + } + if (lastIndex < node.value.length) { + children.push({ type: "text", value: node.value.slice(lastIndex) }); + } + return children; + }; + + const walk = (parent: Parent) => { + // Not every element carries children. A directive that builds a void + // element like an img can leave them out. + if (!Array.isArray(parent.children)) { + return; + } + + const children: ElementContent[] = []; + let replaced = false; + + for (const child of parent.children as ElementContent[]) { + if (child.type === "text") { + const split = splitText(child); + if (split) { + children.push(...split); + replaced = true; + continue; + } + } else if ( + child.type === "element" && + !skippedTags.has(child.tagName) + ) { + walk(child); + } + children.push(child); + } + + if (replaced) { + parent.children = children; + } + }; + + walk(tree); + + if (used.size > 0) { + file.data.emojis = [...used]; + } + }; + +export default rehypeEmoji; diff --git a/packages/markdown/src/rehypeHtmlStructure.ts b/packages/markdown/src/rehypeHtmlStructure.ts index 2eb55b6a5..5254a7f9b 100644 --- a/packages/markdown/src/rehypeHtmlStructure.ts +++ b/packages/markdown/src/rehypeHtmlStructure.ts @@ -357,6 +357,25 @@ document.documentElement.classList.remove('no-js');`, }, ], }, + ...(config.elements?.emoji?.style === "twemoji" + ? [ + { + type: "element" as const, + tagName: "script", + properties: {}, + children: [ + { + type: "raw" as const, + // Lets the client rebuild an emoji image from its id, + // for example when it renders a stored bookmark. + value: ` +window.hyperbook = window.hyperbook || {}; +window.hyperbook.emoji = { style: "twemoji", base: "${makeUrl(["emoji"], "assets", undefined, { versioned: false })}" };`, + }, + ], + } as ElementContent, + ] + : []), { type: "element", tagName: "script", diff --git a/packages/markdown/src/remarkHeadings.ts b/packages/markdown/src/remarkHeadings.ts index 9bcfc3750..e9a800c67 100644 --- a/packages/markdown/src/remarkHeadings.ts +++ b/packages/markdown/src/remarkHeadings.ts @@ -97,17 +97,20 @@ export default ({ type: "element", tagName: "button", properties: { + type: "button", class: "bookmark", - onclick: `hyperbook.ui.toggleBookmark("${key}", "${label}")`, title: i18n.get("toggle-bookmark"), + "aria-label": i18n.get("toggle-bookmark"), + "aria-pressed": "false", "data-key": key, + // Plain text fallback. The label is normally read from the + // rendered heading, so it keeps whatever the page shows. + "data-label": label, }, - children: [ - { - type: "text", - value: "🔖", - }, - ], + // The icon comes from the stylesheet, so it does not depend on + // the emoji font of the reader and does not change with the + // emoji style of the hyperbook. + children: [], }); } }); diff --git a/packages/markdown/tests/__snapshots__/process.test.ts.snap b/packages/markdown/tests/__snapshots__/process.test.ts.snap index c4aa9b115..d811d1025 100644 --- a/packages/markdown/tests/__snapshots__/process.test.ts.snap +++ b/packages/markdown/tests/__snapshots__/process.test.ts.snap @@ -380,7 +380,7 @@ HYPERBOOK_ASSETS = "/assets/" -

Heading 1:1

+

Heading 1:1

© Copyright 2026
" `; diff --git a/packages/markdown/tests/mock.ts b/packages/markdown/tests/mock.ts index e4efaa620..8308727b7 100644 --- a/packages/markdown/tests/mock.ts +++ b/packages/markdown/tests/mock.ts @@ -1,4 +1,4 @@ -import { HyperbookContext } from "@hyperbook/types"; +import { HyperbookContext, isExternalUrl } from "@hyperbook/types"; import path from "path"; export const ctx: HyperbookContext = { @@ -25,7 +25,7 @@ export const ctx: HyperbookContext = { }, makeUrl: (p, base) => { if (typeof p === "string") { - if (p.startsWith("http://") || p.startsWith("https://") || p.startsWith("mailto:")) { + if (isExternalUrl(p)) { return p; } if (base === "public" || base === "book") { diff --git a/packages/markdown/tests/rehypeEmoji.test.ts b/packages/markdown/tests/rehypeEmoji.test.ts new file mode 100644 index 000000000..e34a65d65 --- /dev/null +++ b/packages/markdown/tests/rehypeEmoji.test.ts @@ -0,0 +1,122 @@ +import { HyperbookContext } from "@hyperbook/types/dist"; +import { describe, expect, it } from "vitest"; +import rehypeStringify from "rehype-stringify"; +import remarkToRehype from "remark-rehype"; +import { unified } from "unified"; +import { VFile } from "vfile"; +import { ctx as baseCtx } from "./mock"; +import { rehypeEmoji, toEmojiId } from "../src/rehypeEmoji"; +import { remarkGithubEmoji } from "../src/remarkGithubEmoji"; +import remarkParse from "../src/remarkParse"; + +const twemojiCtx: HyperbookContext = { + ...baseCtx, + config: { + ...baseCtx.config, + elements: { ...baseCtx.config.elements, emoji: { style: "twemoji" } }, + }, +}; + +const toHtml = (md: string, ctx: HyperbookContext) => { + const file = new VFile(md); + const value = unified() + .use(remarkParse) + .use(remarkGithubEmoji) + .use(remarkToRehype) + .use(rehypeEmoji, ctx) + .use(rehypeStringify, { + allowDangerousCharacters: true, + allowDangerousHtml: true, + }) + .processSync(file).value; + return { html: String(value), file }; +}; + +describe("toEmojiId", () => { + it("should drop the variation selector", () => { + expect(toEmojiId("❤️")).toBe("2764"); + }); + it("should keep the variation selector in a joined sequence", () => { + expect(toEmojiId("❤️‍🔥")).toBe("2764-fe0f-200d-1f525"); + }); + it("should keep skin tone modifiers", () => { + expect(toEmojiId("👍🏻")).toBe("1f44d-1f3fb"); + }); + it("should build keycaps without the variation selector", () => { + expect(toEmojiId("1️⃣")).toBe("31-20e3"); + }); +}); + +describe("rehypeEmoji", () => { + it("should replace a shortcode with an image", () => { + const { html } = toHtml(":penguin:", twemojiCtx); + expect(html).toContain(`src="/assets/emoji/1f427.svg"`); + expect(html).toContain(`alt="🐧"`); + expect(html).toContain(`class="emoji"`); + }); + + it("should tag the image with the emoji id", () => { + // The id lets the client rebuild the image, for example for a bookmark. + const { html } = toHtml(":penguin:", twemojiCtx); + expect(html).toContain(`data-emoji="1f427"`); + }); + + it("should replace an emoji that was typed directly", () => { + const { html } = toHtml("Hello 🐧 world", twemojiCtx); + expect(html).toContain(`src="/assets/emoji/1f427.svg"`); + expect(html).toContain("Hello "); + expect(html).toContain(" world"); + }); + + it("should replace flags, keycaps and joined sequences", () => { + const { html } = toHtml("🇩🇪 1️⃣ 👩‍💻 👍🏻", twemojiCtx); + expect(html).toContain(`src="/assets/emoji/1f1e9-1f1ea.svg"`); + expect(html).toContain(`src="/assets/emoji/31-20e3.svg"`); + expect(html).toContain(`src="/assets/emoji/1f469-200d-1f4bb.svg"`); + expect(html).toContain(`src="/assets/emoji/1f44d-1f3fb.svg"`); + }); + + it("should keep characters that are text by default", () => { + const { html } = toHtml("© 2024 — ½ ‼", twemojiCtx); + expect(html).not.toContain(" { + const { html } = toHtml("There are 3 issues in #42", twemojiCtx); + expect(html).not.toContain(" { + const { html } = toHtml("`🐧` and\n\n```\n🐧\n```\n", twemojiCtx); + expect(html).not.toContain(" { + const { file } = toHtml(":penguin: :penguin: :apple:", twemojiCtx); + expect(file.data.emojis?.sort()).toEqual(["1f34e", "1f427"]); + }); + + it("should walk past an element without children", () => { + // Directives build hast by hand and may leave children out entirely. + const tree: any = { + type: "root", + children: [ + { type: "element", tagName: "img", properties: { src: "/a.png" } }, + { type: "text", value: "🐧" }, + ], + }; + const file = new VFile(""); + expect(() => (rehypeEmoji as any)(twemojiCtx)(tree, file)).not.toThrow(); + expect(tree.children[1].tagName).toBe("img"); + expect(tree.children[1].properties["data-emoji"]).toBe("1f427"); + }); + + it("should keep emojis as text when the style is native", () => { + const { html, file } = toHtml(":penguin:", baseCtx); + expect(html).not.toContain(" { `); expect(String(file)).toMatchInlineSnapshot(` - "

head

-

cus head1

-

cus head2

-

cus head3

" + "

head

+

cus head1

+

cus head2

+

cus head3

" `); }); + it("should not put the label into executable markup", async () => { + // A quote or a backslash in a heading used to end up inside a JavaScript + // string in an onclick attribute, which made the bookmark button throw. + let file = await unified() + .use(remarkParse) + .use(remarkHeadings(ctx)) + .use(remarkGfm) + .use(remarkRehype) + .use(rehypeStringify, { + allowDangerousCharacters: true, + allowDangerousHtml: true, + }) + .process(`# He said "hi" and \\ backslash`); + + const html = String(file); + expect(html).not.toContain("onclick"); + // The icon is drawn by the stylesheet, not by the emoji font. + expect(html).toContain(` -

cus head2

-

cus head2

" + "

cus head1

+

cus head2

+

cus head2

" `); }); @@ -67,7 +92,7 @@ describe("remarkHeadingId", () => { `); expect(String(file)).toMatchInlineSnapshot( - `"

Heading 1:1

"`, + `"

Heading 1:1

"`, ); }); }); diff --git a/packages/markdown/tests/remarkLink.test.ts b/packages/markdown/tests/remarkLink.test.ts index a3f68810a..bcaebabf4 100644 --- a/packages/markdown/tests/remarkLink.test.ts +++ b/packages/markdown/tests/remarkLink.test.ts @@ -42,4 +42,18 @@ describe("remarkLink", () => { ), ).toMatchSnapshot(); }); + it("should keep mailto links", () => { + const html = toHtml(`[Write us](mailto:contact@openpatch.org)`, ctx); + expect(html).toContain(`href="mailto:contact@openpatch.org"`); + expect(html).not.toContain("target"); + }); + it("should keep tel links", () => { + const html = toHtml(`[Call us](tel:+4912345678)`, ctx); + expect(html).toContain(`href="tel:+4912345678"`); + }); + it("should keep external links and open them in a new tab", () => { + const html = toHtml(`[OpenPatch](https://openpatch.org)`, ctx); + expect(html).toContain(`href="https://openpatch.org"`); + expect(html).toContain(`target="_blank"`); + }); }); diff --git a/packages/markdown/updateEmojiAssets.mjs b/packages/markdown/updateEmojiAssets.mjs new file mode 100644 index 000000000..b4770ca56 --- /dev/null +++ b/packages/markdown/updateEmojiAssets.mjs @@ -0,0 +1,24 @@ +// Builds the manifest of Twemoji SVG assets that ship with @hyperbook/markdown. +// +// rehypeEmoji only replaces an emoji with an image when the matching file is +// listed here, so the manifest is the single source of truth for what the +// "twemoji" emoji style covers. Run `pnpm emojis:update` after bumping +// @twemoji/svg. +import fs from "fs"; +import path from "path"; +import { createRequire } from "module"; + +const require = createRequire(import.meta.url); + +const twemojiDir = path.dirname(require.resolve("@twemoji/svg/package.json")); + +const files = fs + .readdirSync(twemojiDir) + .filter((file) => file.endsWith(".svg")) + .map((file) => file.slice(0, -4)) + .sort(); + +const outputPath = path.join("src", "emoji-assets.json"); +fs.writeFileSync(outputPath, JSON.stringify(files, null, 2) + "\n", "utf8"); + +console.log(`✅ Saved ${files.length} emoji assets to ${outputPath}`); diff --git a/packages/markdown/updateEmojis.mjs b/packages/markdown/updateEmojis.mjs index bd27fb24d..8d3ee3564 100644 --- a/packages/markdown/updateEmojis.mjs +++ b/packages/markdown/updateEmojis.mjs @@ -43,6 +43,29 @@ Just like in most chat apps you can insert an emoji by using its name. \`\`\` :smiley: :apple: :penguin: + +## Consistent Emojis + +Emojis are drawn by the operating system of your reader, so the same emoji looks +different on Windows, macOS, Android and Linux. Set `elements.emoji.style` to +`twemoji` to replace them with [Twemoji](https://github.com/jdecked/twemoji) +images, which look the same everywhere. + +```json +{ + "elements": { + "emoji": { + "style": "twemoji" + } + } +} +``` + +This covers emojis in your content as well as the icons you configure, for +example for links. Emojis in code blocks are never replaced, and only the emojis +you actually use are copied into your build. + +Twemoji graphics are licensed CC-BY 4.0 by Twitter, Inc and other contributors. `; const deBase = `--- @@ -59,6 +82,31 @@ Wie in den meisten Chat-Apps kannst du Emojis durch Eingabe ihres Namens einfüg \`\`\` :smiley: :apple: :penguin: + +## Einheitliche Emojis + +Emojis werden vom Betriebssystem deiner Leser gezeichnet, dasselbe Emoji sieht +daher unter Windows, macOS, Android und Linux unterschiedlich aus. Setze +`elements.emoji.style` auf `twemoji`, um sie durch +[Twemoji](https://github.com/jdecked/twemoji)-Bilder zu ersetzen, die überall +gleich aussehen. + +```json +{ + "elements": { + "emoji": { + "style": "twemoji" + } + } +} +``` + +Das gilt für Emojis in deinen Inhalten und für die Icons, die du konfigurierst, +zum Beispiel für Links. Emojis in Codeblöcken werden nie ersetzt und es werden +nur die Emojis in deinen Build kopiert, die du tatsächlich verwendest. + +Twemoji-Grafiken stehen unter der CC-BY-4.0-Lizenz von Twitter, Inc und weiteren +Mitwirkenden. `; const enHeader = `# GitHub Emoji Cheat Sheet diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 4c13f71f5..805db9c6b 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -203,6 +203,16 @@ export type HyperbookJson = { db?: string; height?: string | number; }; + emoji?: ElementConfig & { + /** + * How emojis are rendered. + * - "native": Emojis are kept as text and drawn with the emoji font of + * the reader's operating system (default). + * - "twemoji": Emojis are replaced with Twemoji images, so they look the + * same on every platform. + */ + style?: "native" | "twemoji"; + }; }; }; @@ -248,3 +258,17 @@ export interface HyperbookContext { */ dependencies?: Set; } + +/** + * Matches a URI scheme at the start of a string, as defined by RFC 3986. + * This covers `https://` and `data:` as well as schemes without an authority + * like `mailto:`, `tel:` or `sms:`. + */ +const URL_SCHEME = /^[a-zA-Z][a-zA-Z0-9+.-]*:/; + +/** + * Checks whether a path is an absolute URL and therefore must be passed + * through untouched by `makeUrl`, instead of being resolved against the + * hyperbook's base path. + */ +export const isExternalUrl = (path: string): boolean => URL_SCHEME.test(path); diff --git a/platforms/vscode/schemas/hyperbook.schema.json b/platforms/vscode/schemas/hyperbook.schema.json index 7956991fc..c5efbe7e7 100644 --- a/platforms/vscode/schemas/hyperbook.schema.json +++ b/platforms/vscode/schemas/hyperbook.schema.json @@ -194,6 +194,31 @@ } ] }, + "emoji": { + "allOf": [ + { + "properties": { + "version": { + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "style": { + "description": "How emojis are rendered.\n- \"native\": Emojis are kept as text and drawn with the emoji font of\n the reader's operating system (default).\n- \"twemoji\": Emojis are replaced with Twemoji images, so they look the\n same on every platform.", + "enum": [ + "native", + "twemoji" + ], + "type": "string" + } + }, + "type": "object" + } + ] + }, "excalidraw": { "allOf": [ { @@ -223,6 +248,29 @@ } ] }, + "kirimoto": { + "allOf": [ + { + "properties": { + "version": { + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "height": { + "type": "string" + }, + "settings": { + "type": "string" + } + }, + "type": "object" + } + ] + }, "onlineide": { "allOf": [ { diff --git a/platforms/vscode/src/Preview.ts b/platforms/vscode/src/Preview.ts index 50087ca5c..6fa2cd6b9 100644 --- a/platforms/vscode/src/Preview.ts +++ b/platforms/vscode/src/Preview.ts @@ -10,7 +10,13 @@ import { import { process } from "@hyperbook/markdown"; import { disposeAll } from "./utils/dispose"; import path, { posix } from "path"; -import { HyperbookContext, HyperbookJson, HyperbookPage, Navigation } from "@hyperbook/types"; +import { + HyperbookContext, + HyperbookJson, + HyperbookPage, + Navigation, + isExternalUrl, +} from "@hyperbook/types"; // Helper function to resolve relative paths const resolveRelativePath = (path: string, page: HyperbookPage): string => { @@ -189,7 +195,7 @@ export default class Preview { makeUrl: (path, base, page) => { if (typeof path === "string") { // Handle absolute URLs - if (path.includes("://") || path.startsWith("data:")) { + if (isExternalUrl(path)) { return path; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index baee9568b..d005acea7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -399,6 +399,9 @@ importers: '@shikijs/types': specifier: ^3.21.0 version: 3.21.0 + '@twemoji/svg': + specifier: 15.0.0 + version: 15.0.0 '@types/hast': specifier: 3.0.4 version: 3.0.4 @@ -2585,6 +2588,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@twemoji/svg@15.0.0': + resolution: {integrity: sha512-ZSPef2B6nBaYnfgdTbAy4jgW95o7pi2xPGwGCU+WMTxo7J6B1lMPTWwSq/wTuiMq+N0khQ90CcvYp1wFoQpo/w==} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -11025,6 +11031,8 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@twemoji/svg@15.0.0': {} + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 diff --git a/website/de/book/elements/emoji.md b/website/de/book/elements/emoji.md index b43605021..8b3588b6e 100644 --- a/website/de/book/elements/emoji.md +++ b/website/de/book/elements/emoji.md @@ -13,6 +13,31 @@ Wie in den meisten Chat-Apps kannst du Emojis durch Eingabe ihres Namens einfüg :smiley: :apple: :penguin: +## Einheitliche Emojis + +Emojis werden vom Betriebssystem deiner Leser gezeichnet, dasselbe Emoji sieht +daher unter Windows, macOS, Android und Linux unterschiedlich aus. Setze +`elements.emoji.style` auf `twemoji`, um sie durch +[Twemoji](https://github.com/jdecked/twemoji)-Bilder zu ersetzen, die überall +gleich aussehen. + +```json +{ + "elements": { + "emoji": { + "style": "twemoji" + } + } +} +``` + +Das gilt für Emojis in deinen Inhalten und für die Icons, die du konfigurierst, +zum Beispiel für Links. Emojis in Codeblöcken werden nie ersetzt und es werden +nur die Emojis in deinen Build kopiert, die du tatsächlich verwendest. + +Twemoji-Grafiken stehen unter der CC-BY-4.0-Lizenz von Twitter, Inc und weiteren +Mitwirkenden. + # GitHub Emoji Cheat Sheet Diese Datei listet alle unterstützten GitHub-Emoji-Kürzel und ihre entsprechenden Unicode-Zeichen auf. diff --git a/website/de/hyperbook.json b/website/de/hyperbook.json index 041186074..5c2e0527e 100644 --- a/website/de/hyperbook.json +++ b/website/de/hyperbook.json @@ -46,6 +46,9 @@ "elements": { "code": { "showLineNumbers": true + }, + "emoji": { + "style": "twemoji" } }, "language": "de", diff --git a/website/en/book/changelog.md b/website/en/book/changelog.md index a392ab897..e1dcd2c5e 100644 --- a/website/en/book/changelog.md +++ b/website/en/book/changelog.md @@ -38,6 +38,34 @@ If you need a new feature, open an [issue](https://github.com/openpatch/hyperboo :::: --> +## v0.101.0 + +::::tabs + +:::tab{title="New :rocket:" id="new"} + +**emoji**: Emojis can be rendered as images, so they look the same on every platform instead of being drawn with the emoji font of the reader's operating system. Set `elements.emoji.style` to `twemoji` in your `hyperbook.json`. Only the emojis your book uses end up in your build. + +::: + +:::tab{title="Improved :+1:" id="improved"} + +**bookmarks**: The bookmark icon is drawn by the stylesheet instead of being an emoji. It looks the same everywhere, takes the color of its heading, and a saved bookmark shows a filled icon. + +**bookmarks**: An entry in the bookmark list shows the same emojis as the heading it points at. If you call `hyperbook.ui.toggleBookmark` yourself, it no longer takes a label. + +::: + +:::tab{title="Fixed :bug:" id="fixed"} + +**markdown**: `mailto:` and `tel:` links in your content kept the base path of the book in front of them, so `[Write us](mailto:hi@example.org)` became `/mailto:hi@example.org`. + +**bookmarks**: A heading that contains a quote or a backslash can be bookmarked again. + +::: + +:::: + ## v0.100.5 ::::tabs diff --git a/website/en/book/elements/emoji.md b/website/en/book/elements/emoji.md index d71da5577..be2669fea 100644 --- a/website/en/book/elements/emoji.md +++ b/website/en/book/elements/emoji.md @@ -13,6 +13,29 @@ Just like in most chat apps you can insert an emoji by using its name. :smiley: :apple: :penguin: +## Consistent Emojis + +Emojis are drawn by the operating system of your reader, so the same emoji looks +different on Windows, macOS, Android and Linux. Set `elements.emoji.style` to +`twemoji` to replace them with [Twemoji](https://github.com/jdecked/twemoji) +images, which look the same everywhere. + +```json +{ + "elements": { + "emoji": { + "style": "twemoji" + } + } +} +``` + +This covers emojis in your content as well as the icons you configure, for +example for links. Emojis in code blocks are never replaced, and only the emojis +you actually use are copied into your build. + +Twemoji graphics are licensed CC-BY 4.0 by Twitter, Inc and other contributors. + # GitHub Emoji Cheat Sheet This file lists all supported GitHub-style emoji shortcodes and their corresponding Unicode characters. diff --git a/website/en/hyperbook.json b/website/en/hyperbook.json index c4f904df8..b79383eda 100644 --- a/website/en/hyperbook.json +++ b/website/en/hyperbook.json @@ -42,6 +42,9 @@ "elements": { "code": { "showLineNumbers": true + }, + "emoji": { + "style": "twemoji" } }, "language": "en",