sourojitdotcom commited on
Commit
e91ee18
·
verified ·
1 Parent(s): 572a1ec

AksharaLP demo v1.0.0

Browse files
.gitattributes CHANGED
@@ -1,35 +1,5 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
1
  *.bin filter=lfs diff=lfs merge=lfs -text
2
+ *.int8 filter=lfs diff=lfs merge=lfs -text
3
+ *.q4 filter=lfs diff=lfs merge=lfs -text
4
+ *.q8 filter=lfs diff=lfs merge=lfs -text
5
+ *.woff2 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LICENSE ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ AksharaLP by Sourojit Dhua — https://huggingface.co/sourojitdotcom/AksharaLP-1M
2
+ Copyright 2026 Sourojit Dhua
3
+
4
+ Licensed under the Apache License, Version 2.0 (the full text follows). Commercial use is permitted.
5
+ Redistributions and derivative works must include the attribution notices in the accompanying
6
+ NOTICE file, as required by Section 4(d) of the License.
7
+
8
+
9
+ Apache License
10
+ Version 2.0, January 2004
11
+ http://www.apache.org/licenses/
12
+
13
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
14
+
15
+ 1. Definitions.
16
+
17
+ "License" shall mean the terms and conditions for use, reproduction,
18
+ and distribution as defined by Sections 1 through 9 of this document.
19
+
20
+ "Licensor" shall mean the copyright owner or entity authorized by
21
+ the copyright owner that is granting the License.
22
+
23
+ "Legal Entity" shall mean the union of the acting entity and all
24
+ other entities that control, are controlled by, or are under common
25
+ control with that entity. For the purposes of this definition,
26
+ "control" means (i) the power, direct or indirect, to cause the
27
+ direction or management of such entity, whether by contract or
28
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
29
+ outstanding shares, or (iii) beneficial ownership of such entity.
30
+
31
+ "You" (or "Your") shall mean an individual or Legal Entity
32
+ exercising permissions granted by this License.
33
+
34
+ "Source" form shall mean the preferred form for making modifications,
35
+ including but not limited to software source code, documentation
36
+ source, and configuration files.
37
+
38
+ "Object" form shall mean any form resulting from mechanical
39
+ transformation or translation of a Source form, including but
40
+ not limited to compiled object code, generated documentation,
41
+ and conversions to other media types.
42
+
43
+ "Work" shall mean the work of authorship, whether in Source or
44
+ Object form, made available under the License, as indicated by a
45
+ copyright notice that is included in or attached to the work
46
+ (an example is provided in the Appendix below).
47
+
48
+ "Derivative Works" shall mean any work, whether in Source or Object
49
+ form, that is based on (or derived from) the Work and for which the
50
+ editorial revisions, annotations, elaborations, or other modifications
51
+ represent, as a whole, an original work of authorship. For the purposes
52
+ of this License, Derivative Works shall not include works that remain
53
+ separable from, or merely link (or bind by name) to the interfaces of,
54
+ the Work and Derivative Works thereof.
55
+
56
+ "Contribution" shall mean any work of authorship, including
57
+ the original version of the Work and any modifications or additions
58
+ to that Work or Derivative Works thereof, that is intentionally
59
+ submitted to Licensor for inclusion in the Work by the copyright owner
60
+ or by an individual or Legal Entity authorized to submit on behalf of
61
+ the copyright owner. For the purposes of this definition, "submitted"
62
+ means any form of electronic, verbal, or written communication sent
63
+ to the Licensor or its representatives, including but not limited to
64
+ communication on electronic mailing lists, source code control systems,
65
+ and issue tracking systems that are managed by, or on behalf of, the
66
+ Licensor for the purpose of discussing and improving the Work, but
67
+ excluding communication that is conspicuously marked or otherwise
68
+ designated in writing by the copyright owner as "Not a Contribution."
69
+
70
+ "Contributor" shall mean Licensor and any individual or Legal Entity
71
+ on behalf of whom a Contribution has been received by Licensor and
72
+ subsequently incorporated within the Work.
73
+
74
+ 2. Grant of Copyright License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ copyright license to reproduce, prepare Derivative Works of,
78
+ publicly display, publicly perform, sublicense, and distribute the
79
+ Work and such Derivative Works in Source or Object form.
80
+
81
+ 3. Grant of Patent License. Subject to the terms and conditions of
82
+ this License, each Contributor hereby grants to You a perpetual,
83
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
84
+ (except as stated in this section) patent license to make, have made,
85
+ use, offer to sell, sell, import, and otherwise transfer the Work,
86
+ where such license applies only to those patent claims licensable
87
+ by such Contributor that are necessarily infringed by their
88
+ Contribution(s) alone or by combination of their Contribution(s)
89
+ with the Work to which such Contribution(s) was submitted. If You
90
+ institute patent litigation against any entity (including a
91
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
92
+ or a Contribution incorporated within the Work constitutes direct
93
+ or contributory patent infringement, then any patent licenses
94
+ granted to You under this License for that Work shall terminate
95
+ as of the date such litigation is filed.
96
+
97
+ 4. Redistribution. You may reproduce and distribute copies of the
98
+ Work or Derivative Works thereof in any medium, with or without
99
+ modifications, and in Source or Object form, provided that You
100
+ meet the following conditions:
101
+
102
+ (a) You must give any other recipients of the Work or
103
+ Derivative Works a copy of this License; and
104
+
105
+ (b) You must cause any modified files to carry prominent notices
106
+ stating that You changed the files; and
107
+
108
+ (c) You must retain, in the Source form of any Derivative Works
109
+ that You distribute, all copyright, patent, trademark, and
110
+ attribution notices from the Source form of the Work,
111
+ excluding those notices that do not pertain to any part of
112
+ the Derivative Works; and
113
+
114
+ (d) If the Work includes a "NOTICE" text file as part of its
115
+ distribution, then any Derivative Works that You distribute must
116
+ include a readable copy of the attribution notices contained
117
+ within such NOTICE file, excluding those notices that do not
118
+ pertain to any part of the Derivative Works, in at least one
119
+ of the following places: within a NOTICE text file distributed
120
+ as part of the Derivative Works; within the Source form or
121
+ documentation, if provided along with the Derivative Works; or,
122
+ within a display generated by the Derivative Works, if and
123
+ wherever such third-party notices normally appear. The contents
124
+ of the NOTICE file are for informational purposes only and
125
+ do not modify the License. You may add Your own attribution
126
+ notices within Derivative Works that You distribute, alongside
127
+ or as an addendum to the NOTICE text from the Work, provided
128
+ that such additional attribution notices cannot be construed
129
+ as modifying the License.
130
+
131
+ You may add Your own copyright statement to Your modifications and
132
+ may provide additional or different license terms and conditions
133
+ for use, reproduction, or distribution of Your modifications, or
134
+ for any such Derivative Works as a whole, provided Your use,
135
+ reproduction, and distribution of the Work otherwise complies with
136
+ the conditions stated in this License.
137
+
138
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
139
+ any Contribution intentionally submitted for inclusion in the Work
140
+ by You to the Licensor shall be under the terms and conditions of
141
+ this License, without any additional terms or conditions.
142
+ Notwithstanding the above, nothing herein shall supersede or modify
143
+ the terms of any separate license agreement you may have executed
144
+ with Licensor regarding such Contributions.
145
+
146
+ 6. Trademarks. This License does not grant permission to use the trade
147
+ names, trademarks, service marks, or product names of the Licensor,
148
+ except as required for reasonable and customary use in describing the
149
+ origin of the Work and reproducing the content of the NOTICE file.
150
+
151
+ 7. Disclaimer of Warranty. Unless required by applicable law or
152
+ agreed to in writing, Licensor provides the Work (and each
153
+ Contributor provides its Contributions) on an "AS IS" BASIS,
154
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
155
+ implied, including, without limitation, any warranties or conditions
156
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
157
+ PARTICULAR PURPOSE. You are solely responsible for determining the
158
+ appropriateness of using or redistributing the Work and assume any
159
+ risks associated with Your exercise of permissions under this License.
160
+
161
+ 8. Limitation of Liability. In no event and under no legal theory,
162
+ whether in tort (including negligence), contract, or otherwise,
163
+ unless required by applicable law (such as deliberate and grossly
164
+ negligent acts) or agreed to in writing, shall any Contributor be
165
+ liable to You for damages, including any direct, indirect, special,
166
+ incidental, or consequential damages of any character arising as a
167
+ result of this License or out of the use or inability to use the
168
+ Work (including but not limited to damages for loss of goodwill,
169
+ work stoppage, computer failure or malfunction, or any and all
170
+ other commercial damages or losses), even if such Contributor
171
+ has been advised of the possibility of such damages.
172
+
173
+ 9. Accepting Warranty or Additional Liability. While redistributing
174
+ the Work or Derivative Works thereof, You may choose to offer,
175
+ and charge a fee for, acceptance of support, warranty, indemnity,
176
+ or other liability obligations and/or rights consistent with this
177
+ License. However, in accepting such obligations, You may act only
178
+ on Your own behalf and on Your sole responsibility, not on behalf
179
+ of any other Contributor, and only if You agree to indemnify,
180
+ defend, and hold each Contributor harmless for any liability
181
+ incurred by, or claims asserted against, such Contributor by reason
182
+ of your accepting any such warranty or additional liability.
183
+
184
+ END OF TERMS AND CONDITIONS
185
+
186
+ APPENDIX: How to apply the Apache License to your work.
187
+
188
+ To apply the Apache License to your work, attach the following
189
+ boilerplate notice, with the fields enclosed by brackets "[]"
190
+ replaced with your own identifying information. (Don't include
191
+ the brackets!) The text should be enclosed in the appropriate
192
+ comment syntax for the file format. We also recommend that a
193
+ file or class name and description of purpose be included on the
194
+ same "printed page" as the copyright notice for easier
195
+ identification within third-party archives.
196
+
197
+ Copyright [yyyy] [name of copyright owner]
198
+
199
+ Licensed under the Apache License, Version 2.0 (the "License");
200
+ you may not use this file except in compliance with the License.
201
+ You may obtain a copy of the License at
202
+
203
+ http://www.apache.org/licenses/LICENSE-2.0
204
+
205
+ Unless required by applicable law or agreed to in writing, software
206
+ distributed under the License is distributed on an "AS IS" BASIS,
207
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
208
+ See the License for the specific language governing permissions and
209
+ limitations under the License.
NOTICE ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ AksharaLP by Sourojit Dhua — https://huggingface.co/sourojitdotcom/AksharaLP-1M
2
+ Copyright 2026 Sourojit Dhua
3
+
4
+ AksharaLP-1M (model weights, word lists and JavaScript/Python runtime) is licensed under the
5
+ Apache License, Version 2.0 (see LICENSE). Commercial use is permitted.
6
+
7
+ ATTRIBUTION
8
+ Under Section 4(d) of the Apache License, any redistribution of this work or of a Derivative Work
9
+ (for example: shipping these files in an app, a web page, a browser extension or an SDK, or
10
+ releasing a fine-tuned, quantized or converted model based on it) must include a readable copy of
11
+ this NOTICE file. Please use the following attribution text:
12
+
13
+ Transliteration by AksharaLP (Sourojit Dhua) — https://huggingface.co/sourojitdotcom/AksharaLP-1M
14
+
15
+ Recommended in-app credit (e.g. in an About, Settings or Licenses screen):
16
+
17
+ Hindi typing powered by AksharaLP by Sourojit Dhua
18
+
19
+ The model files in this repository were built with the help of the following
20
+ third-party resources. No third-party data is redistributed in this repository
21
+ other than as learned model parameters and derived word lists.
22
+
23
+ ------------------------------------------------------------------------------
24
+ Aksharantar — AI4Bharat
25
+ https://huggingface.co/datasets/ai4bharat/Aksharantar
26
+ License: CC0 (mined data and existing sources) / CC-BY (manually collected data)
27
+ Used for: romanized <-> Devanagari word pairs for training.
28
+ Note from the dataset card: "We do not own any of the text from which this data has been extracted."
29
+
30
+ IndicXlit — AI4Bharat
31
+ https://github.com/AI4Bharat/IndicXlit
32
+ License: MIT. Copyright (c) 2022 AI4Bhārat
33
+ Used for: teacher model for knowledge distillation.
34
+
35
+ IndicCorp v2 — AI4Bharat
36
+ https://github.com/AI4Bharat/IndicBERT/tree/main#indiccorp-v2
37
+ License: CC0
38
+ Used for: Hindi lexicon and word-bigram language model (lex.bin, lm.bin).
39
+
40
+ CMUdict — Carnegie Mellon University
41
+ https://github.com/cmusphinx/cmudict
42
+ License: BSD-2-Clause-style. The full notice is reproduced below.
43
+ Used for: English word cues.
44
+
45
+ ------------------------------------------------------------------------------
46
+ CMUdict license notice:
47
+
48
+ Copyright (C) 1993-2015 Carnegie Mellon University. All rights reserved.
49
+
50
+ Redistribution and use in source and binary forms, with or without
51
+ modification, are permitted provided that the following conditions
52
+ are met:
53
+
54
+ 1. Redistributions of source code must retain the above copyright
55
+ notice, this list of conditions and the following disclaimer.
56
+ The contents of this file are deemed to be source code.
57
+
58
+ 2. Redistributions in binary form must reproduce the above copyright
59
+ notice, this list of conditions and the following disclaimer in
60
+ the documentation and/or other materials provided with the
61
+ distribution.
62
+
63
+ This work was supported in part by funding from the Defense Advanced
64
+ Research Projects Agency, the Office of Naval Research and the National
65
+ Science Foundation of the United States of America, and by member
66
+ companies of the Carnegie Mellon Sphinx Speech Consortium. We acknowledge
67
+ the contributions of many volunteers to the expansion and improvement of
68
+ this dictionary.
69
+
70
+ THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
71
+ ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
72
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
73
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
74
+ NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
75
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
76
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
77
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
78
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
79
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
80
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.md CHANGED
@@ -1,10 +1,46 @@
1
  ---
2
- title: AksharaLP Demo
3
- emoji: 🐨
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: static
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: AksharaLP — Hindi Transliteration
3
+ emoji: 🪔
4
+ colorFrom: indigo
5
+ colorTo: pink
6
  sdk: static
7
+ app_file: index.html
8
+ pinned: true
9
+ license: apache-2.0
10
+ short_description: Tiny on-device romanized Hindi → Devanagari transliteration
11
+ models:
12
+ - sourojitdotcom/AksharaLP-1M
13
+ tags:
14
+ - transliteration
15
+ - hindi
16
+ - devanagari
17
+ - on-device
18
  ---
19
 
20
+ # AksharaLP — live demo
21
+
22
+ Type romanized Hindi and get Devanagari as you type. Everything runs **entirely in your browser** in a Web Worker —
23
+ no server, no API key, nothing you type leaves the page.
24
+
25
+ - **Two-tier download:** the core (0.57 MB gzipped: character model + reranker + runtime) starts working right away;
26
+ the dictionary pack (3.97 MB gzipped: 1.5M-word lexicon + bigram LM) is fetched in the background on
27
+ first use and cached (Cache API). If the pack cannot be loaded, the demo keeps working with the core alone (lower accuracy).
28
+ Add `?dict=0` to the URL to try the core only.
29
+ - **Modes:** Default · Strict (brand names stay in Latin)
30
+ - **Sentence context** (with the pack): a compact bigram LM picks the right word for ambiguous spellings (e.g. *main* → मैं / में)
31
+ - **Suggestions:** click any output word for ranked alternatives; picking one re-decodes the rest of the sentence
32
+ - After the first visit it works offline.
33
+
34
+ Model card, benchmarks and licensing: [sourojitdotcom/AksharaLP-1M](https://huggingface.co/sourojitdotcom/AksharaLP-1M)
35
+
36
+ Runtime: `js/aksharalp.mjs` — a dependency-free ES module (Apache-2.0) you can use in your own app.
37
+ Fonts: Inter and Noto Sans Devanagari, SIL Open Font License 1.1 (see `fonts/`).
38
+
39
+ ## License & attribution
40
+
41
+ Apache-2.0, Copyright 2026 Sourojit Dhua. Commercial use is allowed; redistributions and derivatives must include
42
+ the `NOTICE` file (Apache-2.0 §4(d)). Attribution text:
43
+
44
+ ```text
45
+ Transliteration by AksharaLP (Sourojit Dhua) — https://huggingface.co/sourojitdotcom/AksharaLP-1M
46
+ ```
assets/favicon.svg ADDED
css/app.css ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @font-face { font-family: "Inter"; src: url("../fonts/Inter-var.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
2
+ @font-face { font-family: "Noto Sans Devanagari"; src: url("../fonts/NotoSansDevanagari-var.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
3
+
4
+ :root {
5
+ --font: "Inter", "Noto Sans Devanagari", system-ui, -apple-system, "Segoe UI", sans-serif;
6
+ --deva: "Noto Sans Devanagari", "Inter", sans-serif;
7
+ --bg: #f6f6fb; --bg2: #ffffff; --ink: #0f1222; --ink2: #4a4f6a; --ink3: #8a8fa8;
8
+ --line: rgba(15, 18, 34, .08); --line2: rgba(15, 18, 34, .14);
9
+ --card: rgba(255, 255, 255, .78); --card-solid: #fff; --glass-blur: 18px;
10
+ --accent: #6d4aff; --a1: #6366f1; --a2: #a855f7; --a3: #f97316; --a4: #ec4899;
11
+ --hi: #7c3aed; --en: #0ea5e9; --dev: #f97316; --oth: #94a3b8;
12
+ --shadow: 0 1px 2px rgba(16, 20, 50, .04), 0 8px 24px -6px rgba(16, 20, 50, .10), 0 30px 60px -30px rgba(80, 60, 200, .18);
13
+ --radius: 22px; --ease: cubic-bezier(.2, .8, .2, 1);
14
+ color-scheme: light;
15
+ }
16
+ :root[data-theme="dark"] {
17
+ --bg: #090b14; --bg2: #0e1120; --ink: #eef0ff; --ink2: #b3b8d6; --ink3: #7b81a3;
18
+ --line: rgba(255, 255, 255, .07); --line2: rgba(255, 255, 255, .13);
19
+ --card: rgba(18, 21, 38, .72); --card-solid: #12152a;
20
+ --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 20px 50px -20px rgba(0, 0, 0, .7), 0 0 0 1px rgba(255, 255, 255, .03);
21
+ color-scheme: dark;
22
+ }
23
+ @media (prefers-color-scheme: dark) {
24
+ :root[data-theme="auto"] {
25
+ --bg: #090b14; --bg2: #0e1120; --ink: #eef0ff; --ink2: #b3b8d6; --ink3: #7b81a3;
26
+ --line: rgba(255, 255, 255, .07); --line2: rgba(255, 255, 255, .13);
27
+ --card: rgba(18, 21, 38, .72); --card-solid: #12152a;
28
+ --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 20px 50px -20px rgba(0, 0, 0, .7), 0 0 0 1px rgba(255, 255, 255, .03);
29
+ color-scheme: dark;
30
+ }
31
+ }
32
+
33
+ * { box-sizing: border-box; }
34
+ html, body { margin: 0; }
35
+ body { font-family: var(--font); background: var(--bg); color: var(--ink); -webkit-font-smoothing: antialiased; font-feature-settings: "cv11", "ss01"; line-height: 1.5;
36
+ transition: background .4s var(--ease), color .4s var(--ease); overflow-x: hidden; }
37
+ a { color: inherit; }
38
+ .deva { font-family: var(--deva); }
39
+
40
+ /* ---------- ambient background */
41
+ .bg { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
42
+ .blob { position: absolute; border-radius: 50%; filter: blur(80px); opacity: .55; animation: float 22s var(--ease) infinite alternate; }
43
+ .b1 { width: 620px; height: 620px; background: radial-gradient(circle at 30% 30%, #8b7bff, transparent 65%); top: -220px; left: -160px; }
44
+ .b2 { width: 560px; height: 560px; background: radial-gradient(circle at 50% 50%, #ff9a5a, transparent 65%); top: -120px; right: -200px; animation-delay: -6s; opacity: .38; }
45
+ .b3 { width: 700px; height: 700px; background: radial-gradient(circle at 50% 50%, #e879f9, transparent 65%); top: 520px; left: 35%; animation-delay: -12s; opacity: .22; }
46
+ :root[data-theme="dark"] .blob { opacity: .28; }
47
+ @media (prefers-color-scheme: dark) { :root[data-theme="auto"] .blob { opacity: .28; } }
48
+ .grid { position: absolute; inset: 0; background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px);
49
+ background-size: 56px 56px; mask-image: radial-gradient(ellipse 70% 50% at 50% 0%, #000 30%, transparent 75%); opacity: .55; }
50
+ @keyframes float { to { transform: translate3d(40px, 30px, 0) scale(1.08); } }
51
+
52
+ /* ---------- nav */
53
+ .nav { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; justify-content: space-between; padding: 14px clamp(16px, 4vw, 40px);
54
+ backdrop-filter: saturate(1.4) blur(14px); -webkit-backdrop-filter: saturate(1.4) blur(14px); background: color-mix(in oklab, var(--bg) 70%, transparent); border-bottom: 1px solid var(--line); }
55
+ .brand { display: flex; align-items: center; gap: 10px; text-decoration: none; }
56
+ .mark { width: 34px; height: 34px; filter: drop-shadow(0 6px 14px rgba(124, 58, 237, .35)); }
57
+ .brand-name { font-weight: 750; letter-spacing: -.02em; font-size: 18px; }
58
+ .brand-tag { font-size: 12px; color: var(--ink3); border: 1px solid var(--line2); padding: 2px 8px; border-radius: 99px; }
59
+ .nav-right { display: flex; align-items: center; gap: 10px; }
60
+ .nav-link { text-decoration: none; font-size: 14px; font-weight: 550; color: var(--ink2); padding: 8px 12px; border-radius: 10px; transition: background .2s; }
61
+ .nav-link:hover { background: var(--line); color: var(--ink); }
62
+ .pill { display: inline-flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 550; padding: 6px 12px; border-radius: 99px; background: var(--card); border: 1px solid var(--line2); color: var(--ink2); }
63
+ .status .dot { width: 8px; height: 8px; border-radius: 50%; background: #f59e0b; box-shadow: 0 0 0 0 rgba(245, 158, 11, .6); animation: pulse 1.6s infinite; }
64
+ .status.ready .dot { background: #10b981; animation: pulse-g 2.4s infinite; }
65
+ @keyframes pulse { 70% { box-shadow: 0 0 0 8px rgba(245, 158, 11, 0); } 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); } }
66
+ @keyframes pulse-g { 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, .55); } 70% { box-shadow: 0 0 0 8px rgba(16, 185, 129, 0); } 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } }
67
+ .icon-btn { width: 38px; height: 38px; border-radius: 11px; border: 1px solid var(--line2); background: var(--card); color: var(--ink); display: grid; place-items: center; cursor: pointer; transition: transform .2s var(--ease); }
68
+ .icon-btn:hover { transform: rotate(-12deg); }
69
+ .icon-btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; grid-area: 1/1; transition: opacity .3s, transform .4s var(--ease); }
70
+ .icon-btn .moon { fill: currentColor; stroke: none; }
71
+ :root .icon-btn .moon, :root[data-theme="dark"] .icon-btn .sun { opacity: 0; transform: scale(.5) rotate(-60deg); }
72
+ :root[data-theme="dark"] .icon-btn .moon { opacity: 1; transform: none; }
73
+ @media (prefers-color-scheme: dark) { :root[data-theme="auto"] .icon-btn .sun { opacity: 0; } :root[data-theme="auto"] .icon-btn .moon { opacity: 1; transform: none; } }
74
+
75
+ /* ---------- hero */
76
+ main { max-width: 1160px; margin: 0 auto; padding: 0 clamp(16px, 4vw, 40px); }
77
+ .hero { text-align: center; padding: clamp(34px, 6vw, 64px) 0 30px; animation: rise .9s var(--ease) both; }
78
+ .eyebrow { display: inline-flex; gap: 8px; align-items: center; font-size: 13px; font-weight: 600; color: var(--ink2); background: var(--card); border: 1px solid var(--line2);
79
+ padding: 7px 14px; border-radius: 99px; box-shadow: var(--shadow); }
80
+ .spark { background: linear-gradient(90deg, var(--a1), var(--a3)); -webkit-background-clip: text; background-clip: text; color: transparent; }
81
+ h1 { font-size: clamp(30px, 4.9vw, 58px); line-height: 1.06; letter-spacing: -.04em; font-weight: 800; margin: 22px auto 18px; max-width: 1020px; text-wrap: balance; }
82
+ .grad { background: linear-gradient(100deg, var(--a1) 0%, var(--a2) 40%, var(--a4) 70%, var(--a3) 100%); background-size: 200% 100%; -webkit-background-clip: text; background-clip: text; color: transparent;
83
+ animation: sheen 8s ease-in-out infinite alternate; }
84
+ @keyframes sheen { to { background-position: 100% 0; } }
85
+ .lede { max-width: 700px; margin: 0 auto; font-size: clamp(16px, 2vw, 19px); color: var(--ink2); }
86
+ .lede-strong { color: var(--ink); font-weight: 700; display: block; margin-bottom: 4px; }
87
+ .stats { display: inline-grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 26px auto 0; border: 1px solid var(--line2); border-radius: 18px; background: var(--card); box-shadow: var(--shadow); overflow: hidden; }
88
+ .stat { padding: 14px 26px; display: flex; gap: 12px; align-items: center; text-align: left; }
89
+ .stat + .stat { border-left: 1px solid var(--line2); }
90
+ .stat b { font-size: 26px; font-weight: 800; letter-spacing: -.03em; background: linear-gradient(100deg, var(--a1), var(--a4)); -webkit-background-clip: text; background-clip: text; color: transparent; white-space: nowrap; }
91
+ .stat span { font-size: 12.5px; line-height: 1.3; color: var(--ink2); }
92
+ .stat-src { margin: 10px 0 0; font-size: 11.5px; color: var(--ink3, var(--ink2)); opacity: .8; }
93
+ .stat-src a { color: inherit; }
94
+ @media (max-width: 600px) { .stats { grid-template-columns: 1fr; width: 100%; } .stat + .stat { border-left: 0; border-top: 1px solid var(--line2); } .stat { padding: 10px 18px; } .stat b { min-width: 74px; } }
95
+ .size-chip { font-weight: 700; color: var(--ink); background: linear-gradient(90deg, rgba(99, 102, 241, .14), rgba(249, 115, 22, .14)); padding: 1px 9px; border-radius: 8px; white-space: nowrap; }
96
+ @keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
97
+
98
+ /* ---------- cards */
99
+ .card { background: var(--card); border: 1px solid var(--line2); border-radius: var(--radius); box-shadow: var(--shadow); backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); }
100
+ .playground { position: relative; padding: 18px; animation: rise 1s .1s var(--ease) both; }
101
+ .playground::before { content: ""; position: absolute; inset: -1px; border-radius: inherit; padding: 1px; background: linear-gradient(120deg, rgba(99, 102, 241, .55), rgba(168, 85, 247, .15) 35%, rgba(249, 115, 22, .45));
102
+ -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none; }
103
+
104
+ .toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 2px 2px 14px; }
105
+ .spacer { flex: 1; }
106
+ .seg { position: relative; display: inline-flex; background: color-mix(in oklab, var(--ink) 6%, transparent); border-radius: 13px; padding: 4px; }
107
+ .seg button { position: relative; z-index: 1; border: 0; background: none; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--ink2); padding: 8px 14px; border-radius: 10px; cursor: pointer; transition: color .25s; white-space: nowrap; }
108
+ .seg button[aria-checked="true"] { color: var(--ink); }
109
+ .seg-glider { position: absolute; top: 4px; bottom: 4px; left: 4px; border-radius: 10px; background: var(--card-solid); box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 4px 14px -6px rgba(80, 60, 200, .35);
110
+ transition: transform .35s var(--ease), width .35s var(--ease); }
111
+ .switch { display: inline-flex; align-items: center; gap: 9px; font-size: 13.5px; font-weight: 600; color: var(--ink2); cursor: pointer; user-select: none; }
112
+ .switch input { position: absolute; opacity: 0; pointer-events: none; }
113
+ .track { width: 38px; height: 22px; border-radius: 99px; background: color-mix(in oklab, var(--ink) 18%, transparent); position: relative; transition: background .25s; }
114
+ .thumb { position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .25); transition: transform .3s var(--ease); }
115
+ .switch input:checked + .track { background: linear-gradient(90deg, var(--a1), var(--a2)); }
116
+ .switch input:checked + .track .thumb { transform: translateX(16px); }
117
+ .switch input:focus-visible + .track { outline: 2px solid var(--a2); outline-offset: 2px; }
118
+ .lang-badge { font-size: 12.5px; font-weight: 650; padding: 6px 11px; border-radius: 99px; border: 1px solid var(--line2); display: inline-flex; gap: 7px; align-items: center; animation: pop .35s var(--ease); }
119
+ .lang-badge::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--c, var(--hi)); }
120
+
121
+ .panes { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
122
+ .pane { border-radius: 16px; border: 1px solid var(--line2); background: var(--bg2); padding: 14px 16px 16px; min-height: 210px; display: flex; flex-direction: column; transition: border-color .25s, box-shadow .25s; }
123
+ .pane.in:focus-within { border-color: color-mix(in oklab, var(--a2) 60%, transparent); box-shadow: 0 0 0 4px color-mix(in oklab, var(--a2) 14%, transparent); }
124
+ .pane.out { background: linear-gradient(180deg, color-mix(in oklab, var(--a1) 5%, var(--bg2)), var(--bg2)); }
125
+ .pane-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; min-height: 30px; }
126
+ .label { font-size: 12px; font-weight: 650; text-transform: uppercase; letter-spacing: .08em; color: var(--ink3); }
127
+ .deva-label { font-family: var(--deva); text-transform: none; letter-spacing: 0; font-size: 13px; }
128
+ textarea { flex: 1; width: 100%; resize: none; border: 0; outline: 0; background: transparent; color: var(--ink); font: 500 clamp(18px, 2.2vw, 22px)/1.55 var(--font); letter-spacing: -.01em; }
129
+ textarea::placeholder { color: var(--ink3); opacity: .8; }
130
+ .output { flex: 1; font-family: var(--deva); font-size: clamp(22px, 2.8vw, 30px); line-height: 1.6; font-weight: 500; word-wrap: break-word; }
131
+ .output .ph { color: var(--ink3); opacity: .6; font-size: .8em; }
132
+ .w { position: relative; display: inline; border-radius: 8px; padding: 0 2px; cursor: pointer; transition: background .2s; }
133
+ .wt { background-image: linear-gradient(var(--c), var(--c)); background-size: 100% 2px; background-position: 0 96%; background-repeat: no-repeat; padding-bottom: 2px; }
134
+ .pn { color: var(--ink2); }
135
+ .w:hover, .w.sel { background-color: color-mix(in oklab, var(--c) 14%, transparent); }
136
+ .w.lang-HI { --c: color-mix(in oklab, var(--hi) 55%, transparent); }
137
+ .w.lang-EN { --c: color-mix(in oklab, var(--en) 60%, transparent); }
138
+ .w.lang-DEV { --c: color-mix(in oklab, var(--dev) 55%, transparent); }
139
+ .w.pinned { --c: var(--a3); }
140
+ .w.new { animation: wordin .45s var(--ease) both; }
141
+ @keyframes wordin { from { opacity: 0; filter: blur(4px); transform: translateY(6px); } to { opacity: 1; filter: none; transform: none; } }
142
+ .ghost-btn, .copy-btn { font: inherit; font-size: 12.5px; font-weight: 600; color: var(--ink2); border: 1px solid var(--line2); background: var(--card-solid); border-radius: 10px; padding: 6px 11px; cursor: pointer; display: inline-flex; gap: 6px; align-items: center; transition: transform .15s, background .2s, color .2s; }
143
+ .ghost-btn:hover, .copy-btn:hover:not(:disabled) { color: var(--ink); transform: translateY(-1px); }
144
+ .copy-btn svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.8; }
145
+ .copy-btn.done { color: #10b981; border-color: rgba(16, 185, 129, .45); }
146
+ .copy-btn:disabled { opacity: .5; cursor: default; }
147
+
148
+ .suggest { display: flex; align-items: center; gap: 12px; padding: 14px 4px 4px; min-height: 58px; }
149
+ .suggest-label { font-size: 12px; font-weight: 650; text-transform: uppercase; letter-spacing: .08em; color: var(--ink3); white-space: nowrap; }
150
+ .chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
151
+ .chips .hint { color: var(--ink3); font-size: 13.5px; }
152
+ .chips .src { font-size: 13px; color: var(--ink3); margin-right: 2px; font-weight: 600; }
153
+ .chip { position: relative; overflow: hidden; font-family: var(--deva); font-size: 18px; font-weight: 500; padding: 5px 14px 7px; border-radius: 12px; border: 1px solid var(--line2); background: var(--card-solid); color: var(--ink); cursor: pointer;
154
+ transition: transform .18s var(--ease), border-color .2s, box-shadow .2s; animation: pop .35s var(--ease) both; }
155
+ .chip:hover { transform: translateY(-2px); border-color: color-mix(in oklab, var(--a2) 55%, transparent); box-shadow: 0 6px 16px -8px rgba(124, 58, 237, .5); }
156
+ .chip.on { border-color: transparent; color: #fff; background: linear-gradient(120deg, var(--a1), var(--a2)); }
157
+ .chip .p { position: absolute; left: 0; bottom: 0; height: 2px; background: linear-gradient(90deg, var(--a1), var(--a3)); border-radius: 2px; }
158
+ .chip.on .p { background: rgba(255, 255, 255, .7); }
159
+ .chip .rk { font-family: var(--font); font-size: 10.5px; font-weight: 700; opacity: .55; margin-left: 6px; vertical-align: 2px; }
160
+ .chip.latin { font-family: var(--font); font-size: 15px; }
161
+ @keyframes pop { from { opacity: 0; transform: scale(.92) translateY(4px); } to { opacity: 1; transform: none; } }
162
+
163
+ .meter { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-top: 12px; border-top: 1px solid var(--line); padding-top: 14px; }
164
+ .m { display: flex; flex-direction: column; gap: 2px; padding: 0 6px; }
165
+ .m .k { font-size: 11.5px; font-weight: 650; text-transform: uppercase; letter-spacing: .08em; color: var(--ink3); }
166
+ .m .v { font-size: 17px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
167
+ .m .v small { font-size: 12px; font-weight: 600; color: var(--ink3); margin-left: 3px; }
168
+ .lock { font-size: 13px; }
169
+ .loader { position: absolute; left: 18px; right: 18px; bottom: 10px; display: flex; gap: 12px; align-items: center; font-size: 12.5px; color: var(--ink3); transition: opacity .5s; }
170
+ .loader .bar { flex: 1; height: 3px; border-radius: 3px; background: var(--line); overflow: hidden; }
171
+ .loader .bar span { display: block; height: 100%; width: 40%; background: linear-gradient(90deg, var(--a1), var(--a3)); border-radius: 3px; animation: load 1.2s ease-in-out infinite; }
172
+ @keyframes load { from { transform: translateX(-100%); } to { transform: translateX(260%); } }
173
+ .loader.gone { opacity: 0; pointer-events: none; }
174
+
175
+ /* ---------- sections */
176
+ .section { padding: 64px 0 0; }
177
+ .sec-head { text-align: center; margin-bottom: 26px; }
178
+ .sec-head h2 { font-size: clamp(26px, 3.6vw, 38px); letter-spacing: -.035em; margin: 0 0 8px; font-weight: 780; }
179
+ .sec-head p { color: var(--ink2); margin: 0; }
180
+ .examples { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
181
+ .ex { text-align: left; font: inherit; color: inherit; border: 1px solid var(--line2); background: var(--card); border-radius: 18px; padding: 16px 18px 18px; cursor: pointer; box-shadow: var(--shadow);
182
+ transition: transform .25s var(--ease), border-color .25s; display: flex; flex-direction: column; gap: 8px; backdrop-filter: blur(10px); }
183
+ .ex:hover { transform: translateY(-3px); border-color: color-mix(in oklab, var(--a2) 45%, transparent); }
184
+ .ex .tag { align-self: flex-start; font-size: 11.5px; font-weight: 700; letter-spacing: .04em; color: var(--a2); background: color-mix(in oklab, var(--a2) 12%, transparent); padding: 3px 9px; border-radius: 99px; }
185
+ .ex .ro { font-size: 15px; color: var(--ink2); }
186
+ .ex .dv { font-family: var(--deva); font-size: 21px; font-weight: 550; line-height: 1.5; min-height: 32px; }
187
+ .shimmer { display: inline-block; width: 70%; height: 18px; border-radius: 6px; background: linear-gradient(90deg, var(--line) 0%, var(--line2) 50%, var(--line) 100%); background-size: 200% 100%; animation: shim 1.2s linear infinite; }
188
+ @keyframes shim { to { background-position: -200% 0; } }
189
+
190
+ .features { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
191
+ .feat { padding: 20px 20px 18px; border-radius: 18px; background: var(--card); border: 1px solid var(--line2); box-shadow: var(--shadow); }
192
+ .feat .ic { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; font-size: 18px; color: #fff; background: linear-gradient(135deg, var(--a1), var(--a2)); box-shadow: 0 8px 18px -8px rgba(124, 58, 237, .7); }
193
+ .feat:nth-child(3n+2) .ic { background: linear-gradient(135deg, var(--a2), var(--a4)); }
194
+ .feat:nth-child(3n) .ic { background: linear-gradient(135deg, var(--a4), var(--a3)); }
195
+ .feat h3 { margin: 14px 0 4px; font-size: 17px; letter-spacing: -.02em; }
196
+ .feat p { margin: 0 0 12px; color: var(--ink2); font-size: 14.5px; }
197
+ .demo-line { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: baseline; font-size: 14px; padding: 10px 12px; border-radius: 12px; background: color-mix(in oklab, var(--ink) 4%, transparent); }
198
+ .demo-line .r { color: var(--ink3); }
199
+ .demo-line .arrow { color: var(--ink3); }
200
+ .demo-line .d { font-family: var(--deva); font-size: 18px; font-weight: 600; }
201
+ .demo-line + .demo-line { margin-top: 6px; }
202
+
203
+ .sizes { padding: 24px 26px; display: flex; flex-direction: column; gap: 18px; }
204
+ .srow { display: grid; grid-template-columns: 240px 1fr 120px; gap: 16px; align-items: center; }
205
+ .srow .nm { font-weight: 650; font-size: 14.5px; }
206
+ .srow .nm small { display: block; color: var(--ink3); font-weight: 500; font-size: 12.5px; }
207
+ .sbar { height: 14px; border-radius: 8px; background: color-mix(in oklab, var(--ink) 6%, transparent); overflow: hidden; }
208
+ .sbar span { display: block; height: 100%; border-radius: 8px; width: 0; transition: width 1.4s var(--ease); background: color-mix(in oklab, var(--ink) 28%, transparent); }
209
+ .srow.us .sbar span { background: linear-gradient(90deg, var(--a1), var(--a2), var(--a3)); min-width: 6px; box-shadow: 0 0 18px rgba(168, 85, 247, .6); }
210
+ .srow .val { text-align: right; font-weight: 750; font-variant-numeric: tabular-nums; }
211
+ .srow.us .val { background: linear-gradient(90deg, var(--a1), var(--a3)); -webkit-background-clip: text; background-clip: text; color: transparent; }
212
+ .sizes .note { font-size: 12.5px; color: var(--ink3); }
213
+
214
+ .how .steps { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; counter-reset: s; }
215
+ .steps li { padding: 20px; border-radius: 18px; border: 1px dashed var(--line2); }
216
+ .steps .n { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; font-weight: 750; font-size: 13px; color: #fff; background: linear-gradient(135deg, var(--a1), var(--a3)); margin-right: 8px; }
217
+ .steps b { font-size: 16.5px; }
218
+ .steps p { color: var(--ink2); font-size: 14.5px; margin: 10px 0 0; }
219
+
220
+ .foot { max-width: 1160px; margin: 70px auto 0; padding: 26px clamp(16px, 4vw, 40px) 40px; border-top: 1px solid var(--line); font-size: 13px; color: var(--ink2); display: flex; flex-direction: column; gap: 4px; }
221
+ .muted { color: var(--ink3); }
222
+ .toast { position: fixed; left: 50%; bottom: 26px; transform: translate(-50%, 20px); opacity: 0; background: var(--ink); color: var(--bg); padding: 10px 16px; border-radius: 12px; font-size: 14px; font-weight: 600; transition: all .35s var(--ease); pointer-events: none; z-index: 50; }
223
+ .toast.on { opacity: 1; transform: translate(-50%, 0); }
224
+
225
+ .l-short { display: none; }
226
+ @media (max-width: 600px) {
227
+ .l-long { display: none; } .l-short { display: inline; }
228
+ .seg { width: 100%; } .seg button { flex: 1; padding: 8px 6px; }
229
+ .toolbar { gap: 10px 14px; } .switch { font-size: 13px; }
230
+ .playground { padding: 14px; } h1 { letter-spacing: -.04em; }
231
+ .suggest { flex-direction: column; align-items: flex-start; gap: 8px; }
232
+ }
233
+ @media (max-width: 900px) {
234
+ .panes, .examples, .features, .how .steps { grid-template-columns: 1fr; }
235
+ .meter { grid-template-columns: repeat(2, 1fr); row-gap: 14px; }
236
+ .srow { grid-template-columns: 1fr 90px; } .srow .sbar { grid-column: 1 / -1; grid-row: 2; }
237
+ .brand-tag, .nav-link { display: none; }
238
+ .pane { min-height: 150px; }
239
+ }
240
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }
241
+
242
+ /* ---------- word language tags, chips meta, feature demos */
243
+ .playground.tags .output { line-height: 1.9; }
244
+ .playground.tags .w { display: inline-flex; flex-direction: column; align-items: center; vertical-align: top; line-height: 1.35; padding: 2px 3px 3px; }
245
+ .tg { font-family: var(--font); font-size: 9.5px; font-weight: 750; letter-spacing: .06em; line-height: 1; padding: 3px 5px 2px; border-radius: 5px; margin-top: 1px; color: var(--hi); background: color-mix(in oklab, var(--hi) 13%, transparent); vertical-align: middle; }
246
+ .tg-EN { color: var(--en); background: color-mix(in oklab, var(--en) 14%, transparent); }
247
+ .tg-DEV { color: var(--dev); background: color-mix(in oklab, var(--dev) 14%, transparent); }
248
+ .tg-OTHER { color: var(--oth); background: color-mix(in oklab, var(--oth) 14%, transparent); }
249
+ .playground.tags .w.pinned .tg { color: #fff; background: var(--a3); }
250
+ .chips .meta { font-size: 12px; color: var(--ink3); font-weight: 600; margin-left: 4px; padding-left: 10px; border-left: 1px solid var(--line2); }
251
+ .chips .reset { padding: 5px 10px; }
252
+ .demo-line.big { align-items: center; }
253
+ .demo-line.big .d { font-family: var(--font); font-size: 30px; font-weight: 800; letter-spacing: -.03em; }
254
+ .mini-chip { font-family: var(--deva); font-size: 15px; padding: 2px 9px 3px; border-radius: 8px; border: 1px solid var(--line2); background: var(--card-solid); }
255
+ .mini-chip.on { color: #fff; border-color: transparent; background: linear-gradient(120deg, var(--a1), var(--a2)); }
256
+ .lid-w { display: inline-flex; flex-direction: column; align-items: center; gap: 3px; }
257
+ .lid-w .lw { font-size: 14px; color: var(--ink); }
258
+ .lang-badge.sm { font-size: 11.5px; padding: 3px 9px; align-self: center; }
259
+
260
+ /* ---------- on-device capabilities */
261
+ .kicker { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--a2); margin-bottom: 10px; }
262
+ .live-dot { width: 8px; height: 8px; border-radius: 50%; background: #10b981; animation: pulse-g 2.4s infinite; }
263
+ .caps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
264
+ .cap { position: relative; padding: 20px 20px 18px; border-radius: 20px; background: var(--card); border: 1px solid var(--line2); box-shadow: var(--shadow); display: flex; flex-direction: column;
265
+ backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); transition: transform .35s var(--ease), border-color .3s, box-shadow .35s; overflow: hidden; }
266
+ .cap::after { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; opacity: 0; transition: opacity .4s;
267
+ background: radial-gradient(420px circle at 0% 0%, color-mix(in oklab, var(--a2) 10%, transparent), transparent 60%); }
268
+ .cap:hover { transform: translateY(-4px); border-color: color-mix(in oklab, var(--a2) 40%, transparent); box-shadow: var(--shadow), 0 18px 40px -22px rgba(124, 58, 237, .55); }
269
+ .cap:hover::after { opacity: 1; }
270
+ .cap h3 { margin: 14px 0 4px; font-size: 17px; letter-spacing: -.02em; }
271
+ .cap p { margin: 0 0 12px; color: var(--ink2); font-size: 14.5px; }
272
+ .cap .fslot { margin-top: auto; }
273
+ .cic { width: 42px; height: 42px; border-radius: 13px; display: grid; place-items: center; color: #fff; background: linear-gradient(135deg, var(--a1), var(--a2)); box-shadow: 0 10px 22px -10px rgba(124, 58, 237, .8);
274
+ transition: transform .45s var(--ease); }
275
+ .cap:nth-child(3n+2) .cic { background: linear-gradient(135deg, var(--a2), var(--a4)); box-shadow: 0 10px 22px -10px rgba(236, 72, 153, .7); }
276
+ .cap:nth-child(3n) .cic { background: linear-gradient(135deg, var(--a4), var(--a3)); box-shadow: 0 10px 22px -10px rgba(249, 115, 22, .7); }
277
+ .cap:hover .cic { transform: translateY(-2px) rotate(-6deg) scale(1.06); }
278
+ .cic svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
279
+ .cic svg .f { fill: currentColor; stroke: none; }
280
+ .cap-offline .cic svg { animation: drift 4.5s ease-in-out infinite; }
281
+ .cap-realtime .cic svg { animation: flick 3.2s ease-in-out infinite; }
282
+ .cap-private .cic svg { animation: breathe 3.6s ease-in-out infinite; }
283
+ .cap-bharat .cic svg path { transform-origin: bottom; transform-box: fill-box; animation: bars 2.4s ease-in-out infinite; }
284
+ .cap-bharat .cic .bar2 { animation-delay: .15s; } .cap-bharat .cic .bar3 { animation-delay: .3s; } .cap-bharat .cic .bar4 { animation-delay: .45s; }
285
+ @keyframes drift { 0%, 100% { transform: translate(0, 0) rotate(0); } 50% { transform: translate(1.5px, -1.5px) rotate(-4deg); } }
286
+ @keyframes flick { 0%, 86%, 100% { opacity: 1; } 90% { opacity: .45; } 94% { opacity: 1; } 97% { opacity: .6; } }
287
+ @keyframes breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
288
+ @keyframes bars { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(.55); } }
289
+ .pills { display: flex; flex-wrap: wrap; gap: 6px; margin: -2px 0 12px; }
290
+ .pl { font-size: 11.5px; font-weight: 650; color: var(--ink2); padding: 3px 9px; border-radius: 99px; border: 1px solid var(--line2); background: var(--card-solid); }
291
+ .net { font-size: 12px; font-weight: 650; margin: -2px 0 10px; display: inline-flex; align-items: center; gap: 7px; color: var(--ink2); }
292
+ .net::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: #10b981; }
293
+ .net.off::before { background: var(--a3); } .net.off { color: var(--a3); }
294
+ .steps-live .st { opacity: 0; animation: stepin .5s var(--ease) forwards; animation-delay: calc(var(--i) * .45s + .2s); }
295
+ .steps-live .st:last-child { box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--a2) 35%, transparent); }
296
+ @keyframes stepin { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: none; } }
297
+ .lat-live { margin-top: 8px; font-size: 12.5px; color: var(--ink3); }
298
+ .lat-live b { color: var(--ink); font-variant-numeric: tabular-nums; }
299
+ .ime { border-radius: 14px; border: 1px solid var(--line2); background: var(--bg2); padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; }
300
+ .ime-lbl { font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink3); }
301
+ .ime-in { font-size: 14px; color: var(--ink2); min-height: 21px; }
302
+ .caret { display: inline-block; width: 2px; height: 1.05em; margin-left: 1px; vertical-align: -2px; background: var(--a2); animation: blink 1s steps(1) infinite; }
303
+ @keyframes blink { 50% { opacity: 0; } }
304
+ .ime-out { font-family: var(--deva); font-size: 18px; font-weight: 600; min-height: 28px; padding-top: 6px; border-top: 1px dashed var(--line2); }
305
+ .mode-row .mlab { font-size: 11.5px; font-weight: 700; color: var(--a2); min-width: 108px; }
306
+ .cap .mini-chip { animation: pop .35s var(--ease) both; }
307
+ .rv { opacity: 0; transform: translateY(18px); transition: opacity .7s var(--ease) var(--d, 0ms), transform .7s var(--ease) var(--d, 0ms), border-color .3s, box-shadow .35s; }
308
+ .rv.in { opacity: 1; transform: none; }
309
+ .rv.in.cap:hover { transform: translateY(-4px); }
310
+
311
+ /* ---------- best in class for its size */
312
+ .tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
313
+ .tile { position: relative; padding: 22px 22px 20px; border-radius: 20px; background: var(--card); box-shadow: var(--shadow); isolation: isolate; overflow: hidden; }
314
+ .tile::before { content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px; z-index: -1;
315
+ background: conic-gradient(from var(--ang, 0deg), rgba(99, 102, 241, .65), rgba(168, 85, 247, .15), rgba(249, 115, 22, .6), rgba(236, 72, 153, .15), rgba(99, 102, 241, .65));
316
+ -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; animation: spin 9s linear infinite; }
317
+ @property --ang { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
318
+ @keyframes spin { to { --ang: 360deg; } }
319
+ .tv { display: flex; align-items: baseline; gap: 4px; }
320
+ .tv .num { font-size: clamp(36px, 4.6vw, 50px); font-weight: 800; letter-spacing: -.045em; line-height: 1; font-variant-numeric: tabular-nums;
321
+ background: linear-gradient(100deg, var(--a1), var(--a2) 45%, var(--a4) 75%, var(--a3)); -webkit-background-clip: text; background-clip: text; color: transparent; }
322
+ .tv .tu { font-size: 20px; font-weight: 750; color: var(--a2); }
323
+ .tl { margin-top: 10px; font-weight: 700; font-size: 15px; letter-spacing: -.01em; }
324
+ .ts { margin-top: 4px; font-size: 12.5px; color: var(--ink3); }
325
+ .board { margin-top: 14px; padding: 18px 22px; display: flex; flex-direction: column; gap: 12px; }
326
+ .board .bh { font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink3); }
327
+ .brow { display: grid; grid-template-columns: 34px 220px 1fr 120px; gap: 14px; align-items: center; font-size: 14px; }
328
+ .brow .rk { font-weight: 800; color: var(--ink3); }
329
+ .brow.us .rk, .brow.us .nm { color: var(--hi); font-weight: 750; }
330
+ .brow .val { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
331
+ .brow.us .sbar span { background: linear-gradient(90deg, var(--a1), var(--a2), var(--a3)); box-shadow: 0 0 18px rgba(168, 85, 247, .6); }
332
+ .best-hl { margin: 14px auto 0; text-align: center; font-weight: 650; color: var(--ink2); }
333
+ @media (max-width: 900px) {
334
+ .caps, .tiles { grid-template-columns: 1fr; }
335
+ .brow { grid-template-columns: 30px 1fr 90px; } .brow .sbar { grid-column: 1 / -1; grid-row: 2; }
336
+ }
337
+ .board[hidden], .best-hl[hidden], .section[hidden] { display: none; }
338
+
339
+ /* ---------- license & attribution */
340
+ .lic { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
341
+ .lic-card { padding: 20px; border-radius: 18px; background: var(--card); border: 1px solid var(--line2); box-shadow: var(--shadow); }
342
+ .lic-card h3 { margin: 12px 0 4px; font-size: 17px; letter-spacing: -.02em; }
343
+ .lic-card p { margin: 0; color: var(--ink2); font-size: 14.5px; }
344
+ .lic-card code, .attr code { font-size: 12.5px; padding: 1px 5px; border-radius: 6px; background: color-mix(in oklab, var(--ink) 7%, transparent); }
345
+ .lic-ic { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; font-weight: 800; color: #fff; background: linear-gradient(135deg, #10b981, #0ea5e9); }
346
+ .lic-card.req .lic-ic { background: linear-gradient(135deg, var(--a2), var(--a3)); }
347
+ .attr { margin-top: 14px; display: flex; flex-direction: column; gap: 10px; }
348
+ .attr-row { display: grid; grid-template-columns: 190px 1fr auto; gap: 12px; align-items: center; padding: 12px 14px; border-radius: 16px; border: 1px solid var(--line2); background: var(--card); }
349
+ .attr-k { font-size: 12px; font-weight: 700; letter-spacing: .04em; color: var(--ink2); }
350
+ .attr-k em { display: block; font-style: normal; font-weight: 600; color: var(--a2); }
351
+ .attr-code { margin: 0; font: 500 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: pre-wrap; word-break: break-word; color: var(--ink); background: var(--bg2); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; }
352
+ .attr-copy.done { color: #10b981; border-color: rgba(16, 185, 129, .45); }
353
+ @media (max-width: 900px) { .lic { grid-template-columns: 1fr; } .attr-row { grid-template-columns: 1fr; } }
fonts/Inter-var.woff2 ADDED
Binary file (92.3 kB). View file
 
fonts/NotoSansDevanagari-var.woff2 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:755bf24ca84bf1d8633c9487fa6c2b230efed3f8b66844736ec6dd997a935d2f
3
+ size 226472
fonts/OFL-Inter.txt ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
fonts/OFL-NotoSansDevanagari.txt ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2022 The Noto Project Authors (https://github.com/notofonts/devanagari)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
index.html CHANGED
@@ -1,19 +1,171 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en" data-theme="auto">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <title>AksharaLP — Hindi transliteration that runs in your browser</title>
7
+ <meta name="description" content="Type Hindi in English letters, get Devanagari instantly. A tiny, context-aware model that runs fully offline in your browser." />
8
+ <meta name="theme-color" content="#0b0d17" media="(prefers-color-scheme: dark)" />
9
+ <meta name="theme-color" content="#f7f7fb" media="(prefers-color-scheme: light)" />
10
+ <meta property="og:title" content="AksharaLP — Hindi transliteration, on-device" />
11
+ <meta property="og:description" content="Romanized Hindi → Devanagari. Tiny. Context-aware. Private." />
12
+ <link rel="icon" href="assets/favicon.svg" type="image/svg+xml" />
13
+ <link rel="preload" href="fonts/Inter-var.woff2" as="font" type="font/woff2" crossorigin />
14
+ <link rel="preload" href="fonts/NotoSansDevanagari-var.woff2" as="font" type="font/woff2" crossorigin />
15
+ <link rel="stylesheet" href="css/app.css" />
16
+ <script>
17
+ (function () { try { var t = localStorage.getItem("aklp-theme"); if (t) document.documentElement.dataset.theme = t; } catch (e) {} })();
18
+ </script>
19
+ </head>
20
+ <body>
21
+ <div class="bg" aria-hidden="true"><span class="blob b1"></span><span class="blob b2"></span><span class="blob b3"></span><span class="grid"></span></div>
22
+
23
+ <header class="nav">
24
+ <a class="brand" href="#" aria-label="AksharaLP home">
25
+ <svg class="mark" viewBox="0 0 40 40" aria-hidden="true"><defs><linearGradient id="mk" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#6366f1"/><stop offset=".55" stop-color="#a855f7"/><stop offset="1" stop-color="#f97316"/></linearGradient></defs><rect width="40" height="40" rx="11" fill="url(#mk)"/><text x="20" y="28.5" text-anchor="middle" font-family="Noto Sans Devanagari" font-weight="700" font-size="22" fill="#fff">भ</text></svg>
26
+ <span class="brand-name">AksharaLP</span>
27
+ <span class="brand-tag">Hindi</span>
28
+ </a>
29
+ <nav class="nav-right">
30
+ <span class="pill status" id="status"><span class="dot"></span><span id="status-text">Loading model…</span></span>
31
+ <a class="nav-link" id="card-link" href="https://huggingface.co/sourojitdotcom/AksharaLP-1M" target="_blank" rel="noopener">Model card <span aria-hidden="true">↗</span></a>
32
+ <button class="icon-btn" id="theme" aria-label="Toggle dark mode" title="Toggle theme">
33
+ <svg class="sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4.2"/><path d="M12 2.5v2.2M12 19.3v2.2M4.6 4.6l1.6 1.6M17.8 17.8l1.6 1.6M2.5 12h2.2M19.3 12h2.2M4.6 19.4l1.6-1.6M17.8 6.2l1.6-1.6"/></svg>
34
+ <svg class="moon" viewBox="0 0 24 24"><path d="M20.5 14.6A8.5 8.5 0 0 1 9.4 3.5a8.5 8.5 0 1 0 11.1 11.1z"/></svg>
35
+ </button>
36
+ </nav>
37
+ </header>
38
+
39
+ <main>
40
+ <section class="hero">
41
+ <div class="eyebrow"><span class="spark">✦</span> Open model · 100% on-device · No server</div>
42
+ <h1>The world’s 3rd most spoken language, typed on the world’s most used keyboard. <span class="grad">In <span id="hero-size-short">a few MB</span>.</span></h1>
43
+ <p class="lede"><b class="lede-strong">Hindi typed in English letters → Devanagari, fully offline.</b>
44
+ Context-aware, as you type. The whole thing is <b class="size-chip" id="hero-size">—</b> and runs inside this tab; nothing you write ever leaves your device.</p>
45
+ <div class="stats" aria-label="Why it matters">
46
+ <div class="stat"><b>#3</b><span>most spoken language<br>in the world</span></div>
47
+ <div class="stat"><b>611M</b><span>people speak Hindi<br>(first or second language)</span></div>
48
+ <div class="stat"><b id="stat-size">—</b><span>to type it, with<br>no server and no GPU</span></div>
49
+ </div>
50
+ <p class="stat-src">Source: <a href="https://www.ethnologue.com/insights/ethnologue200/" target="_blank" rel="noopener">Ethnologue 200</a>, 2026 (29th edition, SIL Global). Speaker counts include second-language speakers.</p>
51
+ </section>
52
+
53
+ <section class="playground card" id="playground" aria-label="Live transliteration playground">
54
+ <div class="toolbar">
55
+ <div class="seg" role="radiogroup" aria-label="Vocabulary mode" id="modes">
56
+ <button role="radio" aria-checked="true" data-mode="conversational" title="Everything in Devanagari">Default</button>
57
+ <button role="radio" aria-checked="false" data-mode="strict" title="Keep brand and product names in Latin script">Strict</button>
58
+ <span class="seg-glider" aria-hidden="true"></span>
59
+ </div>
60
+ <label class="switch" title="Use the sentence-level language model to pick words from context">
61
+ <input type="checkbox" id="ctx" checked /><span class="track"><span class="thumb"></span></span><span class="l-long">Sentence context</span><span class="l-short">Context</span>
62
+ </label>
63
+ <span class="spacer"></span>
64
+ <span class="lang-badge" id="sent-lang" hidden></span>
65
+ </div>
66
+
67
+ <div class="panes">
68
+ <div class="pane in">
69
+ <div class="pane-head"><span class="label">Romanized Hindi</span><button class="ghost-btn" id="clear" title="Clear">Clear</button></div>
70
+ <textarea id="input" spellcheck="false" autocomplete="off" autocapitalize="off" placeholder="Type here… e.g. kal meri meeting hai, please mujhe remind kar dena" rows="4" disabled></textarea>
71
+ </div>
72
+ <div class="pane out">
73
+ <div class="pane-head"><span class="label deva-label">देवनागरी</span>
74
+ <button class="copy-btn" id="copy" title="Copy Devanagari" disabled>
75
+ <svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button>
76
+ </div>
77
+ <div id="output" class="output" aria-live="polite"><span class="ph">आपका देवनागरी पाठ यहाँ दिखेगा</span></div>
78
+ </div>
79
+ </div>
80
+
81
+ <div class="suggest" id="suggest" aria-label="Suggestions">
82
+ <span class="suggest-label">Suggestions</span>
83
+ <div class="chips" id="chips"><span class="hint">Click any word in the output — or just keep typing — to see ranked alternatives.</span></div>
84
+ </div>
85
+
86
+ <div class="meter">
87
+ <div class="m"><span class="k">Latency</span><span class="v" id="m-lat">—</span></div>
88
+ <div class="m"><span class="k">Download</span><span class="v" id="m-size">—</span></div>
89
+ <div class="m"><span class="k">Parameters</span><span class="v" id="m-params">—</span></div>
90
+ <div class="m"><span class="k">Dictionary</span><span class="v" id="m-lex">—</span></div>
91
+ <div class="m"><span class="k">Runs on</span><span class="v">Your device <span class="lock">🔒</span></span></div>
92
+ </div>
93
+ <div class="loader" id="loader"><div class="bar"><span></span></div><span id="loader-text">Downloading model…</span></div>
94
+ </section>
95
+
96
+ <section class="section" id="examples-sec">
97
+ <div class="sec-head"><h2>Try an example</h2><p>Every preview below is computed live by the model in your browser.</p></div>
98
+ <div class="examples" id="examples"></div>
99
+ </section>
100
+
101
+ <section class="section" id="ondevice" aria-labelledby="ondevice-h">
102
+ <div class="sec-head"><span class="kicker"><span class="live-dot"></span>Live in this tab</span><h2 id="ondevice-h">What it brings on-device</h2>
103
+ <p>No server in the loop. Every example below is computed right now, inside this page.</p></div>
104
+ <div class="caps" id="caps"></div>
105
+ </section>
106
+
107
+ <section class="section" id="best" aria-labelledby="best-h" hidden>
108
+ <div class="sec-head"><span class="kicker">Measured, not claimed</span><h2 id="best-h">By the numbers</h2>
109
+ <p>Each tile is generated by the release build from measured results and appears only if its rule holds.</p></div>
110
+ <div class="tiles" id="tiles"></div>
111
+ <div class="board card" id="board" hidden></div>
112
+ <p class="best-hl" id="best-hl" hidden></p>
113
+ </section>
114
+ <script type="application/json" id="claims-data">{"heading": "Best in class for its size", "tiles": [{"id": "accuracy_vs:indicxlit_model", "value": 70.4, "dec": 1, "unit": "%", "label": "top-1 — higher than IndicXlit (model only) (59.2%)", "sub": "at 5× smaller · Aksharantar Hindi test"}, {"id": "accuracy_vs:indicxlit_full", "value": 70.4, "dec": 1, "unit": "%", "label": "top-1 — higher than IndicXlit (full package) (69.3%)", "sub": "at 47× smaller · Aksharantar Hindi test"}, {"id": "accuracy_per_mb_rank1", "value": 8.1, "dec": 1, "unit": "pts/MB", "label": "highest top-1 accuracy per MB among compared models", "sub": "next: IndicXlit (model only) at 1.28 pts/MB"}, {"id": "top3_vs_top1:indicxlit_model", "value": 87.7, "dec": 1, "unit": "%", "label": "right word in the first 3 suggestions — vs 59.2% top-1 for IndicXlit (model only)", "sub": "Aksharantar Hindi test"}, {"id": "top3_vs_top1:indicxlit_full", "value": 87.7, "dec": 1, "unit": "%", "label": "right word in the first 3 suggestions — vs 69.3% top-1 for IndicXlit (full package)", "sub": "Aksharantar Hindi test"}, {"id": "smaller_than:indicxlit_model", "value": 5, "dec": 0, "unit": "×", "label": "smaller than IndicXlit (model only)", "sub": "8.68 MB vs 46.0 MB on disk (fp32 weights)"}, {"id": "smaller_than:indicxlit_full", "value": 47, "dec": 0, "unit": "×", "label": "smaller than IndicXlit (full package)", "sub": "8.68 MB vs 404 MB on disk (fp32 weights + 358 MB rescoring dictionary)"}, {"id": "browser_latency", "value": 219.7, "dec": 1, "unit": "ms", "label": "per word (p50) in the browser", "sub": "an 8-vCPU Intel Xeon cloud VM (headless Chrome 156, Linux)"}, {"id": "package_size", "value": 4.53, "dec": 2, "unit": "MB", "label": "complete download", "sub": "core + 1.5M-word dictionary pack · 989K parameters"}, {"id": "core_size", "value": 0.57, "dec": 2, "unit": "MB", "label": "to start typing", "sub": "the core works on its own (53.4% top-1 without the pack); the dictionary pack streams in after and is cached"}], "board": [{"name": "AksharaLP-1M", "bytes": 8684745, "top1": 70.37, "us": true, "per_mb": 8.10271343603065}, {"name": "IndicXlit (model only)", "bytes": 46043828, "top1": 59.16, "us": false, "per_mb": 1.2848627616278994}, {"name": "IndicXlit (full package)", "bytes": 404043828, "top1": 69.33, "us": false, "per_mb": 0.1715902958923555}], "highlight": "No compared model is both smaller and more accurate: AksharaLP-1M sits on the size–accuracy frontier."}</script>
115
+
116
+ <section class="section">
117
+ <div class="sec-head"><h2>Small model. Serious accuracy.</h2><p>Built for keyboards, chat apps and browsers where every kilobyte and millisecond counts.</p></div>
118
+ <div class="features" id="features"></div>
119
+ </section>
120
+
121
+ <section class="section">
122
+ <div class="sec-head"><h2>Fits where big models can’t</h2><p>Download size of the core and of core + dictionary versus a widely used open transliteration model.</p></div>
123
+ <div class="sizes card" id="sizes"></div>
124
+ </section>
125
+
126
+ <section class="section how">
127
+ <div class="sec-head"><h2>How it works</h2></div>
128
+ <ol class="steps">
129
+ <li><span class="n">1</span><b>Generate</b><p>A 4-bit character Transformer (the core) proposes spellings. With the dictionary pack attached it also searches a lexicon trie and consonant-skeleton matches, and adds acronym and spelling-variant candidates.</p></li>
130
+ <li><span class="n">2</span><b>Rerank</b><p>A tiny learned reranker scores every candidate with model, lexicon and frequency features.</p></li>
131
+ <li><span class="n">3</span><b>Decide in context</b><p>With the dictionary pack, a compact word-bigram language model chooses the best sentence, e.g. <span class="deva">मैं</span> vs <span class="deva">में</span>. Without it, each word keeps its top-ranked spelling.</p></li>
132
+ </ol>
133
+ </section>
134
+ <section class="section" id="license" aria-labelledby="license-h">
135
+ <div class="sec-head"><span class="kicker">Apache-2.0 · Copyright 2026 Sourojit Dhua</span><h2 id="license-h">License &amp; attribution</h2>
136
+ <p>Free for commercial use. Credit AksharaLP and Sourojit Dhua when you ship it.</p></div>
137
+ <div class="lic">
138
+ <div class="lic-card ok"><div class="lic-ic">✓</div><h3>Commercial use allowed</h3>
139
+ <p>Use, modify and ship the model and runtime in commercial products, free of charge, under the Apache License 2.0.</p></div>
140
+ <div class="lic-card req"><div class="lic-ic">§</div><h3>Attribution required</h3>
141
+ <p>Apache-2.0 §4(d): every redistribution or derivative (apps, web pages, extensions, SDKs, fine-tuned or converted models) must include the <code>NOTICE</code> file. The runtime keeps its <code>/*! … */</code> header, including when minified.</p></div>
142
+ </div>
143
+ <div class="attr">
144
+ <div class="attr-row"><span class="attr-k">Attribution text <em>(required)</em></span>
145
+ <pre class="attr-code" id="attr-req">Transliteration by AksharaLP (Sourojit Dhua) — https://huggingface.co/sourojitdotcom/AksharaLP-1M</pre>
146
+ <button class="ghost-btn attr-copy" data-copy="attr-req">Copy</button></div>
147
+ <div class="attr-row"><span class="attr-k">In-app credit <em>(recommended)</em></span>
148
+ <pre class="attr-code" id="attr-app">Hindi typing powered by &lt;a href="https://huggingface.co/sourojitdotcom/AksharaLP-1M"&gt;AksharaLP by Sourojit Dhua&lt;/a&gt;</pre>
149
+ <button class="ghost-btn attr-copy" data-copy="attr-app">Copy</button></div>
150
+ <div class="attr-row"><span class="attr-k">Citation <em>(BibTeX)</em></span>
151
+ <pre class="attr-code" id="attr-bib">@software{dhua_aksharalp_2026,
152
+ author = {Dhua, Sourojit},
153
+ title = {AksharaLP: Tiny, Context-Aware Hindi Transliteration for the Browser},
154
+ year = {2026},
155
+ url = {https://huggingface.co/sourojitdotcom/AksharaLP-1M},
156
+ license = {Apache-2.0}
157
+ }</pre>
158
+ <button class="ghost-btn attr-copy" data-copy="attr-bib">Copy</button></div>
159
+ </div>
160
+ </section>
161
+ </main>
162
+
163
+ <footer class="foot">
164
+ <div>AksharaLP by Sourojit Dhua — <a href="https://huggingface.co/sourojitdotcom/AksharaLP-1M" target="_blank" rel="noopener">huggingface.co/sourojitdotcom/AksharaLP-1M</a> · Copyright 2026 Sourojit Dhua · Code &amp; weights Apache-2.0 (attribution required, see NOTICE) · Data attributions in the <a href="https://huggingface.co/sourojitdotcom/AksharaLP-1M" target="_blank" rel="noopener">model card</a></div>
165
+ <div class="muted">Inference runs locally in a Web Worker (dependency-free JavaScript). No analytics, no network calls after load.</div>
166
+ </footer>
167
+
168
+ <div class="toast" id="toast" role="status"></div>
169
+ <script type="module" src="js/app.js"></script>
170
+ </body>
171
  </html>
js/aksharalp.mjs ADDED
@@ -0,0 +1,718 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * BhashaCore JS runtime — romanized Hindi / Hinglish -> Devanagari, fully offline (browser, Web Worker, Node >= 18).
3
+ * Zero dependencies. Pure-JS port of the reference Python runtime (bhc/translit2.py, bhc/xlit_q.py, bhc/lexlm.py,
4
+ * bhc/lextrie.py, bhc/lid.py, bhc/hinglish.py) over the exported asset formats:
5
+ * xlit.q4 | xlit.q8 | xlit.int8 char Transformer (pre-LN enc/dec), 4/8-bit grouped or per-row INT8 weights
6
+ * lex.bin Devanagari lexicon (LX4 alphabetical front-coded + 4-bit bands, or v2 bucketed)
7
+ * lm.bin pruned backoff word-bigram LM (varint CSR + 4-bit codebooks)
8
+ * params.json decoding params + reranker (linear w or MLP w1/b1/w2/b2; mu / sd)
9
+ * lid.int8 char-CNN + BiGRU token / sentence language ID (optional)
10
+ * loan.tsv, natural_hi.tsv Hinglish loanword lexicon, Natural-Hindi glossary (optional)
11
+ * Bundle layout: a small CORE (model + params.json) that works standalone, plus an optional dictionary pack in
12
+ * dict/ (lex.bin + lm.bin + params.json overriding the core params), fetched on first use, cached with the Cache API
13
+ * when available, and attached with engine.loadDict(); if it cannot be fetched the engine stays in core-only mode.
14
+ *
15
+ * AksharaLP by Sourojit Dhua — https://huggingface.co/sourojitdotcom/AksharaLP-1M
16
+ * Copyright 2026 Sourojit Dhua. Licensed under the Apache License, Version 2.0; commercial use permitted.
17
+ * Attribution required (Apache-2.0 §4(d)): keep this notice and the NOTICE file in redistributions and derivatives:
18
+ * "Transliteration by AksharaLP (Sourojit Dhua) — https://huggingface.co/sourojitdotcom/AksharaLP-1M"
19
+ */
20
+
21
+ // ------------------------------------------------------------------ vocab
22
+ const PAD = 0, BOS = 1, EOS = 2, UNK = 3, MAXLEN = 32, V = 132;
23
+ const NEG = -Infinity;
24
+ const encSrc = (w) => {
25
+ const s = w.toLowerCase().slice(0, MAXLEN - 1), ids = [];
26
+ for (const c of s) { const k = c.charCodeAt(0); ids.push(c.length === 1 && k >= 97 && k <= 122 ? k - 97 + 4 : UNK); }
27
+ ids.push(EOS); return ids;
28
+ };
29
+ const t2i = (ch) => { const k = ch.codePointAt(0); return k >= 0x900 && k < 0x980 ? k - 0x900 + 4 : -1; };
30
+ const encTgt = (w) => { const ids = []; for (const c of [...w].slice(0, MAXLEN - 2)) { const i = t2i(c); ids.push(i < 0 ? UNK : i); } ids.push(EOS); return ids; };
31
+ const decTgt = (ids) => { let s = ""; for (const i of ids) if (i > UNK) s += String.fromCharCode(0x900 + i - 4); return s; };
32
+
33
+ // ------------------------------------------------------------------ binary helpers
34
+ function f16(h) {
35
+ const s = (h & 0x8000) ? -1 : 1, e = (h >> 10) & 0x1f, f = h & 0x3ff;
36
+ if (e === 0) return s * f * 5.960464477539063e-8;
37
+ if (e === 31) return f ? NaN : s * Infinity;
38
+ return s * (1 + f / 1024) * Math.pow(2, e - 15);
39
+ }
40
+ function readHeader(buf) {
41
+ const u8 = new Uint8Array(buf), dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
42
+ const n = dv.getUint32(0, true);
43
+ const meta = JSON.parse(new TextDecoder().decode(u8.subarray(4, 4 + n)));
44
+ const data = u8.subarray(4 + n);
45
+ return { meta, data, dv: new DataView(data.buffer, data.byteOffset, data.byteLength) };
46
+ }
47
+ const prod = (a) => a.reduce((x, y) => x * y, 1);
48
+ const f16arr = (dv, off, n) => { const o = new Float32Array(n); for (let i = 0; i < n; i++) o[i] = f16(dv.getUint16(off + 2 * i, true)); return o; };
49
+
50
+ /** Parse xlit.int8 / xlit.q4 / xlit.q8 / lid.int8 into {cfg, extra, T: {name: {shape, data: Float32Array}}}. */
51
+ export function parseWeights(buf) {
52
+ const { meta, data, dv } = readHeader(buf); const T = {};
53
+ for (const t of meta.tensors) {
54
+ const [k, shape, kind, off] = t; const cnt = prod(shape); let w;
55
+ if (kind === "f16") w = f16arr(dv, off, cnt);
56
+ else if (kind === "i8") { // per-row symmetric INT8 (+ fp16 row scale)
57
+ const r = shape[0], c = cnt / r, sc = f16arr(dv, off + cnt, r); w = new Float32Array(cnt);
58
+ for (let i = 0; i < r; i++) for (let j = 0; j < c; j++) { const q = (data[off + i * c + j] << 24) >> 24; w[i * c + j] = q * sc[i]; }
59
+ } else { // q4 / q8: group-wise symmetric (G inputs per fp16 scale)
60
+ const bits = kind.length > 1 ? parseInt(kind.slice(1)) : meta.bits, G = meta.G, ln = t[4];
61
+ const [r, c] = shape, ng = Math.ceil(c / G), sc = f16arr(dv, off + ln, r * ng); w = new Float32Array(cnt);
62
+ for (let i = 0; i < cnt; i++) {
63
+ let q;
64
+ if (bits === 4) { const b = data[off + (i >> 1)]; q = ((i & 1) ? (b & 15) : (b >> 4)) - 8; }
65
+ else q = (data[off + i] << 24) >> 24;
66
+ const row = (i / c) | 0, col = i - row * c; w[i] = q * sc[row * ng + ((col / G) | 0)];
67
+ }
68
+ }
69
+ T[k] = { shape, data: w };
70
+ }
71
+ return { cfg: meta.cfg, extra: meta.extra || {}, T };
72
+ }
73
+
74
+ // ------------------------------------------------------------------ math kernels
75
+ function linear(x, xo, W, b, out, oo, nin, nout) { // out[oo..] = W x + b ; W row-major [nout, nin]
76
+ for (let o = 0; o < nout; o++) {
77
+ let s = b ? b[o] : 0; const r = o * nin;
78
+ for (let i = 0; i < nin; i++) s += W[r + i] * x[xo + i];
79
+ out[oo + o] = s;
80
+ }
81
+ }
82
+ function layerNorm(x, xo, g, b, out, oo, d) {
83
+ let m = 0; for (let i = 0; i < d; i++) m += x[xo + i]; m /= d;
84
+ let v = 0; for (let i = 0; i < d; i++) { const t = x[xo + i] - m; v += t * t; } v /= d;
85
+ const r = 1 / Math.sqrt(v + 1e-5);
86
+ for (let i = 0; i < d; i++) out[oo + i] = (x[xo + i] - m) * r * g[i] + b[i];
87
+ }
88
+ function erf(x) { // |err| < 1.2e-7 (Numerical Recipes erfc)
89
+ const z = Math.abs(x), t = 1 / (1 + 0.5 * z);
90
+ const r = t * Math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * (0.37409196 + t * (0.09678418 + t * (-0.18628806 +
91
+ t * (0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (-0.82215223 + t * 0.17087277)))))))));
92
+ return x >= 0 ? 1 - r : r - 1;
93
+ }
94
+ const gelu = (x) => 0.5 * x * (1 + erf(x / Math.SQRT2));
95
+ const sigmoid = (x) => 1 / (1 + Math.exp(-x));
96
+
97
+ // ------------------------------------------------------------------ char Transformer
98
+ export class XlitModel {
99
+ constructor(w) {
100
+ const c = w.cfg; this.d = c.d; this.h = c.h; this.dk = c.d / c.h; this.ff = c.ff; this.ne = c.enc; this.nd = c.dec;
101
+ const g = (k) => { const t = w.T[k]; if (!t) throw new Error("missing tensor " + k); return t.data; };
102
+ this.semb = g("semb.weight"); this.temb = g("temb.weight"); this.spos = g("spos.weight"); this.tpos = g("tpos.weight");
103
+ const att = (p) => ({ q: g(p + ".q.weight"), qb: g(p + ".q.bias"), kv: g(p + ".kv.weight"), kvb: g(p + ".kv.bias"), o: g(p + ".o.weight"), ob: g(p + ".o.bias") });
104
+ const ln = (p) => ({ g: g(p + ".weight"), b: g(p + ".bias") });
105
+ const ffn = (p) => ({ w1: g(p + ".ff.0.weight"), b1: g(p + ".ff.0.bias"), w2: g(p + ".ff.2.weight"), b2: g(p + ".ff.2.bias") });
106
+ this.enc = []; for (let i = 0; i < c.enc; i++) { const p = "enc." + i; this.enc.push({ n1: ln(p + ".n1"), att: att(p + ".att"), n2: ln(p + ".n2"), ff: ffn(p) }); }
107
+ this.dec = []; for (let i = 0; i < c.dec; i++) { const p = "dec." + i; this.dec.push({ n1: ln(p + ".n1"), sa: att(p + ".sa"), n2: ln(p + ".n2"), ca: att(p + ".ca"), n3: ln(p + ".n3"), ff: ffn(p) }); }
108
+ this.en = ln("en"); this.dn = ln("dn"); this.scale = Math.sqrt(c.d);
109
+ this.nParams = Object.values(w.T).reduce((a, t) => a + t.data.length, 0);
110
+ const d = this.d; this._y = new Float32Array(d); this._a = new Float32Array(d); this._f = new Float32Array(this.ff); this._q = new Float32Array(d); this._kv = new Float32Array(2 * d);
111
+ this._sc = new Float64Array(MAXLEN);
112
+ }
113
+ _ffn(L, x, xo, n) { // x[xo] += FF(LN(x))
114
+ const d = this.d, y = this._y, f = this._f, a = this._a;
115
+ layerNorm(x, xo, n.g, n.b, y, 0, d); linear(y, 0, L.w1, L.b1, f, 0, d, this.ff);
116
+ for (let i = 0; i < this.ff; i++) f[i] = gelu(f[i]);
117
+ linear(f, 0, L.w2, L.b2, a, 0, this.ff, d); for (let i = 0; i < d; i++) x[xo + i] += a[i];
118
+ }
119
+ /** attention of one query q (len d) over K/V rows (stride 2d in kvbuf: K at +0, V at +d) -> out (len d) */
120
+ _attend(q, kvbuf, kvo, n, out) {
121
+ const { h, dk, d } = this, sc = n <= MAXLEN ? this._sc : new Float64Array(n), inv = 1 / Math.sqrt(dk);
122
+ for (let hh = 0; hh < h; hh++) {
123
+ let mx = -Infinity;
124
+ for (let j = 0; j < n; j++) { let s = 0; const ko = kvo + j * 2 * d + hh * dk; for (let i = 0; i < dk; i++) s += q[hh * dk + i] * kvbuf[ko + i]; s *= inv; sc[j] = s; if (s > mx) mx = s; }
125
+ let z = 0; for (let j = 0; j < n; j++) { sc[j] = Math.exp(sc[j] - mx); z += sc[j]; }
126
+ for (let i = 0; i < dk; i++) { let s = 0; for (let j = 0; j < n; j++) s += sc[j] * kvbuf[kvo + j * 2 * d + d + hh * dk + i]; out[hh * dk + i] = s / z; }
127
+ }
128
+ }
129
+ /** encode one word -> {S, cross: per decoder layer K/V of memory (Float32Array S*2d)} */
130
+ encode(ids) {
131
+ const d = this.d, S = ids.length, x = new Float32Array(S * d), y = new Float32Array(S * d), kv = new Float32Array(S * 2 * d), att = new Float32Array(d), o = this._a;
132
+ for (let t = 0; t < S; t++) for (let i = 0; i < d; i++) x[t * d + i] = this.semb[ids[t] * d + i] * this.scale + this.spos[t * d + i];
133
+ for (const L of this.enc) {
134
+ for (let t = 0; t < S; t++) { layerNorm(x, t * d, L.n1.g, L.n1.b, y, t * d, d); linear(y, t * d, L.att.kv, L.att.kvb, kv, t * 2 * d, d, 2 * d); }
135
+ const nx = new Float32Array(x);
136
+ for (let t = 0; t < S; t++) {
137
+ linear(y, t * d, L.att.q, L.att.qb, this._q, 0, d, d); this._attend(this._q, kv, 0, S, att);
138
+ linear(att, 0, L.att.o, L.att.ob, o, 0, d, d); for (let i = 0; i < d; i++) nx[t * d + i] += o[i];
139
+ }
140
+ for (let t = 0; t < S; t++) this._ffn(L.ff, nx, t * d, L.n2);
141
+ x.set(nx);
142
+ }
143
+ const mem = new Float32Array(S * d); for (let t = 0; t < S; t++) layerNorm(x, t * d, this.en.g, this.en.b, mem, t * d, d);
144
+ const cross = this.dec.map((L) => { const c = new Float32Array(S * 2 * d); for (let t = 0; t < S; t++) linear(mem, t * d, L.ca.kv, L.ca.kvb, c, t * 2 * d, d, 2 * d); return c; });
145
+ return { S, cross };
146
+ }
147
+ newCache(K) { const d = this.d; return this.dec.map(() => new Float32Array(K * MAXLEN * 2 * d)); }
148
+ /** one decoder step for beam rows `rows` (indices into cache) with tokens tok[row] at position t -> logprobs[row] (Float64Array V) */
149
+ step(enc, cache, rows, tok, t, out) {
150
+ const d = this.d, x = new Float32Array(d), y = this._y, att = new Float32Array(d), o = this._a;
151
+ for (const r of rows) {
152
+ for (let i = 0; i < d; i++) x[i] = this.temb[tok[r] * d + i] * this.scale + this.tpos[t * d + i];
153
+ for (let l = 0; l < this.nd; l++) {
154
+ const L = this.dec[l], C = cache[l], base = r * MAXLEN * 2 * d;
155
+ layerNorm(x, 0, L.n1.g, L.n1.b, y, 0, d);
156
+ linear(y, 0, L.sa.kv, L.sa.kvb, C, base + t * 2 * d, d, 2 * d);
157
+ linear(y, 0, L.sa.q, L.sa.qb, this._q, 0, d, d); this._attend(this._q, C, base, t + 1, att);
158
+ linear(att, 0, L.sa.o, L.sa.ob, o, 0, d, d); for (let i = 0; i < d; i++) x[i] += o[i];
159
+ layerNorm(x, 0, L.n2.g, L.n2.b, y, 0, d);
160
+ linear(y, 0, L.ca.q, L.ca.qb, this._q, 0, d, d); this._attend(this._q, enc.cross[l], 0, enc.S, att);
161
+ linear(att, 0, L.ca.o, L.ca.ob, o, 0, d, d); for (let i = 0; i < d; i++) x[i] += o[i];
162
+ this._ffn(L.ff, x, 0, L.n3);
163
+ }
164
+ layerNorm(x, 0, this.dn.g, this.dn.b, y, 0, d);
165
+ const lp = out[r]; let mx = -Infinity;
166
+ for (let v = 0; v < V; v++) { let s = 0; for (let i = 0; i < d; i++) s += y[i] * this.temb[v * d + i]; lp[v] = s; if (s > mx) mx = s; }
167
+ let z = 0; for (let v = 0; v < V; v++) z += Math.exp(lp[v] - mx); const lz = mx + Math.log(z);
168
+ for (let v = 0; v < V; v++) lp[v] -= lz;
169
+ }
170
+ }
171
+ /** beam search for one word (identical procedure to bhc.xlit_model.beam_search_fast for one batch row) */
172
+ beam(enc, S, { beam: K = 8, nbest = 8, trie = null } = {}) {
173
+ const d = this.d, cache = this.newCache(K), out = Array.from({ length: K }, () => new Float64Array(V));
174
+ let score = new Array(K).fill(NEG); score[0] = 0; let tok = new Array(K).fill(BOS); let hist = Array.from({ length: K }, () => []);
175
+ let nodes = trie ? new Array(K).fill(0) : null; const done = []; const L = Math.min(MAXLEN - 1, S + 8);
176
+ const tmp = new Float32Array(MAXLEN * 2 * d);
177
+ for (let t = 0; t < L; t++) {
178
+ const rows = []; for (let k = 0; k < K; k++) if (score[k] > NEG) rows.push(k);
179
+ this.step(enc, cache, rows, tok, t, out);
180
+ const cand = []; // (total, beam, v)
181
+ for (const k of rows) {
182
+ const lp = out[k]; let allow = null;
183
+ if (trie) { const n = nodes[k]; if (n === null) continue; allow = trie.allowed(n); }
184
+ if (allow) { for (const v of allow) if (v !== PAD && v !== BOS && v !== UNK) cand.push([score[k] + lp[v], k, v]); }
185
+ else { cand.push([score[k] + lp[EOS], k, EOS]); for (let v = 4; v < V; v++) cand.push([score[k] + lp[v], k, v]); }
186
+ }
187
+ cand.sort((a, b) => b[0] - a[0] || (a[1] * V + a[2]) - (b[1] * V + b[2]));
188
+ const top = cand.slice(0, 2 * K);
189
+ const ns = new Array(K).fill(NEG), nt = new Array(K).fill(EOS), par = new Array(K).fill(0), nn = trie ? new Array(K).fill(null) : null;
190
+ let j = 0;
191
+ for (const [sc, b, v] of top) {
192
+ if (!(sc > NEG)) continue;
193
+ if (v === EOS) { if (done.length < nbest) done.push([decTgt(hist[b]), sc]); continue; }
194
+ if (j < K) { ns[j] = sc; nt[j] = v; par[j] = b; if (trie) nn[j] = trie.child(nodes[b], v); j++; }
195
+ }
196
+ let alive = true;
197
+ if (done.length >= nbest && (j === 0 || ns[0] < Math.min(...done.map((x) => x[1])))) alive = false;
198
+ else if (!j) alive = false;
199
+ if (!alive) break;
200
+ for (const C of cache) { // reorder self-attention caches by parent beam
201
+ const n = (t + 1) * 2 * d, olds = [];
202
+ for (let k = 0; k < j; k++) { const src = par[k] * MAXLEN * 2 * d; olds.push(C.slice(src, src + n)); }
203
+ for (let k = 0; k < j; k++) C.set(olds[k], k * MAXLEN * 2 * d);
204
+ }
205
+ hist = Array.from({ length: K }, (_, k) => (k < j ? hist[par[k]].concat([nt[k]]) : []));
206
+ score = ns; tok = nt; nodes = nn;
207
+ }
208
+ const best = new Map();
209
+ for (const [s, sc] of done) if (!best.has(s) || sc > best.get(s)) best.set(s, sc);
210
+ return [...best.entries()].sort((a, b) => b[1] - a[1]).slice(0, nbest);
211
+ }
212
+ /** teacher-forced ln P(tgt | src) (as bhc.xlit_model.score_pairs) */
213
+ scorePair(enc, tgt) {
214
+ const ids = encTgt(tgt), cache = this.newCache(1), out = [new Float64Array(V)]; let tok = [BOS], s = 0;
215
+ for (let t = 0; t < ids.length; t++) { this.step(enc, cache, [0], tok, t, out); s += out[0][ids[t]]; tok = [ids[t]]; }
216
+ return s;
217
+ }
218
+ }
219
+
220
+ // ------------------------------------------------------------------ lexicon + bigram LM
221
+ function readVarints(b, n, pos) {
222
+ const out = new Uint32Array(n);
223
+ for (let i = 0; i < n; i++) { let v = 0, sh = 0, c; do { c = b[pos++]; v += (c & 0x7f) * 2 ** sh; sh += 7; } while (c >= 0x80); out[i] = v; }
224
+ return [out, pos];
225
+ }
226
+ const unpack4 = (b, n) => { const o = new Uint8Array(n); for (let i = 0; i < n; i++) o[i] = (i & 1) ? b[i >> 1] & 15 : b[i >> 1] >> 4; return o; };
227
+
228
+ export class LexLM {
229
+ constructor(lexBuf, lmBuf) {
230
+ if (!lexBuf) { // core-only: empty lexicon, no LM
231
+ this.words = []; this.sortedWords = []; this.uni = new Float32Array(0); this.N = 0; this.unk = 0; this.alpha = true; this.hasLM = false; return;
232
+ }
233
+ const b = new Uint8Array(lexBuf), dv = new DataView(b.buffer, b.byteOffset, b.byteLength); const words = [];
234
+ const decodeFC = (P, S, nWords) => { let prev = [], j = 0, pi = 0;
235
+ while (words.length < nWords) { const k = P ? P[pi++] : S[j++]; const cur = prev.slice(0, k);
236
+ for (;;) { const c = S[j++], ch = c & 0x7f; if (ch !== 0x7f) cur.push(ch); if (c & 0x80) break; }
237
+ words.push(String.fromCharCode(...cur.map((x) => 0x900 + x))); prev = cur; } };
238
+ if (b[0] === 0x4c && b[1] === 0x58 && b[2] === 0x34 && b[3] === 0) { // "LX4\0"
239
+ const N = dv.getUint32(4, true), nfc = dv.getUint32(8, true), NB = dv.getUint32(16, true); this.unk = dv.getFloat32(12, true);
240
+ let pos = 20; const ucb = new Float32Array(NB); for (let i = 0; i < NB; i++) ucb[i] = dv.getFloat32(pos + 4 * i, true); pos += 4 * NB;
241
+ const band = unpack4(b.subarray(pos, pos + ((N + 1) >> 1)), N); pos += (N + 1) >> 1;
242
+ this.uni = new Float32Array(N); for (let i = 0; i < N; i++) this.uni[i] = ucb[band[i]];
243
+ decodeFC(b.subarray(pos, pos + N), b.subarray(pos + N, pos + N + nfc), N); this.N = N; this.alpha = true;
244
+ } else { // v2 (frequency-bucketed)
245
+ const N = dv.getUint32(0, true), nb = dv.getUint32(4, true), nfc = dv.getUint32(8, true); this.unk = dv.getFloat32(12, true);
246
+ let [bn, pos] = readVarints(b, nb, 16); const blp = []; for (let i = 0; i < nb; i++) blp.push(f16(dv.getUint16(pos + 2 * i, true))); pos += 2 * nb;
247
+ this.uni = new Float32Array(N); let w = 0; for (let i = 0; i < nb; i++) for (let k = 0; k < bn[i]; k++) this.uni[w++] = blp[i];
248
+ decodeFC(null, b.subarray(pos, pos + nfc), N); this.N = N; this.alpha = false;
249
+ }
250
+ this.words = words;
251
+ let sorted = this.alpha; for (let i = 1; sorted && i < words.length; i++) if (!(words[i - 1] < words[i])) sorted = false;
252
+ if (!sorted) { this.map = new Map(); words.forEach((w, i) => this.map.set(w, i)); }
253
+ this.sortedWords = sorted ? words : [...new Set(words)].sort();
254
+ this.hasLM = false;
255
+ if (lmBuf) this._loadLM(new Uint8Array(lmBuf));
256
+ }
257
+ id(w) {
258
+ if (this.map) { const i = this.map.get(w); return i === undefined ? null : i; }
259
+ let lo = 0, hi = this.words.length; while (lo < hi) { const m = (lo + hi) >> 1; if (this.words[m] < w) lo = m + 1; else hi = m; }
260
+ return lo < this.words.length && this.words[lo] === w ? lo : null;
261
+ }
262
+ _loadLM(b) {
263
+ const dv = new DataView(b.buffer, b.byteOffset, b.byteLength), N = this.N;
264
+ const nbg = dv.getUint32(4, true), nh = dv.getUint32(16, true); let pos = 20;
265
+ const cb = [], cbb = []; for (let i = 0; i < 16; i++) cb.push(dv.getFloat32(pos + 4 * i, true)); pos += 64;
266
+ for (let i = 0; i < 16; i++) cbb.push(dv.getFloat32(pos + 4 * i, true)); pos += 64;
267
+ let cnt, delta; [cnt, pos] = readVarints(b, N + 1, pos); [delta, pos] = readVarints(b, nbg, pos);
268
+ const q = unpack4(b.subarray(pos, pos + ((nbg + 1) >> 1)), nbg); pos += (nbg + 1) >> 1;
269
+ const qb = unpack4(b.subarray(pos, pos + ((nh + 1) >> 1)), nh);
270
+ this.off = new Uint32Array(N + 2); this.succ = new Uint32Array(nbg); this.bq = q; this.cb = Float32Array.from(cb);
271
+ this.bow = new Float32Array(N + 1); let i = 0, hi = 0;
272
+ for (let h = 0; h <= N; h++) {
273
+ this.off[h] = i; const n = cnt[h]; if (!n) continue;
274
+ this.bow[h] = cbb[qb[hi++]]; let acc = -1;
275
+ for (let k = 0; k < n; k++) { acc = acc < 0 ? delta[i] : acc + delta[i] + 1; this.succ[i++] = acc; }
276
+ }
277
+ this.off[N + 1] = i; this.hasLM = nbg > 0;
278
+ }
279
+ unigram(wid) { return wid === null ? this.unk : wid === this.N ? Math.log(0.04) : this.uni[wid]; }
280
+ /** ln P(wid | prev); prev null = sentence start; wid null = OOV; wid N = </s> */
281
+ logp(prev, wid) {
282
+ const h = prev === null ? this.N : prev;
283
+ if (wid !== null && this.hasLM) {
284
+ let lo = this.off[h], hi = this.off[h + 1];
285
+ while (lo < hi) { const m = (lo + hi) >> 1; if (this.succ[m] < wid) lo = m + 1; else hi = m; }
286
+ if (lo < this.off[h + 1] && this.succ[lo] === wid) return this.cb[this.bq[lo]];
287
+ }
288
+ return (this.hasLM ? this.bow[h] : 0) + this.unigram(wid);
289
+ }
290
+ }
291
+
292
+ /** lazy lexicon trie over the sorted word list (same node semantics as bhc/lextrie.py) */
293
+ class LazyTrie {
294
+ constructor(sortedWords) { this.w = sortedWords; this.nodes = [[0, sortedWords.length, 0]]; this.ids = new Map([["0,0," + sortedWords.length, 0]]); this.cache = new Map(); this.allow = new Map(); }
295
+ _bisect(x, lo, hi) { const w = this.w; while (lo < hi) { const m = (lo + hi) >> 1; if (w[m] < x) lo = m + 1; else hi = m; } return lo; }
296
+ children(n) {
297
+ let c = this.cache.get(n); if (c) return c;
298
+ const [lo, hi, d] = this.nodes[n], w = this.w; c = new Map(); let pos = lo;
299
+ if (pos < hi && w[pos].length === d) pos++;
300
+ while (pos < hi) {
301
+ const ch = w[pos].charCodeAt(d), pre = w[pos].slice(0, d);
302
+ const end = this._bisect(pre + String.fromCharCode(ch + 1), pos, hi);
303
+ const key = pos + "," + (d + 1) + "," + end; let nid = this.ids.get(key);
304
+ if (nid === undefined) { nid = this.nodes.length; this.nodes.push([pos, end, d + 1]); this.ids.set(key, nid); }
305
+ c.set(ch - 0x900 + 4, nid); pos = end;
306
+ }
307
+ if (this.cache.size > 400000) { this.cache.clear(); this.allow.clear(); }
308
+ this.cache.set(n, c); return c;
309
+ }
310
+ final(n) { const [lo, , d] = this.nodes[n]; return lo < this.w.length && this.w[lo].length === d; }
311
+ allowed(n) { let a = this.allow.get(n); if (!a) { a = [...this.children(n).keys()]; if (this.final(n)) a.push(EOS); this.allow.set(n, a); } return a; }
312
+ child(n, v) { return this.children(n).get(v); }
313
+ }
314
+
315
+ // ------------------------------------------------------------------ language ID (char-CNN + BiGRU)
316
+ const LID_CH = {}; [..."abcdefghijklmnopqrstuvwxyz'"].forEach((c, i) => (LID_CH[c] = i + 3));
317
+ const WMAX = 16, MODES3 = ["english", "romanized_hindi", "hinglish"];
318
+ const RE_LATIN_APOS = /^[A-Za-z]+(?:'[A-Za-z]+)?$/, RE_DEVA = /^[\u0900-\u0963\u0971-\u097F]+$/;
319
+ function whash(w, n) { let h = 2166136261; for (const ch of new TextEncoder().encode(w.toLowerCase())) h = Math.imul(h ^ ch, 16777619) >>> 0; return h % n; }
320
+
321
+ export class LID {
322
+ constructor(w) {
323
+ this.cfg = w.cfg; this.x = w.extra || {}; const g = (k) => w.T[k] && w.T[k].data;
324
+ Object.assign(this, { ce: g("ce.weight"), c1: g("c1.weight"), c1b: g("c1.bias"), c2: g("c2.weight"), c2b: g("c2.bias"), wemb: g("wemb.weight"),
325
+ wih: g("gru.weight_ih_l0"), whh: g("gru.weight_hh_l0"), bih: g("gru.bias_ih_l0"), bhh: g("gru.bias_hh_l0"),
326
+ wihR: g("gru.weight_ih_l0_reverse"), whhR: g("gru.weight_hh_l0_reverse"), bihR: g("gru.bias_ih_l0_reverse"), bhhR: g("gru.bias_hh_l0_reverse"),
327
+ tw: g("tok.weight"), tb: g("tok.bias"), s0: g("sent.0.weight"), s0b: g("sent.0.bias"), s2: g("sent.2.weight"), s2b: g("sent.2.bias") });
328
+ this.nParams = Object.values(w.T).reduce((a, t) => a + t.data.length, 0);
329
+ }
330
+ _wvec(word) {
331
+ const { ce: E, cf } = this.cfg; const ids = new Array(WMAX).fill(0); const w = [...word.toLowerCase()].slice(0, WMAX - 2);
332
+ ids[0] = 1; w.forEach((c, i) => (ids[i + 1] = LID_CH[c] ?? 2)); ids[w.length + 1] = 2;
333
+ const x = new Float32Array(E * WMAX); for (let t = 0; t < WMAX; t++) for (let i = 0; i < E; i++) x[i * WMAX + t] = this.ce[ids[t] * E + i];
334
+ const conv = (inp, cin, W, b) => { const o = new Float32Array(cf * WMAX);
335
+ for (let oc = 0; oc < cf; oc++) for (let t = 0; t < WMAX; t++) { let s = b[oc];
336
+ for (let ic = 0; ic < cin; ic++) for (let k = 0; k < 3; k++) { const tt = t + k - 1; if (tt >= 0 && tt < WMAX) s += W[(oc * cin + ic) * 3 + k] * inp[ic * WMAX + tt]; }
337
+ o[oc * WMAX + t] = s > 0 ? s : 0; } return o; };
338
+ const h1 = conv(x, E, this.c1, this.c1b), h2 = conv(h1, cf, this.c2, this.c2b);
339
+ const wh = this.cfg.wh, wd = this.cfg.wd, v = new Float32Array(cf + (wh ? wd : 0));
340
+ for (let oc = 0; oc < cf; oc++) { let m = -Infinity; for (let t = 0; t < WMAX; t++) { const val = ids[t] === 0 ? -1e4 : h2[oc * WMAX + t] + h1[oc * WMAX + t]; if (val > m) m = val; } v[oc] = m; }
341
+ if (wh) { const r = whash(word, wh); for (let i = 0; i < wd; i++) v[cf + i] = this.wemb[r * wd + i]; }
342
+ return v;
343
+ }
344
+ _gru(xs, Wih, Whh, bih, bhh, rev) {
345
+ const H = this.cfg.hid, nin = xs[0].length, T = xs.length, out = new Array(T); let h = new Float32Array(H);
346
+ const gi = new Float32Array(3 * H), gh = new Float32Array(3 * H);
347
+ for (let s = 0; s < T; s++) {
348
+ const t = rev ? T - 1 - s : s; linear(xs[t], 0, Wih, bih, gi, 0, nin, 3 * H); linear(h, 0, Whh, bhh, gh, 0, H, 3 * H);
349
+ const nh = new Float32Array(H);
350
+ for (let i = 0; i < H; i++) { const r = sigmoid(gi[i] + gh[i]), z = sigmoid(gi[H + i] + gh[H + i]), n = Math.tanh(gi[2 * H + i] + r * gh[2 * H + i]); nh[i] = (1 - z) * n + z * h[i]; }
351
+ h = nh; out[t] = h;
352
+ }
353
+ return out;
354
+ }
355
+ /** Latin words -> {pen: P(EN) per word, ps: [english, romanized_hindi, hinglish]} */
356
+ run(words) {
357
+ if (!words.length) return { pen: [], ps: null };
358
+ const H = this.cfg.hid, xs = words.map((w) => this._wvec(w)), T = xs.length, nin = xs[0].length;
359
+ const f = this._gru(xs, this.wih, this.whh, this.bih, this.bhh, false), r = this._gru(xs, this.wihR, this.whhR, this.bihR, this.bhhR, true);
360
+ const pen = [], hm = new Float32Array(2 * H), hx = new Float32Array(2 * H).fill(-1e4), z = new Float32Array(2 * H + nin), lo = new Float32Array(2);
361
+ for (let t = 0; t < T; t++) {
362
+ for (let i = 0; i < H; i++) { z[i] = f[t][i]; z[H + i] = r[t][i]; } z.set(xs[t], 2 * H);
363
+ linear(z, 0, this.tw, this.tb, lo, 0, 2 * H + nin, 2); const m = Math.max(lo[0], lo[1]), e0 = Math.exp(lo[0] - m), e1 = Math.exp(lo[1] - m); pen.push(e0 / (e0 + e1));
364
+ for (let i = 0; i < 2 * H; i++) { hm[i] += z[i] / T; if (z[i] > hx[i]) hx[i] = z[i]; }
365
+ }
366
+ const sin = new Float32Array(4 * H + 1); sin.set(hm, 0); sin.set(hx, 2 * H); sin[4 * H] = pen.reduce((a, b) => a + b, 0) / T;
367
+ const h1 = new Float32Array(32); linear(sin, 0, this.s0, this.s0b, h1, 0, 4 * H + 1, 32); for (let i = 0; i < 32; i++) h1[i] = Math.max(0, h1[i]);
368
+ const s = new Float32Array(3); linear(h1, 0, this.s2, this.s2b, s, 0, 32, 3); const m = Math.max(...s); const e = [...s].map((v) => Math.exp(v - m)); const Z = e.reduce((a, b) => a + b, 0);
369
+ return { pen, ps: e.map((v) => v / Z) };
370
+ }
371
+ tag(tokens) {
372
+ const lat = tokens.filter((t) => RE_LATIN_APOS.test(t)); const { pen } = this.run(lat); let k = 0; const th = this.x.tok_th ?? 0.5;
373
+ return tokens.map((t) => (RE_DEVA.test(t) ? "DEV" : RE_LATIN_APOS.test(t) ? (pen[k++] >= th ? "EN" : "HI") : "OTHER"));
374
+ }
375
+ mode(tokens) {
376
+ const nd = tokens.filter((t) => RE_DEVA.test(t)).length, nl = tokens.filter((t) => RE_LATIN_APOS.test(t)).length;
377
+ if (nd && !nl) return "devanagari";
378
+ if (nd && nl) return nd / (nd + nl) >= (this.x.dev_frac ?? 0.5) ? "devanagari" : "mixed_script";
379
+ if (!nl) return "english";
380
+ const { ps } = this.run(tokens.filter((t) => RE_LATIN_APOS.test(t))); const bias = this.x.mode_bias || [0, 0, 0];
381
+ let best = 0, bv = -Infinity; ps.forEach((p, i) => { const v = Math.log(p + 1e-9) + bias[i]; if (v > bv) { bv = v; best = i; } });
382
+ return MODES3[best];
383
+ }
384
+ }
385
+
386
+ // ------------------------------------------------------------------ tokenizer + policy tables
387
+ export const TOKEN_RE = /(https?:\/\/\S+|www\.\S+|[\p{L}\p{N}_.+-]+@[\p{L}\p{N}_-]+\.[\p{L}\p{N}_.]+|[@#][\p{L}\p{N}_]+|[A-Za-z]+(?:'[A-Za-z]+)?|[\u0900-\u0963\u0971-\u097F]+|[0-9\u0966-\u096F]+(?:[.,:][0-9\u0966-\u096F]+)*|[\u0964\u0965]|[^\sA-Za-z0-9\u0900-\u097F])/gu;
388
+ const RE_LATIN = /^[A-Za-z]+$/;
389
+ const RE_ACRO = /^[b-df-hj-np-tv-z]{2,7}$/;
390
+ const LETTER = Object.fromEntries([..."abcdefghijklmnopqrstuvwxyz"].map((c, i) => [c, ("\u090f \u092c\u0940 \u0938\u0940 \u0921\u0940 \u0908 \u090f\u092b \u091c\u0940 \u090f\u091a \u0906\u0908 \u091c\u0947 \u0915\u0947 \u090f\u0932 \u090f\u092e \u090f\u0928 " +
391
+ "\u0913 \u092a\u0940 \u0915\u094d\u092f\u0942 \u0906\u0930 \u090f\u0938 \u091f\u0940 \u092f\u0942 \u0935\u0940 \u0921\u092c\u094d\u0932\u094d\u092f\u0942 \u090f\u0915\u094d\u0938 \u0935\u093e\u0908 \u091c\u0947\u0921").split(" ")[i]]));
392
+ // Hinglish policy letter names (bhc/hinglish baseline): f -> एफ़, z -> ज़ेड
393
+ const LETTERS_H = { ...LETTER, f: "\u090f\u092b\u093c", z: "\u091c\u093c\u0947\u0921" };
394
+ const VAR_SWAPS = [["\u0949", "\u094b"], ["\u094b", "\u0949"], ["\u093e", "\u0949"], ["\u0949", "\u093e"], ["\u0903", ""], ["\u092b\u093c", "\u092b"], ["\u092b", "\u092b\u093c"],
395
+ ["\u091c\u093c", "\u091c"], ["\u091c", "\u091c\u093c"], ["\u0901", "\u0902"], ["\u0902", "\u0901"]];
396
+ export const FEATS = ["sc", "scn", "inlex", "uni", "free0", "infree", "incon", "aw", "visarga", "nukta", "chandra", "acro", "variant", "frank",
397
+ "skel", "skmatch", "lev", "sc_rel", "scn_rel", "uni_rel", "lenr", "visr", "hfin", "acro_nv"];
398
+ const BRANDS = new Set(`whatsapp google meet paytm amazon flipkart youtube instagram facebook gmail excel uber ola swiggy zomato
399
+ netflix linkedin github python java jira slack zoom iphone android windows microsoft apple samsung twitter telegram
400
+ chrome firefox safari office365 outlook teams spotify hotstar jio airtel phonepe gpay`.split(/\s+/));
401
+ const KEEP_RE = /^(https?:\/\/|www\.|[@#])|@.+\.|_|\d.*[A-Za-z]|[A-Za-z].*\d|^[a-z]+[A-Z]/;
402
+ const isUpper = (t) => /[A-Z]/.test(t) && t === t.toUpperCase();
403
+ const DEFAULTS = { beam: 8, nbest: 8, cbeam: 8, variants: true, acro: true, w: null, mu: null, sd: null, w_lm: 0.8, oov_pen_s: 4.0, w_ch_s: 1.0, use_lm: true,
404
+ skel: false, skel_k: 200, skel_m: 16, skel_wu: 0.03, visr: false, acro_v: false };
405
+ const parseTSV = (txt) => { const m = new Map(); for (const l of txt.split("\n")) { const i = l.indexOf("\t"); if (i > 0) m.set(l.slice(0, i).trim(), l.slice(i + 1).trim()); } return m; };
406
+ const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
407
+
408
+ async function fetchBytes(base, name, fetchImpl) {
409
+ const url = base.endsWith("/") ? base + name : base + "/" + name;
410
+ const r = await fetchImpl(url); if (!r.ok) throw new Error(`fetch ${url}: ${r.status}`);
411
+ if (name.endsWith(".gz") && typeof DecompressionStream !== "undefined") return new Uint8Array(await new Response(r.body.pipeThrough(new DecompressionStream("gzip"))).arrayBuffer());
412
+ return new Uint8Array(await r.arrayBuffer());
413
+ }
414
+
415
+ // ------------------------------------------------------------------ consonant-skeleton index (bhc/skel.py; tables generated from it)
416
+ const SK_DEV = {"\u0915": "K", "\u0916": "K", "\u0917": "G", "\u0918": "G", "\u091a": "C", "\u091b": "C", "\u091c": "J", "\u091d": "J", "\u091f": "T", "\u0920": "T", "\u0924": "T", "\u0925": "T", "\u0921": "D", "\u0922": "D", "\u0926": "D", "\u0927": "D", "\u0919": "N", "\u091e": "N", "\u0923": "N", "\u0928": "N", "\u0929": "N", "\u092e": "N", "\u0902": "N", "\u0901": "N", "\u092a": "P", "\u092b": "P", "\u092c": "B", "\u092d": "B", "\u0930": "R", "\u0931": "R", "\u090b": "R", "\u0943": "R", "\u0932": "L", "\u0933": "L", "\u0935": "V", "\u0936": "S", "\u0937": "S", "\u0938": "S", "\u0939": "H", "\u0903": "H"};
417
+ const SK_ROM1 = {"k": "K", "q": "K", "g": "G", "j": "J", "z": "J", "t": "T", "d": "D", "n": "N", "m": "N", "p": "P", "f": "P", "b": "B", "r": "R", "l": "L", "v": "V", "w": "V", "s": "S", "h": "H"};
418
+ const SK_ASP = "bcdgjkpstz";
419
+ const DR_C = {"\u0915": "k", "\u0916": "kh", "\u0917": "g", "\u0918": "gh", "\u0919": "n", "\u091a": "ch", "\u091b": "chh", "\u091c": "j", "\u091d": "jh", "\u091e": "n", "\u091f": "t", "\u0920": "th", "\u0921": "d", "\u0922": "dh", "\u0923": "n", "\u0924": "t", "\u0925": "th", "\u0926": "d", "\u0927": "dh", "\u0928": "n", "\u092a": "p", "\u092b": "ph", "\u092c": "b", "\u092d": "bh", "\u092e": "m", "\u092f": "y", "\u0930": "r", "\u0932": "l", "\u0935": "v", "\u0936": "sh", "\u0937": "sh", "\u0938": "s", "\u0939": "h", "\u0933": "l"};
420
+ const DR_V = {"\u0905": "a", "\u0906": "aa", "\u0907": "i", "\u0908": "ee", "\u0909": "u", "\u090a": "oo", "\u090b": "ri", "\u090f": "e", "\u0910": "ai", "\u0913": "o", "\u0914": "au", "\u0911": "o"};
421
+ const DR_M = {"\u093e": "a", "\u093f": "i", "\u0940": "ee", "\u0941": "u", "\u0942": "oo", "\u0943": "ri", "\u0947": "e", "\u0948": "ai", "\u094b": "o", "\u094c": "au", "\u0949": "o", "\u0945": "e"};
422
+ const NUKTA_ROM = { k: "q", kh: "kh", g: "g", j: "z", ph: "f", d: "r", dh: "rh" };
423
+ export function devSkel(w) { let o = ""; for (const ch of w) { const c = SK_DEV[ch]; if (c && o[o.length - 1] !== c) o += c; } return o; }
424
+ export function romSkels(w, maxk = 4) {
425
+ w = w.toLowerCase(); let outs = [""]; let i = 0; const n = w.length;
426
+ while (i < n) {
427
+ const ch = w[i]; let step = 1, opts;
428
+ if ((ch === "t" || ch === "s") && w.startsWith("ion", i + 1)) { opts = ch === "t" ? ["SN", "TN"] : ["SN"]; step = 4; }
429
+ else if (ch === "c") {
430
+ const nx = i + 1 < n ? w[i + 1] : "";
431
+ if (nx === "h") { opts = ["C", "K"]; step = 2; while (i + step < n && (w[i + step] === "c" || w[i + step] === "h")) step++; }
432
+ else if (nx === "e" || nx === "i" || nx === "y") opts = ["S", "K"];
433
+ else if (nx === "k") { opts = ["K"]; step = 2; }
434
+ else opts = ["K", "S"];
435
+ } else if (ch === "x") opts = ["KS"];
436
+ else if (SK_ASP.includes(ch) && i + 1 < n && w[i + 1] === "h") { opts = [SK_ROM1[ch] || ""]; step = 2; while (i + step < n && w[i + step] === "h") step++; }
437
+ else opts = [SK_ROM1[ch] || ""];
438
+ const nw = [];
439
+ for (const o of outs) for (const q of opts) { let s = o; for (const c of q) if (!s || s[s.length - 1] !== c) s += c; nw.push(s); }
440
+ outs = [...new Set(nw)].slice(0, maxk); i += step;
441
+ }
442
+ return outs;
443
+ }
444
+ export function devRom(w) {
445
+ const out = [], a = [...w], L = a.length;
446
+ for (let j = 0; j < L; j++) {
447
+ const ch = a[j];
448
+ if (DR_C[ch] !== undefined) {
449
+ out.push(DR_C[ch]); let nx = j + 1 < L ? a[j + 1] : "";
450
+ if (nx === "\u093c") nx = j + 2 < L ? a[j + 2] : "";
451
+ if (nx && DR_M[nx] === undefined && nx !== "\u094d" && j + 1 < L - 1) out.push("a");
452
+ } else if (DR_V[ch] !== undefined) out.push(DR_V[ch]);
453
+ else if (DR_M[ch] !== undefined) out.push(DR_M[ch]);
454
+ else if (ch === "\u0902" || ch === "\u0901") out.push("n");
455
+ else if (ch === "\u0903") out.push("h");
456
+ else if (ch === "\u093c" && out.length) { const l = out[out.length - 1]; out[out.length - 1] = NUKTA_ROM[l] ?? l; }
457
+ }
458
+ return out.join("");
459
+ }
460
+ export function lev(a, b) {
461
+ if (a.length < b.length) [a, b] = [b, a];
462
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
463
+ for (let i = 1; i <= a.length; i++) {
464
+ const cur = [i];
465
+ for (let j = 1; j <= b.length; j++) cur.push(Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] !== b[j - 1] ? 1 : 0)));
466
+ prev = cur;
467
+ }
468
+ return prev[b.length];
469
+ }
470
+ class SkelIndex {
471
+ constructor(lm) {
472
+ const b = new Map(), N = lm.words.length;
473
+ for (let i = 0; i < N; i++) { const k = devSkel(lm.words[i]); if (k) { let v = b.get(k); if (!v) b.set(k, (v = [])); v.push(i); } }
474
+ for (const v of b.values()) v.sort((x, y) => (lm.uni[y] - lm.uni[x]) || (x - y));
475
+ this.b = b;
476
+ }
477
+ lookup(roman, topk = 24) {
478
+ const seen = [], S = new Set();
479
+ for (const k of romSkels(roman)) { const v = this.b.get(k); if (!v) continue; for (const i of v.slice(0, topk)) if (!S.has(i)) { S.add(i); seen.push(i); } }
480
+ return seen;
481
+ }
482
+ }
483
+ const VIS_RE = /[aeiou]h$/;
484
+
485
+ // ------------------------------------------------------------------ the engine
486
+ export class BhashaCore {
487
+ /**
488
+ * Load a bundle (directory with manifest.json) from a URL (browser / worker) or via a custom fetch (Node: file loader).
489
+ * dict: true (default) = also load the dictionary pack and wait for it; false = core only (call loadDict() later).
490
+ * cache: Cache API cache name for the dictionary pack (browser / worker; default "aksharalp-dict"), or false.
491
+ */
492
+ static async load(base, { fetch: fetchImpl = globalThis.fetch, onProgress, dict = true, cache = "aksharalp-dict" } = {}) {
493
+ const t0 = now(); const txt = (b) => new TextDecoder().decode(b);
494
+ let man; try { man = JSON.parse(txt(await fetchBytes(base, "manifest.json", fetchImpl))); } catch { man = { files: { model: "xlit.q4", lex: "lex.bin", lm: "lm.bin", params: "params.json", lid: "lid.int8", loan: "loan.tsv", natural: "natural_hi.tsv" } }; }
495
+ const F = man.files, get = async (n, opt) => { if (!n) return null; try { const b = await fetchBytes(base, n, fetchImpl); onProgress && onProgress(n); return b; } catch (e) { if (opt) return null; throw e; } };
496
+ const [mb, lexb, lmb, pb, lidb, loanb, natb] = await Promise.all([get(F.model), get(F.lex, true), get(F.lm, true), get(F.params, true), get(F.lid, true), get(F.loan, true), get(F.natural, true)]);
497
+ const e = new BhashaCore({ model: mb, lex: lexb, lm: lmb, params: pb ? JSON.parse(txt(pb)) : {}, lid: lidb, loan: loanb ? txt(loanb) : null, natural: natb ? txt(natb) : null, manifest: man });
498
+ e.base = base; e.fetchImpl = fetchImpl;
499
+ if (dict && man.packs && man.packs.dict) await e.loadDict({ onProgress, cache });
500
+ e.loadMs = now() - t0; return e;
501
+ }
502
+ /** Fetch (or read from cache) the dictionary pack and attach it. Returns true on success; on any failure the engine
503
+ * keeps working in core-only mode and the error is kept in this.dictError. */
504
+ async loadDict({ onProgress, cache = "aksharalp-dict" } = {}) {
505
+ const pk = this.manifest && this.manifest.packs && this.manifest.packs.dict; if (!pk) return false;
506
+ const dir = pk.dir.endsWith("/") ? pk.dir : pk.dir + "/", fetchImpl = this.fetchImpl || globalThis.fetch;
507
+ const useCache = cache && typeof caches !== "undefined";
508
+ const get = async (n) => {
509
+ if (!n) return null;
510
+ const ver = (pk.sizes && pk.sizes[n] && pk.sizes[n].sha256) || "";
511
+ if (useCache) {
512
+ try {
513
+ const url = new URL(dir + n, new URL(this.base.endsWith("/") ? this.base : this.base + "/", globalThis.location && globalThis.location.href)).href;
514
+ const c = await caches.open(cache), key = url + "?v=" + ver.slice(0, 16), hit = await c.match(key);
515
+ if (hit) { onProgress && onProgress(n, "cache"); return new Uint8Array(await hit.arrayBuffer()); }
516
+ const r = await fetchImpl(url); if (!r.ok) throw new Error(`fetch ${url}: ${r.status}`);
517
+ const b = new Uint8Array(await r.arrayBuffer()); try { await c.put(key, new Response(b)); } catch (_) {}
518
+ onProgress && onProgress(n, "network"); return b;
519
+ } catch (e) { if (String(e).includes("fetch ")) throw e; }
520
+ }
521
+ const b = await fetchBytes(this.base, dir + n, fetchImpl); onProgress && onProgress(n, "network"); return b;
522
+ };
523
+ try {
524
+ const [lexb, lmb, pb] = await Promise.all([get(pk.files.lex), get(pk.files.lm), get(pk.files.params)]);
525
+ this.attachDict({ lex: lexb, lm: lmb, params: pb ? JSON.parse(new TextDecoder().decode(pb)) : {} });
526
+ this.dictInfo = { name: pk.name || "dict", words: this.lm.N, bytes: pk.bytes, gz: pk.gz }; return true;
527
+ } catch (e) { this.dictError = String(e && e.message || e); return false; }
528
+ }
529
+ /** attach an already-fetched dictionary pack: {lex, lm, params} (params override the core params) */
530
+ attachDict({ lex, lm = null, params = {} }) {
531
+ this.p = { ...DEFAULTS, ...this.coreParams, ...params };
532
+ this._setLex(lex, lm); this.cache = new Map(); this.hasDict = this.lm.N > 0;
533
+ }
534
+ _setLex(lex, lm) {
535
+ this.lm = new LexLM(lex, lm && this.p.use_lm ? lm : null);
536
+ if (!this.lm.N) { this.p.cbeam = 0; this.p.skel = false; }
537
+ // the trie never contains the empty string (as in Python's LazyTrie), otherwise "" would take a constrained-beam slot
538
+ const sw = this.lm.sortedWords, tw = sw.length && sw[0] === "" ? sw.slice(1) : sw;
539
+ this.trie = this.p.cbeam ? new LazyTrie(tw) : null;
540
+ this.skix = this.p.skel ? new SkelIndex(this.lm) : null;
541
+ }
542
+ constructor({ model, lex = null, lm = null, params = {}, lid = null, loan = null, natural = null, manifest = null }) {
543
+ this.coreParams = params; this.p = { ...DEFAULTS, ...params }; this.manifest = manifest;
544
+ this.model = new XlitModel(parseWeights(model));
545
+ this._setLex(lex, lm); this.hasDict = this.lm.N > 0; this.dictInfo = null; this.dictError = null;
546
+ this.lid = lid ? new LID(parseWeights(lid)) : null;
547
+ this.loan = loan ? parseTSV(loan) : new Map();
548
+ this.natural = natural ? parseTSV(natural) : new Map();
549
+ this.cache = new Map();
550
+ }
551
+ info() {
552
+ const s = this.manifest && this.manifest.sizes ? this.manifest.sizes : {};
553
+ const used = Object.values(this.manifest ? this.manifest.files : {}).filter((f) => f && s[f]);
554
+ return { params: this.model.nParams + (this.lid ? this.lid.nParams : 0), xlitParams: this.model.nParams, lidParams: this.lid ? this.lid.nParams : 0,
555
+ lexWords: this.lm.N, bytes: used.reduce((a, f) => a + s[f].bytes, 0), gz: used.reduce((a, f) => a + s[f].gz, 0), hasLID: !!this.lid, hasLM: this.lm.hasLM,
556
+ hasDict: this.hasDict, dict: this.dictInfo, dictError: this.dictError, hasLoan: this.loan.size > 0, hasNatural: this.natural.size > 0 };
557
+ }
558
+
559
+ /** candidate rows with reranker features for one (lower-cased) Latin word: [[devanagari, fv, wid], ...] */
560
+ candFeats(word) {
561
+ const w = word.toLowerCase(); const hit = this.cache.get(w); if (hit) return hit;
562
+ const p = this.p, m = this.model, ids = encSrc(w), enc = m.encode(ids), S = ids.length;
563
+ const free = m.beam(enc, S, { beam: p.beam, nbest: p.nbest });
564
+ const con = p.cbeam && this.trie ? m.beam(enc, S, { beam: p.beam, nbest: p.nbest, trie: this.trie }) : [];
565
+ const d = new Map();
566
+ free.forEach(([s, sc], i) => { if (s) d.set(s, { sc, infree: 1, frank: i, free0: +(i === 0), beamEntry: 1 }); });
567
+ for (const [s, sc] of con) if (s) { if (!d.has(s)) d.set(s, { sc, infree: 0, frank: p.nbest, free0: 0, beamEntry: 1 }); d.get(s).incon = 1; }
568
+ if (p.variants) {
569
+ const top = [...d.keys()].sort((a, b) => d.get(b).sc - d.get(a).sc).slice(0, 4);
570
+ for (const s of top) for (const [a, b] of VAR_SWAPS) if (s.includes(a)) {
571
+ const v = s.split(a).join(b); if (!d.has(v) && this.lm.id(v) !== null) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0, variant: 1 });
572
+ }
573
+ }
574
+ if (p.acro && (RE_ACRO.test(w) || (p.acro_v && w.length >= 2 && w.length <= 4))) {
575
+ const v = [...w].map((c) => LETTER[c]).join("");
576
+ if (!d.has(v)) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0, acro: 1 }); else d.get(v).acro = 1;
577
+ }
578
+ if (p.visr && VIS_RE.test(w)) { // Aksharantar convention: literal word-final h -> visarga
579
+ const top = [...d.keys()].filter((x) => d.get(x).beamEntry).sort((x, y) => d.get(y).sc - d.get(x).sc).slice(0, 2);
580
+ for (const s of top) {
581
+ const v = s.endsWith("\u0939") ? s.slice(0, -1) + "\u0903" : (!s.endsWith("\u0903") ? s + "\u0903" : null);
582
+ if (v && !d.has(v)) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0, visr: 1 });
583
+ }
584
+ }
585
+ if (this.skix) {
586
+ const L = w.length + 1, words = this.lm.words, uni = this.lm.uni;
587
+ const ids = this.skix.lookup(w, p.skel_k).map((i) => [i, lev(w, devRom(words[i])) / L - p.skel_wu * uni[i]])
588
+ .sort((x, y) => x[1] - y[1]).slice(0, p.skel_m);
589
+ for (const [i] of ids) { const v = words[i]; if (!d.has(v)) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0, skel: 1 }); else d.get(v).skel = 1; }
590
+ }
591
+ const rows = [], sks = new Set(romSkels(w)), Lw = w.length + 1, vis = VIS_RE.test(w), novow = !/[aeiou]/.test(w);
592
+ for (const [s, e] of d) {
593
+ const wid = this.lm.id(s), L = [...s].length;
594
+ rows.push([s, [e.sc, e.sc / (L + 1), +(wid !== null), wid !== null ? this.lm.unigram(wid) : 0, e.free0 || 0, e.infree || 0, e.incon || 0,
595
+ +(s.includes("\u0949") || s.includes("\u0911")), +s.includes("\u0903"), +s.includes("\u093c"), +s.includes("\u0901"), e.acro || 0, e.variant || 0, (e.frank ?? p.nbest) / p.nbest,
596
+ e.skel || 0, +sks.has(devSkel(s)), lev(w, devRom(s)) / Lw, 0, 0, 0, L / Lw, e.visr || 0, +(vis && s.endsWith("\u0903")), +(!!e.acro && novow)], wid]);
597
+ }
598
+ if (rows.length) {
599
+ let msc = -Infinity, mscn = -Infinity, mu = null;
600
+ for (const [, fv] of rows) { msc = Math.max(msc, fv[0]); mscn = Math.max(mscn, fv[1]); if (fv[2]) mu = mu === null ? fv[3] : Math.max(mu, fv[3]); }
601
+ for (const [, fv] of rows) { fv[17] = fv[0] - msc; fv[18] = fv[1] - mscn; fv[19] = fv[2] ? fv[3] - (mu ?? 0) : 0; }
602
+ }
603
+ if (this.cache.size > 20000) this.cache.clear();
604
+ this.cache.set(w, rows); return rows;
605
+ }
606
+ score(fv) {
607
+ const p = this.p; if (!p.w && !p.w1) return fv[0] + 0.2 * (fv[2] ? fv[3] : -25.0);
608
+ const n = p.mu.length, z = new Array(n); for (let i = 0; i < n; i++) z[i] = (fv[i] - p.mu[i]) / p.sd[i];
609
+ if (p.w1) { // 1-hidden-layer tanh MLP reranker (fit on VALID only)
610
+ let s = p.b2 || 0;
611
+ for (let h = 0; h < p.w1.length; h++) { const r = p.w1[h]; let a = p.b1[h]; for (let i = 0; i < n; i++) a += r[i] * z[i]; s += p.w2[h] * Math.tanh(a); }
612
+ return s;
613
+ }
614
+ let s = 0; for (let i = 0; i < Math.min(n, p.w.length); i++) s += p.w[i] * z[i]; return s;
615
+ }
616
+ /** word mode: top-k Devanagari candidates for a romanized word */
617
+ word(w, k = 8) {
618
+ if (!RE_LATIN.test(w)) return [{ text: w, score: 0 }];
619
+ const rows = this.candFeats(w).map(([s, fv]) => ({ text: s, score: this.score(fv) }));
620
+ return rows.sort((a, b) => b.score - a.score).slice(0, k);
621
+ }
622
+ /** sentence Viterbi over candidate lattices with the bigram LM (bhc/translit2.py decode_slots) */
623
+ decodeSlots(slots, context = true) {
624
+ const p = this.p, lm = this.lm, lat = [];
625
+ for (const [kind, t] of slots) {
626
+ if (kind === "x") {
627
+ let sc = this.candFeats(t).map(([s, fv, wid]) => [s, this.score(fv), wid]);
628
+ if (sc.length) {
629
+ const mx = Math.max(...sc.map((x) => x[1])), lse = mx + Math.log(sc.reduce((a, x) => a + Math.exp(x[1] - mx), 0));
630
+ sc = sc.map(([s, v, wid]) => [s, p.w_ch_s * (v - lse), wid]).sort((a, b) => b[1] - a[1]).slice(0, 12);
631
+ }
632
+ lat.push(sc.length ? sc : [[t, 0, null]]);
633
+ } else {
634
+ const wid = RE_DEVA.test(t) ? lm.id(t) ?? -1 : -2; lat.push([[t, 0, wid]]);
635
+ }
636
+ }
637
+ const post = lat.map((o) => o.map(([s, v]) => [s, v])); // per-slot candidate list (for UI chips)
638
+ if (!context || !p.use_lm || !lm.hasLM) return { out: lat.map((o) => o.reduce((a, b) => (b[1] > a[1] ? b : a))[0]), lat: post };
639
+ let beams = [[0, null, []]];
640
+ for (const opts of lat) {
641
+ if (opts[0][2] === -2 && opts.length === 1) { beams = beams.map(([s, h, ch]) => [s, h, ch.concat([opts[0][0]])]); continue; }
642
+ const best = new Map();
643
+ for (const [s, h, ch] of beams) for (const [cand, csc, wid] of opts) {
644
+ const w = wid === null || wid === -1 ? null : wid;
645
+ const tot = s + csc + p.w_lm * (lm.logp(h, w) - (w === null ? p.oov_pen_s : 0));
646
+ if (!best.has(cand) || tot > best.get(cand)[0]) best.set(cand, [tot, w, ch.concat([cand])]);
647
+ }
648
+ beams = [...best.values()].sort((a, b) => b[0] - a[0]).slice(0, 16);
649
+ }
650
+ let bb = null, bv = -Infinity;
651
+ for (const b of beams) { const v = b[0] + p.w_lm * lm.logp(b[1], lm.N); if (v > bv) { bv = v; bb = b; } }
652
+ return { out: bb[2], lat: post };
653
+ }
654
+ /** plain Track-B sentence conversion (bhc/translit2.py convert) */
655
+ convertPlain(text) {
656
+ const spans = [...text.matchAll(TOKEN_RE)].map((m) => [m.index, m.index + m[0].length, m[0]]);
657
+ const { out } = this.decodeSlots(spans.map(([, , t]) => (RE_LATIN.test(t) ? ["x", t] : ["fix", t])));
658
+ if (out.length && out[out.length - 1] === ".") out[out.length - 1] = "\u0964";
659
+ let s = "", pos = 0; spans.forEach(([a, b], i) => { s += text.slice(pos, a) + out[i]; pos = b; }); return s + text.slice(pos);
660
+ }
661
+
662
+ /**
663
+ * Full pipeline: tokenise -> language ID -> policy (keep / acronym / loanword / glossary) -> contextual decoding.
664
+ * @param {string} text
665
+ * @param {{mode?: 'conversational'|'natural'|'strict', k?: number, context?: boolean, overrides?: Object<number,string>}} opt
666
+ * context: false = word-by-word (no sentence LM)
667
+ * overrides: {tokenStartOffset: chosenDevanagari} pins a user-picked suggestion (re-decodes the rest in context)
668
+ * @returns {{output: string, tokens: Array<{text,start,end,output,lang,source,candidates:Array<{text,score}>}>, sentence: string|null, ms: number}}
669
+ */
670
+ transliterate(text, { mode = "conversational", k = 5, overrides = {}, context = true } = {}) {
671
+ const t0 = now(); const policy = mode === "strict" ? "strict" : "conversational";
672
+ const spans = [...text.matchAll(TOKEN_RE)].map((m) => [m.index, m.index + m[0].length, m[0]]);
673
+ const toks = spans.map((s) => s[2]);
674
+ const tags = this.lid ? this.lid.tag(toks) : toks.map((t) => (RE_DEVA.test(t) ? "DEV" : RE_LATIN_APOS.test(t) ? (this.loan.has(t.toLowerCase()) ? "EN" : "HI") : "OTHER"));
675
+ const slots = [], src = []; let prevBrand = false;
676
+ for (let i = 0; i < toks.length; i++) {
677
+ const t = toks[i], tag = tags[i], nxt = toks[i + 1] || "", low = t.toLowerCase(), ov = overrides[spans[i][0]];
678
+ if (ov !== undefined) { slots.push(["fix", ov]); src.push("user"); prevBrand = false; continue; }
679
+ if (!RE_LATIN_APOS.test(t) || KEEP_RE.test(t) || nxt === "(") {
680
+ if (t === "." && (i === toks.length - 1 || /^[A-Z]/.test(toks[i + 1] || ""))) slots.push(["fix", "\u0964"]); else slots.push(["fix", t]);
681
+ src.push(RE_DEVA.test(t) ? "devanagari" : KEEP_RE.test(t) ? "keep" : "punct"); prevBrand = false; continue;
682
+ }
683
+ const isBrand = BRANDS.has(low) || (prevBrand && /^[A-Z]/.test(t)); prevBrand = isBrand;
684
+ if (policy === "strict" && isBrand) { slots.push(["fix", t]); src.push("brand"); continue; }
685
+ if (mode === "natural" && tag === "EN" && this.natural.has(low)) { slots.push(["fix", this.natural.get(low)]); src.push("glossary"); continue; }
686
+ if (isUpper(t) && t.length >= 2 && t.length <= 5 && !this.loan.has(low)) { slots.push(["fix", [...low].map((c) => LETTERS_H[c]).join("")]); src.push("acronym"); continue; }
687
+ if ((tag === "EN" || isBrand) && this.loan.has(low)) { slots.push(["fix", this.loan.get(low)]); src.push("loanword"); continue; }
688
+ slots.push(["x", t]); src.push("model");
689
+ }
690
+ const { out, lat } = this.decodeSlots(slots, context);
691
+ let output = "", pos = 0; spans.forEach(([a, b], i) => { output += text.slice(pos, a) + out[i]; pos = b; }); output = (output + text.slice(pos)).trim();
692
+ const tokens = spans.map(([a, b, t], i) => {
693
+ let cands = [];
694
+ if (slots[i][0] === "x") {
695
+ const mx = lat[i][0] ? lat[i][0][1] : 0;
696
+ cands = lat[i].slice(0, k).map(([s, v]) => ({ text: s, score: Math.exp(v - mx) }));
697
+ } else if (RE_LATIN_APOS.test(t) && !KEEP_RE.test(t)) {
698
+ cands = [{ text: out[i], score: 1 }];
699
+ for (const c of this.word(t, k)) if (c.text !== out[i]) cands.push({ text: c.text, score: null });
700
+ if (out[i] !== t) cands.push({ text: t, score: null });
701
+ cands = cands.slice(0, k);
702
+ }
703
+ const isW = RE_LATIN_APOS.test(t) || RE_DEVA.test(t);
704
+ return { text: t, start: a, end: b, output: out[i], lang: isW ? tags[i] : "OTHER", source: src[i], candidates: cands };
705
+ });
706
+ let sentence = null; if (this.lid && toks.length) try { sentence = this.lid.mode(toks); } catch { sentence = null; }
707
+ return { output, tokens, sentence, ms: now() - t0 };
708
+ }
709
+ }
710
+
711
+ /** Node helper: load a bundle from a local directory. */
712
+ export async function loadFromDir(dir, opts = {}) {
713
+ const fs = await import("node:fs/promises"), path = await import("node:path");
714
+ const f = async (u) => { try { const b = await fs.readFile(path.resolve(u)); return new Response(b); } catch { return new Response(null, { status: 404 }); } };
715
+ return BhashaCore.load(dir, { fetch: f, cache: false, ...opts });
716
+ }
717
+ export { BhashaCore as AksharaLP };
718
+ export default BhashaCore;
js/app.js ADDED
@@ -0,0 +1,407 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*! AksharaLP Space UI — AksharaLP by Sourojit Dhua — https://huggingface.co/sourojitdotcom/AksharaLP-1M · Copyright 2026 Sourojit Dhua · Apache-2.0 (attribution required, see NOTICE) */
2
+ // runtime (aksharalp.mjs) size, stamped by the release build so the sizes shown match the model card (0 in a dev checkout)
3
+ const RUNTIME_BYTES = Number("51518") || 0, RUNTIME_GZ = Number("17259") || 0;
4
+ // All inference runs in ./worker.js; this file only renders.
5
+
6
+ /* Reference sizes of a widely used open model, measured by us (see model card for method). */
7
+ const REF = [
8
+ { name: "IndicXlit", sub: "model weights only (fp32)", bytes: 46043828 },
9
+ { name: "IndicXlit + dictionary", sub: "official package with rescoring dictionary", bytes: 46043828 + 358e6 },
10
+ ];
11
+
12
+ const EXAMPLES = [
13
+ { tag: "Everyday", text: "aap kaise ho? sab theek hai na" },
14
+ { tag: "Context: main → मैं / में", text: "main theek hoon, tum ghar main ho?" },
15
+ { tag: "English words, Hindi script", text: "kal subah meeting hai, office jaldi aana" },
16
+ { tag: "Acronyms & brands", text: "SBI ka statement whatsapp pe bhej do" },
17
+ { tag: "Strict (brands in Latin)", text: "whatsapp aur paytm dono kaam nahi kar rahe", mode: "strict" },
18
+ ];
19
+
20
+ const FEATURES = [
21
+ { ic: "◆", title: "Extremely small", body: "A sub-megabyte core that works on its own, plus an optional dictionary pack (lexicon + sentence language model) fetched once and cached.", size: true },
22
+ { ic: "↹", title: "Understands context", body: "A compact bigram LM resolves ambiguous spellings from the words around them — the same “main” becomes मैं or में.", demos: ["main theek hoon", "ghar main kaun hai"] },
23
+ { ic: "≋", title: "English words, phonetically", body: "English words typed inside Hindi sentences are written phonetically in Devanagari.", demos: ["meeting office mein hai", "whatsapp pe message karo"] },
24
+ { ic: "Aa", title: "Acronyms & English", body: "Bank names, initialisms and English words are spelled letter-by-letter or phonetically.", demos: ["SBI ka ATM", "PNR status check karo"] },
25
+ { ic: "☰", title: "Top-k suggestions", body: "Every word comes with ranked alternatives — perfect for keyboard suggestion bars.", chips: "kaise" },
26
+ { ic: "◐", title: "Core first, dictionary later", body: "Typing starts as soon as the small core has loaded; the dictionary pack attaches in the background. If it cannot be fetched, the core keeps working on its own.", dict: true },
27
+ ];
28
+
29
+ const SENT = { romanized_hindi: ["Hindi (romanized)", "var(--hi)"], english: ["English", "var(--en)"], devanagari: ["Devanagari", "var(--dev)"], mixed_script: ["Mixed script", "var(--dev)"] };
30
+ const SRC = { model: "model", loanword: "loanword", acronym: "acronym", brand: "kept (brand)", glossary: "natural Hindi", keep: "kept", user: "your pick", devanagari: "Devanagari input" };
31
+
32
+ const $ = (s) => document.querySelector(s);
33
+ const el = (tag, cls, txt) => { const e = document.createElement(tag); if (cls) e.className = cls; if (txt != null) e.textContent = txt; return e; };
34
+ const fmtMB = (b) => (b >= 100e6 ? Math.round(b / 1e6) + " MB" : b >= 10e6 ? (b / 1e6).toFixed(1) + " MB" : (b / 1e6).toFixed(2) + " MB");
35
+ const fmtParams = (n) => (n >= 1e6 ? (n / 1e6).toFixed(2) + " M" : Math.round(n / 1e3) + " K");
36
+ const fmtWords = (n) => (n >= 1e6 ? (n / 1e6).toFixed(1) + "M" : Math.round(n / 1e3) + "K") + " words";
37
+
38
+ /* ---------------------------------------------------------------- worker bridge */
39
+ const wurl = new URL("./worker.js", import.meta.url); if (new URLSearchParams(location.search).get("dict") === "0") wurl.search = "?dict=0";
40
+ const worker = new Worker(wurl, { type: "module" });
41
+ let seq = 0; const pending = new Map();
42
+ const call = (msg) => new Promise((res, rej) => { const id = ++seq; pending.set(id, { res, rej }); worker.postMessage({ ...msg, id }); });
43
+
44
+ const state = { dict: null, dictDone: false, mode: "conversational", ctx: true, text: "", overrides: [], res: null, sel: null, lat: [], ready: false, info: null, busy: false, again: false, prevKeys: [] };
45
+ const input = $("#input"), output = $("#output"), chips = $("#chips");
46
+
47
+ worker.onmessage = ({ data }) => {
48
+ if (data.type === "progress") $("#loader-text").textContent = "Loaded " + data.file;
49
+ else if (data.type === "ready") onReady(data);
50
+ else if (data.type === "dict") onDict(data);
51
+ else if (data.type === "error" && !data.id) { $("#status-text").textContent = "Model failed to load"; $("#loader-text").textContent = data.message; console.error(data.message); }
52
+ else if (pending.has(data.id)) { const p = pending.get(data.id); pending.delete(data.id); data.type === "error" ? p.rej(new Error(data.message)) : p.res(data); }
53
+ };
54
+
55
+ /* ---------------------------------------------------------------- live transliteration */
56
+ function opts() {
57
+ const ov = {}; for (const o of state.overrides) ov[o.start] = o.pick;
58
+ return { mode: state.mode, context: state.ctx, k: 6, overrides: ov };
59
+ }
60
+ async function run() {
61
+ if (!state.ready) return;
62
+ if (state.busy) { state.again = true; return; }
63
+ state.busy = true;
64
+ const text = input.value;
65
+ try {
66
+ const { res } = await call({ type: "run", text, opts: opts() });
67
+ if (text === input.value || !state.again) render(res, text);
68
+ if (text.trim()) { state.lat.push(res.ms); if (state.lat.length > 50) state.lat.shift(); updateLatency(res.ms); updateCapLatency(); }
69
+ } catch (e) { console.error(e); }
70
+ state.busy = false;
71
+ if (state.again) { state.again = false; run(); }
72
+ }
73
+
74
+ function shiftOverrides(oldT, newT) {
75
+ let p = 0; const n = Math.min(oldT.length, newT.length);
76
+ while (p < n && oldT[p] === newT[p]) p++;
77
+ let s = 0; while (s < n - p && oldT[oldT.length - 1 - s] === newT[newT.length - 1 - s]) s++;
78
+ const oEnd = oldT.length - s, d = newT.length - oldT.length;
79
+ state.overrides = state.overrides.flatMap((o) => (o.end < p ? [o] : o.start > oEnd ? [{ ...o, start: o.start + d, end: o.end + d }] : []));
80
+ }
81
+
82
+ function render(res, text) {
83
+ state.res = res;
84
+ const out = output; out.textContent = "";
85
+ $("#copy").disabled = !res.output;
86
+ if (!res.tokens.length) { out.append(el("span", "ph", "आपका देवनागरी पाठ यहाँ दिखेगा")); state.prevKeys = []; renderChips(); setSentence(null); return; }
87
+ let pos = 0; const keys = []; let prevW = null; const tags = false;
88
+ res.tokens.forEach((t, i) => {
89
+ const gap = text.slice(pos, t.start);
90
+ const key = t.text + "|" + t.output; keys.push(key);
91
+ const isNew = state.prevKeys[i] !== key;
92
+ if (t.lang === "OTHER" && !t.candidates.length) {
93
+ // punctuation: glue to the previous word so it never wraps onto its own line
94
+ if (prevW && !gap) { prevW.querySelector(".row").append(el("span", "pn", t.output)); pos = t.end; return; }
95
+ if (gap) out.append(document.createTextNode(gap)); out.append(el("span", "pn", t.output)); pos = t.end; prevW = null; return;
96
+ }
97
+ if (gap) out.append(document.createTextNode(gap));
98
+ pos = t.end;
99
+ const w = el("span", `w lang-${t.lang}` + (isNew ? " new" : "") + (t.source === "user" ? " pinned" : "") + (state.sel === t.start ? " sel" : ""));
100
+ const row = el("span", "row"); row.append(el("span", "wt", t.output)); w.append(row);
101
+ w.dataset.start = t.start; w.title = `${t.text} · ${SRC[t.source] || t.source}`;
102
+ if (tags) w.append(el("span", "tg tg-" + t.lang, t.lang));
103
+ w.addEventListener("click", () => { state.sel = t.start; render(state.res, input.value); });
104
+ out.append(w); prevW = w;
105
+ });
106
+ if (pos < text.length) out.append(document.createTextNode(text.slice(pos)));
107
+ state.prevKeys = keys;
108
+ renderChips();
109
+ }
110
+
111
+ function activeToken() {
112
+ const r = state.res; if (!r || !r.tokens.length) return null;
113
+ const words = r.tokens.filter((t) => t.candidates.length);
114
+ if (state.sel != null) { const t = words.find((x) => x.start === state.sel); if (t) return t; }
115
+ const c = input.selectionStart ?? input.value.length;
116
+ return words.find((t) => t.start <= c && c <= t.end) || [...words].reverse().find((t) => t.end <= c) || words[words.length - 1] || null;
117
+ }
118
+
119
+ function renderChips() {
120
+ const t = activeToken(); chips.textContent = "";
121
+ if (!t) { chips.append(el("span", "hint", "Click any word in the output — or just keep typing — to see ranked alternatives.")); return; }
122
+ const src = el("span", "src", t.text + " →"); chips.append(src);
123
+ const pinned = state.overrides.find((o) => o.start === t.start);
124
+ t.candidates.forEach((c, i) => {
125
+ const latin = /^[A-Za-z']+$/.test(c.text);
126
+ const b = el("button", "chip" + (c.text === t.output ? " on" : "") + (latin ? " latin" : ""), c.text);
127
+ b.style.animationDelay = i * 35 + "ms";
128
+ if (c.score != null) { const p = el("span", "p"); p.style.width = Math.max(6, Math.round(c.score * 100)) + "%"; b.append(p); }
129
+ b.title = c.score != null ? `relative probability ${(c.score * 100).toFixed(0)}%` : "alternative";
130
+ b.addEventListener("click", () => pick(t, c.text));
131
+ chips.append(b);
132
+ });
133
+ if (pinned) { const r = el("button", "ghost-btn reset", "↺ Auto"); r.addEventListener("click", () => { state.overrides = state.overrides.filter((o) => o !== pinned); run(); }); chips.append(r); }
134
+ const meta = el("span", "meta", SRC[t.source] || t.source); chips.append(meta);
135
+ }
136
+
137
+ function pick(t, text) {
138
+ state.overrides = state.overrides.filter((o) => o.start !== t.start);
139
+ state.overrides.push({ start: t.start, end: t.end, word: t.text, pick: text });
140
+ state.sel = t.start; run();
141
+ }
142
+
143
+ function setSentence(s) {
144
+ const b = $("#sent-lang");
145
+ if (!s || !SENT[s]) { b.hidden = true; return; }
146
+ const [label, c] = SENT[s]; if (b.textContent !== label) { b.hidden = false; b.textContent = label; b.style.setProperty("--c", c); b.style.animation = "none"; void b.offsetWidth; b.style.animation = ""; }
147
+ }
148
+
149
+ function updateLatency(ms) {
150
+ const a = [...state.lat].sort((x, y) => x - y), p50 = a[Math.floor(a.length / 2)];
151
+ $("#m-lat").innerHTML = `${ms < 10 ? ms.toFixed(1) : Math.round(ms)}<small>ms</small> <small>p50 ${p50 < 10 ? p50.toFixed(1) : Math.round(p50)}</small>`;
152
+ }
153
+
154
+ /* ---------------------------------------------------------------- ready: stats, gallery, features */
155
+ function showSizes(info) {
156
+ const sz = (state.dictDone && info.dict ? info.gz + info.dict.gz : info.gz || info.bytes) + RUNTIME_GZ;
157
+ $("#hero-size").textContent = fmtMB(sz);
158
+ const short = (sz / 1e6).toFixed(1) + " MB"; $("#hero-size-short").textContent = short; $("#stat-size").textContent = short;
159
+ $("#m-size").innerHTML = `${fmtMB(sz).replace(" MB", "<small>MB</small>")}`;
160
+ $("#m-params").innerHTML = fmtParams(info.params).replace(" M", "<small>M</small>").replace(" K", "<small>K</small>");
161
+ }
162
+ function setDictStatus(txt, cls) { const d = $("#m-lex"); d.textContent = txt; d.className = "v dict-" + cls; }
163
+ async function onReady({ info, loadMs, name }) {
164
+ if (name && /^AksharaLP/.test(name)) $(".brand-tag").textContent = name.replace(/^AksharaLP-?/, "") + " · Hindi";
165
+ state.ready = true; state.info = info;
166
+ $("#status").classList.add("ready"); $("#status-text").textContent = "Core running on-device";
167
+ $("#loader-text").textContent = "Core ready — fetching the dictionary pack…";
168
+ input.disabled = false; $("#copy").disabled = true;
169
+ showSizes(info); setDictStatus("loading…", "loading");
170
+ $("#m-lat").innerHTML = `${Math.round(loadMs)}<small>ms load</small>`;
171
+ renderSizes(info);
172
+ if (input.value) run();
173
+ }
174
+ async function onDict({ ok, info, from, error, skipped }) {
175
+ state.info = info; state.dictDone = true; state.dict = ok ? info.dict : null;
176
+ $("#loader").classList.add("gone");
177
+ if (ok) { setDictStatus(fmtWords(info.dict.words) + (from === "cache" ? " · cached" : ""), "ok"); $("#status-text").textContent = "Running on-device"; }
178
+ else { setDictStatus("core only", "off"); $("#status-text").textContent = "Core only (no dictionary)"; if (error) console.warn("dictionary pack not loaded:", error); }
179
+ showSizes(info); renderSizes(info);
180
+ await fillExamples(); await fillCaps(); await fillFeatures();
181
+ if ("serviceWorker" in navigator && location.protocol !== "file:" && !navigator.webdriver) navigator.serviceWorker.register("./sw.js").then(() => { $("#status-text").textContent = (ok ? "On-device" : "Core only") + " · works offline"; }).catch(() => {});
182
+ if (input.value) run();
183
+ document.dispatchEvent(new CustomEvent("bhc-ready"));
184
+ }
185
+
186
+ async function batch(items) { const { out } = await call({ type: "batch", items }); return out; }
187
+
188
+ function buildExamples() {
189
+ const g = $("#examples");
190
+ EXAMPLES.forEach((ex, i) => {
191
+ const b = el("button", "ex"); b.append(el("span", "tag", ex.tag), el("span", "ro", ex.text));
192
+ const dv = el("span", "dv"); dv.append(el("span", "shimmer")); b.append(dv); b.dataset.i = i;
193
+ b.addEventListener("click", () => typeIn(ex));
194
+ g.append(b);
195
+ });
196
+ }
197
+ async function fillExamples() {
198
+ const out = await batch(EXAMPLES.map((e) => ({ text: e.text, opts: { mode: e.mode || "conversational" } })));
199
+ document.querySelectorAll(".ex .dv").forEach((d, i) => { d.textContent = out[i].output; d.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 500 }); });
200
+ }
201
+
202
+ function buildFeatures() {
203
+ const g = $("#features");
204
+ FEATURES.forEach((f) => {
205
+ const c = el("div", "feat"); c.append(el("div", "ic", f.ic), el("h3", "", f.title), el("p", "", f.body));
206
+ const slot = el("div", "fslot"); slot.append(el("div", "demo-line", "")); slot.firstChild.append(el("span", "shimmer"));
207
+ c.append(slot); g.append(c); f.slot = slot;
208
+ });
209
+ }
210
+ async function fillFeatures() {
211
+ for (const f of FEATURES) {
212
+ const s = f.slot; s.textContent = "";
213
+ if (f.size) {
214
+ const d = el("div", "demo-line big");
215
+ d.append(el("span", "d grad", fmtMB(state.info.gz)), el("span", "r", `core · ${fmtParams(state.info.params)} parameters`)); s.append(d);
216
+ if (state.info.dict) { const e = el("div", "demo-line"); e.append(el("span", "d", "+ " + fmtMB(state.info.dict.gz)), el("span", "r", `dictionary pack · ${fmtWords(state.info.dict.words)}`)); s.append(e); }
217
+ } else if (f.demos) {
218
+ const out = await batch(f.demos.map((t) => ({ text: t, opts: { mode: "conversational" } })));
219
+ f.demos.forEach((t, i) => { const d = el("div", "demo-line"); d.append(el("span", "r", t), el("span", "arrow", "→"), el("span", "d", out[i].output)); s.append(d); });
220
+ } else if (f.chips) {
221
+ const [r] = await batch([{ text: f.chips, opts: { k: 5 } }]);
222
+ const d = el("div", "demo-line"); d.append(el("span", "r", f.chips + " →"));
223
+ r.tokens[0].candidates.forEach((c, i) => d.append(el("span", "mini-chip" + (i === 0 ? " on" : ""), c.text))); s.append(d);
224
+ } else if (f.dict) {
225
+ const d = el("div", "demo-line"); const st = state.dict ? `Dictionary attached · ${fmtWords(state.dict.words)}` : "Dictionary not loaded · running core-only";
226
+ d.append(el("span", "r", st)); s.append(d);
227
+ }
228
+ }
229
+ }
230
+
231
+ function renderSizes(info) {
232
+ const box = $("#sizes"); box.textContent = "";
233
+ const core = info.bytes + RUNTIME_BYTES, ours = core + (info.dict ? info.dict.bytes : 0);
234
+ const rows = [{ name: "AksharaLP core", sub: "model + reranker + runtime, works on its own", bytes: core, us: true }];
235
+ if (info.dict) rows.push({ name: "AksharaLP core + dictionary", sub: `adds the ${fmtWords(info.dict.words)} lexicon and sentence LM`, bytes: ours, us: true });
236
+ rows.push(...REF);
237
+ const max = Math.max(...rows.map((r) => r.bytes));
238
+ rows.forEach((r) => {
239
+ const row = el("div", "srow" + (r.us ? " us" : "")); const nm = el("div", "nm", r.name); nm.append(el("small", "", r.sub));
240
+ const bar = el("div", "sbar"); const sp = el("span"); bar.append(sp);
241
+ row.append(nm, bar, el("div", "val", fmtMB(r.bytes))); box.append(row);
242
+ requestAnimationFrame(() => requestAnimationFrame(() => { sp.style.width = (100 * Math.sqrt(r.bytes / max)).toFixed(2) + "%"; }));
243
+ });
244
+ const x = Math.round(REF[0].bytes / ours);
245
+ box.append(el("div", "note", `Uncompressed on-disk sizes. AksharaLP is ~${x}× smaller than IndicXlit’s model weights alone. Bars use a square-root scale so the smallest bar stays visible.`));
246
+ }
247
+
248
+
249
+ /* On-device capabilities. Every Devanagari string shown in these cards is computed live by the worker. */
250
+ const IC = {
251
+ offline: '<path class="f" d="M21 16v-2l-8-5V3.5a1.5 1.5 0 0 0-3 0V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5z"/>',
252
+ private: '<rect x="5" y="11" width="14" height="10" rx="2.5"/><path d="M8 11V7.5a4 4 0 0 1 8 0V11M12 15v2.5"/>',
253
+ anywhere: '<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.6 2.6 3.8 5.6 3.8 9s-1.2 6.4-3.8 9M12 3C9.4 5.6 8.2 8.6 8.2 12s1.2 6.4 3.8 9"/>',
254
+ realtime: '<path class="f" d="M13 2 4.5 13.5H11L10 22l8.5-11.5H12z"/>',
255
+ ime: '<rect x="2.5" y="6" width="19" height="12" rx="2.5"/><path d="M6.5 10h.01M10 10h.01M13.5 10h.01M17 10h.01M7.5 14h9"/>',
256
+ brands: '<path d="M20.5 12a8.5 8.5 0 0 1-12.3 7.6L3.5 21l1.3-4.4A8.5 8.5 0 1 1 20.5 12z"/><path d="M8.5 10.5h7M8.5 13.5h4.5"/>',
257
+ topk: '<path d="M9 6h11M9 12h11M9 18h11"/><circle class="f" cx="4.5" cy="6" r="1.3"/><circle class="f" cx="4.5" cy="12" r="1.3"/><circle class="f" cx="4.5" cy="18" r="1.3"/>',
258
+ bharat: '<path class="bar1" d="M5 20v-3"/><path class="bar2" d="M10 20v-6.5"/><path class="bar3" d="M15 20V10"/><path class="bar4" d="M20 20V5.5"/>',
259
+ scale: '<path d="M7 4.5h11M7 9h11M7 4.5h3.2a4.5 4.5 0 0 1 0 9H7l8.5 7"/>',
260
+ };
261
+ const CAPS = [
262
+ { key: "offline", title: "Fully offline", body: "After the first visit nothing is fetched. Switch on airplane mode and keep typing — on a flight, in a lift, on a train.", demos: ["flight mein hoon, land karke call karta hoon"], net: true },
263
+ { key: "private", title: "Private by design", body: "Text never leaves the device: no API call, no logging server. Built for banking, health and personal chat.", demos: ["mera account balance kitna hai", "doctor ne din mein do baar dawai lene ko kaha"] },
264
+ { key: "anywhere", title: "Runs wherever JavaScript runs", body: "A browser tab, an Android or iOS WebView, an installable PWA, or Node.js. CPU only — no GPU, no server, no inference bill.", pills: ["Browser tab", "WebView", "PWA", "Node.js"], demos: ["namaste, aapka swagat hai"] },
265
+ { key: "realtime", title: "Real-time, as you type", body: "The sentence is re-decoded on every keystroke in a Web Worker, so earlier words are corrected as context arrives.", steps: ["main", "main theek", "main theek hoon"] },
266
+ { key: "ime", title: "Keyboard & input-box integration", body: "Wire it to any input or textarea: chat, search, forms, CRM notes, customer-support replies.", pills: ["Chat", "Search", "Forms", "CRM", "Support"], ime: "aapka refund teen din mein aa jayega" },
267
+ { key: "brands", title: "Brands your way", body: "Default writes everything in Devanagari; Strict keeps well-known brand and product names in Latin script.", mix: "kal whatsapp pe meeting hai" },
268
+ { key: "topk", title: "Top-k suggestions", body: "Ranked candidates for every word, ready for a suggestion bar or candidate picker. A pick re-decodes the rest of the sentence.", chips: "main" },
269
+ { key: "bharat", title: "Built for Bharat", body: "", size: true, demos: ["signal nahi aa raha, baad mein baat karte hain"] },
270
+ { key: "scale", title: "₹0 per call at scale", body: "Inference runs on your users’ devices: no per-request API fees, no GPU servers to scale — just static files on any CDN. Apache-2.0, free for commercial use.", demos: ["mera order kab tak deliver hoga"] },
271
+ ];
272
+ const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches || navigator.webdriver;
273
+ const io = "IntersectionObserver" in window && !reduced ? new IntersectionObserver((es) => es.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }), { threshold: 0.15 }) : null;
274
+ const reveal = (n, i = 0) => { n.classList.add("rv"); n.style.setProperty("--d", (i % 3) * 90 + "ms"); if (io) io.observe(n); else n.classList.add("in"); };
275
+ const icon = (k) => `<svg viewBox="0 0 24 24" aria-hidden="true">${IC[k]}</svg>`;
276
+
277
+ function buildCaps() {
278
+ const g = $("#caps");
279
+ CAPS.forEach((c, i) => {
280
+ const n = el("article", "cap cap-" + c.key); const ic = el("div", "cic"); ic.innerHTML = icon(c.key);
281
+ n.append(ic, el("h3", "", c.title), c.body ? el("p", "", c.body) : el("p", "cap-body"));
282
+ if (c.pills) { const pr = el("div", "pills"); c.pills.forEach((p) => pr.append(el("span", "pl", p))); n.append(pr); }
283
+ if (c.net) { const nb = el("div", "net"); n.append(nb); c.netEl = nb; }
284
+ const slot = el("div", "fslot"); const sh = el("div", "demo-line"); sh.append(el("span", "shimmer")); slot.append(sh); n.append(slot); c.slot = slot;
285
+ g.append(n); reveal(n, i);
286
+ });
287
+ const net = () => CAPS.forEach((c) => { if (!c.netEl) return; const on = navigator.onLine; c.netEl.className = "net " + (on ? "on" : "off");
288
+ c.netEl.textContent = on ? "Network: online — try airplane mode, then type above" : "Network: offline — and still transliterating"; });
289
+ addEventListener("online", net); addEventListener("offline", net); net();
290
+ }
291
+ const line = (t, o) => { const d = el("div", "demo-line"); d.append(el("span", "r", t), el("span", "arrow", "→"), el("span", "d", o)); return d; };
292
+ async function fillCaps() {
293
+ for (const c of CAPS) {
294
+ const s = c.slot;
295
+ if (c.size) s.parentNode.querySelector(".cap-body").textContent = `A ${fmtMB(state.info.gz + (state.info.dict ? state.info.dict.gz : 0))} one-time download that caches (${fmtMB(state.info.gz)} core${state.info.dict ? " + " + fmtMB(state.info.dict.gz) + " dictionary" : ""}); after that, patchy or missing connectivity does not matter. Pure JavaScript on the CPU, designed with entry-level Android phones in mind.`;
296
+ if (c.demos) { const out = await batch(c.demos.map((t) => ({ text: t, opts: { mode: "conversational" } }))); s.textContent = ""; c.demos.forEach((t, i) => s.append(line(t, out[i].output))); }
297
+ else if (c.steps) {
298
+ const out = await batch(c.steps.map((t) => ({ text: t, opts: { mode: "conversational" } }))); s.textContent = "";
299
+ const box = el("div", "steps-live"); c.steps.forEach((t, i) => { const r = line(t, out[i].output); r.classList.add("st"); r.style.setProperty("--i", i); box.append(r); });
300
+ const lat = el("div", "lat-live"); lat.id = "cap-lat"; lat.textContent = "Type in the playground to measure latency in this tab";
301
+ s.append(box, lat); updateCapLatency();
302
+ } else if (c.ime) {
303
+ const words = c.ime.split(" "), prefixes = words.map((_, i) => words.slice(0, i + 1).join(" "));
304
+ const out = await batch(prefixes.map((t) => ({ text: t, opts: { mode: "conversational" } }))); s.textContent = "";
305
+ const box = el("div", "ime"); const top = el("div", "ime-in"); const caret = el("span", "caret"); const typed = el("span", "ime-r");
306
+ top.append(typed, caret); const bot = el("div", "ime-out"); box.append(el("span", "ime-lbl", "Support reply"), top, bot); s.append(box);
307
+ let k = reduced ? prefixes.length - 1 : 0;
308
+ const tick = () => { typed.textContent = prefixes[k]; bot.textContent = out[k].output; bot.animate && !reduced && bot.animate([{ opacity: .55 }, { opacity: 1 }], { duration: 260 }); };
309
+ tick(); if (!reduced) setInterval(() => { k = (k + 1) % (prefixes.length + 2); if (k < prefixes.length) tick(); }, 650);
310
+ } else if (c.mix) {
311
+ const modes = [["conversational", "Default"], ["strict", "Strict"]];
312
+ const out = await batch(modes.map(([m]) => ({ text: c.mix, opts: { mode: m } }))); s.textContent = "";
313
+ s.append(line(c.mix, ""));
314
+ modes.forEach(([, lab], i) => { const r = el("div", "demo-line mode-row"); r.append(el("span", "mlab", lab), el("span", "d", out[i].output)); s.append(r); });
315
+ } else if (c.chips) {
316
+ const [r] = await batch([{ text: c.chips, opts: { k: 5 } }]); s.textContent = "";
317
+ const d = el("div", "demo-line"); d.append(el("span", "r", c.chips + " →"));
318
+ r.tokens[0].candidates.forEach((x, i) => { const m = el("span", "mini-chip" + (i === 0 ? " on" : ""), x.text); m.style.animationDelay = i * 60 + "ms"; d.append(m); }); s.append(d);
319
+ }
320
+ }
321
+ }
322
+ function updateCapLatency() {
323
+ const e = document.getElementById("cap-lat"); if (!e || !state.lat.length) return;
324
+ const a = [...state.lat].sort((x, y) => x - y), p50 = a[Math.floor(a.length / 2)];
325
+ e.innerHTML = `Measured in this tab: <b>${p50 < 10 ? p50.toFixed(1) : Math.round(p50)} ms</b> per update (p50 of ${a.length})`;
326
+ }
327
+
328
+ /* "Best in class for its size": tiles come only from claims that passed in build_release.py (see build_report.json). */
329
+ function buildBest() {
330
+ let C; try { C = JSON.parse($("#claims-data").textContent); } catch { return; }
331
+ if (!C || !C.tiles || !C.tiles.length) return;
332
+ $("#best").hidden = false; $("#best-h").textContent = C.heading;
333
+ const g = $("#tiles");
334
+ C.tiles.forEach((t, i) => {
335
+ const n = el("div", "tile"); const v = el("div", "tv"); const num = el("span", "num", fmtNum(t.value, t.dec)); v.append(num, el("span", "tu", t.unit));
336
+ n.append(v, el("div", "tl", t.label), el("div", "ts", t.sub)); g.append(n); reveal(n, i);
337
+ const go = () => countUp(num, t.value, t.dec);
338
+ if (io) new IntersectionObserver((es, o) => { if (es[0].isIntersecting) { go(); o.disconnect(); } }, { threshold: 0.4 }).observe(n);
339
+ });
340
+ if (C.board && C.board.length) {
341
+ const b = $("#board"); b.hidden = false; b.append(el("div", "bh", "Accuracy per MB · top-1 on Aksharantar Hindi test ÷ on-disk size"));
342
+ const max = C.board[0].per_mb;
343
+ C.board.forEach((r, i) => { const row = el("div", "brow" + (r.us ? " us" : "")); const bar = el("div", "sbar"); const sp = el("span"); bar.append(sp);
344
+ row.append(el("span", "rk", "#" + (i + 1)), el("span", "nm", r.name), bar, el("span", "val", r.per_mb.toFixed(2) + " pts/MB")); b.append(row);
345
+ requestAnimationFrame(() => requestAnimationFrame(() => { sp.style.width = Math.max(1.5, 100 * Math.sqrt(r.per_mb / max)).toFixed(2) + "%"; })); });
346
+ }
347
+ if (C.highlight) { const h = $("#best-hl"); h.hidden = false; h.textContent = "🏆 " + C.highlight; }
348
+ }
349
+ const fmtNum = (v, dec) => v.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
350
+ function countUp(node, to, dec) {
351
+ if (reduced) return; const t0 = performance.now(), dur = 1100;
352
+ const step = (t) => { const p = Math.min(1, (t - t0) / dur), e = 1 - Math.pow(1 - p, 3); node.textContent = fmtNum(to * e, dec); if (p < 1) requestAnimationFrame(step); };
353
+ requestAnimationFrame(step);
354
+ }
355
+
356
+ /* ---------------------------------------------------------------- interactions */
357
+ let lastText = "";
358
+ input.addEventListener("input", () => { shiftOverrides(lastText, input.value); lastText = input.value; state.sel = null; autosize(); run(); });
359
+ input.addEventListener("keyup", (e) => { if (e.key.startsWith("Arrow") || e.key === "Home" || e.key === "End") { state.sel = null; renderChips(); } });
360
+ input.addEventListener("click", () => { state.sel = null; renderChips(); });
361
+ function autosize() { input.style.height = "auto"; input.style.height = Math.max(input.scrollHeight, 120) + "px"; }
362
+
363
+ const seg = $("#modes"), glider = seg.querySelector(".seg-glider");
364
+ function placeGlider() { const b = seg.querySelector('[aria-checked="true"]'); glider.style.width = b.offsetWidth + "px"; glider.style.transform = `translateX(${b.offsetLeft - 4}px)`; }
365
+ function setMode(m) { state.mode = m; seg.querySelectorAll("button").forEach((b) => b.setAttribute("aria-checked", String(b.dataset.mode === m))); placeGlider(); }
366
+ seg.querySelectorAll("button").forEach((b) => b.addEventListener("click", () => { setMode(b.dataset.mode); run(); }));
367
+ window.addEventListener("resize", placeGlider); document.fonts && document.fonts.ready.then(placeGlider); placeGlider();
368
+ $("#ctx").addEventListener("change", (e) => { state.ctx = e.target.checked; run(); });
369
+ $("#clear").addEventListener("click", () => { input.value = ""; lastText = ""; state.overrides = []; state.sel = null; autosize(); run(); input.focus(); });
370
+ $("#copy").addEventListener("click", async () => {
371
+ const txt = state.res ? state.res.output : ""; if (!txt) return;
372
+ try { await navigator.clipboard.writeText(txt); } catch { const t = el("textarea"); t.value = txt; document.body.append(t); t.select(); document.execCommand("copy"); t.remove(); }
373
+ const b = $("#copy"); b.classList.add("done"); b.querySelector("span").textContent = "Copied"; toast("Copied Devanagari to clipboard");
374
+ setTimeout(() => { b.classList.remove("done"); b.querySelector("span").textContent = "Copy"; }, 1600);
375
+ });
376
+ function toast(msg) { const t = $("#toast"); t.textContent = msg; t.classList.add("on"); clearTimeout(toast.h); toast.h = setTimeout(() => t.classList.remove("on"), 1800); }
377
+
378
+ document.querySelectorAll(".attr-copy").forEach((b) => b.addEventListener("click", async () => {
379
+ const txt = document.getElementById(b.dataset.copy).textContent;
380
+ try { await navigator.clipboard.writeText(txt); } catch { const t = el("textarea"); t.value = txt; document.body.append(t); t.select(); document.execCommand("copy"); t.remove(); }
381
+ b.textContent = "Copied"; b.classList.add("done"); toast("Copied to clipboard"); setTimeout(() => { b.textContent = "Copy"; b.classList.remove("done"); }, 1600);
382
+ }));
383
+
384
+ const root = document.documentElement;
385
+ $("#theme").addEventListener("click", () => {
386
+ const dark = root.dataset.theme === "dark" || (root.dataset.theme === "auto" && matchMedia("(prefers-color-scheme: dark)").matches);
387
+ root.dataset.theme = dark ? "light" : "dark"; try { localStorage.setItem("aklp-theme", root.dataset.theme); } catch {}
388
+ });
389
+
390
+ let typing = 0;
391
+ async function typeIn(ex, { speed = 32 } = {}) {
392
+ const my = ++typing; if (ex.mode) setMode(ex.mode); else setMode("conversational");
393
+ state.overrides = []; state.sel = null;
394
+ $("#playground").scrollIntoView({ behavior: "smooth", block: "center" });
395
+ input.value = ""; lastText = ""; input.focus();
396
+ for (const ch of ex.text) {
397
+ if (my !== typing) return;
398
+ input.value += ch; lastText = input.value; autosize(); run();
399
+ await new Promise((r) => setTimeout(r, speed + (ch === " " ? speed : 0)));
400
+ }
401
+ }
402
+ window.bhcTypeIn = typeIn; window.bhcState = state; window.bhcSetMode = setMode;
403
+
404
+ buildExamples(); buildCaps(); buildBest(); buildFeatures();
405
+ const q = new URLSearchParams(location.search);
406
+ if (q.get("text")) { input.value = q.get("text"); lastText = input.value; }
407
+ if (q.get("mode") && seg.querySelector(`[data-mode="${q.get("mode")}"]`)) setMode(q.get("mode"));
js/worker.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // AksharaLP Space — inference worker (keeps the UI thread at 60 fps).
2
+ // Two-tier load: the small core (model + reranker) first, so typing works immediately; then the dictionary pack
3
+ // (lexicon + sentence LM), read from the Cache API after the first visit. If the pack cannot be fetched, the engine
4
+ // keeps running core-only.
5
+ import BhashaCore from "./aksharalp.mjs";
6
+
7
+ let engine = null;
8
+ const base = new URL("../model/", self.location.href).href;
9
+ const q = new URLSearchParams(self.location.search);
10
+
11
+ (async () => {
12
+ try {
13
+ engine = await BhashaCore.load(base, { dict: false, onProgress: (file) => postMessage({ type: "progress", file }) });
14
+ const t0 = performance.now();
15
+ engine.transliterate("main ghar ja raha hoon", { k: 5 });
16
+ postMessage({ type: "ready", info: engine.info(), loadMs: engine.loadMs, warmMs: performance.now() - t0, note: engine.manifest && engine.manifest.note, name: engine.manifest && engine.manifest.name });
17
+ if (q.get("dict") === "0") { postMessage({ type: "dict", ok: false, skipped: true, info: engine.info() }); return; }
18
+ const t1 = performance.now(); let src = null;
19
+ const ok = await engine.loadDict({ onProgress: (file, from) => { src = from; postMessage({ type: "progress", file: "dictionary/" + file, from }); } });
20
+ if (ok) engine.transliterate("main ghar ja raha hoon", { k: 5 });
21
+ postMessage({ type: "dict", ok, from: src, ms: performance.now() - t1, error: engine.dictError, info: engine.info() });
22
+ } catch (e) {
23
+ postMessage({ type: "error", message: String(e && e.message || e) });
24
+ }
25
+ })();
26
+
27
+ onmessage = ({ data }) => {
28
+ if (!engine) return postMessage({ type: "error", id: data.id, message: "model not loaded" });
29
+ try {
30
+ if (data.type === "run") {
31
+ const res = engine.transliterate(data.text, data.opts || {});
32
+ postMessage({ type: "result", id: data.id, tag: data.tag, res });
33
+ } else if (data.type === "batch") {
34
+ const out = data.items.map((it) => engine.transliterate(it.text, it.opts || {}));
35
+ postMessage({ type: "batch", id: data.id, out });
36
+ }
37
+ } catch (e) {
38
+ postMessage({ type: "error", id: data.id, message: String(e && e.message || e) });
39
+ }
40
+ };
model/dict/lex.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8220fbc4c5d9ce06888182c6d8f8d53920987acf08449ccf4f7062b0158f4fa2
3
+ size 5647376
model/dict/lm.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:01090665a81a4df02113720b755f3e884d5dccf0a9e24ea413606e0d3f132f0d
3
+ size 2372806
model/dict/params.json ADDED
@@ -0,0 +1,545 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "skel": true,
3
+ "visr": true,
4
+ "acro_v": true,
5
+ "beam": 16,
6
+ "nbest": 16,
7
+ "cbeam": 12,
8
+ "model_src": "models/xlit/v2S/best.pt step 36000",
9
+ "w1": [
10
+ [
11
+ 0.17813511192798615,
12
+ 0.08036437630653381,
13
+ 0.19679796695709229,
14
+ 0.01708296313881874,
15
+ 0.03845738619565964,
16
+ 0.0380135253071785,
17
+ -0.09296119213104248,
18
+ 0.050487861037254333,
19
+ -0.08029724657535553,
20
+ 0.04498514533042908,
21
+ -0.021634582430124283,
22
+ -0.004892073571681976,
23
+ -0.002368832938373089,
24
+ -0.0911487340927124,
25
+ 0.05593618005514145,
26
+ 0.08526270091533661,
27
+ 0.12243881821632385,
28
+ 0.18907511234283447,
29
+ 0.10099691152572632,
30
+ -0.038430821150541306,
31
+ -0.11288196593523026,
32
+ -0.0007177454535849392,
33
+ 0.0035953472834080458,
34
+ 0.021397588774561882
35
+ ],
36
+ [
37
+ -0.006028763949871063,
38
+ -0.12106657773256302,
39
+ 0.042146626859903336,
40
+ -0.4276510179042816,
41
+ 0.012659783475100994,
42
+ 0.06286242604255676,
43
+ 0.22930926084518433,
44
+ 0.07123792916536331,
45
+ -0.020782090723514557,
46
+ 0.11607885360717773,
47
+ 0.14936329424381256,
48
+ 0.09462408721446991,
49
+ 0.16972823441028595,
50
+ 0.04129566624760628,
51
+ 0.14494967460632324,
52
+ -0.020984815433621407,
53
+ -0.10806243121623993,
54
+ -0.10849157720804214,
55
+ -0.19437053799629211,
56
+ -0.31409937143325806,
57
+ -0.586150050163269,
58
+ 0.0009765241993591189,
59
+ -0.04616982117295265,
60
+ 0.018640238791704178
61
+ ],
62
+ [
63
+ 0.18020284175872803,
64
+ 0.24925513565540314,
65
+ -0.3156750202178955,
66
+ 0.7410120368003845,
67
+ 0.05266919732093811,
68
+ 0.023455671966075897,
69
+ -0.10111861675977707,
70
+ -0.052379947155714035,
71
+ -0.019208308309316635,
72
+ -0.24292567372322083,
73
+ -0.32397910952568054,
74
+ -0.29089751839637756,
75
+ -0.04565871134400368,
76
+ 0.029478222131729126,
77
+ 0.06633909791707993,
78
+ -0.24604307115077972,
79
+ 0.2161150425672531,
80
+ 0.051885999739170074,
81
+ 0.07496750354766846,
82
+ 0.24305206537246704,
83
+ 0.03907898813486099,
84
+ 0.004860813729465008,
85
+ -0.005158992484211922,
86
+ -0.009064738638699055
87
+ ],
88
+ [
89
+ 0.6659682393074036,
90
+ 0.6271504759788513,
91
+ 0.8267438411712646,
92
+ -0.0196339413523674,
93
+ -0.0030769642908126116,
94
+ 0.07327580451965332,
95
+ -0.1458965241909027,
96
+ 0.03747393563389778,
97
+ 0.021843524649739265,
98
+ -0.06475707143545151,
99
+ -0.000621478189714253,
100
+ -0.06723200529813766,
101
+ 0.05655393749475479,
102
+ 0.10602056980133057,
103
+ -0.18477147817611694,
104
+ 0.00010097643098561093,
105
+ 0.1388845443725586,
106
+ 0.6009563207626343,
107
+ 0.4833626449108124,
108
+ 0.3857249915599823,
109
+ -0.06341658532619476,
110
+ -0.006819506175816059,
111
+ -0.0049257464706897736,
112
+ 0.02972896583378315
113
+ ],
114
+ [
115
+ 0.1958463191986084,
116
+ 0.060451485216617584,
117
+ -0.1030188724398613,
118
+ 0.06920769810676575,
119
+ 0.020387552678585052,
120
+ -0.34019315242767334,
121
+ 0.37711480259895325,
122
+ 0.11028768122196198,
123
+ 0.08611816167831421,
124
+ -0.0002559171407483518,
125
+ 0.0328388549387455,
126
+ -0.008063660003244877,
127
+ 0.05640581250190735,
128
+ -0.09801948070526123,
129
+ -0.292701780796051,
130
+ 0.25763431191444397,
131
+ 0.4377913773059845,
132
+ 0.11875400692224503,
133
+ 0.07167524099349976,
134
+ -0.44701263308525085,
135
+ 0.1679624617099762,
136
+ -0.013724947348237038,
137
+ -0.002409612061455846,
138
+ 0.018655171617865562
139
+ ],
140
+ [
141
+ -0.4902501702308655,
142
+ -0.1282314509153366,
143
+ -0.33340755105018616,
144
+ 0.19906993210315704,
145
+ 0.08487821370363235,
146
+ -0.3559322953224182,
147
+ -0.3382606506347656,
148
+ -0.001005868660286069,
149
+ -0.04727927967905998,
150
+ -0.06208423525094986,
151
+ -0.04709282144904137,
152
+ 0.018225908279418945,
153
+ -0.04079766198992729,
154
+ 0.19882585108280182,
155
+ 0.11422300338745117,
156
+ -0.13318148255348206,
157
+ 0.21266044676303864,
158
+ -0.366843044757843,
159
+ -0.2040664702653885,
160
+ 0.33902281522750854,
161
+ -0.10848524421453476,
162
+ 0.00449015898630023,
163
+ 0.007850070483982563,
164
+ -0.0006993354763835669
165
+ ],
166
+ [
167
+ 0.026607975363731384,
168
+ -0.3412873446941376,
169
+ -0.10275707393884659,
170
+ 0.062105849385261536,
171
+ -0.0532095842063427,
172
+ 0.34755098819732666,
173
+ 0.12969380617141724,
174
+ -0.2929081916809082,
175
+ 0.021593520417809486,
176
+ 0.04590011015534401,
177
+ 0.2509603798389435,
178
+ -0.2497452050447464,
179
+ 0.022632254287600517,
180
+ -0.18051375448703766,
181
+ -0.10409258306026459,
182
+ 0.25614461302757263,
183
+ 0.5726763010025024,
184
+ -0.03362720087170601,
185
+ -0.27375736832618713,
186
+ 0.042891211807727814,
187
+ -0.228028804063797,
188
+ 0.011015079915523529,
189
+ -0.015549133531749249,
190
+ 0.08396020531654358
191
+ ],
192
+ [
193
+ 0.3212287425994873,
194
+ 0.0899890661239624,
195
+ 0.49252521991729736,
196
+ 0.2562199831008911,
197
+ -0.002049004193395376,
198
+ 0.0033560586161911488,
199
+ 0.1140650138258934,
200
+ -0.010557741858065128,
201
+ 0.0027649865951389074,
202
+ -0.019670777022838593,
203
+ 0.08739964663982391,
204
+ 0.013221975415945053,
205
+ 0.006536773405969143,
206
+ -0.11335998773574829,
207
+ 0.5197339653968811,
208
+ -0.0663549154996872,
209
+ -0.02756597474217415,
210
+ 0.24392786622047424,
211
+ 0.15097546577453613,
212
+ 0.14113834500312805,
213
+ 0.16664031147956848,
214
+ -0.0027184777427464724,
215
+ -0.019448233768343925,
216
+ -0.01375599391758442
217
+ ],
218
+ [
219
+ 0.07800205796957016,
220
+ 0.034818798303604126,
221
+ 0.05502532050013542,
222
+ -2.590376615524292,
223
+ 0.02294650301337242,
224
+ -0.11992327123880386,
225
+ -0.09393031895160675,
226
+ 0.00041266169864684343,
227
+ -0.004656890872865915,
228
+ 0.00025705998996272683,
229
+ -0.3377724587917328,
230
+ 0.00022986403200775385,
231
+ -0.01075729914009571,
232
+ -0.09341470152139664,
233
+ -0.06531170010566711,
234
+ 0.016492702066898346,
235
+ -0.01895187422633171,
236
+ 0.1475711315870285,
237
+ 0.05689940229058266,
238
+ -0.14803127944469452,
239
+ 0.09427160024642944,
240
+ -0.009358881041407585,
241
+ -0.0029688519425690174,
242
+ -0.04647454619407654
243
+ ],
244
+ [
245
+ 0.4999156892299652,
246
+ 0.2220914363861084,
247
+ 0.33505380153656006,
248
+ -0.49453970789909363,
249
+ -0.06474141031503677,
250
+ -0.1383964568376541,
251
+ -0.011427058838307858,
252
+ -0.2955826222896576,
253
+ -0.018822673708200455,
254
+ -0.007339491043239832,
255
+ -0.46729031205177307,
256
+ -0.011370798572897911,
257
+ -0.0015873261727392673,
258
+ -0.07475392520427704,
259
+ 0.2003595232963562,
260
+ 0.05121266096830368,
261
+ 0.018622763454914093,
262
+ 0.25806617736816406,
263
+ 0.1493949294090271,
264
+ -0.533134400844574,
265
+ -0.05132618173956871,
266
+ -0.006106889806687832,
267
+ -0.01681436225771904,
268
+ 0.02741990238428116
269
+ ],
270
+ [
271
+ 0.0681074932217598,
272
+ 0.038069501519203186,
273
+ 0.10067056864500046,
274
+ -0.008472531102597713,
275
+ 0.02333664335310459,
276
+ -0.00102467299439013,
277
+ -0.01994895562529564,
278
+ 0.01857077144086361,
279
+ -0.047107942402362823,
280
+ 0.04690350964665413,
281
+ -0.03669547289609909,
282
+ -0.006082897540181875,
283
+ -0.012960744090378284,
284
+ -0.04504256695508957,
285
+ 0.030808277428150177,
286
+ 0.006868493743240833,
287
+ 0.006323779933154583,
288
+ 0.0695348009467125,
289
+ 0.04156721383333206,
290
+ -0.02024010196328163,
291
+ -0.028812535107135773,
292
+ -0.0014116355450823903,
293
+ -0.0018215596210211515,
294
+ 0.028311712667346
295
+ ],
296
+ [
297
+ 0.1624487191438675,
298
+ -0.20145384967327118,
299
+ 0.2947416603565216,
300
+ -0.18476316332817078,
301
+ 0.10492225736379623,
302
+ 0.02244037389755249,
303
+ 0.08404528349637985,
304
+ -0.25871041417121887,
305
+ -0.04818446561694145,
306
+ -0.010960616171360016,
307
+ 0.1961546391248703,
308
+ 0.10029289871454239,
309
+ 0.1505267322063446,
310
+ 0.07109440118074417,
311
+ -0.12312804162502289,
312
+ 0.34896931052207947,
313
+ -0.2323760688304901,
314
+ 0.0922774225473404,
315
+ -0.13451746106147766,
316
+ 0.1011989414691925,
317
+ 0.5111384391784668,
318
+ -0.006019487511366606,
319
+ -0.004957660101354122,
320
+ -0.046582095324993134
321
+ ],
322
+ [
323
+ 0.04909073933959007,
324
+ 0.030656171962618828,
325
+ 0.15804661810398102,
326
+ 0.10214020311832428,
327
+ 0.16083794832229614,
328
+ 0.0853763297200203,
329
+ 0.03564980998635292,
330
+ 0.09763889014720917,
331
+ -0.10411129146814346,
332
+ 0.05565249174833298,
333
+ -0.06129959598183632,
334
+ 0.016972968354821205,
335
+ 0.0011989623308181763,
336
+ -0.07131221890449524,
337
+ -0.009343582205474377,
338
+ 0.15363125503063202,
339
+ -0.10034770518541336,
340
+ 0.11451990902423859,
341
+ 0.09195035696029663,
342
+ 0.05555299296975136,
343
+ -0.0026579059194773436,
344
+ -0.00019085707026533782,
345
+ -0.00809928122907877,
346
+ -0.003035692498087883
347
+ ],
348
+ [
349
+ -0.4292859435081482,
350
+ -0.30912452936172485,
351
+ -0.2200385481119156,
352
+ -0.116905577480793,
353
+ -0.025338981300592422,
354
+ 0.06098977103829384,
355
+ 0.3440200388431549,
356
+ -0.05883513391017914,
357
+ -0.1605994701385498,
358
+ 0.17751748859882355,
359
+ -0.008709670975804329,
360
+ 0.010275691747665405,
361
+ 0.02400972694158554,
362
+ 0.08755376935005188,
363
+ -0.22082173824310303,
364
+ -0.07206287980079651,
365
+ -0.040061041712760925,
366
+ -0.46672096848487854,
367
+ -0.28407829999923706,
368
+ 0.11538465321063995,
369
+ 0.1171279102563858,
370
+ 0.011859945952892303,
371
+ -0.001572370994836092,
372
+ -0.040817804634571075
373
+ ],
374
+ [
375
+ 0.1292542964220047,
376
+ 0.05990736559033394,
377
+ 0.15684916079044342,
378
+ 0.027777886018157005,
379
+ -0.018983762711286545,
380
+ 0.02759557031095028,
381
+ -0.05131753534078598,
382
+ -0.0059871189296245575,
383
+ -0.08437316864728928,
384
+ 0.06277438253164291,
385
+ -0.03361068293452263,
386
+ -0.012865407392382622,
387
+ -0.007676516659557819,
388
+ -0.09351667761802673,
389
+ 0.06732753664255142,
390
+ 0.024253742769360542,
391
+ 0.045625172555446625,
392
+ 0.13008730113506317,
393
+ 0.08235563337802887,
394
+ -0.03187905624508858,
395
+ -0.05381424352526665,
396
+ -0.002394674578681588,
397
+ 0.0015019901329651475,
398
+ 0.029919074848294258
399
+ ],
400
+ [
401
+ 0.07045336067676544,
402
+ 0.03842192888259888,
403
+ 0.10945315659046173,
404
+ 0.011984778568148613,
405
+ -0.004143330734223127,
406
+ 0.013621147722005844,
407
+ -0.03646434098482132,
408
+ -0.003110044403001666,
409
+ -0.06586327403783798,
410
+ 0.03978915140032768,
411
+ -0.05047072842717171,
412
+ -0.01864797994494438,
413
+ -0.021004721522331238,
414
+ -0.05321501940488815,
415
+ 0.03387031704187393,
416
+ 0.028345610946416855,
417
+ 0.01672627031803131,
418
+ 0.0735769271850586,
419
+ 0.04922883212566376,
420
+ -0.023395273834466934,
421
+ -0.024696746841073036,
422
+ -0.0015744910342618823,
423
+ -0.0015623833751305938,
424
+ 0.02748068980872631
425
+ ]
426
+ ],
427
+ "b1": [
428
+ -0.4892691373825073,
429
+ -0.5365275144577026,
430
+ -0.7306413650512695,
431
+ -1.6562087535858154,
432
+ -0.5151417851448059,
433
+ 0.708844780921936,
434
+ 0.7126868367195129,
435
+ -0.3240874707698822,
436
+ -1.7912263870239258,
437
+ 0.7072461843490601,
438
+ -0.12453608959913254,
439
+ 0.5035669803619385,
440
+ -0.040696702897548676,
441
+ 1.397632122039795,
442
+ -0.16429384052753448,
443
+ 0.12540210783481598
444
+ ],
445
+ "w2": [
446
+ 0.4470292627811432,
447
+ -0.7432265281677246,
448
+ -0.9799825549125671,
449
+ 1.462009072303772,
450
+ 0.7532961964607239,
451
+ -0.946394145488739,
452
+ -0.8859969973564148,
453
+ 0.9506633281707764,
454
+ -2.2269387245178223,
455
+ 1.2120633125305176,
456
+ 0.21145373582839966,
457
+ 0.8740106225013733,
458
+ 0.4242953956127167,
459
+ -0.8897668123245239,
460
+ 0.3393680155277252,
461
+ 0.22115716338157654
462
+ ],
463
+ "b2": -0.06786252558231354,
464
+ "mu": [
465
+ -10.59149414686468,
466
+ -1.6581433637635177,
467
+ 0.7237200583211831,
468
+ -12.3009989777537,
469
+ 0.02630701937096438,
470
+ 0.4139387627577588,
471
+ 0.4209123099354301,
472
+ 0.02563215996667361,
473
+ 0.004328264944803166,
474
+ 0.0609081441366382,
475
+ 0.012364090814413664,
476
+ 0.0024453238908560718,
477
+ 0.0026619454280358257,
478
+ 0.7775174442824412,
479
+ 0.3690856071651739,
480
+ 0.7192209956259112,
481
+ 0.34080003067104875,
482
+ -9.126102225106102,
483
+ -1.4507808667211526,
484
+ -5.180230198802364,
485
+ 0.7058964616693312,
486
+ 0.00014580295771714227,
487
+ 0.0010414496979795876,
488
+ 0.00020828993959591752
489
+ ],
490
+ "sd": [
491
+ 7.073135913287144,
492
+ 1.0987393915943025,
493
+ 0.44715794728364644,
494
+ 8.142382377773437,
495
+ 0.1600477434927935,
496
+ 0.49253877869736523,
497
+ 0.49370651676030625,
498
+ 0.15803628828118246,
499
+ 0.0656480187243049,
500
+ 0.2391627488529239,
501
+ 0.11050538938222514,
502
+ 0.04939071838272889,
503
+ 0.05152632847625885,
504
+ 0.32244279318200153,
505
+ 0.4825581694084105,
506
+ 0.44938074484411195,
507
+ 0.21476388807955527,
508
+ 7.099387943262964,
509
+ 1.1012014396087357,
510
+ 4.397275599530713,
511
+ 0.19354262566657165,
512
+ 0.012075009243560244,
513
+ 0.032255690829585425,
514
+ 0.01443175032345713
515
+ ],
516
+ "feats": [
517
+ "sc",
518
+ "scn",
519
+ "inlex",
520
+ "uni",
521
+ "free0",
522
+ "infree",
523
+ "incon",
524
+ "aw",
525
+ "visarga",
526
+ "nukta",
527
+ "chandra",
528
+ "acro",
529
+ "variant",
530
+ "frank",
531
+ "skel",
532
+ "skmatch",
533
+ "lev",
534
+ "sc_rel",
535
+ "scn_rel",
536
+ "uni_rel",
537
+ "lenr",
538
+ "visr",
539
+ "hfin",
540
+ "acro_nv"
541
+ ],
542
+ "rerank_hid": 16,
543
+ "reranker_fit_on": "Aksharantar hin_valid (all distinct romans)",
544
+ "val_rerank_cv2": 69.3
545
+ }
model/manifest.json ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "bhashacore-bundle/1",
3
+ "name": "AksharaLP-1M",
4
+ "note": "AksharaLP-1M v1.0.0",
5
+ "files": {
6
+ "model": "xlit.q4",
7
+ "lex": null,
8
+ "lm": null,
9
+ "params": "params.json",
10
+ "lid": null,
11
+ "loan": null,
12
+ "natural": null
13
+ },
14
+ "sizes": {
15
+ "params.json": {
16
+ "bytes": 12120,
17
+ "gz": 5316,
18
+ "sha256": "67a2a1477eafe7fc34bd20fb93c16fbd23b8353a35bef9dc88814ea6b2705c1d"
19
+ },
20
+ "xlit.q4": {
21
+ "bytes": 588891,
22
+ "gz": 543767,
23
+ "sha256": "cf8ff5255105f556b1865c9db1c837bdafaa8b1b93c9e3e5e6e38afb994e34e2"
24
+ }
25
+ },
26
+ "model_name": "AksharaLP-1M",
27
+ "version": "1.0.0",
28
+ "dict_name": "dict-1.5M",
29
+ "packs": {
30
+ "dict": {
31
+ "dir": "dict/",
32
+ "name": "dict-1.5M",
33
+ "files": {
34
+ "lex": "lex.bin",
35
+ "lm": "lm.bin",
36
+ "params": "params.json"
37
+ },
38
+ "sizes": {
39
+ "lex.bin": {
40
+ "bytes": 5647376,
41
+ "gz": 3137141,
42
+ "sha256": "8220fbc4c5d9ce06888182c6d8f8d53920987acf08449ccf4f7062b0158f4fa2"
43
+ },
44
+ "lm.bin": {
45
+ "bytes": 2372806,
46
+ "gz": 825559,
47
+ "sha256": "01090665a81a4df02113720b755f3e884d5dccf0a9e24ea413606e0d3f132f0d"
48
+ },
49
+ "params.json": {
50
+ "bytes": 12034,
51
+ "gz": 5261,
52
+ "sha256": "0031609f902ec3d5be042b855d8801eb57a5ae54a32a1fa035a59f98629c2857"
53
+ }
54
+ },
55
+ "bytes": 8032216,
56
+ "gz": 3967961
57
+ }
58
+ }
59
+ }
model/params.json ADDED
@@ -0,0 +1,544 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "visr": true,
3
+ "acro_v": true,
4
+ "beam": 16,
5
+ "nbest": 16,
6
+ "model_file": "xlit.q4",
7
+ "model_src": "models/xlit/v2S/best.pt step 36000",
8
+ "w1": [
9
+ [
10
+ -0.015561956912279129,
11
+ -0.2590724527835846,
12
+ -4.91664104629308e-05,
13
+ -4.396255826577544e-05,
14
+ -0.044401731342077255,
15
+ 0.03714057803153992,
16
+ -1.183583208330674e-06,
17
+ -0.08884930610656738,
18
+ -0.045074667781591415,
19
+ -0.08021057397127151,
20
+ 0.07171142846345901,
21
+ -0.18331405520439148,
22
+ -5.70711272303015e-05,
23
+ -0.22631709277629852,
24
+ -2.46238851104863e-05,
25
+ 0.055078305304050446,
26
+ 0.8362643122673035,
27
+ 0.24692192673683167,
28
+ -0.2144010215997696,
29
+ -2.6011599402409047e-05,
30
+ 0.14259004592895508,
31
+ -0.0004840311303269118,
32
+ -0.04343048110604286,
33
+ -0.015337128192186356
34
+ ],
35
+ [
36
+ -0.3434857726097107,
37
+ -0.12918725609779358,
38
+ 5.408696597442031e-05,
39
+ -5.541890277527273e-05,
40
+ 0.04218575730919838,
41
+ 0.10622035712003708,
42
+ -2.3284759663511068e-05,
43
+ -0.16739186644554138,
44
+ -0.0073206983506679535,
45
+ -0.1622564047574997,
46
+ 0.07639061659574509,
47
+ 0.012584622949361801,
48
+ -3.4871758543886244e-05,
49
+ 0.17102842032909393,
50
+ 2.6655099645722657e-05,
51
+ 0.11252907663583755,
52
+ 0.0712190493941307,
53
+ -0.11443258821964264,
54
+ -0.031135937198996544,
55
+ -5.5775875807739794e-05,
56
+ -0.10498590022325516,
57
+ 0.012443359941244125,
58
+ -0.03877473995089531,
59
+ -0.047652486711740494
60
+ ],
61
+ [
62
+ -0.32081305980682373,
63
+ 0.14551419019699097,
64
+ 3.820210986305028e-05,
65
+ 5.938495451118797e-05,
66
+ -0.20604437589645386,
67
+ -0.012057994492352009,
68
+ 4.005451046396047e-05,
69
+ -0.2183503955602646,
70
+ 0.0041027250699698925,
71
+ -0.15540724992752075,
72
+ -0.09696532040834427,
73
+ -0.15008299052715302,
74
+ 2.703051723074168e-05,
75
+ 0.3145005702972412,
76
+ -3.53828800143674e-05,
77
+ -0.046421173959970474,
78
+ -0.23587073385715485,
79
+ -0.15307018160820007,
80
+ 0.02828281745314598,
81
+ 1.3840537576470524e-05,
82
+ 0.22715377807617188,
83
+ 0.0005053964559920132,
84
+ -0.021614709869027138,
85
+ 0.028349272906780243
86
+ ],
87
+ [
88
+ 0.22036844491958618,
89
+ -0.1495584100484848,
90
+ 5.1986484322696924e-05,
91
+ 5.271370173431933e-05,
92
+ -0.0721922442317009,
93
+ 0.1422671228647232,
94
+ 5.49514516023919e-06,
95
+ -0.32195183634757996,
96
+ -0.09321869909763336,
97
+ -0.23727498948574066,
98
+ 0.0014756782911717892,
99
+ -0.20423376560211182,
100
+ 1.11804147309158e-05,
101
+ -0.06835587322711945,
102
+ -9.829884220380336e-06,
103
+ 0.06813204288482666,
104
+ 0.13348449766635895,
105
+ 0.10155773907899857,
106
+ -0.08579937368631363,
107
+ 3.0208364478312433e-05,
108
+ 0.657234787940979,
109
+ -0.00018066636403091252,
110
+ -0.06816288083791733,
111
+ 0.012126388028264046
112
+ ],
113
+ [
114
+ 0.22822436690330505,
115
+ 0.48054882884025574,
116
+ -4.6600878704339266e-05,
117
+ -3.439883585087955e-05,
118
+ -0.03180981054902077,
119
+ 0.07301792502403259,
120
+ -2.6048139261547476e-05,
121
+ 0.2848097085952759,
122
+ -0.02384837530553341,
123
+ -0.006694837473332882,
124
+ 0.17171575129032135,
125
+ 0.19061139225959778,
126
+ -3.2914358598645777e-06,
127
+ -0.019714917987585068,
128
+ -2.4464614398311824e-05,
129
+ -0.10351742058992386,
130
+ -0.4626687467098236,
131
+ 0.30317893624305725,
132
+ 0.34827369451522827,
133
+ -5.0375922000966966e-05,
134
+ 0.22715719044208527,
135
+ -0.0019869711250066757,
136
+ 0.01730377785861492,
137
+ 0.021236998960375786
138
+ ],
139
+ [
140
+ -0.35157841444015503,
141
+ -0.15391534566879272,
142
+ -2.9147995519451797e-05,
143
+ 2.8266847948543727e-05,
144
+ -0.07769758254289627,
145
+ -0.07346295565366745,
146
+ -1.49544830492232e-05,
147
+ 0.0257745198905468,
148
+ 0.045600246638059616,
149
+ 0.1594177633523941,
150
+ -0.05174051225185394,
151
+ -0.10748539865016937,
152
+ -3.891884989570826e-05,
153
+ -0.2300061136484146,
154
+ 4.276317486073822e-05,
155
+ 0.3480663597583771,
156
+ 0.1923782080411911,
157
+ -0.32707351446151733,
158
+ -0.08953503519296646,
159
+ 3.798186662606895e-05,
160
+ 0.004443146288394928,
161
+ 0.0027272962033748627,
162
+ -1.0397139703854918e-05,
163
+ -0.006435581482946873
164
+ ],
165
+ [
166
+ -0.0968092828989029,
167
+ -0.021128321066498756,
168
+ 4.0671322494745255e-05,
169
+ -3.8725966078345664e-07,
170
+ -0.01838799938559532,
171
+ 0.0019020137842744589,
172
+ -5.590620276052505e-05,
173
+ -0.01308813039213419,
174
+ 0.01992219313979149,
175
+ -0.020882558077573776,
176
+ 0.043948717415332794,
177
+ -0.01846550777554512,
178
+ 3.7261590478010476e-05,
179
+ 0.033619239926338196,
180
+ -1.2629399861907586e-05,
181
+ -0.020962243899703026,
182
+ 0.007218125741928816,
183
+ -0.10624664276838303,
184
+ -0.021414361894130707,
185
+ -5.157808482181281e-05,
186
+ 0.0351666584610939,
187
+ 0.004914436023682356,
188
+ 0.0009687513811513782,
189
+ -0.018581906333565712
190
+ ],
191
+ [
192
+ 0.10835482180118561,
193
+ 0.06843508780002594,
194
+ 5.253354902379215e-05,
195
+ -2.4364875571336597e-05,
196
+ 0.14761097729206085,
197
+ 0.08214167505502701,
198
+ -5.3904863307252526e-05,
199
+ 0.09664969891309738,
200
+ -0.04199868068099022,
201
+ -0.05236470699310303,
202
+ -0.08479249477386475,
203
+ 0.003061349969357252,
204
+ 6.410497007891536e-06,
205
+ -0.13751299679279327,
206
+ 4.266103496775031e-05,
207
+ 0.3822084069252014,
208
+ 0.2888137400150299,
209
+ 0.3472263813018799,
210
+ 0.01722201518714428,
211
+ -1.2563064956339076e-05,
212
+ -0.10046061128377914,
213
+ -0.0014096840750426054,
214
+ 0.0015327114379033446,
215
+ -0.00038207584293559194
216
+ ],
217
+ [
218
+ -0.23380811512470245,
219
+ -0.03130961209535599,
220
+ -4.211945633869618e-05,
221
+ -3.28843598254025e-05,
222
+ 0.17667055130004883,
223
+ -0.06306526809930801,
224
+ -3.5599005059339106e-05,
225
+ 0.24570702016353607,
226
+ 0.05341082438826561,
227
+ 0.25450292229652405,
228
+ 0.22926999628543854,
229
+ -0.12199188768863678,
230
+ -3.4688186133280396e-05,
231
+ 0.0721987634897232,
232
+ -2.1470805222634226e-05,
233
+ 0.10122892260551453,
234
+ 0.24094152450561523,
235
+ -0.281928688287735,
236
+ -0.10936792194843292,
237
+ -1.1808137060143054e-05,
238
+ 0.3210824728012085,
239
+ 0.004107347689568996,
240
+ 0.008137108758091927,
241
+ -0.0287738386541605
242
+ ],
243
+ [
244
+ 0.10402468591928482,
245
+ 0.014269252307713032,
246
+ 4.823542258236557e-05,
247
+ -4.848210664931685e-05,
248
+ 0.006701572798192501,
249
+ -0.023116495460271835,
250
+ 2.1578460291493684e-05,
251
+ 0.04466652497649193,
252
+ -0.008599045686423779,
253
+ 0.027766602113842964,
254
+ -0.03312336280941963,
255
+ 0.02025029808282852,
256
+ -2.004284760914743e-05,
257
+ -0.029677756130695343,
258
+ 6.53383722237777e-07,
259
+ -0.007501651998609304,
260
+ -0.012901795096695423,
261
+ 0.08347664773464203,
262
+ 0.004463751334697008,
263
+ -3.77678225049749e-05,
264
+ -0.006038411054760218,
265
+ -0.004326044116169214,
266
+ 0.011772082187235355,
267
+ 0.023333216086030006
268
+ ],
269
+ [
270
+ 0.13031642138957977,
271
+ 0.0029603540897369385,
272
+ -1.9229766621720046e-05,
273
+ 9.563422054270632e-08,
274
+ 0.14972083270549774,
275
+ 0.018399246037006378,
276
+ 4.318736318964511e-05,
277
+ 0.1394825577735901,
278
+ -0.009943442419171333,
279
+ 0.05047810822725296,
280
+ -0.11288079619407654,
281
+ -0.08239683508872986,
282
+ 4.939647624269128e-05,
283
+ -0.07140836119651794,
284
+ -3.546756488503888e-05,
285
+ -0.2619844675064087,
286
+ -0.19682355225086212,
287
+ 0.04883984848856926,
288
+ 0.041489358991384506,
289
+ -3.986437513958663e-05,
290
+ -0.015265467576682568,
291
+ -0.0024833392817527056,
292
+ 0.015719031915068626,
293
+ 0.015553155913949013
294
+ ],
295
+ [
296
+ -0.09164544194936752,
297
+ -0.020419778302311897,
298
+ -1.6692949429852888e-05,
299
+ 1.7647056665737182e-05,
300
+ 0.002488643629476428,
301
+ 0.027437882497906685,
302
+ -2.8264359571039677e-05,
303
+ -0.03701603412628174,
304
+ 0.009159324690699577,
305
+ -0.020621521398425102,
306
+ 0.03447571396827698,
307
+ 0.0011249551316723228,
308
+ 5.2376475650817156e-05,
309
+ 0.017352906987071037,
310
+ -6.795644367230125e-06,
311
+ 0.008722764439880848,
312
+ 0.01097520999610424,
313
+ -0.0790662169456482,
314
+ -0.01081581600010395,
315
+ 4.761465243063867e-05,
316
+ 0.00738158356398344,
317
+ 0.005967006087303162,
318
+ -0.009170559234917164,
319
+ -0.027141228318214417
320
+ ],
321
+ [
322
+ 0.1246870830655098,
323
+ 0.016494568437337875,
324
+ -3.6376790376380086e-05,
325
+ 2.020174724748358e-05,
326
+ 0.05191154405474663,
327
+ 0.03699144348502159,
328
+ -1.3433265849016607e-05,
329
+ -0.05896291136741638,
330
+ -0.03726135939359665,
331
+ 0.044923651963472366,
332
+ -0.06443153321743011,
333
+ 0.024894172325730324,
334
+ -2.8819398721680045e-05,
335
+ -0.06437183916568756,
336
+ -3.1028001103550196e-05,
337
+ 0.10513761639595032,
338
+ -0.040632519870996475,
339
+ 0.15700025856494904,
340
+ 0.02873876690864563,
341
+ 2.544223389122635e-05,
342
+ -0.050497379153966904,
343
+ -0.0029438186902552843,
344
+ -0.01204095222055912,
345
+ -0.0032614609226584435
346
+ ],
347
+ [
348
+ -0.108024001121521,
349
+ -0.028248196467757225,
350
+ 2.2672298655379564e-05,
351
+ 3.978270979132503e-05,
352
+ -0.03387359157204628,
353
+ -0.016672611236572266,
354
+ 2.4699060304556042e-05,
355
+ 0.002938284073024988,
356
+ 0.02763514779508114,
357
+ -0.026506410911679268,
358
+ 0.05401856079697609,
359
+ -0.025770967826247215,
360
+ 4.1366132791154087e-05,
361
+ 0.04225965589284897,
362
+ 3.5108641895931214e-05,
363
+ -0.042118534445762634,
364
+ 0.005915872752666473,
365
+ -0.13225020468235016,
366
+ -0.030211828649044037,
367
+ -5.136957042850554e-05,
368
+ 0.05410256236791611,
369
+ 0.0035985622089356184,
370
+ 0.004466131795197725,
371
+ -0.009501196444034576
372
+ ],
373
+ [
374
+ 0.07347481697797775,
375
+ -0.02266073226928711,
376
+ 5.2228133426979184e-05,
377
+ 4.62813040940091e-05,
378
+ -0.11873267590999603,
379
+ -0.09743958711624146,
380
+ 4.904656088910997e-05,
381
+ 0.1482430398464203,
382
+ -0.044910795986652374,
383
+ 0.2667579650878906,
384
+ -0.09406531602144241,
385
+ -0.08873501420021057,
386
+ -3.744564310181886e-05,
387
+ 0.17381106317043304,
388
+ -2.094046794809401e-05,
389
+ 0.3961288332939148,
390
+ -0.06043681502342224,
391
+ 0.24953748285770416,
392
+ 0.1394886076450348,
393
+ 2.5415887648705393e-05,
394
+ -0.33052584528923035,
395
+ -0.007915235124528408,
396
+ 0.013453835621476173,
397
+ 0.11794132739305496
398
+ ],
399
+ [
400
+ 0.03664092719554901,
401
+ 0.18091842532157898,
402
+ -4.797168367076665e-05,
403
+ 4.716057446785271e-05,
404
+ -0.1518329530954361,
405
+ -0.04683017358183861,
406
+ 4.785861528944224e-05,
407
+ -0.09540998190641403,
408
+ 0.01912587322294712,
409
+ 0.10115578025579453,
410
+ -0.026265788823366165,
411
+ -0.10423006862401962,
412
+ -4.515917680691928e-05,
413
+ 0.20828509330749512,
414
+ -5.7712546549737453e-05,
415
+ -0.1024600937962532,
416
+ 0.3419700264930725,
417
+ -0.023042183369398117,
418
+ 0.17009925842285156,
419
+ 4.6127388486638665e-05,
420
+ 0.22005531191825867,
421
+ 0.0006003690650686622,
422
+ 0.07457850128412247,
423
+ 0.03790649399161339
424
+ ]
425
+ ],
426
+ "b1": [
427
+ -0.5934574007987976,
428
+ 1.7688686847686768,
429
+ 1.6331863403320312,
430
+ 0.6320905685424805,
431
+ -1.6366946697235107,
432
+ 0.45766252279281616,
433
+ -0.014123601838946342,
434
+ -0.08012986928224564,
435
+ -0.3310701847076416,
436
+ -0.44332027435302734,
437
+ -0.9740999937057495,
438
+ 0.35315409302711487,
439
+ 0.417784720659256,
440
+ -0.14263728260993958,
441
+ 0.40707024931907654,
442
+ 0.3663896918296814
443
+ ],
444
+ "w2": [
445
+ 0.7841601967811584,
446
+ -0.5244224667549133,
447
+ -0.6873168349266052,
448
+ 0.8126538395881653,
449
+ 0.8223074674606323,
450
+ -0.6758157014846802,
451
+ -0.19274172186851501,
452
+ 0.641558825969696,
453
+ -0.6333699822425842,
454
+ 0.18355154991149902,
455
+ 0.42537039518356323,
456
+ -0.15498623251914978,
457
+ 0.29152750968933105,
458
+ -0.23573102056980133,
459
+ 0.6270305514335632,
460
+ 0.41381654143333435
461
+ ],
462
+ "b2": -0.06786282360553741,
463
+ "mu": [
464
+ -5.227207906607518,
465
+ -0.8422915469418415,
466
+ 0.0,
467
+ 0.0,
468
+ 0.06320372316468999,
469
+ 0.9945053295301006,
470
+ 0.0,
471
+ 0.03328829505079317,
472
+ 0.004113496472001201,
473
+ 0.06248311064404744,
474
+ 0.01726467497372767,
475
+ 0.0058749937446829804,
476
+ 0.0,
477
+ 0.46547627983786216,
478
+ 0.0,
479
+ 0.8586198268528249,
480
+ 0.28142605827852935,
481
+ -3.7420752639781365,
482
+ -0.6289379733718058,
483
+ 0.0,
484
+ 0.7304234069567472,
485
+ 0.0003402892458589801,
486
+ 0.0018615823449932443,
487
+ 0.0005004253615573238
488
+ ],
489
+ "sd": [
490
+ 1.8052377132935122,
491
+ 0.549715606696829,
492
+ 1e-06,
493
+ 1e-06,
494
+ 0.2433300211683481,
495
+ 0.07392311486649306,
496
+ 1e-06,
497
+ 0.17938936211796294,
498
+ 0.06400549686371873,
499
+ 0.2420319309327287,
500
+ 0.13025692490079885,
501
+ 0.07642402135598905,
502
+ 1e-06,
503
+ 0.2880909253656169,
504
+ 1e-06,
505
+ 0.3484142887647117,
506
+ 0.2384648076717676,
507
+ 1.9471261794389574,
508
+ 0.5453126573397205,
509
+ 1e-06,
510
+ 0.18576650509517462,
511
+ 0.01844479161365384,
512
+ 0.043106879600848774,
513
+ 0.022365591121132802
514
+ ],
515
+ "feats": [
516
+ "sc",
517
+ "scn",
518
+ "inlex",
519
+ "uni",
520
+ "free0",
521
+ "infree",
522
+ "incon",
523
+ "aw",
524
+ "visarga",
525
+ "nukta",
526
+ "chandra",
527
+ "acro",
528
+ "variant",
529
+ "frank",
530
+ "skel",
531
+ "skmatch",
532
+ "lev",
533
+ "sc_rel",
534
+ "scn_rel",
535
+ "uni_rel",
536
+ "lenr",
537
+ "visr",
538
+ "hfin",
539
+ "acro_nv"
540
+ ],
541
+ "rerank_hid": 16,
542
+ "reranker_fit_on": "Aksharantar hin_valid (all distinct romans)",
543
+ "val_rerank_cv2": 53.92
544
+ }
model/xlit.q4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cf8ff5255105f556b1865c9db1c837bdafaa8b1b93c9e3e5e6e38afb994e34e2
3
+ size 588891
sw.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // AksharaLP Space — offline cache (cache-first for app shell + core model files).
2
+ // The dictionary pack (model/dict/) is cached by the runtime itself (Cache API "aksharalp-dict"), so it is left alone here.
3
+ const CACHE = "aksharalp-v2", DICT_CACHE = "aksharalp-dict";
4
+ const SHELL = ["./", "./index.html", "./css/app.css", "./js/app.js", "./js/worker.js", "./js/aksharalp.mjs",
5
+ "./fonts/Inter-var.woff2", "./fonts/NotoSansDevanagari-var.woff2", "./assets/favicon.svg", "./model/manifest.json"];
6
+
7
+ self.addEventListener("install", (e) => {
8
+ e.waitUntil((async () => {
9
+ const c = await caches.open(CACHE);
10
+ await c.addAll(SHELL);
11
+ try { // precache exactly the files the bundle manifest references
12
+ const man = await (await c.match("./model/manifest.json")).json();
13
+ await c.addAll(Object.values(man.files).filter(Boolean).map((f) => "./model/" + f));
14
+ } catch (_) {}
15
+ self.skipWaiting();
16
+ })());
17
+ });
18
+ self.addEventListener("activate", (e) => e.waitUntil((async () => {
19
+ for (const k of await caches.keys()) if (k !== CACHE && k !== DICT_CACHE) await caches.delete(k);
20
+ await self.clients.claim();
21
+ })()));
22
+ self.addEventListener("fetch", (e) => {
23
+ const u = new URL(e.request.url);
24
+ if (e.request.method !== "GET" || u.origin !== location.origin || u.pathname.includes("/model/dict/")) return;
25
+ e.respondWith((async () => {
26
+ const c = await caches.open(CACHE);
27
+ const hit = await c.match(e.request, { ignoreSearch: true });
28
+ if (hit) return hit;
29
+ const r = await fetch(e.request);
30
+ if (r.ok) c.put(e.request, r.clone());
31
+ return r;
32
+ })());
33
+ });