summaryrefslogtreecommitdiff
path: root/packages/prosody-ui/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/prosody-ui/src')
-rw-r--r--packages/prosody-ui/src/LangText.tsx78
-rw-r--r--packages/prosody-ui/src/Paragraph.tsx56
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/BeiShiDaJiaGuWenZiTi-1.ttfbin0 -> 1984156 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/GaiLiangShouJinTi-2.ttfbin0 -> 4827080 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/GuDianMingChaoKai-2.ttfbin0 -> 30564660 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/Iansui-Regular.ttfbin0 -> 9396328 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/LiuJianMaoCao-Regular.ttfbin0 -> 4940224 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/LongCang-Regular.ttfbin0 -> 5151180 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/Meirenzhuan.ttfbin0 -> 7336648 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/No.300-ShangShouGuHuangTi-2.ttfbin0 -> 4890264 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Black.ttfbin0 -> 7082052 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Bold.ttfbin0 -> 7088136 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-ExtraBold.ttfbin0 -> 7084512 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-ExtraLight.ttfbin0 -> 7095176 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Light.ttfbin0 -> 7095576 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Medium.ttfbin0 -> 7089444 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Regular.ttfbin0 -> 7093428 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-SemiBold.ttfbin0 -> 7086788 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Thin.ttfbin0 -> 7092016 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-VariableFont_wght.ttfbin0 -> 11909304 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/YeZiGongChangZuiHanJiangXingCao-2.ttfbin0 -> 5486956 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/YiShanBeiZhuanTi.ttfbin0 -> 2314256 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/ZhiMangXing-Regular.ttfbin0 -> 4052388 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/dingliezhuhaifont-20240831GengXinBan)-2.ttfbin0 -> 5359608 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/style.css223
-rw-r--r--packages/prosody-ui/src/assets/fonts/Hani/字魂天龙行楷.ttfbin0 -> 3344412 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/IPA/Judson-Bold.ttfbin0 -> 192452 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/IPA/Judson-Italic.ttfbin0 -> 209028 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/IPA/Judson-Regular.ttfbin0 -> 217096 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/IPA/NotoSans-Italic-VariableFont_wdth,wght.ttfbin0 -> 2637272 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/IPA/NotoSans-VariableFont_wdth,wght.ttfbin0 -> 2490816 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/IPA/Voces-Regular.ttfbin0 -> 119992 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/IPA/style.css33
-rw-r--r--packages/prosody-ui/src/assets/fonts/Jpan/DelaGothicOne-Regular.ttfbin0 -> 2487960 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Bold.ttfbin0 -> 4256124 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Medium.ttfbin0 -> 4265764 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Regular.ttfbin0 -> 4472380 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Jpan/ZenAntiqueSoft-Regular.ttfbin0 -> 7095584 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Jpan/ZenKurenaido-Regular.ttfbin0 -> 4303064 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Jpan/style.css223
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/OFL.txt93
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Black.ttfbin0 -> 151396 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-BlackItalic.ttfbin0 -> 171604 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Bold.ttfbin0 -> 153944 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-BoldItalic.ttfbin0 -> 176588 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraBold.ttfbin0 -> 152764 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraBoldItalic.ttfbin0 -> 173916 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraLight.ttfbin0 -> 161456 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraLightItalic.ttfbin0 -> 186168 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Italic.ttfbin0 -> 182012 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Light.ttfbin0 -> 159892 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-LightItalic.ttfbin0 -> 184460 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Medium.ttfbin0 -> 156520 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-MediumItalic.ttfbin0 -> 180444 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Regular.ttfbin0 -> 158240 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-SemiBold.ttfbin0 -> 155232 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-SemiBoldItalic.ttfbin0 -> 178584 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Thin.ttfbin0 -> 161652 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ThinItalic.ttfbin0 -> 187044 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Latn/style.css11
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Charm-Bold.ttfbin0 -> 135416 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Charm-Regular.ttfbin0 -> 134652 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Chonburi-Regular.ttfbin0 -> 168404 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Bold.ttfbin0 -> 113924 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-ExtraLight.ttfbin0 -> 117248 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Light.ttfbin0 -> 115728 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Medium.ttfbin0 -> 113640 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Regular.ttfbin0 -> 113392 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-SemiBold.ttfbin0 -> 113856 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Thin.ttfbin0 -> 118256 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-Black.ttfbin0 -> 173492 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-BlackItalic.ttfbin0 -> 182076 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-Bold.ttfbin0 -> 172876 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-BoldItalic.ttfbin0 -> 180308 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraBold.ttfbin0 -> 174464 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraBoldItalic.ttfbin0 -> 184928 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraLight.ttfbin0 -> 160796 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraLightItalic.ttfbin0 -> 164908 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-Italic.ttfbin0 -> 171876 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-Light.ttfbin0 -> 168036 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-LightItalic.ttfbin0 -> 171596 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-Medium.ttfbin0 -> 171336 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-MediumItalic.ttfbin0 -> 172360 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-Regular.ttfbin0 -> 169744 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-SemiBold.ttfbin0 -> 171548 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-SemiBoldItalic.ttfbin0 -> 172244 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-Thin.ttfbin0 -> 155788 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kanit-ThinItalic.ttfbin0 -> 161688 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Bold.ttfbin0 -> 98600 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-BoldItalic.ttfbin0 -> 104048 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-ExtraLight.ttfbin0 -> 96992 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-ExtraLightItalic.ttfbin0 -> 101308 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Italic.ttfbin0 -> 103088 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Light.ttfbin0 -> 98168 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-LightItalic.ttfbin0 -> 102364 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Medium.ttfbin0 -> 98740 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-MediumItalic.ttfbin0 -> 103552 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Regular.ttfbin0 -> 98392 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-SemiBold.ttfbin0 -> 98824 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Kodchasan-SemiBoldItalic.ttfbin0 -> 104024 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-Bold.ttfbin0 -> 105372 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-BoldItalic.ttfbin0 -> 108796 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-ExtraLight.ttfbin0 -> 105668 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-ExtraLightItalic.ttfbin0 -> 108572 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-Italic.ttfbin0 -> 108960 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-Light.ttfbin0 -> 105864 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-LightItalic.ttfbin0 -> 108732 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-Medium.ttfbin0 -> 105988 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-MediumItalic.ttfbin0 -> 109008 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-Regular.ttfbin0 -> 105896 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-SemiBold.ttfbin0 -> 106032 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Mali-SemiBoldItalic.ttfbin0 -> 109000 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-Bold.ttfbin0 -> 82592 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-BoldItalic.ttfbin0 -> 85636 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraBold.ttfbin0 -> 82632 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraBoldItalic.ttfbin0 -> 85448 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraLight.ttfbin0 -> 83276 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraLightItalic.ttfbin0 -> 86368 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-Italic.ttfbin0 -> 86024 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-Light.ttfbin0 -> 83224 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-LightItalic.ttfbin0 -> 86072 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-Medium.ttfbin0 -> 83080 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-MediumItalic.ttfbin0 -> 86136 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-Regular.ttfbin0 -> 83080 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-SemiBold.ttfbin0 -> 82952 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-SemiBoldItalic.ttfbin0 -> 86020 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-Thin.ttfbin0 -> 83408 bytes
-rwxr-xr-xpackages/prosody-ui/src/assets/fonts/Thai/Sarabun-ThinItalic.ttfbin0 -> 86344 bytes
-rw-r--r--packages/prosody-ui/src/assets/fonts/Thai/style.css76
-rwxr-xr-xpackages/prosody-ui/src/assets/icons/bookmark.svg4
-rwxr-xr-xpackages/prosody-ui/src/assets/icons/font.svg2
-rwxr-xr-xpackages/prosody-ui/src/assets/icons/heart.svg4
-rwxr-xr-xpackages/prosody-ui/src/assets/icons/play.svg4
-rwxr-xr-xpackages/prosody-ui/src/assets/icons/quote.svg28
-rwxr-xr-xpackages/prosody-ui/src/assets/icons/react.svg1
-rwxr-xr-xpackages/prosody-ui/src/assets/icons/share.svg28
-rw-r--r--packages/prosody-ui/src/assets/icons/speaker.svg32
-rwxr-xr-xpackages/prosody-ui/src/assets/icons/spinner.svg1
-rw-r--r--packages/prosody-ui/src/components/Sentence.tsx57
-rw-r--r--packages/prosody-ui/src/components/Word.tsx119
-rw-r--r--packages/prosody-ui/src/components/sentence.css272
-rw-r--r--packages/prosody-ui/src/components/word.css0
-rw-r--r--packages/prosody-ui/src/files.d.ts4
-rw-r--r--packages/prosody-ui/src/fonts/FontChanger.tsx64
-rw-r--r--packages/prosody-ui/src/fonts/Hani.tsx14
-rw-r--r--packages/prosody-ui/src/fonts/Thai.tsx8
-rw-r--r--packages/prosody-ui/src/fonts/useLangFont.tsx44
-rw-r--r--packages/prosody-ui/src/hooks/useLang.tsx184
-rw-r--r--packages/prosody-ui/src/hooks/useModal.tsx53
-rw-r--r--packages/prosody-ui/src/hooks/useTTS.tsx3
-rw-r--r--packages/prosody-ui/src/latin/LatinText.tsx77
-rw-r--r--packages/prosody-ui/src/logic/iso6393to1.ts186
-rw-r--r--packages/prosody-ui/src/logic/stanza.ts86
-rw-r--r--packages/prosody-ui/src/logic/types.ts48
-rw-r--r--packages/prosody-ui/src/logic/utils.ts66
-rw-r--r--packages/prosody-ui/src/logic/wiki.ts138
-rw-r--r--packages/prosody-ui/src/sortug.css248
-rw-r--r--packages/prosody-ui/src/styles/styles.css281
-rw-r--r--packages/prosody-ui/src/thai/ThaiText.tsx49
-rw-r--r--packages/prosody-ui/src/thai/logic/thainlp.ts90
-rw-r--r--packages/prosody-ui/src/zoom/FullText.tsx60
-rw-r--r--packages/prosody-ui/src/zoom/Paragraph.tsx60
-rw-r--r--packages/prosody-ui/src/zoom/Sentence.tsx46
-rw-r--r--packages/prosody-ui/src/zoom/SpacyClause.tsx125
-rw-r--r--packages/prosody-ui/src/zoom/animations.ts199
-rw-r--r--packages/prosody-ui/src/zoom/hooks/useZoom.tsx135
-rw-r--r--packages/prosody-ui/src/zoom/index.ts8
-rw-r--r--packages/prosody-ui/src/zoom/logic/types.ts53
-rw-r--r--packages/prosody-ui/src/zoom/spacy.css39
169 files changed, 3713 insertions, 0 deletions
diff --git a/packages/prosody-ui/src/LangText.tsx b/packages/prosody-ui/src/LangText.tsx
new file mode 100644
index 0000000..790c499
--- /dev/null
+++ b/packages/prosody-ui/src/LangText.tsx
@@ -0,0 +1,78 @@
+import { franc } from "franc-all";
+import React, { useEffect, useState } from "react";
+import ThaiText from "./thai/ThaiText";
+import { ColoredText } from "./components/Sentence";
+import type { AnalyzeRes, WordData } from "./logic/types";
+import { detectScript, scriptFromLang } from "./logic/utils";
+import LatinText from "./latin/LatinText";
+import { buildWiktionaryURL, parseWiktionary } from "./logic/wiki";
+import type { Result } from "sortug";
+
+export default function LangText({
+ text,
+ lang,
+ theme,
+ fetchWiki,
+ handleWord,
+}: {
+ text: string;
+ lang?: string;
+ theme?: string;
+ fetchWiki?: (url: string) => Promise<string>;
+ handleWord?: (wd: Result<WordData>) => any;
+}) {
+ const [llang, setLang] = useState("");
+ const [script, setScript] = useState(scriptFromLang(lang || "", text));
+ useEffect(() => {
+ if (!lang) {
+ const res = franc(text);
+ setLang(res);
+ } else setLang(lang);
+ }, [text]);
+ console.log("langtext", { text, llang, script });
+
+ async function openWord(word: string) {
+ // console.log("looking up", word);
+ // const url = buildWiktionaryURL(word);
+ // const html = await fetchWiki(url);
+ // const parsed = parseWiktionary(html, url);
+ // console.log({ parsed });
+ // if ("error" in parsed) handleWord(parsed);
+ // else {
+ // const wd: WordData = {
+ // spelling: word,
+ // lang: llang,
+ // ipa: parsed.ok.ipa[0],
+ // meanings: parsed.ok.meanings,
+ // references: { url: parsed.ok.url },
+ // };
+ // handleWord({ ok: wd });
+ // }
+ // // const d = data[s];
+ // // setModal(d);
+ // // setModal(<WordModal data={d} lang={lang} />);
+ }
+
+ return (
+ <div className="lang-text-container">
+ {script === "Thai" ? (
+ <ThaiText text={text} openWord={openWord} />
+ ) : script === "Latin" ? (
+ <LatinText text={text} lang={llang} openWord={openWord} />
+ ) : (
+ <Generic text={text} lang={llang} />
+ )}
+ </div>
+ );
+}
+function Generic({ text, lang }: { text: string; lang: string }) {
+ const [data, setData] = useState<AnalyzeRes>();
+ useEffect(() => {
+ // segmentate(text, lang)
+ }, [text, lang]);
+ console.log({ lang }, "generic");
+ // <p className="lang-lang">{lang}</p>
+ // <p className="lang-text">{text}</p>
+ // {data && <ColoredText frags={Object.keys(data)} />}
+ return <div className="lang-text-div"></div>;
+}
diff --git a/packages/prosody-ui/src/Paragraph.tsx b/packages/prosody-ui/src/Paragraph.tsx
new file mode 100644
index 0000000..72c43a7
--- /dev/null
+++ b/packages/prosody-ui/src/Paragraph.tsx
@@ -0,0 +1,56 @@
+import { franc } from "franc-all";
+import React, { useCallback, useEffect, useState } from "react";
+import ThaiText from "./thai/ThaiText";
+import { ColoredText } from "./components/Sentence";
+import type { AnalyzeRes, WordData } from "./logic/types";
+import { detectScript, langFromScript } from "./logic/utils";
+import LatinText from "./latin/LatinText";
+import { buildWiktionaryURL, parseWiktionary } from "./logic/wiki";
+import type { Result } from "sortug";
+import * as Stanza from "./logic/stanza";
+import { iso6393To1 } from "./logic/iso6393to1";
+
+export default function Paragraph({
+ text,
+}: {
+ text: string;
+ handleWord?: (wd: Result<WordData>) => any;
+}) {
+ useEffect(() => {
+ segmentString();
+ }, [text]);
+ const [lang, setLang] = useState("");
+ const [script, setScript] = useState("");
+ const [segs, setSegs] = useState<Stanza.StanzaSegment[]>([]);
+ useEffect(() => {
+ const res = franc(text);
+ console.log();
+ console.log({ res, text });
+ if (res === "und") detectLanguage();
+ else {
+ const smol = iso6393To1[res];
+ if (!smol) console.log("lang", res);
+ else setLang(smol);
+ }
+ }, [text]);
+
+ const segmentString = useCallback(async () => {
+ if (lang) {
+ const res = await Stanza.segmenter(text, lang);
+ if ("ok" in res) setSegs(res.ok.segments);
+ console.log("stanza", res);
+ }
+ }, [text, lang]);
+ const detectLanguage = useCallback(async () => {
+ const script = detectScript(text);
+ if ("error" in script) console.log("ded");
+ else {
+ setScript(script.ok);
+ const lng = langFromScript(script.ok);
+ if ("error" in lng) console.log("ded again!");
+ else setLang(lng.ok);
+ }
+ }, [text]);
+
+ return <div className="segmented-text">{text}</div>;
+}
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/BeiShiDaJiaGuWenZiTi-1.ttf b/packages/prosody-ui/src/assets/fonts/Hani/BeiShiDaJiaGuWenZiTi-1.ttf
new file mode 100644
index 0000000..5ada19c
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/BeiShiDaJiaGuWenZiTi-1.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/GaiLiangShouJinTi-2.ttf b/packages/prosody-ui/src/assets/fonts/Hani/GaiLiangShouJinTi-2.ttf
new file mode 100644
index 0000000..2606d1c
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/GaiLiangShouJinTi-2.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/GuDianMingChaoKai-2.ttf b/packages/prosody-ui/src/assets/fonts/Hani/GuDianMingChaoKai-2.ttf
new file mode 100644
index 0000000..49f5405
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/GuDianMingChaoKai-2.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/Iansui-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Hani/Iansui-Regular.ttf
new file mode 100644
index 0000000..5da538b
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/Iansui-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/LiuJianMaoCao-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Hani/LiuJianMaoCao-Regular.ttf
new file mode 100644
index 0000000..4b7e9bc
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/LiuJianMaoCao-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/LongCang-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Hani/LongCang-Regular.ttf
new file mode 100644
index 0000000..44f6a9f
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/LongCang-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/Meirenzhuan.ttf b/packages/prosody-ui/src/assets/fonts/Hani/Meirenzhuan.ttf
new file mode 100644
index 0000000..8eb1209
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/Meirenzhuan.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/No.300-ShangShouGuHuangTi-2.ttf b/packages/prosody-ui/src/assets/fonts/Hani/No.300-ShangShouGuHuangTi-2.ttf
new file mode 100644
index 0000000..1f16ba8
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/No.300-ShangShouGuHuangTi-2.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Black.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Black.ttf
new file mode 100644
index 0000000..e86fb96
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Black.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Bold.ttf
new file mode 100644
index 0000000..76812d7
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-ExtraBold.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-ExtraBold.ttf
new file mode 100644
index 0000000..db381ed
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-ExtraBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-ExtraLight.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-ExtraLight.ttf
new file mode 100644
index 0000000..e30cdd8
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-ExtraLight.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Light.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Light.ttf
new file mode 100644
index 0000000..8be49fc
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Light.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Medium.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Medium.ttf
new file mode 100644
index 0000000..f45d4ec
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Medium.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Regular.ttf
new file mode 100644
index 0000000..2a62b97
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-SemiBold.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-SemiBold.ttf
new file mode 100644
index 0000000..bc3e266
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-SemiBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Thin.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Thin.ttf
new file mode 100644
index 0000000..832c29f
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-Thin.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-VariableFont_wght.ttf b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-VariableFont_wght.ttf
new file mode 100644
index 0000000..47f2c84
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/NotoSansHK-VariableFont_wght.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/YeZiGongChangZuiHanJiangXingCao-2.ttf b/packages/prosody-ui/src/assets/fonts/Hani/YeZiGongChangZuiHanJiangXingCao-2.ttf
new file mode 100644
index 0000000..b59a399
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/YeZiGongChangZuiHanJiangXingCao-2.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/YiShanBeiZhuanTi.ttf b/packages/prosody-ui/src/assets/fonts/Hani/YiShanBeiZhuanTi.ttf
new file mode 100644
index 0000000..9364f2d
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/YiShanBeiZhuanTi.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/ZhiMangXing-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Hani/ZhiMangXing-Regular.ttf
new file mode 100644
index 0000000..e2d4fad
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/ZhiMangXing-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/dingliezhuhaifont-20240831GengXinBan)-2.ttf b/packages/prosody-ui/src/assets/fonts/Hani/dingliezhuhaifont-20240831GengXinBan)-2.ttf
new file mode 100644
index 0000000..b387fc5
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/dingliezhuhaifont-20240831GengXinBan)-2.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/style.css b/packages/prosody-ui/src/assets/fonts/Hani/style.css
new file mode 100644
index 0000000..2048617
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/style.css
@@ -0,0 +1,223 @@
+/* https://www.fonts.net.cn/fonts-zh/tag-yankai-1.html */
+
+
+@font-face {
+ font-family: "BeiShiDaJiaGuWen";
+ src: url(./BeiShiDaJiaGuWenZiTi-1.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+@font-face {
+ font-family: "ShanShouGuHuangTi";
+ src: url(./No.300-ShangShouGuHuangTi-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "DinglieZhuhai";
+ src: url(./dingliezhuhaifont-20240831GengXinBan-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "ShouJin";
+ src: url(./GaiLiangShouJinTi-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "GudianMingChaoKai";
+ src: url(./GuDianMingChaoKai-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-SemiBold.ttf);
+ font-weight: 500;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-Black.ttf);
+ font-weight: 900;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-Thin.ttf);
+ font-weight: 200;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-Bold.ttf);
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-VariableFont_wght.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-ExtraBold.ttf);
+ font-weight: 800;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-ExtraLight.ttf);
+ font-weight: 100;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-Light.ttf);
+ font-weight: 200;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(./NotoSansHK-Medium.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "Iansui";
+ src: url(./Iansui-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "YeZiGongXingCao";
+ src: url(./YeZiGongChangZuiHanJiangXingCao-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "LiuJianMaoCao";
+ src: url(./LiuJianMaoCao-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "YiShanBeiZhuanti";
+ src: url(./YiShanBeiZhuanTi.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "LongCang";
+ src: url(./LongCang-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "ZhiMangXing";
+ src: url(./ZhiMangXing-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "Meirenzhuan";
+ src: url(./Meirenzhuan.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "TianlongXingkai";
+ src: url(./字魂天龙行楷.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+.font-Hani-0 {
+ font-family: "ShouJin";
+}
+
+.font-Hani-1 {
+ font-family: "DinglieZhuhai";
+}
+
+.font-Hani-2 {
+ font-family: "GudianMingChaokai";
+}
+
+.font-Hani-3 {
+ font-family: "NotoSansHK";
+}
+
+.font-Hani-4 {
+ font-family: "ShanShouGuHuangTi";
+}
+
+.font-Hani-5 {
+ font-family: "Iansui";
+}
+
+.font-Hani-6 {
+ font-family: "YeZiGongXingCao";
+}
+
+.font-Hani-7 {
+ font-family: "LiuJianMaoCao";
+}
+
+.font-Hani-8 {
+ font-family: "YiShanBeiZhuanti";
+}
+
+.font-Hani-9 {
+ font-family: "LongCang";
+}
+
+.font-Hani-10 {
+ font-family: "ZhiMangXing";
+}
+
+.font-Hani-11 {
+ font-family: "Meirenzhuan";
+}
+
+.font-Hani-12 {
+ font-family: "TianlongXingkai";
+} \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/fonts/Hani/字魂天龙行楷.ttf b/packages/prosody-ui/src/assets/fonts/Hani/字魂天龙行楷.ttf
new file mode 100644
index 0000000..b565e37
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Hani/字魂天龙行楷.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/IPA/Judson-Bold.ttf b/packages/prosody-ui/src/assets/fonts/IPA/Judson-Bold.ttf
new file mode 100755
index 0000000..4b977b0
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/IPA/Judson-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/IPA/Judson-Italic.ttf b/packages/prosody-ui/src/assets/fonts/IPA/Judson-Italic.ttf
new file mode 100755
index 0000000..b2ea190
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/IPA/Judson-Italic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/IPA/Judson-Regular.ttf b/packages/prosody-ui/src/assets/fonts/IPA/Judson-Regular.ttf
new file mode 100755
index 0000000..a1cbb58
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/IPA/Judson-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/IPA/NotoSans-Italic-VariableFont_wdth,wght.ttf b/packages/prosody-ui/src/assets/fonts/IPA/NotoSans-Italic-VariableFont_wdth,wght.ttf
new file mode 100755
index 0000000..4e962ee
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/IPA/NotoSans-Italic-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/IPA/NotoSans-VariableFont_wdth,wght.ttf b/packages/prosody-ui/src/assets/fonts/IPA/NotoSans-VariableFont_wdth,wght.ttf
new file mode 100755
index 0000000..f7d0d78
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/IPA/NotoSans-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/IPA/Voces-Regular.ttf b/packages/prosody-ui/src/assets/fonts/IPA/Voces-Regular.ttf
new file mode 100755
index 0000000..36aa975
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/IPA/Voces-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/IPA/style.css b/packages/prosody-ui/src/assets/fonts/IPA/style.css
new file mode 100644
index 0000000..ecc0a8a
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/IPA/style.css
@@ -0,0 +1,33 @@
+@font-face {
+ font-family: "Voces";
+ src: url(./Voces-Regular.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+@font-face {
+ font-family: "Judson";
+ src: url(./Judson-Regular.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+/* TODO check the specifics of variable fonts */
+@font-face {
+ font-family: "Noto Sans";
+ src: url(./NotoSans-VariableFont_wdth,wght.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+.font-IPA-0 {
+ font-family: "Voces";
+}
+
+.font-IPA-1 {
+ font-family: "Judson";
+}
+
+.font-IPA-2 {
+ font-family: "Noto Sans";
+} \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/fonts/Jpan/DelaGothicOne-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Jpan/DelaGothicOne-Regular.ttf
new file mode 100644
index 0000000..475c4dd
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Jpan/DelaGothicOne-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Bold.ttf
new file mode 100644
index 0000000..7d13d05
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Medium.ttf b/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Medium.ttf
new file mode 100644
index 0000000..1e35712
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Medium.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Regular.ttf
new file mode 100644
index 0000000..7d68508
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Jpan/KaiseiDecol-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Jpan/ZenAntiqueSoft-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Jpan/ZenAntiqueSoft-Regular.ttf
new file mode 100644
index 0000000..c78f7d6
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Jpan/ZenAntiqueSoft-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Jpan/ZenKurenaido-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Jpan/ZenKurenaido-Regular.ttf
new file mode 100644
index 0000000..cb3044e
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Jpan/ZenKurenaido-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Jpan/style.css b/packages/prosody-ui/src/assets/fonts/Jpan/style.css
new file mode 100644
index 0000000..cd1b79f
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Jpan/style.css
@@ -0,0 +1,223 @@
+/* https://www.fonts.net.cn/fonts-zh/tag-yankai-1.html */
+
+
+@font-face {
+ font-family: "BeiShiDaJiaGuWen";
+ src: url(../Hani/BeiShiDaJiaGuWenZiTi-1.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+@font-face {
+ font-family: "ShanShouGuHuangTi";
+ src: url(../Hani/No.300-ShangShouGuHuangTi-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "DinglieZhuhai";
+ src: url(../Hani/dingliezhuhaifont-20240831GengXinBan-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "ShouJin";
+ src: url(../Hani/GaiLiangShouJinTi-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "GudianMingChaoKai";
+ src: url(../Hani/GuDianMingChaoKai-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-SemiBold.ttf);
+ font-weight: 500;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-Black.ttf);
+ font-weight: 900;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-Thin.ttf);
+ font-weight: 200;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-Bold.ttf);
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-VariableFont_wght.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-ExtraBold.ttf);
+ font-weight: 800;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-ExtraLight.ttf);
+ font-weight: 100;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-Light.ttf);
+ font-weight: 200;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "NotoSansHK";
+ src: url(../Hani/NotoSansHK-Medium.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "Iansui";
+ src: url(../Hani/Iansui-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "YeZiGongXingCao";
+ src: url(../Hani/YeZiGongChangZuiHanJiangXingCao-2.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "LiuJianMaoCao";
+ src: url(../Hani/LiuJianMaoCao-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "YiShanBeiZhuanti";
+ src: url(../Hani/YiShanBeiZhuanTi.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "LongCang";
+ src: url(../Hani/LongCang-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "ZhiMangXing";
+ src: url(../Hani/ZhiMangXing-Regular.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "Meirenzhuan";
+ src: url(../Hani/Meirenzhuan.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+@font-face {
+ font-family: "TianlongXingkai";
+ src: url(../Hani/字魂天龙行楷.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+.font-Hani-0 {
+ font-family: "ShouJin";
+}
+
+.font-Hani-1 {
+ font-family: "DinglieZhuhai";
+}
+
+.font-Hani-2 {
+ font-family: "GudianMingChaokai";
+}
+
+.font-Hani-3 {
+ font-family: "NotoSansHK";
+}
+
+.font-Hani-4 {
+ font-family: "ShanShouGuHuangTi";
+}
+
+.font-Hani-5 {
+ font-family: "Iansui";
+}
+
+.font-Hani-6 {
+ font-family: "YeZiGongXingCao";
+}
+
+.font-Hani-7 {
+ font-family: "LiuJianMaoCao";
+}
+
+.font-Hani-8 {
+ font-family: "YiShanBeiZhuanti";
+}
+
+.font-Hani-9 {
+ font-family: "LongCang";
+}
+
+.font-Hani-10 {
+ font-family: "ZhiMangXing";
+}
+
+.font-Hani-11 {
+ font-family: "Meirenzhuan";
+}
+
+.font-Hani-12 {
+ font-family: "TianlongXingkai";
+} \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/OFL.txt b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/OFL.txt
new file mode 100644
index 0000000..3e92931
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Black.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Black.ttf
new file mode 100644
index 0000000..71c0f99
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Black.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-BlackItalic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-BlackItalic.ttf
new file mode 100644
index 0000000..7aeb58b
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-BlackItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Bold.ttf
new file mode 100644
index 0000000..00559ee
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-BoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-BoldItalic.ttf
new file mode 100644
index 0000000..e61e8e8
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-BoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraBold.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraBold.ttf
new file mode 100644
index 0000000..df70936
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraBoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..14d2b37
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraBoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraLight.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraLight.ttf
new file mode 100644
index 0000000..e76ec69
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraLight.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraLightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraLightItalic.ttf
new file mode 100644
index 0000000..89513d9
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ExtraLightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Italic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Italic.ttf
new file mode 100644
index 0000000..12b7b3c
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Italic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Light.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Light.ttf
new file mode 100644
index 0000000..bc36bcc
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Light.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-LightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-LightItalic.ttf
new file mode 100644
index 0000000..9e70be6
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-LightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Medium.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Medium.ttf
new file mode 100644
index 0000000..6bcdcc2
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Medium.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-MediumItalic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-MediumItalic.ttf
new file mode 100644
index 0000000..be67410
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-MediumItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Regular.ttf
new file mode 100644
index 0000000..9f0c71b
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-SemiBold.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-SemiBold.ttf
new file mode 100644
index 0000000..74c726e
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-SemiBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-SemiBoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-SemiBoldItalic.ttf
new file mode 100644
index 0000000..3e6c942
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-SemiBoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Thin.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Thin.ttf
new file mode 100644
index 0000000..03e7366
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-Thin.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ThinItalic.ttf b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ThinItalic.ttf
new file mode 100644
index 0000000..e26db5d
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/Poppins/Poppins-ThinItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Latn/style.css b/packages/prosody-ui/src/assets/fonts/Latn/style.css
new file mode 100644
index 0000000..e2aef9b
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Latn/style.css
@@ -0,0 +1,11 @@
+@font-face {
+ font-family: "Poppins";
+ src: url(./Poppins.ttf);
+ font-weight: 300;
+ font-style: normal;
+}
+
+
+.font-Latn-0 {
+ font-family: "Poppins";
+} \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Charm-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Charm-Bold.ttf
new file mode 100755
index 0000000..d32f072
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Charm-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Charm-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Charm-Regular.ttf
new file mode 100755
index 0000000..02013a4
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Charm-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Chonburi-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Chonburi-Regular.ttf
new file mode 100755
index 0000000..52fedc7
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Chonburi-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Bold.ttf
new file mode 100755
index 0000000..3a8583f
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-ExtraLight.ttf b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-ExtraLight.ttf
new file mode 100755
index 0000000..c4161bd
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-ExtraLight.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Light.ttf b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Light.ttf
new file mode 100755
index 0000000..a676550
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Light.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Medium.ttf b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Medium.ttf
new file mode 100755
index 0000000..75e62df
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Medium.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Regular.ttf
new file mode 100755
index 0000000..c31b8c2
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-SemiBold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-SemiBold.ttf
new file mode 100755
index 0000000..9812b5a
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-SemiBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Thin.ttf b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Thin.ttf
new file mode 100755
index 0000000..2cc968f
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/IBMPlexSansThai-Thin.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Black.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Black.ttf
new file mode 100755
index 0000000..2e37a00
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Black.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-BlackItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-BlackItalic.ttf
new file mode 100755
index 0000000..dc81853
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-BlackItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Bold.ttf
new file mode 100755
index 0000000..4686906
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-BoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-BoldItalic.ttf
new file mode 100755
index 0000000..17eda2a
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-BoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraBold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraBold.ttf
new file mode 100755
index 0000000..5240d38
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraBoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraBoldItalic.ttf
new file mode 100755
index 0000000..037187a
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraBoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraLight.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraLight.ttf
new file mode 100755
index 0000000..404c1e0
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraLight.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraLightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraLightItalic.ttf
new file mode 100755
index 0000000..5d575cc
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ExtraLightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Italic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Italic.ttf
new file mode 100755
index 0000000..e6d868b
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Italic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Light.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Light.ttf
new file mode 100755
index 0000000..3c9d4b5
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Light.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-LightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-LightItalic.ttf
new file mode 100755
index 0000000..59a3f2e
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-LightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Medium.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Medium.ttf
new file mode 100755
index 0000000..50413d0
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Medium.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-MediumItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-MediumItalic.ttf
new file mode 100755
index 0000000..2c6aa14
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-MediumItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Regular.ttf
new file mode 100755
index 0000000..ef204c1
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-SemiBold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-SemiBold.ttf
new file mode 100755
index 0000000..7501a2e
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-SemiBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-SemiBoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-SemiBoldItalic.ttf
new file mode 100755
index 0000000..2284650
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-SemiBoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Thin.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Thin.ttf
new file mode 100755
index 0000000..5c835ad
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-Thin.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ThinItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ThinItalic.ttf
new file mode 100755
index 0000000..25fd1f9
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kanit-ThinItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Bold.ttf
new file mode 100755
index 0000000..cd36ffc
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-BoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-BoldItalic.ttf
new file mode 100755
index 0000000..f86a715
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-BoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-ExtraLight.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-ExtraLight.ttf
new file mode 100755
index 0000000..af416d2
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-ExtraLight.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-ExtraLightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-ExtraLightItalic.ttf
new file mode 100755
index 0000000..3478406
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-ExtraLightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Italic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Italic.ttf
new file mode 100755
index 0000000..3032d77
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Italic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Light.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Light.ttf
new file mode 100755
index 0000000..b3db128
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Light.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-LightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-LightItalic.ttf
new file mode 100755
index 0000000..5acfdd5
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-LightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Medium.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Medium.ttf
new file mode 100755
index 0000000..de8f843
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Medium.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-MediumItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-MediumItalic.ttf
new file mode 100755
index 0000000..2c0cb1e
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-MediumItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Regular.ttf
new file mode 100755
index 0000000..b6cb5d5
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-SemiBold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-SemiBold.ttf
new file mode 100755
index 0000000..917a988
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-SemiBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-SemiBoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-SemiBoldItalic.ttf
new file mode 100755
index 0000000..36d3579
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Kodchasan-SemiBoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Bold.ttf
new file mode 100755
index 0000000..db8c190
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-BoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-BoldItalic.ttf
new file mode 100755
index 0000000..a67a0ae
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-BoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-ExtraLight.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-ExtraLight.ttf
new file mode 100755
index 0000000..0e3f955
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-ExtraLight.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-ExtraLightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-ExtraLightItalic.ttf
new file mode 100755
index 0000000..6f4d3d3
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-ExtraLightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-Italic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Italic.ttf
new file mode 100755
index 0000000..479a8e8
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Italic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-Light.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Light.ttf
new file mode 100755
index 0000000..aaad3b6
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Light.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-LightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-LightItalic.ttf
new file mode 100755
index 0000000..cdfa5c6
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-LightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-Medium.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Medium.ttf
new file mode 100755
index 0000000..658a6a1
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Medium.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-MediumItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-MediumItalic.ttf
new file mode 100755
index 0000000..4b88574
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-MediumItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Regular.ttf
new file mode 100755
index 0000000..70cd35b
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-SemiBold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-SemiBold.ttf
new file mode 100755
index 0000000..acdce49
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-SemiBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Mali-SemiBoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Mali-SemiBoldItalic.ttf
new file mode 100755
index 0000000..fc76e74
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Mali-SemiBoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Bold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Bold.ttf
new file mode 100755
index 0000000..4d6bf36
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Bold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-BoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-BoldItalic.ttf
new file mode 100755
index 0000000..42857a5
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-BoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraBold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraBold.ttf
new file mode 100755
index 0000000..27ba997
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraBoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraBoldItalic.ttf
new file mode 100755
index 0000000..bc36f97
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraBoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraLight.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraLight.ttf
new file mode 100755
index 0000000..060c6c1
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraLight.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraLightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraLightItalic.ttf
new file mode 100755
index 0000000..beaa024
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ExtraLightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Italic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Italic.ttf
new file mode 100755
index 0000000..51d6dbe
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Italic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Light.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Light.ttf
new file mode 100755
index 0000000..d2f2291
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Light.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-LightItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-LightItalic.ttf
new file mode 100755
index 0000000..75eb8d8
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-LightItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Medium.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Medium.ttf
new file mode 100755
index 0000000..e03148e
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Medium.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-MediumItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-MediumItalic.ttf
new file mode 100755
index 0000000..b172a09
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-MediumItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Regular.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Regular.ttf
new file mode 100755
index 0000000..50fa707
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Regular.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-SemiBold.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-SemiBold.ttf
new file mode 100755
index 0000000..7b760ce
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-SemiBold.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-SemiBoldItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-SemiBoldItalic.ttf
new file mode 100755
index 0000000..ecc5fb6
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-SemiBoldItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Thin.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Thin.ttf
new file mode 100755
index 0000000..cdaabcb
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-Thin.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ThinItalic.ttf b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ThinItalic.ttf
new file mode 100755
index 0000000..e96d17e
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/Sarabun-ThinItalic.ttf
Binary files differ
diff --git a/packages/prosody-ui/src/assets/fonts/Thai/style.css b/packages/prosody-ui/src/assets/fonts/Thai/style.css
new file mode 100644
index 0000000..14b828c
--- /dev/null
+++ b/packages/prosody-ui/src/assets/fonts/Thai/style.css
@@ -0,0 +1,76 @@
+@font-face {
+ font-family: "Kanit";
+ src: url(./Kanit-Regular.ttf);
+}
+
+@font-face {
+ font-family: "Sarabun";
+ src: url(./Sarabun-Regular.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+@font-face {
+ font-family: "IBM Plex Sans Thai";
+ src: url(./IBMPlexSansThai-Regular.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+@font-face {
+ font-family: "Mali";
+ src: url(./Mali-Regular.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+@font-face {
+ font-family: "Kodchasan";
+ src: url(./Kodchasan-Regular.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+@font-face {
+ font-family: "Chonburi";
+ src: url(./Chonburi-Regular.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+@font-face {
+ font-family: "Charm";
+ src: url(./Charm-Regular.ttf);
+ font-weight: 300;
+ font-style: normal
+}
+
+
+
+.font-Thai-0 {
+ font-family: "Sarabun";
+}
+
+.font-Thai-1 {
+ font-family: "Kanit";
+}
+
+.font-Thai-2 {
+ font-family: "IBM Plex Sans Thai";
+}
+
+.font-Thai-3 {
+ font-family: "Mali";
+}
+
+.font-Thai-4 {
+ font-family: "Kodchasan";
+}
+
+.font-Thai-5 {
+ font-family: "Charm";
+}
+
+.font-Thai-6 {
+ font-family: "Chonburi";
+} \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/icons/bookmark.svg b/packages/prosody-ui/src/assets/icons/bookmark.svg
new file mode 100755
index 0000000..07c1129
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/bookmark.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5 19.6693V4C5 3.44772 5.44772 3 6 3H18C18.5523 3 19 3.44772 19 4V19.6693C19 20.131 18.4277 20.346 18.1237 19.9985L12 13L5.87629 19.9985C5.57227 20.346 5 20.131 5 19.6693Z" stroke="#000000" stroke-linejoin="round"/>
+</svg>
diff --git a/packages/prosody-ui/src/assets/icons/font.svg b/packages/prosody-ui/src/assets/icons/font.svg
new file mode 100755
index 0000000..d4d5c72
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/font.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg fill="#000000" width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M4.51 2.6.25 13.67h1.34l1.49-3.86h4l1.52 3.86h1.34L5.68 2.6a.63.63 0 0 0-1.17 0zm-.95 6 1.54-4 1.53 4zm9.35-2.54a2.8 2.8 0 0 0-3 2.08l1.21.31a1.6 1.6 0 0 1 1.78-1.14c.77 0 1.59.26 1.59 1v.75c-.27 0-.63.09-.94.13a9.12 9.12 0 0 0-2.5.52 2.06 2.06 0 0 0-1.41 2.23 1.94 1.94 0 0 0 .88 1.44 3 3 0 0 0 1.62.43 4.39 4.39 0 0 0 1.36-.22 2.92 2.92 0 0 0 1-.52v.61h1.25V8.3c0-1.3-1.14-2.24-2.84-2.24zm.22 6.33a2.4 2.4 0 0 1-1.91-.07.64.64 0 0 1-.32-.52c-.1-.89.82-1.16 2.8-1.38l.76-.1c-.19 1.68-.94 1.94-1.33 2.07z"/></svg> \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/icons/heart.svg b/packages/prosody-ui/src/assets/icons/heart.svg
new file mode 100755
index 0000000..f728e1e
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/heart.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M21 8.99998C21 12.7539 15.7156 17.9757 12.5857 20.5327C12.2416 20.8137 11.7516 20.8225 11.399 20.5523C8.26723 18.1523 3 13.1225 3 8.99998C3 2.00001 12 2.00002 12 8C12 2.00001 21 1.99999 21 8.99998Z" stroke="#000000" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
diff --git a/packages/prosody-ui/src/assets/icons/play.svg b/packages/prosody-ui/src/assets/icons/play.svg
new file mode 100755
index 0000000..9a56073
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/play.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg width="800px" height="800px" viewBox="0 -2 19 19" xmlns="http://www.w3.org/2000/svg">
+ <path fill="#000000" fill-rule="evenodd" d="M657,246.007484 C657,245.451066 657.450925,245 657.994771,245 L675.005229,245 C675.554626,245 676,245.44892 676,246.007484 L676,258.992516 C676,259.548934 675.549075,260 675.005229,260 L657.994771,260 C657.445374,260 657,259.55108 657,258.992516 L657,246.007484 Z M658,246 L675,246 L675,259 L658,259 L658,246 Z M670.113445,252.056723 C670.603076,252.301538 670.603593,252.698203 670.113445,252.943277 L664.886555,255.556723 C664.396924,255.801538 664,255.562119 664,254.997071 L664,250.002929 C664,249.449027 664.396407,249.198203 664.886555,249.443277 L670.113445,252.056723 Z" transform="translate(-657 -245)"/>
+</svg> \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/icons/quote.svg b/packages/prosody-ui/src/assets/icons/quote.svg
new file mode 100755
index 0000000..34f5de9
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/quote.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
+<g id="Quotemarks-right">
+ <path d="M14.1933422,9.4116497c-7.8260994,0-14.1922989,6.3662004-14.1922989,14.1924
+ c0,7.5498009,5.9247999,13.7420998,13.3690996,14.169899c0.1288996,1.3916016,0.0321999,5.1797028-3.5977001,10.4491997
+ C9.4980431,48.6206512,9.547843,49.1567497,9.888648,49.497551c1.4853945,1.4853973,2.4033947,2.4208984,3.0458946,3.0751991
+ c0.8408995,0.8554993,1.2247,1.2461014,1.7861996,1.7559013c0.1904001,0.1727982,0.4306002,0.259697,0.6719055,0.259697
+ c0.2342949,0,0.4676943-0.0819969,0.6561956-0.2450981c6.3251991-5.5038986,13.3515987-16.8759995,12.3349991-30.8115005
+ C27.7881413,15.3501501,21.820343,9.4116497,14.1933422,9.4116497z M15.4023428,52.2221489
+ c-0.2723999-0.2684975-0.5830002-0.5848999-1.0410004-1.0508003c-0.5565996-0.5672989-1.3203001-1.3446999-2.4784994-2.5067978
+ c4.4053001-6.7881012,3.5731993-11.6230011,3.2089996-12.3164024c-0.1729002-0.3290977-0.5274-0.5507965-0.8985004-0.5507965
+ c-6.7225995,0-12.1922989-5.4697018-12.1922989-12.1933022c0-6.7227001,5.4696999-12.1924,12.1922989-12.1924
+ c6.5489006,0,11.6777992,5.1582012,12.1963062,12.2646008C27.5322418,39.3501511,18.2168427,49.5268517,15.4023428,52.2221489z"/>
+ <path d="M63.9004402,23.5317497v-0.0009995C63.302742,15.3501501,57.3340416,9.4116497,49.7090416,9.4116497
+ c-7.8261986,0-14.1933937,6.3662004-14.1933937,14.1924c0,7.5498009,5.9257927,13.7420998,13.3710938,14.169899
+ c0.1289062,1.3906021,0.0312004,5.1767006-3.5996017,10.4491997c-0.2743988,0.3975029-0.2245979,0.9336014,0.1162033,1.2744026
+ c1.4794998,1.4794998,2.3955002,2.4130974,3.0380974,3.0663986c0.8446999,0.8613014,1.2304993,1.2538986,1.7949028,1.7656021
+ c0.1903992,0.1718979,0.4315987,0.2587967,0.6718979,0.2587967c0.2344055,0,0.4678001-0.0819969,0.6562004-0.2460976
+ C57.8896484,48.8383484,64.9160385,37.4663506,63.9004402,23.5317497z M50.917942,52.2221489
+ c-0.2743988-0.2705002-0.5877991-0.5887985-1.0498009-1.0594978c-0.5565987-0.5665016-1.3172989-1.3418007-2.4706993-2.4981003
+ c4.4053001-6.7891006,3.5742989-11.6230011,3.2109985-12.3164024c-0.1728973-0.3280983-0.5282974-0.5507965-0.8993988-0.5507965
+ c-6.7237015,0-12.1933937-5.4697018-12.1933937-12.1933022c0-6.7227001,5.4696922-12.1924,12.1933937-12.1924
+ c6.5477982,0,11.6777,5.1582012,12.1972008,12.2656002v-0.0009995
+ C63.0478401,39.3481483,53.7324409,49.5268517,50.917942,52.2221489z"/>
+</g>
+</svg> \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/icons/react.svg b/packages/prosody-ui/src/assets/icons/react.svg
new file mode 100755
index 0000000..6c87de9
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/react.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg> \ No newline at end of file
diff --git a/packages/prosody-ui/src/assets/icons/share.svg b/packages/prosody-ui/src/assets/icons/share.svg
new file mode 100755
index 0000000..92c2b94
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/share.svg
@@ -0,0 +1,28 @@
+<!--?xml version="1.0" encoding="utf-8"?-->
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 256px; height: 256px; opacity: 1;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#4B4B4B;}
+</style>
+<g>
+ <path class="st0" d="M398.73,227.402c62.563,0,113.27-50.793,113.27-113.359c0-62.656-50.707-113.36-113.27-113.36
+ c-62.656,0-113.364,50.704-113.364,113.36c0,11.587,1.733,22.711,4.926,33.292l-114.914,69.397
+ c-18.512-20.154-44.959-32.739-74.417-32.739C45.146,183.993,0,229.228,0,284.954c0,55.816,45.146,100.962,100.962,100.962
+ c30.736,0,58.278-13.778,76.79-35.482l86.824,45.787c-2.646,8.39-4.106,17.323-4.106,26.63
+ c0.093,48.878,39.673,88.466,88.555,88.466c48.976,0,88.556-39.588,88.556-88.466c0-48.976-39.58-88.554-88.556-88.554
+ c-26.812,0-50.886,11.942-67.122,30.825l-84.726-49.431c3.104-9.672,4.742-19.976,4.742-30.736c0-10.393-1.55-20.43-4.56-29.827
+ l118.013-64.294C335.985,213.268,365.715,227.402,398.73,227.402z M344.282,59.687c14.045-13.956,33.11-22.524,54.448-22.524
+ c21.251,0,40.31,8.567,54.356,22.524c13.862,13.956,22.434,33.016,22.434,54.356c0,21.25-8.572,40.399-22.434,54.354
+ c-14.046,13.956-33.105,22.525-54.356,22.525c-19.059,0-36.298-6.84-49.794-18.419h-0.094c-1.55-1.273-3.099-2.645-4.56-4.106
+ c-10.852-10.946-18.422-24.991-21.246-40.852c-0.824-4.382-1.189-8.942-1.189-13.502C321.846,92.703,330.419,73.644,344.282,59.687
+ z M164.343,296.532c-2.28,13.138-8.661,24.902-17.781,34.022c-0.731,0.73-1.55,1.461-2.373,2.192
+ c-11.49,10.393-26.536,16.69-43.227,16.69c-17.874,0-33.928-7.205-45.6-18.881c-11.676-11.765-18.881-27.725-18.881-45.6
+ c0-17.874,7.205-33.834,18.881-45.6c11.672-11.676,27.726-18.881,45.6-18.881c16.232,0,30.825,5.932,42.225,15.782
+ c1.185,0.997,2.28,2.004,3.376,3.099c9.027,9.12,15.413,20.698,17.781,33.746c0.73,3.83,1.095,7.748,1.095,11.854
+ C165.438,288.873,165.074,292.801,164.343,296.532z M297.773,413.73c1.915-10.767,7.022-20.253,14.499-27.725
+ c0.638-0.641,1.367-1.372,2.098-1.915c9.21-8.39,21.251-13.314,34.654-13.314c14.504,0,27.36,5.745,36.846,15.23
+ c9.485,9.485,15.23,22.346,15.23,36.845c0,14.411-5.745,27.272-15.23,36.748c-9.486,9.486-22.342,15.238-36.846,15.238
+ c-14.406,0-27.266-5.753-36.752-15.238c-9.485-9.476-15.23-22.337-15.322-36.748C296.95,419.751,297.225,416.643,297.773,413.73z"></path>
+</g>
+</svg>
diff --git a/packages/prosody-ui/src/assets/icons/speaker.svg b/packages/prosody-ui/src/assets/icons/speaker.svg
new file mode 100644
index 0000000..a16db51
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/speaker.svg
@@ -0,0 +1,32 @@
+<!--?xml version="1.0" encoding="utf-8"?-->
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 256px; height: 256px; opacity: 1;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#4B4B4B;}
+</style>
+<g>
+ <path class="st0" d="M242.908,77.65c-14.745,0-28.695,6.578-41.1,18.231L84.032,170.798H51.761c-4.486,0-8.883,0.893-12.887,2.457
+ c-7.042,2.77-12.814,7.426-17.569,12.941c-7.113,8.319-12.225,18.73-15.808,30.554C1.939,228.592,0,241.889,0,256
+ c0,10.751,1.126,21.037,3.235,30.546c3.19,14.28,8.418,26.855,16.086,36.828c3.861,4.969,8.382,9.312,13.816,12.556
+ c5.407,3.244,11.876,5.29,18.624,5.272h32.271l117.785,74.917c12.396,11.653,26.346,18.231,41.091,18.231
+ c51.529,0,93.3-79.849,93.3-178.35S294.437,77.65,242.908,77.65z M78.732,311.023H51.761c-0.58-0.009-1.116-0.09-1.894-0.394
+ c-1.313-0.491-3.378-1.823-5.684-4.512c-3.468-3.986-7.185-10.84-9.785-19.562c-2.628-8.723-4.219-19.258-4.219-30.555
+ c0-8.615,0.921-16.774,2.52-24.031c2.387-10.884,6.444-19.714,10.492-24.862c2.002-2.601,3.932-4.244,5.38-5.094
+ c1.483-0.858,2.342-1.01,3.19-1.037h26.971c-1.752,3.968-3.297,8.178-4.629,12.61c-3.808,12.646-5.925,27.069-5.925,42.414
+ C68.196,276.742,72.03,295.759,78.732,311.023z M166.32,357.771l-66.372-42.217c-3.915-5.648-7.463-13.101-10.081-21.877
+ c-3.289-10.948-5.228-23.861-5.228-37.677c-0.027-21.046,4.531-40.036,11.386-53.066c1.233-2.359,2.538-4.522,3.878-6.462
+ l66.417-42.244c-10.518,28.866-16.694,63.924-16.694,101.771C149.627,293.838,155.812,328.906,166.32,357.771z M242.908,404.161
+ c-25.719,0-63.102-57.712-63.102-148.161c0-90.448,37.383-148.17,63.102-148.17c25.738,0,63.111,57.722,63.111,148.17
+ C306.018,346.448,268.645,404.161,242.908,404.161z" style="fill: rgb(75, 75, 75);"></path>
+ <path class="st0" d="M243.605,212.684v-0.09c-0.169,0-0.33,0.045-0.491,0.054c-0.233-0.009-0.474-0.054-0.715-0.054v0.134
+ c-11.18,1.322-20.063,21.001-20.063,45.139s8.883,43.816,20.063,45.148v0.125c0.24,0,0.482-0.045,0.715-0.054
+ c0.161,0.009,0.322,0.054,0.491,0.054v-0.09c16.676-0.92,30.01-20.76,30.01-45.184
+ C273.614,233.452,260.281,213.604,243.605,212.684z" style="fill: rgb(75, 75, 75);"></path>
+ <path class="st0" d="M481.696,142.996l-25.085,13.986c17.185,30.859,26.658,65.524,26.666,103.013
+ c-0.008,37.472-9.481,72.137-26.666,102.996l25.085,13.995v-0.009c19.491-34.942,30.313-74.63,30.304-116.981
+ C512.009,217.626,501.187,177.938,481.696,142.996z" style="fill: rgb(75, 75, 75);"></path>
+ <path class="st0" d="M383.026,195.588c9.384,19.456,14.558,41.073,14.567,64.398c-0.009,23.325-5.183,44.952-14.567,64.389
+ l25.862,12.484c11.225-23.244,17.427-49.268,17.418-76.873c0.008-27.614-6.194-53.629-17.418-76.882L383.026,195.588z" style="fill: rgb(75, 75, 75);"></path>
+</g>
+</svg>
diff --git a/packages/prosody-ui/src/assets/icons/spinner.svg b/packages/prosody-ui/src/assets/icons/spinner.svg
new file mode 100755
index 0000000..2a516de
--- /dev/null
+++ b/packages/prosody-ui/src/assets/icons/spinner.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_z9k8{transform-origin:center;animation:spinner_StKS .75s infinite linear}@keyframes spinner_StKS{100%{transform:rotate(360deg)}}</style><path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/><path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" class="spinner_z9k8"/></svg>
diff --git a/packages/prosody-ui/src/components/Sentence.tsx b/packages/prosody-ui/src/components/Sentence.tsx
new file mode 100644
index 0000000..33144ac
--- /dev/null
+++ b/packages/prosody-ui/src/components/Sentence.tsx
@@ -0,0 +1,57 @@
+import React from "react";
+import { notRandomFromArray } from "sortug";
+import "./sentence.css";
+
+export function ColoredText({
+ frags,
+ fn,
+ lang,
+}: {
+ frags: string[];
+ fn?: (s: string) => void;
+ lang?: string;
+}) {
+ return (
+ <>
+ {frags.map((s, i) => {
+ const prev = frags[i - 1];
+ const prevC = prev ? notRandomFromArray(prev, colors) : "lol";
+ const color = notRandomFromArray(s, colors);
+ const opacity = prev && prevC === color ? 0.8 : 1;
+ const style = { color, opacity };
+ console.log({ style });
+ return <CTInner lang={lang} key={s + i} s={s} style={style} fn={fn} />;
+ })}
+ </>
+ );
+}
+
+export function CTInner({
+ s,
+ style,
+ fn,
+ lang,
+}: {
+ s: string;
+ style: any;
+ fn?: (s: string) => void;
+ lang?: string;
+}) {
+ function handleClick(e: React.MouseEvent<HTMLSpanElement>) {
+ console.log(!!fn, "fn");
+ if (fn) fn(e.currentTarget.innerText.trim());
+ }
+ return (
+ <span lang={lang} onClick={handleClick} className="word cp" style={style}>
+ {s}
+ </span>
+ );
+}
+export const colors = [
+ "#8c2c2c",
+ "#000000",
+ "#ffd400",
+ "#1513a0",
+ "#7e7e7e",
+ "1eb52d",
+];
diff --git a/packages/prosody-ui/src/components/Word.tsx b/packages/prosody-ui/src/components/Word.tsx
new file mode 100644
index 0000000..82939ce
--- /dev/null
+++ b/packages/prosody-ui/src/components/Word.tsx
@@ -0,0 +1,119 @@
+import React, { useCallback, useEffect, useState } from "react";
+import spinner from "../assets/icons/spinner.svg";
+import likeIcon from "../assets/icons/heart.svg";
+import commentsIcon from "../assets/icons/quote.svg";
+import shareIcon from "../assets/icons/share.svg";
+import fontIcon from "../assets/icons/font.svg";
+import bookmarkIcon from "../assets/icons/bookmark.svg";
+import speakerIcon from "../assets/icons/speaker.svg";
+import type { AnalyzeRes, Meaning } from "../logic/types";
+import { ColoredText } from "./Sentence.tsx";
+import { P, Span, useSpeechSynthesis } from "../hooks/useLang.tsx";
+
+function Word({ data, lang }: { data: AnalyzeRes; lang: string }) {
+ async function load() {
+ // const wiki = await fetchWiki(data.word);
+ // console.log(wiki, "wiki res");
+ // if ("ok" in wiki) setM(wiki.ok.meanings);
+ // else setError(wiki.error);
+ // setLoading(false);
+ }
+ useEffect(() => {
+ load();
+ }, []);
+ const [error, setError] = useState("");
+ const [loading, setLoading] = useState(true);
+ const [meanings, setM] = useState<Meaning[]>([]);
+ const [font, setFont] = useState(0);
+
+ function changeFont() {
+ if (font === 6) setFont(0);
+ else setFont(font + 1);
+ }
+ const { voices, speaking, speak, stop } = useSpeechSynthesis();
+ function playAudio() {
+ console.log({ voices, speaking });
+ console.log("word", data.word);
+ speak(data.word);
+ }
+
+ async function saveW() {}
+
+ return (
+ <div className={`font-${font}`} id="word-modal" title={data.word}>
+ <img className="font-icon cp" onClick={changeFont} src={fontIcon} />
+ <img className="save-icon cp" onClick={saveW} src={bookmarkIcon} />
+ <div className="original">
+ <ColoredText frags={data.syllables} />
+ </div>
+ <div className="pronunciation IPA flex1 flex-center">
+ <P>{`/${data.ipa.replace(/\s/g, "")}/`}</P>
+ <img onClick={playAudio} className="icon cp" src={speakerIcon} />
+ </div>
+ <div className="meanings">
+ {loading ? (
+ <img src={spinner} className="spinner bc" />
+ ) : (
+ meanings.map((m) => (
+ <div key={JSON.stringify(m)} className="meaning">
+ <div className="pos">
+ <Span>{m.pos}</Span>
+ </div>
+ <ol>
+ {m.meaning.map((t, i) => (
+ <li key={t + i} className="translation">
+ <P>{t}</P>
+ </li>
+ ))}
+ </ol>
+ </div>
+ ))
+ )}
+ {error && <div className="error">{error}</div>}
+ </div>
+ </div>
+ );
+}
+
+export default Word;
+
+// function FloatingButtons({
+// tweet,
+// avatar,
+// changeFont,
+// }: {
+// tweet: Tweet;
+// avatar: string;
+// changeFont: any;
+// }) {
+// const { apiKeys, prompts, setModal } = useGlobalState();
+// function openUser() {}
+// function openComments() {}
+// function openShare() {}
+// async function doLike() {
+// const key = apiKeys.openai;
+// // TODO hide button if key not set
+// console.log(tweet.text, "whole text");
+// console.log(tweet.media, "media");
+// if (tweet.media[0] && !tweet.media[0].isVideo) {
+// const res = await vision(tweet.media[0].url, "", key);
+// console.log(res, "vision res");
+// }
+// // const res = await translate(tweet.text, prompts.translate,key);
+// // if ("ok" in res)
+// // setModal(<TranslationModal text={res.ok.choices[0].message.content}/>)
+// }
+// async function doBookmark() {}
+// return (
+// <div id="entry-icons">
+// <div className="avatar">
+// <img className="cp" onClick={openUser} src={avatar} />
+// </div>
+// <img className="cp" onClick={doLike} src={likeIcon} />
+// <img className="cp" onClick={openComments} src={commentsIcon} />
+// <img className="cp" onClick={doBookmark} src={bookmarkIcon} />
+// <img className="cp" onClick={openShare} src={shareIcon} />
+// <img className="cp" onClick={changeFont} src={fontIcon} />
+// </div>
+// );
+// }
diff --git a/packages/prosody-ui/src/components/sentence.css b/packages/prosody-ui/src/components/sentence.css
new file mode 100644
index 0000000..0bd0a49
--- /dev/null
+++ b/packages/prosody-ui/src/components/sentence.css
@@ -0,0 +1,272 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+#root>.spinner {
+ width: 100px;
+ height: 100px;
+}
+
+#entry>.spinner {
+ width: 80px;
+ height: 80px;
+}
+
+
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+#cookies {
+ & .active {
+ background-color: var(--huang);
+ }
+
+ & input {
+ margin-left: 1rem;
+ width: 100%;
+ }
+
+ & textarea {
+ width: 100%;
+ height: 500px;
+ resize: none;
+ outline: none;
+ }
+}
+
+#entry {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ padding: 1rem;
+ /* prov */
+ border: 2px solid black;
+
+ & div[lang="th"] {
+
+ & .tw-text,
+ & .tw-hashtag {
+ font-size: 3rem;
+ }
+ }
+
+ & .text-wrapper {
+ display: block;
+ margin: 0.5rem 0;
+ /* overflow: hidden; */
+ }
+
+ & .word {
+ display: inline-block;
+ transition: transform 0.3s ease;
+ }
+
+ & .word:hover {
+ transform: scale(1.4);
+ background-color: white;
+ }
+
+ & #tw-media {
+ max-width: 100%;
+
+ & img,
+ & video {
+ max-width: 100%;
+ }
+ }
+}
+
+#inner {
+ height: 100%;
+ max-height: 100%;
+ overflow-y: auto;
+}
+
+#entry-icons {
+ position: absolute;
+ bottom: 5%;
+ right: 5%;
+ width: 50px;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ & .avatar {
+ border: 2px solid black;
+ border-radius: 50%;
+ width: 50px;
+ height: 50px;
+ overflow: hidden;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+
+ & img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+}
+
+#word-modal {
+ position: relative;
+
+ & .font-icon {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 32px;
+ height: 32px;
+ }
+
+ & .save-icon {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 32px;
+ height: 32px;
+ }
+
+ & .original {
+ font-size: 4rem;
+ margin-bottom: 1rem;
+ }
+
+ & .syllable {}
+
+ & .IPA {
+ font-size: 1.6rem;
+ line-height: 1.6rem;
+ & img{
+ width: 50px;
+ margin-left: 1rem;
+ }
+ }
+
+ & .meanings {
+
+ & .spinner {
+ width: 80px;
+ height: 80px;
+ }
+
+ & .meaning {
+ margin: 1rem auto;
+ }
+
+ & .pos {
+ font-size: 1.2rem;
+ margin-bottom: 0.3rem;
+ text-align: left;
+ }
+
+ & ol {
+ word-wrap: normal;
+ margin: auto;
+ text-align: left;
+ }
+ }
+}
+
+img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+.flex1{
+ width: 100%;
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+.flex-center{
+ justify-content: center;
+}
+
+/* p { */
+/* position: absolute; */
+/* top: 50%; */
+/* left: 50%; */
+/* transform: translate(-50%, -50%); */
+/* color: white; */
+/* background-color: rgba(0, 0, 0, 0.5); */
+/* padding: 10px; */
+/* border-radius: 5px; */
+/* } */
+#modal-bg{
+ height: 100vh;
+ width: 100vw;
+ background-color: rgb(0, 0, 0, 0.9);
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 100;
+}
+
+#modal-fg {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ width: 80%;
+ z-index: 101;
+ transform: translate(-50%, -50%);
+ /* background-color: var(--background-color); */
+ background-color: lightgrey;
+ font-size: 1.2rem;
+ padding: 1rem;
+ max-height: 80%;
+ overflow-y: scroll;
+}
+
+
+.text-ipa{
+ font-size: 1.5rem;
+}
+.text-thai{
+ font-size: 1.5rem;
+}
+
+
+/* // new */
+.word.cp{
+ margin: 0 0.5ch;
+}
diff --git a/packages/prosody-ui/src/components/word.css b/packages/prosody-ui/src/components/word.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/prosody-ui/src/components/word.css
diff --git a/packages/prosody-ui/src/files.d.ts b/packages/prosody-ui/src/files.d.ts
new file mode 100644
index 0000000..5e52f80
--- /dev/null
+++ b/packages/prosody-ui/src/files.d.ts
@@ -0,0 +1,4 @@
+declare module "*.svg" {
+ const content: string;
+ export default content;
+}
diff --git a/packages/prosody-ui/src/fonts/FontChanger.tsx b/packages/prosody-ui/src/fonts/FontChanger.tsx
new file mode 100644
index 0000000..15c932e
--- /dev/null
+++ b/packages/prosody-ui/src/fonts/FontChanger.tsx
@@ -0,0 +1,64 @@
+import React, { useEffect, useState, type ReactNode } from "react";
+import fontIcon from "../assets/icons/font.svg";
+import { getScriptPredictor } from "glotscript";
+import ThaiFontLoader from "./Thai";
+
+function FontChanger({ text }: { text: string }) {
+ const [script, setScript] = useState<string | null>(null);
+ useEffect(() => {
+ const predictor = getScriptPredictor();
+ const res = predictor(text);
+ console.log("script predicted", res);
+ setScript(res[0]);
+ }, [text]);
+ useEffect(() => {
+ if (script === "Hani") setFontCount(12);
+ else if (script === "Thai") setFontCount(6);
+ else if (script === "Jpan") setFontCount(5);
+ // else if (script === "Latn") setFontCount(6)
+ }, [script]);
+ const [fontIdx, setFont] = useState(0);
+ const [fontCount, setFontCount] = useState(0);
+ function changeFont() {
+ if (fontIdx === fontCount) setFont(0);
+ else setFont((prev) => prev + 1);
+ }
+ return (
+ <div
+ className={`font-changer font-${script}-${fontIdx}`}
+ lang={script || ""}
+ >
+ <img
+ className="font-icon cp"
+ style={{ width: 25 }}
+ onClick={changeFont}
+ src={fontIcon}
+ />
+ {script === "Thai" ? <ThaiFontLoader text={text} /> : null}
+ </div>
+ );
+}
+// function FontChanger({
+// lang,
+// children,
+// }: {
+// lang: string;
+// children: ReactNode;
+// }) {
+// useEffect(() => {}, []);
+// const [script, setScript] = useState("Latn");
+// const [fontIdx, setFont] = useState(0);
+// const fontCount = 6;
+// function changeFont() {
+// if (fontIdx === fontCount) setFont(0);
+// else setFont((prev) => prev + 1);
+// }
+// return (
+// <div className="font-changer" lang={script}>
+// <img className="font-icon cp" onClick={changeFont} src={fontIcon} />
+// {children}
+// </div>
+// );
+// }
+
+export default FontChanger;
diff --git a/packages/prosody-ui/src/fonts/Hani.tsx b/packages/prosody-ui/src/fonts/Hani.tsx
new file mode 100644
index 0000000..f9cc602
--- /dev/null
+++ b/packages/prosody-ui/src/fonts/Hani.tsx
@@ -0,0 +1,14 @@
+import React, { useState, type ReactNode } from "react";
+import "../assets/fonts/Hani/style.css";
+
+function ChineseFontLoader({ children }: { children: ReactNode }) {
+ const [fontIdx, setFont] = useState(0);
+ const fontCount = 12;
+ function changeFont() {
+ if (fontIdx === fontCount) setFont(0);
+ else setFont((prev) => prev + 1);
+ }
+ return <div>{children}</div>;
+}
+
+export default ChineseFontLoader;
diff --git a/packages/prosody-ui/src/fonts/Thai.tsx b/packages/prosody-ui/src/fonts/Thai.tsx
new file mode 100644
index 0000000..0048316
--- /dev/null
+++ b/packages/prosody-ui/src/fonts/Thai.tsx
@@ -0,0 +1,8 @@
+import React, { useState, type ReactNode } from "react";
+import "../assets/fonts/Thai/style.css";
+
+function ThaiFontLoader({ text }: { text: string }) {
+ return <div>{text}</div>;
+}
+
+export default ThaiFontLoader;
diff --git a/packages/prosody-ui/src/fonts/useLangFont.tsx b/packages/prosody-ui/src/fonts/useLangFont.tsx
new file mode 100644
index 0000000..36fa603
--- /dev/null
+++ b/packages/prosody-ui/src/fonts/useLangFont.tsx
@@ -0,0 +1,44 @@
+import React, { useEffect, useState, type ReactNode } from "react";
+import fontIcon from "../assets/icons/font.svg";
+import { getScriptPredictor } from "glotscript";
+
+function useLangFont({ text }: { text: string }) {
+ useEffect(() => {
+ const predictor = getScriptPredictor();
+ const res = predictor(text);
+ console.log("script predicted", res);
+ setScript(res[0]);
+ }, [text]);
+ const [script, setScript] = useState<string | null>(null);
+ const [fontIdx, setFont] = useState(0);
+ const fontCount = 6;
+ function changeFont() {
+ if (fontIdx === fontCount) setFont(0);
+ else setFont((prev) => prev + 1);
+ }
+ // if (script === "Hani") return {}
+}
+// function FontChanger({
+// lang,
+// children,
+// }: {
+// lang: string;
+// children: ReactNode;
+// }) {
+// useEffect(() => {}, []);
+// const [script, setScript] = useState("Latn");
+// const [fontIdx, setFont] = useState(0);
+// const fontCount = 6;
+// function changeFont() {
+// if (fontIdx === fontCount) setFont(0);
+// else setFont((prev) => prev + 1);
+// }
+// return (
+// <div className="font-changer" lang={script}>
+// <img className="font-icon cp" onClick={changeFont} src={fontIcon} />
+// {children}
+// </div>
+// );
+// }
+
+export default useLangFont;
diff --git a/packages/prosody-ui/src/hooks/useLang.tsx b/packages/prosody-ui/src/hooks/useLang.tsx
new file mode 100644
index 0000000..687c81d
--- /dev/null
+++ b/packages/prosody-ui/src/hooks/useLang.tsx
@@ -0,0 +1,184 @@
+import {
+ useState,
+ useEffect,
+ createContext,
+ useContext,
+ useCallback,
+ type ElementType,
+} from "react";
+type ScriptClass =
+ | "text-ipa"
+ | "text-rtl"
+ | "text-cjk"
+ | "text-thai"
+ | "text-cyrillic"
+ | "text-latin";
+interface ScriptContextType {
+ getClass: (text: string) => ScriptClass;
+}
+const ScriptContext = createContext({
+ getClass: (text: string) => "text-latin" as ScriptClass,
+});
+
+const getScriptClass = (text: string): ScriptClass => {
+ // You can combine these with includes() if text has multiple scripts
+ if (/[\u0591-\u07FF\u200F\u202B]/.test(text)) return "text-rtl";
+
+ if (/[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff]/.test(text)) return "text-cjk";
+
+ if (/[\u0E00-\u0E7F]/.test(text)) return "text-thai";
+
+ if (/[\u0400-\u04FF]/.test(text)) return "text-cyrillic";
+
+ if (/[\u0250-\u02AF\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF]/.test(text))
+ return "text-ipa";
+
+ return "text-latin"; // default
+};
+
+export const ScripProvider: React.FC<{ children: React.ReactNode }> = ({
+ children,
+}) => {
+ const getClass = useCallback((text: string) => {
+ return getScriptClass(text);
+ }, []);
+
+ return (
+ <ScriptContext.Provider value={{ getClass }}>
+ {children}
+ </ScriptContext.Provider>
+ );
+};
+
+export const useScript = () => useContext(ScriptContext);
+
+type TextElementProps = {
+ children?: React.ReactNode;
+ className?: string;
+};
+const createTextElement = (Component: ElementType) => {
+ return function TextElement({
+ children,
+ className,
+ ...rest
+ }: TextElementProps & React.ComponentPropsWithoutRef<ElementType>) {
+ const writingSystemClass =
+ typeof children === "string" ? getScriptClass(children) : "text-latin";
+
+ return (
+ <Component
+ className={`${writingSystemClass} ${className || ""}`}
+ {...rest}
+ >
+ {children}
+ </Component>
+ );
+ };
+};
+
+// Create all the text elements you need
+export const Span = createTextElement("span");
+export const P = createTextElement("p");
+export const H1 = createTextElement("h1");
+export const H2 = createTextElement("h2");
+export const H3 = createTextElement("h3");
+export const H4 = createTextElement("h4");
+export const H5 = createTextElement("h5");
+export const H6 = createTextElement("h6");
+export const Label = createTextElement("label");
+export const Small = createTextElement("small");
+
+interface Voice {
+ default: boolean;
+ lang: string;
+ localService: boolean;
+ name: string;
+ voiceURI: string;
+}
+
+export const useSpeechSynthesis = () => {
+ const [voices, setVoices] = useState<Voice[]>([]);
+ const [speaking, setSpeaking] = useState(false);
+ console.log({ voices }, "voices hook");
+
+ useEffect(() => {
+ // Function to get voices
+ const updateVoices = () => {
+ // Some browsers need a small delay for voices to be available
+ setTimeout(() => {
+ const availableVoices = window.speechSynthesis.getVoices();
+ if (availableVoices.length > 0) {
+ setVoices(availableVoices);
+ }
+ }, 100);
+ };
+
+ // Get initial voices
+ updateVoices();
+
+ window.speechSynthesis.addEventListener("voiceschanged", updateVoices);
+
+ // Cleanup
+ return () => {
+ window.speechSynthesis.removeEventListener("voiceschanged", updateVoices);
+ };
+ }, []);
+
+ const speak = (text: string, voiceName?: string) => {
+ const utterance = new SpeechSynthesisUtterance(text);
+
+ if (voiceName) {
+ const voice = voices.find((v) => v.name === voiceName);
+ if (voice) utterance.voice = voice;
+ }
+
+ utterance.onstart = () => setSpeaking(true);
+ utterance.onend = () => setSpeaking(false);
+ utterance.onerror = () => setSpeaking(false);
+
+ window.speechSynthesis.speak(utterance);
+ };
+
+ const stop = () => {
+ window.speechSynthesis.cancel();
+ setSpeaking(false);
+ };
+
+ return {
+ voices,
+ speaking,
+ speak,
+ stop,
+ };
+};
+
+// Example usage in a component:
+const SpeechComponent = () => {
+ const { voices, speaking, speak, stop } = useSpeechSynthesis();
+ const [selectedVoice, setSelectedVoice] = useState<string>("");
+
+ return (
+ <div>
+ <select
+ value={selectedVoice}
+ onChange={(e) => setSelectedVoice(e.target.value)}
+ >
+ <option value="">Default Voice</option>
+ {voices.map((voice) => (
+ <option key={voice.name} value={voice.name}>
+ {voice.name} ({voice.lang})
+ </option>
+ ))}
+ </select>
+
+ <button
+ onClick={() => speak("Hello, world!", selectedVoice)}
+ disabled={speaking}
+ >
+ {speaking ? "Speaking..." : "Speak"}
+ </button>
+
+ {speaking && <button onClick={stop}>Stop</button>}
+ </div>
+ );
+};
diff --git a/packages/prosody-ui/src/hooks/useModal.tsx b/packages/prosody-ui/src/hooks/useModal.tsx
new file mode 100644
index 0000000..7164bcb
--- /dev/null
+++ b/packages/prosody-ui/src/hooks/useModal.tsx
@@ -0,0 +1,53 @@
+import { franc } from "franc-all";
+import React, {
+ ReactElement,
+ ReactNode,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+
+function useModal() {
+ const [achild, setChild] = useState<ReactNode>(null);
+ return <Modal close={() => setChild(null)}>{achild}</Modal>;
+}
+export default useModal;
+
+function Modal({
+ children,
+ height = "fit-content",
+ width = "80%",
+ close,
+}: {
+ children: ReactNode;
+ close: () => void;
+ width?: any;
+ height?: any;
+}) {
+ function onKey(event: any) {
+ if (event.key === "Escape") close();
+
+ useEffect(() => {
+ document.addEventListener("keyup", onKey);
+ return () => {
+ document.removeEventListener("keyup", onKey);
+ };
+ }, [children]);
+ }
+
+ function clickAway(e: React.MouseEvent) {
+ e.stopPropagation();
+ if (!modalRef.current || !modalRef.current.contains(e.target as any))
+ close();
+ }
+ const modalRef = useRef<HTMLDivElement>(null);
+ const style = { width, height };
+ return (
+ <div id="modal-bg" onClick={clickAway}>
+ <div id="modal-fg" style={style} ref={modalRef}>
+ {children}
+ </div>
+ </div>
+ );
+}
diff --git a/packages/prosody-ui/src/hooks/useTTS.tsx b/packages/prosody-ui/src/hooks/useTTS.tsx
new file mode 100644
index 0000000..671d078
--- /dev/null
+++ b/packages/prosody-ui/src/hooks/useTTS.tsx
@@ -0,0 +1,3 @@
+function useTTS(text: string) {}
+
+export default useTTS;
diff --git a/packages/prosody-ui/src/latin/LatinText.tsx b/packages/prosody-ui/src/latin/LatinText.tsx
new file mode 100644
index 0000000..e5b13ff
--- /dev/null
+++ b/packages/prosody-ui/src/latin/LatinText.tsx
@@ -0,0 +1,77 @@
+import React, { useCallback, useEffect, useState } from "react";
+import type { AnalyzeRes } from "../logic/types";
+import { ColoredText } from "../components/Sentence";
+import Word from "../components/Word";
+import { iso6393To1 } from "../logic/iso6393to1";
+
+function segmentate(
+ text: string,
+ lang: string,
+ granularity: "sentence" | "word" | "grapheme",
+) {
+ // TODO proper error handling here
+ console.log("segmenting", lang);
+ const la = iso6393To1[lang];
+ const lng = la || lang;
+ const segmenter = new Intl.Segmenter(lng, { granularity });
+ const segments = Array.from(segmenter.segment(text));
+ console.log("seg", segments[0]);
+ return segments.reduce((acc: string[], s) => {
+ const trimmed = s.segment.trim();
+ if (trimmed) return [...acc, trimmed];
+ else return acc;
+ }, []);
+}
+
+export default function LatinText({
+ text,
+ lang,
+ openWord,
+}: {
+ text: string;
+ lang: string;
+ openWord?: (word: string) => void;
+}) {
+ useEffect(() => {
+ const sentences = segmentate(text, lang, "sentence");
+ if (sentences) setSentences(sentences);
+ }, [text]);
+ const [sentences, setSentences] = useState<string[]>([]);
+ console.log({ sentences });
+ return (
+ <>
+ {sentences.map((s, i) => (
+ <Sentence key={s + i} text={s} lang={lang} openWord={openWord} />
+ ))}
+ </>
+ );
+}
+
+function Sentence({
+ text,
+ lang,
+ openWord,
+}: {
+ text: string;
+ lang: string;
+
+ openWord?: (word: string) => void;
+}) {
+ useEffect(() => {
+ const w = segmentate(text, lang, "word");
+ if (w) setWords(w);
+ console.log({ words });
+ }, [text]);
+ const [words, setWords] = useState<string[]>([]);
+ console.log({ words });
+
+ // const [data, setData] = useState<Record<string, AnalyzeRes>>({});
+ const [word, setWord] = useState<AnalyzeRes>();
+
+ return (
+ <>
+ <ColoredText frags={words} fn={openWord} />;
+ {word && <Word data={word} lang={lang} />}
+ </>
+ );
+}
diff --git a/packages/prosody-ui/src/logic/iso6393to1.ts b/packages/prosody-ui/src/logic/iso6393to1.ts
new file mode 100644
index 0000000..4c4deed
--- /dev/null
+++ b/packages/prosody-ui/src/logic/iso6393to1.ts
@@ -0,0 +1,186 @@
+export const iso6393To1: Record<string, string> = {
+ aar: "aa",
+ abk: "ab",
+ afr: "af",
+ aka: "ak",
+ amh: "am",
+ ara: "ar",
+ arg: "an",
+ asm: "as",
+ ava: "av",
+ ave: "ae",
+ aym: "ay",
+ aze: "az",
+ bak: "ba",
+ bam: "bm",
+ bel: "be",
+ ben: "bn",
+ bis: "bi",
+ bod: "bo",
+ bos: "bs",
+ bre: "br",
+ bul: "bg",
+ cat: "ca",
+ ces: "cs",
+ cha: "ch",
+ che: "ce",
+ chu: "cu",
+ chv: "cv",
+ cor: "kw",
+ cos: "co",
+ cre: "cr",
+ cym: "cy",
+ dan: "da",
+ deu: "de",
+ div: "dv",
+ dzo: "dz",
+ ell: "el",
+ eng: "en",
+ epo: "eo",
+ est: "et",
+ eus: "eu",
+ ewe: "ee",
+ fao: "fo",
+ fas: "fa",
+ fij: "fj",
+ fin: "fi",
+ fra: "fr",
+ fry: "fy",
+ ful: "ff",
+ gla: "gd",
+ gle: "ga",
+ glg: "gl",
+ glv: "gv",
+ grn: "gn",
+ guj: "gu",
+ hat: "ht",
+ hau: "ha",
+ hbs: "sh",
+ heb: "he",
+ her: "hz",
+ hin: "hi",
+ hmo: "ho",
+ hrv: "hr",
+ hun: "hu",
+ hye: "hy",
+ ibo: "ig",
+ ido: "io",
+ iii: "ii",
+ iku: "iu",
+ ile: "ie",
+ ina: "ia",
+ ind: "id",
+ ipk: "ik",
+ isl: "is",
+ ita: "it",
+ jav: "jv",
+ jpn: "ja",
+ kal: "kl",
+ kan: "kn",
+ kas: "ks",
+ kat: "ka",
+ kau: "kr",
+ kaz: "kk",
+ khm: "km",
+ kik: "ki",
+ kin: "rw",
+ kir: "ky",
+ kom: "kv",
+ kon: "kg",
+ kor: "ko",
+ kua: "kj",
+ kur: "ku",
+ lao: "lo",
+ lat: "la",
+ lav: "lv",
+ lim: "li",
+ lin: "ln",
+ lit: "lt",
+ ltz: "lb",
+ lub: "lu",
+ lug: "lg",
+ mah: "mh",
+ mal: "ml",
+ mar: "mr",
+ mkd: "mk",
+ mlg: "mg",
+ mlt: "mt",
+ mon: "mn",
+ mri: "mi",
+ msa: "ms",
+ mya: "my",
+ nau: "na",
+ nav: "nv",
+ nbl: "nr",
+ nde: "nd",
+ ndo: "ng",
+ nep: "ne",
+ nld: "nl",
+ nno: "nn",
+ nob: "nb",
+ nor: "no",
+ nya: "ny",
+ oci: "oc",
+ oji: "oj",
+ ori: "or",
+ orm: "om",
+ oss: "os",
+ pan: "pa",
+ pli: "pi",
+ pol: "pl",
+ por: "pt",
+ pus: "ps",
+ que: "qu",
+ roh: "rm",
+ ron: "ro",
+ run: "rn",
+ rus: "ru",
+ sag: "sg",
+ san: "sa",
+ sin: "si",
+ slk: "sk",
+ slv: "sl",
+ sme: "se",
+ smo: "sm",
+ sna: "sn",
+ snd: "sd",
+ som: "so",
+ sot: "st",
+ spa: "es",
+ sqi: "sq",
+ srd: "sc",
+ srp: "sr",
+ ssw: "ss",
+ sun: "su",
+ swa: "sw",
+ swe: "sv",
+ tah: "ty",
+ tam: "ta",
+ tat: "tt",
+ tel: "te",
+ tgk: "tg",
+ tgl: "tl",
+ tha: "th",
+ tir: "ti",
+ ton: "to",
+ tsn: "tn",
+ tso: "ts",
+ tuk: "tk",
+ tur: "tr",
+ twi: "tw",
+ uig: "ug",
+ ukr: "uk",
+ urd: "ur",
+ uzb: "uz",
+ ven: "ve",
+ vie: "vi",
+ vol: "vo",
+ wln: "wa",
+ wol: "wo",
+ xho: "xh",
+ yid: "yi",
+ yor: "yo",
+ zha: "za",
+ zho: "zh",
+ zul: "zu",
+};
diff --git a/packages/prosody-ui/src/logic/stanza.ts b/packages/prosody-ui/src/logic/stanza.ts
new file mode 100644
index 0000000..9e59450
--- /dev/null
+++ b/packages/prosody-ui/src/logic/stanza.ts
@@ -0,0 +1,86 @@
+import type { AsyncRes, Result } from "sortug";
+
+const ENDPOINT = "http://localhost:8102";
+export async function segmenter(text: string, lang: string) {
+ try {
+ const body = JSON.stringify({ lang, string: text });
+ const opts = {
+ headers: { "Content-type": "application/json" },
+ method: "POST",
+ body,
+ };
+ const res = await fetch(ENDPOINT + "/segment", opts);
+ console.log("stanza", res);
+ const j = await res.json();
+ return { ok: j };
+ } catch (e) {
+ return { error: `${e}` };
+ }
+}
+export async function idLang(text: string) {
+ try {
+ const body = JSON.stringify({ string: text });
+ const opts = {
+ headers: { "Content-type": "application/json" },
+ method: "POST",
+ body,
+ };
+ const res = await fetch(ENDPOINT + "/detect-lang", opts);
+ const j = await res.json();
+ return { ok: j };
+ } catch (e) {
+ return { error: `${e}` };
+ }
+}
+
+export type Sentence = {
+ text: string;
+ sentiment: number;
+ constituency: string;
+ dependencies: Dependency[];
+ entities: Entity[];
+ tokens: Token[];
+ words: Word[];
+};
+export type Dependency = Array<[Word, string, Word]>;
+export type Word = {
+ id: number;
+ text: string;
+ lemma: string;
+ upos: string;
+ xpos: string;
+ feats: string;
+ head: number;
+ deprel: string;
+ start_char: number;
+ end_char: number;
+};
+export type Token = {
+ id: [number, number];
+ text: string;
+ misc: string;
+ words: Word[];
+ start_char: number;
+ end_char: number;
+ ner: string;
+};
+export type Entity = {
+ text: string;
+ misc: string;
+ start_char: number;
+ end_char: number;
+ type: string;
+};
+// "amod",
+// {
+// "id": 1,
+// "text": "Stony",
+// "lemma": "Stony",
+// "upos": "ADJ",
+// "xpos": "NNP",
+// "feats": "Degree=Pos",
+// "head": 3,
+// "deprel": "amod",
+// "start_char": 0,
+// "end_char": 5
+// }
diff --git a/packages/prosody-ui/src/logic/types.ts b/packages/prosody-ui/src/logic/types.ts
new file mode 100644
index 0000000..ac308cf
--- /dev/null
+++ b/packages/prosody-ui/src/logic/types.ts
@@ -0,0 +1,48 @@
+export type Cookie = {
+ domain: string;
+ path: string;
+ hostOnly: boolean;
+ httpOnly: boolean;
+ secure: boolean;
+ session: boolean;
+ sameSite: SameSite;
+ storeId: null;
+ name: string;
+ value: string;
+};
+export type CookiesMap = Record<string, CookieMap>;
+export type CookieMap = Record<string, Cookie>;
+export type KeyMap = Record<string, string>;
+type SameSite = null | "no_restriction"; // TODO
+
+export type APIRes = { API: { app: string; api_key: string } };
+export type CookieRes = { Cookie: { app: string; cookie: CookieMap } };
+export type CookiesRes = { cookies: CookiesMap; apiKeys: KeyMap };
+
+// words
+export type Meaning = {
+ pos: string; // part of speech;
+ meaning: string[];
+ etymology: string;
+ references?: any;
+};
+
+export type Prompts = {
+ translate: string;
+};
+export type AnalyzeRes = {
+ word: string;
+ syllables: string[];
+ ipa: string;
+ pos: POS;
+};
+type PosTuple = [string, POS];
+type POS = string;
+
+export type WordData = {
+ spelling: string;
+ lang: string;
+ ipa: string;
+ meanings: Meaning[];
+ references?: any;
+};
diff --git a/packages/prosody-ui/src/logic/utils.ts b/packages/prosody-ui/src/logic/utils.ts
new file mode 100644
index 0000000..737a6ec
--- /dev/null
+++ b/packages/prosody-ui/src/logic/utils.ts
@@ -0,0 +1,66 @@
+import type { Result } from "sortug";
+
+export function detectScript(text: string): Result<string> {
+ const scripts = {
+ Latin: /[\u0000-\u007F\u00A0-\u00FF\u0100-\u017F\u0180-\u024F]/g,
+ Cyrillic: /[\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F]/g,
+ Greek: /[\u0370-\u03FF\u1F00-\u1FFF]/g,
+ Hebrew: /[\u0590-\u05FF]/g,
+ Arabic: /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]/g,
+ Devanagari: /[\u0900-\u097F]/g, // Hindi, Sanskrit, etc.
+ Bengali: /[\u0980-\u09FF]/g,
+ Thai: /[\u0E00-\u0E7F]/g,
+ Chinese:
+ /[\u4E00-\u9FFF\u3400-\u4DBF\u20000-\u2A6DF\u2A700-\u2B73F\u2B740-\u2B81F]/g,
+ Japanese: /[\u3040-\u309F\u30A0-\u30FF\uFF00-\uFFEF\u4E00-\u9FAF]/g, // Includes Hiragana, Katakana
+ Korean: /[\uAC00-\uD7AF\u1100-\u11FF\u3130-\u318F]/g, // Includes Hangul
+ Armenian: /[\u0530-\u058F]/g,
+ Georgian: /[\u10A0-\u10FF]/g,
+ Khmer: /[\u1780-\u17FF]/g, // Cambodian
+ Myanmar: /[\u1000-\u109F]/g, // Burmese
+ Tamil: /[\u0B80-\u0BFF]/g,
+ Telugu: /[\u0C00-\u0C7F]/g,
+ Amharic: /[\u1200-\u137F]/g, // Ethiopian
+ };
+ const counts: Record<string, number> = {};
+
+ for (const [scriptName, regex] of Object.entries(scripts)) {
+ // Create an array of matches and count its length
+ const matches = text.match(regex) || [];
+ counts[scriptName] = matches.length;
+ }
+
+ let maxCount = 0;
+ let dominantScript = "Unknown";
+
+ for (const [scriptName, count] of Object.entries(counts)) {
+ if (count > maxCount) {
+ maxCount = count;
+ dominantScript = scriptName;
+ }
+ }
+ if (dominantScript === "Unknown") return { error: "Not detected" };
+ else return { ok: dominantScript };
+}
+
+export function langFromScript(script: string): Result<string> {
+ if (script === "Thai") return { ok: "th" };
+ if (script === "Japanese") return { ok: "ja" };
+ if (script === "Chinese") return { ok: "zh" };
+ if (script === "Korean") return { ok: "ko" };
+ else return { error: "too generic" };
+}
+export function scriptFromLang(lang: string, text: string): string {
+ if (lang == "th") return "Thai";
+ if (lang == "tha") return "Thai";
+ if (lang == "en") return "Engl";
+ if (lang == "es") return "Span";
+ if (lang == "cn") return "Hant";
+ if (lang == "zh") return "Hant";
+ if (lang == "ja") return "Japn";
+ else {
+ const res = detectScript(text);
+ if ("ok" in res) return res.ok;
+ else return "";
+ }
+}
diff --git a/packages/prosody-ui/src/logic/wiki.ts b/packages/prosody-ui/src/logic/wiki.ts
new file mode 100644
index 0000000..1325c0f
--- /dev/null
+++ b/packages/prosody-ui/src/logic/wiki.ts
@@ -0,0 +1,138 @@
+import type { AsyncRes, Result } from "sortug";
+import type { Meaning } from "./types";
+
+export function buildWiktionaryURL(word: string) {
+ const params = new URLSearchParams();
+ params.append("action", "parse");
+ params.append("page", word);
+ params.append("format", "json");
+ params.append("prop", "templates|text");
+ params.append("formatversion", "2");
+
+ const p = params.toString();
+ const url = `https://en.wiktionary.org/w/api.php?${p}`;
+ return url;
+}
+
+// export async function fetchWordInWiki(url: string) {
+// const opts = { method: "GET", body: null, headers: {} };
+// try {
+// const res = await proxyCall(url, opts);
+// console.log(res.headers.get("content-type"));
+// const j = await res.json();
+// return { ok: j };
+// } catch (e) {
+// return { error: `${e}` };
+// }
+// }
+
+export type WikiRes = {
+ url: string;
+ meanings: Meaning[];
+ ipa: string[];
+};
+const poses = [
+ "noun",
+ "verb",
+ "adjective",
+ "adverb",
+ "conjunction",
+ "determiner",
+ "preposition",
+ "definitions",
+];
+
+export function parseWiktionary(html: string, url: string): Result<WikiRes> {
+ try {
+ const dp = new DOMParser();
+ const doc = dp.parseFromString(html, "text/html");
+ const ipas = doc.querySelectorAll(".IPA");
+ const headings = doc.querySelectorAll(".mw-heading");
+ const ms: Meaning[] = [];
+ const doneIdx: number[] = [];
+ let currentRound: Meaning = { pos: "", meaning: [], etymology: "" };
+ for (let [idx, h] of Array.from(headings).entries()) {
+ const headingType: string = (h.firstChild as any).innerText;
+ if (!headingType) continue;
+ const ht = headingType.toLowerCase();
+ if (ht.includes("etymology")) currentRound.etymology = fillEtym(h);
+ else if (poses.includes(ht)) {
+ currentRound.pos = ht;
+ currentRound = fillMeaning(h, currentRound);
+ }
+ if (currentRound.pos) {
+ ms.push({ ...currentRound });
+ currentRound = { pos: "", meaning: [], etymology: "" };
+ }
+ if (ht === "references") break; // make sure it's one single language lol
+ }
+ const ipaStrings = Array.from(ipas).map((el: any) => el.innerText);
+ return { ok: { meanings: ms, ipa: ipaStrings, url } };
+ } catch (e) {
+ return { error: `${e}` };
+ }
+}
+
+function fillMeaning(el: Element, m: Meaning) {
+ const sibling = el.nextElementSibling;
+ if (!sibling) return m;
+ if (sibling?.tagName.toLowerCase() === "ol") {
+ for (let li of Array.from(sibling.children)) {
+ if (li.tagName.toLowerCase() !== "li") continue;
+ if (li.className.includes("empty-elt")) continue;
+ m.meaning.push(li.innerHTML);
+ }
+ }
+ if (m.meaning.length === 0) return fillMeaning(sibling, m);
+ else return m;
+}
+
+function fillEtym(el: Element, acc: string = ""): string {
+ const sibling = el.nextElementSibling;
+ if (!sibling) return acc;
+ if (sibling?.tagName.toLowerCase() === "p") acc += `\n${sibling.innerHTML}`;
+ if (!acc) return fillEtym(sibling, acc);
+ else return acc;
+}
+
+export function parseWiktionaryo(html: string, url: string): Result<WikiRes> {
+ try {
+ const dp = new DOMParser();
+ const doc = dp.parseFromString(html, "text/html");
+ const ipas = doc.querySelectorAll(".IPA");
+ const ols = doc.querySelectorAll("ol");
+ const ms = Array.from(ols).map((el) => {
+ let pos = "";
+ let etymology = "";
+ let meaning: string[] = [];
+ let posr = findPos(el);
+ if ("ok" in posr) pos = posr.ok;
+ for (let li of Array.from(el.children)) {
+ if (li.tagName !== "LI") continue;
+ meaning.push((li as any).innerText);
+ }
+ return { pos, meaning, etymology };
+ });
+ console.log(ipas, "ipa strings");
+ console.log(ols, "lists in wiki");
+ const ipaStrings = Array.from(ipas).map((el: any) => el.innerText);
+ return { ok: { meanings: ms, ipa: ipaStrings, url } };
+ } catch (e) {
+ return { error: `${e}` };
+ }
+}
+
+function findPos(el: Element): Result<string> {
+ let pichai = el.previousElementSibling;
+ console.log(pichai, "previous");
+ if (!pichai) return { error: "no pichai" };
+ if (pichai.classList.contains("mw-heading")) {
+ const h4 = pichai.querySelector("h4");
+ const h3 = pichai.querySelector("h3");
+ if (!h4 && !h3) return findPos(pichai);
+ else {
+ const id = (h4?.innerText || h3?.innerText)!;
+ return { ok: id };
+ }
+ } else return findPos(pichai);
+}
diff --git a/packages/prosody-ui/src/sortug.css b/packages/prosody-ui/src/sortug.css
new file mode 100644
index 0000000..c6280c0
--- /dev/null
+++ b/packages/prosody-ui/src/sortug.css
@@ -0,0 +1,248 @@
+
+/* SORTUG CSS */
+/* variables */
+:root {
+ --bai: rgba(255, 255, 255, 1);
+ --baizi: rgba(230, 230, 230);
+ --hui: rgba(130, 130, 130, 1);
+ --hei: rgba(0, 0, 0, 1);
+ --hong: rgb(141, 15, 15, 1);
+ --huang: rgb(230, 180, 60, 1);
+ --lan: rgb(30, 60, 80, 1);
+}
+
+[data-theme="dark"] {
+ --bg: hei;
+ --fg: baizi;
+}
+
+[data-theme="light"] {
+ --bg: white;
+ --fg: black;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+ height: 100%;
+ min-height: 100%;
+ overscroll-behavior: none;
+ color: var(--fg);
+ -webkit-font-smoothing: antialiased;
+ margin: 0;
+}
+
+/* tailwindy classes */
+.card {
+ padding: 1rem;
+ max-width: max-content;
+}
+
+button,
+.button {
+ max-width: max-content;
+ padding: 0.5rem;
+ border: 1px solid var(--fg);
+}
+
+/* borders */
+.nb {
+ border: none;
+}
+
+/* widths */
+.hw {
+ width: 50%;
+}
+
+.qw {
+ width: 25%;
+}
+
+.tqw {
+ width: 75%;
+}
+
+/* flex */
+.row {
+ display: flex;
+ align-items: center;
+}
+
+.sy {
+ overflow-y: scroll;
+}
+
+.fsy {
+ overflow-y: scroll;
+ height: 100%;
+}
+
+.fxc {
+ display: flex;
+ justify-content: center;
+ align-items: baseline;
+}
+
+/* flex spread */
+.fs {
+ display: flex;
+ justify-content: space-between;
+}
+
+.fsc {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.g1 {
+ gap: 0.5rem;
+}
+
+.g2 {
+ gap: 1rem;
+}
+
+.address {
+ font-family: "Courier New", Courier, monospace;
+}
+
+.spread {
+ justify-content: space-between;
+}
+
+.even {
+ justify-content: space-evenly;
+}
+
+.flexc {
+ justify-content: center;
+}
+
+.cp {
+ cursor: pointer;
+}
+
+/* centering */
+.gc {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.agc {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.ac {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.xc {
+ position: fixed;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 20;
+}
+
+.tc {
+ text-align: center;
+}
+
+.bc {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.blocks {
+ & * {
+ display: block;
+ }
+}
+
+.bold {
+ font-weight: 700;
+}
+
+.weak {
+ opacity: 0.7;
+}
+
+.all-c {
+ & * {
+ margin-left: auto;
+ margin-right: auto;
+ }
+}
+
+.mb-1 {
+ margin-bottom: 1rem;
+}
+
+.error {
+ color: red;
+ text-align: center;
+}
+
+.tabs {
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+
+ & .tab {
+ cursor: pointer;
+ opacity: 0.5;
+ }
+
+ & .tab.active {
+ opacity: 1;
+ }
+}
+
+.disabled {
+ opacity: 0.5;
+}
+
+.smol {
+ font-size: 0.9rem;
+}
+
+/* The Modal (background) */
+#modal-bg {
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(0, 0, 0, 0.4);
+ z-index: 998;
+}
+
+/* Modal Content */
+#modal-fg {
+ background-color: var(--bg);
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ padding: 20px;
+ z-index: 999;
+ max-height: 90vh;
+ min-height: 20vh;
+ max-width: 90vw;
+ overflow: auto;
+}
diff --git a/packages/prosody-ui/src/styles/styles.css b/packages/prosody-ui/src/styles/styles.css
new file mode 100644
index 0000000..69351f1
--- /dev/null
+++ b/packages/prosody-ui/src/styles/styles.css
@@ -0,0 +1,281 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+#root>.spinner {
+ width: 100px;
+ height: 100px;
+}
+
+#entry>.spinner {
+ width: 80px;
+ height: 80px;
+}
+
+
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+#cookies {
+ & .active {
+ background-color: var(--huang);
+ }
+
+ & input {
+ margin-left: 1rem;
+ width: 100%;
+ }
+
+ & textarea {
+ width: 100%;
+ height: 500px;
+ resize: none;
+ outline: none;
+ }
+}
+
+#entry {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ padding: 1rem;
+ /* prov */
+ border: 2px solid black;
+
+ & div[lang="th"] {
+
+ & .tw-text,
+ & .tw-hashtag {
+ font-size: 3rem;
+ }
+ }
+
+ & .text-wrapper {
+ display: block;
+ margin: 0.5rem 0;
+ /* overflow: hidden; */
+ }
+
+ & .word {
+ display: inline-block;
+ transition: transform 0.3s ease;
+ }
+
+ & .word:hover {
+ transform: scale(1.4);
+ background-color: white;
+ }
+
+ & #tw-media {
+ max-width: 100%;
+
+ & img,
+ & video {
+ max-width: 100%;
+ }
+ }
+}
+
+#inner {
+ height: 100%;
+ max-height: 100%;
+ overflow-y: auto;
+}
+
+#entry-icons {
+ position: absolute;
+ bottom: 5%;
+ right: 5%;
+ width: 50px;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ & .avatar {
+ border: 2px solid black;
+ border-radius: 50%;
+ width: 50px;
+ height: 50px;
+ overflow: hidden;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+
+ & img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+}
+
+#word-modal {
+ position: relative;
+
+ & .font-icon {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 32px;
+ height: 32px;
+ }
+
+ & .save-icon {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 32px;
+ height: 32px;
+ }
+
+ & .original {
+ font-size: 4rem;
+ margin-bottom: 1rem;
+ }
+
+ & .syllable {}
+
+ & .IPA {
+ font-size: 1.6rem;
+ line-height: 1.6rem;
+
+ & img {
+ width: 50px;
+ margin-left: 1rem;
+ }
+ }
+
+ & .meanings {
+
+ & .spinner {
+ width: 80px;
+ height: 80px;
+ }
+
+ & .meaning {
+ margin: 1rem auto;
+ }
+
+ & .pos {
+ font-size: 1.2rem;
+ margin-bottom: 0.3rem;
+ text-align: left;
+ }
+
+ & ol {
+ word-wrap: normal;
+ margin: auto;
+ text-align: left;
+ }
+ }
+}
+
+img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.flex1 {
+ width: 100%;
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+
+.flex-center {
+ justify-content: center;
+}
+
+/* p { */
+/* position: absolute; */
+/* top: 50%; */
+/* left: 50%; */
+/* transform: translate(-50%, -50%); */
+/* color: white; */
+/* background-color: rgba(0, 0, 0, 0.5); */
+/* padding: 10px; */
+/* border-radius: 5px; */
+/* } */
+#modal-bg {
+ height: 100vh;
+ width: 100vw;
+ background-color: rgb(0, 0, 0, 0.9);
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 100;
+}
+
+#modal-fg {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ width: 80%;
+ z-index: 101;
+ transform: translate(-50%, -50%);
+ /* background-color: var(--background-color); */
+ background-color: lightgrey;
+ font-size: 1.2rem;
+ padding: 1rem;
+ max-height: 80%;
+ overflow-y: scroll;
+}
+
+
+.text-ipa {
+ font-size: 1.5rem;
+}
+
+.text-thai {
+ font-size: 1.5rem;
+}
+
+
+/* // new */
+.word.cp {
+ margin: 0 0.5ch;
+}
+
+.lang-text-container {
+ display: flex;
+ flex-wrap: wrap;
+} \ No newline at end of file
diff --git a/packages/prosody-ui/src/thai/ThaiText.tsx b/packages/prosody-ui/src/thai/ThaiText.tsx
new file mode 100644
index 0000000..fc1e1e6
--- /dev/null
+++ b/packages/prosody-ui/src/thai/ThaiText.tsx
@@ -0,0 +1,49 @@
+import React, { useCallback, useEffect, useState } from "react";
+import "../assets/fonts/Thai/style.css";
+import { segmentateThai } from "./logic/thainlp";
+import type { AnalyzeRes } from "../logic/types";
+import { ColoredText } from "../components/Sentence";
+import Word from "../components/Word";
+
+export default function ThaiText({
+ text,
+ openWord,
+}: {
+ text: string;
+ openWord: (s: string) => void;
+}) {
+ useEffect(() => {
+ pythonseg();
+ }, [text]);
+
+ const [data, setData] = useState<Record<string, AnalyzeRes>>({});
+ const [modal, setModal] = useState<any>();
+ const pythonseg = useCallback(async () => {
+ const s2 = await segmentateThai(text.trim());
+ if ("ok" in s2) {
+ const ob = s2.ok.reduce((acc, item) => {
+ acc[item.word] = item;
+ return acc;
+ }, {} as any);
+ setData(ob);
+ console.log(s2, "s2");
+ } else console.error(s2.error);
+ }, [text]);
+
+ // function openWord(e: React.MouseEvent<any>) {
+ // const s = e.currentTarget.innerText;
+ // const d = data[s];
+ // setModal(d);
+ // // setModal(<WordModal data={d} lang={lang} />);
+ // }
+ return (
+ <div className="thaitext">
+ <ColoredText lang="tha" frags={Object.keys(data)} fn={openWord} />
+ {modal && <Word data={modal} lang={"tha"} />}
+ </div>
+ );
+}
+
+function ThaiWord() {
+ return <div />;
+}
diff --git a/packages/prosody-ui/src/thai/logic/thainlp.ts b/packages/prosody-ui/src/thai/logic/thainlp.ts
new file mode 100644
index 0000000..031bf4c
--- /dev/null
+++ b/packages/prosody-ui/src/thai/logic/thainlp.ts
@@ -0,0 +1,90 @@
+import type { AsyncRes } from "sortug";
+import type { AnalyzeRes } from "../../logic/types";
+
+const ENDPOINT = "http://192.168.1.110:8001";
+async function call(path: string, body: any) {
+ try {
+ const opts = {
+ method: "POST",
+ headers: { "Content-type": "application/json" },
+ body: JSON.stringify(body),
+ };
+ const r1 = await fetch(ENDPOINT + path, opts);
+ // const r2 = await fetch(`http://192.168.1.110:8000/analyze`, opts);
+ const jj = await r1.json();
+ return { ok: jj };
+ } catch (e) {
+ return { error: `${e}` };
+ }
+}
+export async function analyzeTHWord(word: string): AsyncRes<AnalyzeRes> {
+ return await call("/analyze", { word });
+}
+export async function segmentateThai(sentence: string): AsyncRes<AnalyzeRes[]> {
+ return await call("/segmentate", { word: sentence });
+}
+
+export const POSMAP: Record<string, string> = {
+ ADJ: "Adjective",
+ ADP: "Adposition",
+ ADV: "Adverb",
+ AUX: "Auxiliary",
+ CCONJ: "Coordinating conjunction",
+ DET: "Determiner",
+ INTJ: "Interjection",
+ NOUN: "Noun",
+ NUM: "Numeral",
+ PART: "Particle",
+ PRON: "Pronoun",
+ PROPN: "Proper noun",
+ PUNCT: "Punctuation",
+ SCONJ: "Subordinating conjunction",
+ VERB: "Verb",
+ NPRP: "Proper noun",
+ NCNM: "Cardinal number",
+ NONM: "Ordinal number",
+ NLBL: "Label noun",
+ NCMN: "Common noun",
+ NTTL: "Title noun",
+ PPRS: "Personal pronoun",
+ PDMN: "Demonstrative pronoun",
+ PNTR: "Interrogative pronoun",
+ PREL: "Relative pronoun",
+ VACT: "Active verb",
+ VSTA: "Stative verb",
+ VATT: "Attributive verb",
+ XVBM: "Pre-verb auxiliary, before negator “ไม่”",
+ XVAM: "Pre-verb auxiliary, after negator “ไม่”",
+ XVMM: "Pre-verb, before or after negator “ไม่”",
+ XVBB: "Pre-verb auxiliary, in imperative mood",
+ XVAE: "Post-verb auxiliary",
+ DDAN: "classifier in between",
+ DDAC: "in between",
+ DDBQ: "classifier or preceding quantitative expression",
+ DDAQ: "following quantitative expression",
+ DIAC: "classifier in between",
+ DIBQ: "classifier or preceding quantitative expression",
+ DIAQ: "following quantitative expression",
+ DCNM: "Determiner, cardinal number expression",
+ DONM: "Determiner, ordinal number expression",
+ ADVN: "Adverb with normal form",
+ ADVI: "Adverb with iterative form",
+ ADVP: "Adverb with prefixed form",
+ ADVS: "Sentential adverb",
+ CNIT: "Unit classifier",
+ CLTV: "Collective classifier",
+ CMTR: "Measurement classifier",
+ CFQC: "Frequency classifier",
+ CVBL: "Verbal classifier",
+ JCRG: "Coordinating conjunction",
+ JCMP: "Comparative conjunction",
+ JSBR: "Subordinating conjunction",
+ RPRE: "Preposition",
+ INT: "Interjection",
+ FIXN: "Nominal prefix",
+ FIXV: "Adverbial prefix",
+ EAFF: "Ending for affirmative sentence",
+ EITT: "Ending for interrogative sentence",
+ NEG: "Negator",
+ PUNC: "Punctuation",
+};
diff --git a/packages/prosody-ui/src/zoom/FullText.tsx b/packages/prosody-ui/src/zoom/FullText.tsx
new file mode 100644
index 0000000..ec85f09
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/FullText.tsx
@@ -0,0 +1,60 @@
+import React from "react";
+import { motion, AnimatePresence } from "motion/react";
+import Paragraph from "./Paragraph";
+import { useZoom } from "./hooks/useZoom";
+import { containerVariants, buttonVariants } from "./animations";
+import { NLP } from "sortug-ai";
+
+interface TextFocusMorphProps {
+ text: string;
+ doc: NLP.Spacy.SpacyRes;
+}
+
+const FullText: React.FC<TextFocusMorphProps> = ({ text, doc }) => {
+ const { viewState, navigateBack, handleElementClick } = useZoom();
+ const { level } = viewState;
+
+ // Split text into paragraphs
+ const paragraphs = text
+ .split("\n\n")
+ .map((p) => p.trim())
+ .filter(Boolean);
+
+ return (
+ <div className="text-focus-morph">
+ {level !== "text" && (
+ <AnimatePresence>
+ <motion.button
+ className="back-button"
+ onClick={navigateBack}
+ variants={buttonVariants}
+ initial="initial"
+ animate="animate"
+ exit="exit"
+ >
+ ← Back
+ </motion.button>
+ </AnimatePresence>
+ )}
+
+ <motion.div
+ className="content-container"
+ variants={containerVariants}
+ initial="text"
+ animate={level}
+ >
+ {paragraphs.map((paragraph, idx) => (
+ <Paragraph
+ doc={doc}
+ key={paragraph + idx}
+ rawText={paragraph}
+ context={{ idx, parentText: text, segmented: paragraphs }}
+ idx={idx}
+ />
+ ))}
+ </motion.div>
+ </div>
+ );
+};
+
+export default FullText;
diff --git a/packages/prosody-ui/src/zoom/Paragraph.tsx b/packages/prosody-ui/src/zoom/Paragraph.tsx
new file mode 100644
index 0000000..c26f806
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/Paragraph.tsx
@@ -0,0 +1,60 @@
+import React, { memo, useCallback, useEffect, useState } from "react";
+import { motion } from "motion/react";
+import type { ViewProps, LoadingStatus } from "./logic/types";
+import { NLP } from "sortug-ai";
+import Sentence from "./Sentence";
+import { paragraphVariants, createHoverEffect } from "./animations";
+import { useZoom } from "./hooks/useZoom";
+
+function Paragraph({ rawText, context, idx, doc }: ViewProps) {
+ const { viewState, handleElementClick } = useZoom();
+ const { level, pIndex } = viewState;
+ const selected = pIndex === idx;
+ const isFocused = level === "paragraph" && selected;
+
+ // State for sentences
+ const [loading, setLoading] = useState<LoadingStatus>("pending");
+
+ return (
+ <>
+ <motion.div
+ key={idx + rawText}
+ className={`paragraph-wrapper ${selected ? "selected" : ""}`}
+ custom={selected}
+ variants={paragraphVariants}
+ initial="text"
+ animate={level}
+ onClick={(e) => handleElementClick(e, idx)}
+ whileHover={
+ level === "text"
+ ? createHoverEffect(level, "text", "255, 255, 200")
+ : {}
+ }
+ >
+ {loading === "loading" && <div className="spinner" />}
+ {level === "text" || !selected || doc.segs.length === 0 ? (
+ <p className="paragraph">{rawText}</p>
+ ) : (
+ <div className="sentences-container">
+ {doc.segs.map((sentence, sentIdx) => (
+ <Sentence
+ key={sentence.text + sentIdx}
+ idx={sentIdx}
+ rawText={sentence.text}
+ spacy={sentence}
+ context={{
+ idx: sentIdx,
+ parentText: rawText,
+ segmented: doc.segs.map((s) => s.text),
+ }}
+ doc={doc}
+ />
+ ))}
+ </div>
+ )}
+ </motion.div>
+ </>
+ );
+}
+
+export default memo(Paragraph);
diff --git a/packages/prosody-ui/src/zoom/Sentence.tsx b/packages/prosody-ui/src/zoom/Sentence.tsx
new file mode 100644
index 0000000..1d90346
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/Sentence.tsx
@@ -0,0 +1,46 @@
+import React, { memo } from "react";
+import { motion } from "motion/react";
+import type { ViewProps, LoadingStatus } from "./logic/types";
+import { NLP } from "sortug-ai";
+import SpacyClause from "./SpacyClause";
+import { sentenceVariants, createHoverEffect } from "./animations";
+import { useZoom } from "./hooks/useZoom";
+
+interface Props extends ViewProps {
+ spacy: NLP.Spacy.Sentence;
+ stanza?: NLP.Stanza.Sentence;
+}
+
+function Sentence({ spacy, stanza, context, idx }: Props) {
+ const { viewState, handleElementClick } = useZoom();
+ const { level, sIndex } = viewState;
+ const selected = sIndex === idx;
+ const isFocused = level === "sentence" && selected;
+
+ return (
+ <>
+ <motion.span
+ key={idx + spacy.text}
+ className={`sentence-wrapper ${selected ? "selected" : ""}`}
+ custom={selected}
+ variants={sentenceVariants}
+ initial="paragraph"
+ animate={level}
+ onClick={(e) => handleElementClick(e, idx)}
+ whileHover={
+ level === "paragraph"
+ ? createHoverEffect(level, "paragraph", "200, 220, 255")
+ : {}
+ }
+ >
+ {level === "paragraph" || !selected ? (
+ <span className="sentence">{spacy.text}</span>
+ ) : (
+ <SpacyClause sentence={spacy} />
+ )}
+ </motion.span>
+ </>
+ );
+}
+
+export default memo(Sentence);
diff --git a/packages/prosody-ui/src/zoom/SpacyClause.tsx b/packages/prosody-ui/src/zoom/SpacyClause.tsx
new file mode 100644
index 0000000..6b6f178
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/SpacyClause.tsx
@@ -0,0 +1,125 @@
+import React, { memo, useState } from "react";
+import { motion } from "motion/react";
+import "./spacy.css";
+import { NLP } from "sortug-ai";
+// import { clauseVariants, createHoverEffect } from "./animations";
+// import { useZoom } from "./hooks/useZoom";
+
+function Grammar({ sentence }: { sentence: NLP.Spacy.Sentence }) {
+ const [hoveredClause, setHoveredClause] = useState<number | null>(null);
+
+ // Ref to manage the timeout for debouncing mouse leave
+ return (
+ <div className="clause-container">
+ {sentence.words.map((w, idx) => {
+ const isRoot =
+ w.ancestors.length === 0 || w.dep.toLowerCase() === "root";
+ const isSubj = NLP.Spacy.isChild(w, sentence.subj.id);
+ const isPred = !isSubj && !isRoot;
+ const predClass = isPred ? "pred" : "";
+ const relClass = isRoot ? "root" : `rel-${w.dep}`;
+ const ownClass = isRoot
+ ? ""
+ : isSubj
+ ? "subj"
+ : w.children.length === 0
+ ? ""
+ : `clause-${w.id}`;
+ const clase = w.ancestors.reduce((acc, item) => {
+ if (item === sentence.subj.id || item === sentence.root.id)
+ return acc;
+ else return `${acc} clause-${item}`;
+ }, ``);
+ const className = `suword ${relClass} ${ownClass} ${clase} ${predClass}`;
+ const isHovering =
+ !isRoot &&
+ !!hoveredClause &&
+ (w.id === hoveredClause || w.ancestors.includes(hoveredClause));
+ function handleClick(w: NLP.Spacy.Word) {
+ console.log("show the whole clause and all that", w);
+ }
+ return (
+ <ClauseSpan
+ word={w}
+ key={w.id}
+ className={className}
+ hovering={isHovering}
+ setHovering={setHoveredClause}
+ onClick={handleClick}
+ />
+ );
+ })}
+ </div>
+ );
+}
+
+const spanVariants: any = {
+ initial: {
+ // Base style
+ backgroundColor: "rgba(0, 0, 0, 0)", // Transparent background initially
+ fontWeight: "normal",
+ scale: 1,
+ zIndex: 0, // Default stacking
+ position: "relative", // Needed for zIndex to work reliably
+ // Add other base styles if needed
+ },
+ hovered: {
+ // Style when this span's group is hovered
+ backgroundColor: "rgba(255, 255, 0, 0.5)", // Yellow highlight
+ scale: 1.05,
+ zIndex: 1, // Bring hovered spans slightly forward
+ boxShadow: "0px 2px 5px rgba(0,0,0,0.2)",
+ // Add other hover effects
+ },
+};
+
+// Define the transition
+const spanTransition = {
+ type: "spring",
+ stiffness: 500,
+ damping: 30,
+ // duration: 0.1 // Or use duration for non-spring types
+};
+
+function ClauseSpan({
+ word,
+ className,
+ hovering,
+ setHovering,
+ onClick,
+}: {
+ word: NLP.Spacy.Word;
+ className: string;
+ hovering: boolean;
+ setHovering: (n: number | null) => void;
+ onClick: (w: NLP.Spacy.Word) => void;
+}) {
+ function handleMouseOver() {
+ setHovering(word.id);
+ // if (word.children.length > 0) setHovering(word.id);
+ // else setHovering(word.head);
+ }
+ function handleMouseLeave() {
+ setHovering(null);
+ }
+ function handleClick(e: React.MouseEvent) {
+ e.stopPropagation();
+ onClick(word);
+ }
+ return (
+ <motion.span
+ className={className}
+ variants={spanVariants}
+ initial="initial"
+ animate={hovering ? "hovered" : "initial"}
+ transition={spanTransition}
+ onMouseOver={handleMouseOver}
+ onMouseLeave={handleMouseLeave}
+ onClick={handleClick}
+ >
+ {word.text}
+ </motion.span>
+ );
+}
+
+export default memo(Grammar);
diff --git a/packages/prosody-ui/src/zoom/animations.ts b/packages/prosody-ui/src/zoom/animations.ts
new file mode 100644
index 0000000..6135e7f
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/animations.ts
@@ -0,0 +1,199 @@
+import type { Variants } from "motion/react";
+
+// Base transition configurations for consistent animations
+const baseTransition = {
+ duration: 0.5,
+ ease: [0.43, 0.13, 0.23, 0.96], // Improved easing for smoother feel
+};
+
+export const fadeTransition = {
+ ...baseTransition,
+ duration: 0.3,
+};
+
+// Shared variants for different view levels
+export const containerVariants: Variants = {
+ text: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.05,
+ delayChildren: 0.1,
+ },
+ },
+ paragraph: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.05,
+ delayChildren: 0.1,
+ },
+ },
+ sentence: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.05,
+ delayChildren: 0.1,
+ },
+ },
+ clause: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.05,
+ delayChildren: 0.1,
+ },
+ },
+ word: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.05,
+ delayChildren: 0.1,
+ },
+ },
+ syllable: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.05,
+ delayChildren: 0.1,
+ },
+ },
+ phoneme: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.05,
+ delayChildren: 0.1,
+ },
+ },
+};
+
+// Function to create element variants based on selection state
+export const createElementVariants = (
+ currentLevel: string,
+ nextLevel: string,
+ prevLevel: string,
+ selectedOpacity = 1,
+ unselectedOpacity = 0.1,
+ selectedScale = 1.05,
+ unselectedScale = 0.95,
+ selectedBlur = "0px",
+ unselectedBlur = "2px",
+ bgColor = "rgba(255, 255, 255, 0)", // Use rgba with 0 opacity instead of transparent
+): Variants => {
+ return {
+ [prevLevel]: {
+ opacity: 1,
+ scale: 1,
+ filter: "blur(0px)",
+ backgroundColor: "rgba(255, 255, 255, 0)", // Use rgba with 0 opacity
+ transition: baseTransition,
+ },
+ [currentLevel]: (isSelected: boolean) => ({
+ opacity: isSelected ? selectedOpacity : unselectedOpacity,
+ scale: isSelected ? selectedScale : unselectedScale,
+ filter: isSelected ? `blur(${selectedBlur})` : `blur(${unselectedBlur})`,
+ backgroundColor: isSelected ? bgColor : "rgba(255, 255, 255, 0)", // Use rgba with 0 opacity
+ transition: baseTransition,
+ }),
+ [nextLevel]: (isSelected: boolean) => ({
+ opacity: isSelected ? selectedOpacity : unselectedOpacity / 2,
+ scale: isSelected ? selectedScale : unselectedScale * 0.95,
+ filter: isSelected
+ ? `blur(${selectedBlur})`
+ : `blur(${parseInt(unselectedBlur) + 1}px)`,
+ backgroundColor: isSelected ? bgColor : "rgba(255, 255, 255, 0)", // Use rgba with 0 opacity
+ transition: baseTransition,
+ }),
+ };
+};
+
+// Pre-configured variants for each level
+export const paragraphVariants = createElementVariants(
+ "paragraph",
+ "sentence",
+ "text",
+ 1,
+ 0.1,
+ 1.05,
+ 0.95,
+ "0px",
+ "2px",
+ "rgba(200, 220, 255, 0.1)",
+);
+
+export const sentenceVariants = createElementVariants(
+ "sentence",
+ "clause",
+ "paragraph",
+ 1,
+ 0.1,
+ 1.1,
+ 0.95,
+ "0px",
+ "2px",
+ "rgba(200, 220, 255, 0.2)",
+);
+
+export const clauseVariants = createElementVariants(
+ "clause",
+ "word",
+ "sentence",
+ 1,
+ 0.1,
+ 1.1,
+ 0.95,
+ "0px",
+ "2px",
+ "rgba(220, 200, 255, 0.2)",
+);
+
+export const wordVariants = createElementVariants(
+ "word",
+ "syllable",
+ "clause",
+ 1,
+ 0.1,
+ 1.15,
+ 0.9,
+ "0px",
+ "2px",
+ "rgba(255, 200, 200, 0.2)",
+);
+
+export const syllableVariants = createElementVariants(
+ "syllable",
+ "phoneme",
+ "word",
+ 1,
+ 0.1,
+ 1.2,
+ 0.9,
+ "0px",
+ "2px",
+ "rgba(200, 255, 200, 0.2)",
+);
+
+// Button animations
+export const buttonVariants: Variants = {
+ initial: { opacity: 0, x: -20 },
+ animate: { opacity: 1, x: 0, transition: fadeTransition },
+ exit: { opacity: 0, x: -20, transition: fadeTransition },
+};
+
+// Hover effects
+export const createHoverEffect = (
+ level: string,
+ currentLevel: string,
+ color: string,
+) => {
+ if (level === currentLevel) {
+ return {
+ scale: 1.02,
+ backgroundColor: `rgba(${color}, 0.3)`,
+ transition: { duration: 0.2 },
+ };
+ }
+ return {
+ // Return empty animation with same properties to avoid errors
+ scale: 1,
+ backgroundColor: "rgba(255, 255, 255, 0)",
+ transition: { duration: 0.2 },
+ };
+};
diff --git a/packages/prosody-ui/src/zoom/hooks/useZoom.tsx b/packages/prosody-ui/src/zoom/hooks/useZoom.tsx
new file mode 100644
index 0000000..733ca06
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/hooks/useZoom.tsx
@@ -0,0 +1,135 @@
+import React, {
+ createContext,
+ useState,
+ useContext,
+ type ReactNode,
+} from "react";
+import type { ViewLevel, ViewState } from "../logic/types";
+
+// Type definitions for the context
+interface ZoomContextType {
+ viewState: ViewState;
+ setLevel: (level: ViewLevel) => void;
+ setParagraphIndex: (idx: number | null) => void;
+ setSentenceIndex: (idx: number | null) => void;
+ setClauseIndex: (idx: number | null) => void;
+ setWordIndex: (idx: number | null) => void;
+ setSyllableIndex: (idx: number | null) => void;
+ setPhonemeIndex: (idx: number | null) => void;
+ navigateBack: () => void;
+ handleElementClick: (e: React.MouseEvent, idx: number) => void;
+}
+
+// Create the context with default empty values
+const ZoomContext = createContext<ZoomContextType>({
+ viewState: {
+ level: "text",
+ pIndex: null,
+ sIndex: null,
+ cIndex: null,
+ wIndex: null,
+ yIndex: null,
+ fIndex: null,
+ },
+ setLevel: () => {},
+ setParagraphIndex: () => {},
+ setSentenceIndex: () => {},
+ setClauseIndex: () => {},
+ setWordIndex: () => {},
+ setSyllableIndex: () => {},
+ setPhonemeIndex: () => {},
+ navigateBack: () => {},
+ handleElementClick: () => {},
+});
+
+// Provider component
+export const ZoomProvider: React.FC<{ children: ReactNode }> = ({
+ children,
+}) => {
+ const [viewState, setViewState] = useState<ViewState>({
+ level: "text",
+ pIndex: null,
+ sIndex: null,
+ cIndex: null,
+ wIndex: null,
+ yIndex: null,
+ fIndex: null,
+ });
+
+ // Helper functions to update individual parts of the state
+ const setLevel = (level: ViewLevel) =>
+ setViewState((prev) => ({ ...prev, level }));
+ const setParagraphIndex = (pIndex: number | null) =>
+ setViewState((prev) => ({ ...prev, pIndex }));
+ const setSentenceIndex = (sIndex: number | null) =>
+ setViewState((prev) => ({ ...prev, sIndex }));
+ const setClauseIndex = (cIndex: number | null) =>
+ setViewState((prev) => ({ ...prev, cIndex }));
+ const setWordIndex = (wIndex: number | null) =>
+ setViewState((prev) => ({ ...prev, wIndex }));
+ const setSyllableIndex = (yIndex: number | null) =>
+ setViewState((prev) => ({ ...prev, yIndex }));
+ const setPhonemeIndex = (fIndex: number | null) =>
+ setViewState((prev) => ({ ...prev, fIndex }));
+
+ // Handle navigation levels
+ const navigateBack = () => {
+ const { level } = viewState;
+
+ if (level === "paragraph") {
+ setViewState((prev) => ({ ...prev, level: "text", pIndex: null }));
+ } else if (level === "sentence") {
+ setViewState((prev) => ({ ...prev, level: "paragraph", sIndex: null }));
+ } else if (level === "clause") {
+ setViewState((prev) => ({ ...prev, level: "sentence", cIndex: null }));
+ } else if (level === "word") {
+ setViewState((prev) => ({ ...prev, level: "clause", wIndex: null }));
+ } else if (level === "syllable") {
+ setViewState((prev) => ({ ...prev, level: "word", yIndex: null }));
+ } else if (level === "phoneme") {
+ setViewState((prev) => ({ ...prev, level: "syllable", fIndex: null }));
+ }
+ };
+
+ // Handle clicks on elements to navigate forward
+ const handleElementClick = (e: React.MouseEvent, idx: number) => {
+ e.stopPropagation();
+ const { level } = viewState;
+
+ if (level === "text") {
+ setViewState((prev) => ({ ...prev, level: "paragraph", pIndex: idx }));
+ } else if (level === "paragraph") {
+ setViewState((prev) => ({ ...prev, level: "sentence", sIndex: idx }));
+ } else if (level === "sentence") {
+ setViewState((prev) => ({ ...prev, level: "clause", cIndex: idx }));
+ } else if (level === "clause") {
+ setViewState((prev) => ({ ...prev, level: "word", wIndex: idx }));
+ } else if (level === "word") {
+ setViewState((prev) => ({ ...prev, level: "syllable", yIndex: idx }));
+ } else if (level === "syllable") {
+ setViewState((prev) => ({ ...prev, level: "phoneme", fIndex: idx }));
+ }
+ };
+
+ return (
+ <ZoomContext.Provider
+ value={{
+ viewState,
+ setLevel,
+ setParagraphIndex,
+ setSentenceIndex,
+ setClauseIndex,
+ setWordIndex,
+ setSyllableIndex,
+ setPhonemeIndex,
+ navigateBack,
+ handleElementClick,
+ }}
+ >
+ {children}
+ </ZoomContext.Provider>
+ );
+};
+
+// Custom hook to use the zoom context
+export const useZoom = () => useContext(ZoomContext);
diff --git a/packages/prosody-ui/src/zoom/index.ts b/packages/prosody-ui/src/zoom/index.ts
new file mode 100644
index 0000000..baf5db1
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/index.ts
@@ -0,0 +1,8 @@
+export { ZoomProvider, useZoom } from "./hooks/useZoom";
+import Paragraph from "./Paragraph";
+import FullText from "./FullText";
+import Sentence from "./Paragraph";
+import SpacyClause from "./SpacyClause";
+import type * as Types from "./logic/types";
+
+export { Paragraph, FullText, Sentence, SpacyClause, Types };
diff --git a/packages/prosody-ui/src/zoom/logic/types.ts b/packages/prosody-ui/src/zoom/logic/types.ts
new file mode 100644
index 0000000..bea68ff
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/logic/types.ts
@@ -0,0 +1,53 @@
+import type { NLP } from "sortug-ai";
+
+export type ViewLevel =
+ | "text"
+ | "paragraph"
+ | "sentence"
+ | "clause"
+ | "word"
+ | "syllable"
+ | "phoneme";
+export interface ViewState {
+ level: ViewLevel;
+ pIndex: number | null;
+ sIndex: number | null;
+ cIndex: number | null;
+ wIndex: number | null;
+ yIndex: number | null;
+ fIndex: number | null;
+}
+
+export interface ViewProps {
+ idx: number;
+ rawText: string;
+ context: Context;
+ doc: NLP.Spacy.SpacyRes;
+}
+export type Context = {
+ parentText: string;
+ segmented: string[];
+ idx: number;
+};
+
+export type WordData = {
+ confidence: number;
+ frequency: number | null;
+ id: number;
+ ipa: Array<{ ipa: string; tags: string[] }>;
+ spelling: string;
+ type: ExpressionType;
+ syllables: number;
+ lang: string;
+ prosody: any;
+ senses: Sense[];
+};
+export type ExpressionType = "word" | "expression" | "syllable";
+export type Sense = {
+ etymology: string;
+ pos: string;
+ forms: Array<{ form: string; tags: string[] }>;
+ related: any;
+ senses: Array<{ glosses: string[]; links: Array<[string, string]> }>;
+};
+export type LoadingStatus = "pending" | "loading" | "success" | "error";
diff --git a/packages/prosody-ui/src/zoom/spacy.css b/packages/prosody-ui/src/zoom/spacy.css
new file mode 100644
index 0000000..0077119
--- /dev/null
+++ b/packages/prosody-ui/src/zoom/spacy.css
@@ -0,0 +1,39 @@
+.suword {
+ margin-left: 0.5ch;
+ margin-right: 0.5ch;
+}
+
+/* .suword.pred { */
+/* color: gold; */
+/* } */
+
+/* Clause level */
+.clause-container {
+ max-width: 600px;
+ white-space: normal !important;
+ hyphens: auto;
+
+ padding: 2px;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+ will-change: transform, opacity, filter, background-color;
+
+ span {
+ white-space: normal !important;
+ }
+}
+
+.clause-container.selected {
+ background-color: rgba(220, 200, 255, 0.2);
+ z-index: 3;
+}
+
+.suword.subj {
+ color: blue;
+ /* border-bottom: 2px solid blue; */
+}
+
+.suword.root {
+ color: darkred;
+} \ No newline at end of file