@jjlmoya/utils-developer 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +34 -0
  3. package/src/category/i18n/es.ts +34 -0
  4. package/src/category/i18n/fr.ts +34 -0
  5. package/src/category/index.ts +31 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +78 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/css_specificity_logic.test.ts +39 -0
  17. package/src/tests/faq_count.test.ts +19 -0
  18. package/src/tests/locale_completeness.test.ts +42 -0
  19. package/src/tests/mocks/astro_mock.js +2 -0
  20. package/src/tests/no_h1_in_components.test.ts +48 -0
  21. package/src/tests/schemas_fulfillment.test.ts +23 -0
  22. package/src/tests/seo_length.test.ts +22 -0
  23. package/src/tests/title_quality.test.ts +55 -0
  24. package/src/tests/tool_validation.test.ts +17 -0
  25. package/src/tool/aspectRatio/bibliography.astro +14 -0
  26. package/src/tool/aspectRatio/component.astro +468 -0
  27. package/src/tool/aspectRatio/i18n/en.ts +177 -0
  28. package/src/tool/aspectRatio/i18n/es.ts +177 -0
  29. package/src/tool/aspectRatio/i18n/fr.ts +177 -0
  30. package/src/tool/aspectRatio/index.ts +34 -0
  31. package/src/tool/aspectRatio/seo.astro +14 -0
  32. package/src/tool/aspectRatio/ui.ts +12 -0
  33. package/src/tool/calculadoraTiempoDatos/bibliography.astro +14 -0
  34. package/src/tool/calculadoraTiempoDatos/component.astro +829 -0
  35. package/src/tool/calculadoraTiempoDatos/i18n/en.ts +150 -0
  36. package/src/tool/calculadoraTiempoDatos/i18n/es.ts +163 -0
  37. package/src/tool/calculadoraTiempoDatos/i18n/fr.ts +150 -0
  38. package/src/tool/calculadoraTiempoDatos/index.ts +33 -0
  39. package/src/tool/calculadoraTiempoDatos/seo.astro +14 -0
  40. package/src/tool/calculadoraTiempoDatos/ui.ts +24 -0
  41. package/src/tool/colorConverter/bibliography.astro +14 -0
  42. package/src/tool/colorConverter/component.astro +504 -0
  43. package/src/tool/colorConverter/i18n/en.ts +126 -0
  44. package/src/tool/colorConverter/i18n/es.ts +126 -0
  45. package/src/tool/colorConverter/i18n/fr.ts +126 -0
  46. package/src/tool/colorConverter/index.ts +33 -0
  47. package/src/tool/colorConverter/seo.astro +14 -0
  48. package/src/tool/colorConverter/ui.ts +16 -0
  49. package/src/tool/conversorExcelCsvHtml/bibliography.astro +14 -0
  50. package/src/tool/conversorExcelCsvHtml/component.astro +595 -0
  51. package/src/tool/conversorExcelCsvHtml/i18n/en.ts +129 -0
  52. package/src/tool/conversorExcelCsvHtml/i18n/es.ts +129 -0
  53. package/src/tool/conversorExcelCsvHtml/i18n/fr.ts +129 -0
  54. package/src/tool/conversorExcelCsvHtml/index.ts +33 -0
  55. package/src/tool/conversorExcelCsvHtml/seo.astro +14 -0
  56. package/src/tool/conversorExcelCsvHtml/ui.ts +14 -0
  57. package/src/tool/cronGenerator/bibliography.astro +14 -0
  58. package/src/tool/cronGenerator/component.astro +667 -0
  59. package/src/tool/cronGenerator/i18n/en.ts +191 -0
  60. package/src/tool/cronGenerator/i18n/es.ts +191 -0
  61. package/src/tool/cronGenerator/i18n/fr.ts +191 -0
  62. package/src/tool/cronGenerator/index.ts +34 -0
  63. package/src/tool/cronGenerator/seo.astro +14 -0
  64. package/src/tool/cronGenerator/ui.ts +45 -0
  65. package/src/tool/cssSpecificityCalculator/bibliography.astro +14 -0
  66. package/src/tool/cssSpecificityCalculator/component.astro +292 -0
  67. package/src/tool/cssSpecificityCalculator/i18n/en.ts +168 -0
  68. package/src/tool/cssSpecificityCalculator/i18n/es.ts +168 -0
  69. package/src/tool/cssSpecificityCalculator/i18n/fr.ts +168 -0
  70. package/src/tool/cssSpecificityCalculator/index.ts +34 -0
  71. package/src/tool/cssSpecificityCalculator/logic.ts +100 -0
  72. package/src/tool/cssSpecificityCalculator/seo.astro +14 -0
  73. package/src/tool/cssSpecificityCalculator/ui-controller.ts +57 -0
  74. package/src/tool/cssSpecificityCalculator/ui.ts +11 -0
  75. package/src/tool/cssToInlineConverter/bibliography.astro +14 -0
  76. package/src/tool/cssToInlineConverter/component.astro +308 -0
  77. package/src/tool/cssToInlineConverter/i18n/en.ts +160 -0
  78. package/src/tool/cssToInlineConverter/i18n/es.ts +160 -0
  79. package/src/tool/cssToInlineConverter/i18n/fr.ts +160 -0
  80. package/src/tool/cssToInlineConverter/index.ts +34 -0
  81. package/src/tool/cssToInlineConverter/seo.astro +14 -0
  82. package/src/tool/cssToInlineConverter/ui.ts +12 -0
  83. package/src/tool/duplicateCssRemover/bibliography.astro +14 -0
  84. package/src/tool/duplicateCssRemover/component.astro +353 -0
  85. package/src/tool/duplicateCssRemover/i18n/en.ts +167 -0
  86. package/src/tool/duplicateCssRemover/i18n/es.ts +167 -0
  87. package/src/tool/duplicateCssRemover/i18n/fr.ts +167 -0
  88. package/src/tool/duplicateCssRemover/index.ts +34 -0
  89. package/src/tool/duplicateCssRemover/seo.astro +14 -0
  90. package/src/tool/duplicateCssRemover/ui.ts +12 -0
  91. package/src/tool/generadorSecurityTxt/bibliography.astro +14 -0
  92. package/src/tool/generadorSecurityTxt/component.astro +473 -0
  93. package/src/tool/generadorSecurityTxt/i18n/en.ts +173 -0
  94. package/src/tool/generadorSecurityTxt/i18n/es.ts +173 -0
  95. package/src/tool/generadorSecurityTxt/i18n/fr.ts +173 -0
  96. package/src/tool/generadorSecurityTxt/index.ts +33 -0
  97. package/src/tool/generadorSecurityTxt/seo.astro +14 -0
  98. package/src/tool/generadorSecurityTxt/ui.ts +27 -0
  99. package/src/tool/hashGenerator/bibliography.astro +14 -0
  100. package/src/tool/hashGenerator/component.astro +367 -0
  101. package/src/tool/hashGenerator/i18n/en.ts +118 -0
  102. package/src/tool/hashGenerator/i18n/es.ts +118 -0
  103. package/src/tool/hashGenerator/i18n/fr.ts +118 -0
  104. package/src/tool/hashGenerator/index.ts +33 -0
  105. package/src/tool/hashGenerator/md5.ts +138 -0
  106. package/src/tool/hashGenerator/seo.astro +14 -0
  107. package/src/tool/hashGenerator/ui.ts +11 -0
  108. package/src/tool/inspectorCertificadosSsl/bibliography.astro +14 -0
  109. package/src/tool/inspectorCertificadosSsl/component.astro +580 -0
  110. package/src/tool/inspectorCertificadosSsl/i18n/en.ts +214 -0
  111. package/src/tool/inspectorCertificadosSsl/i18n/es.ts +214 -0
  112. package/src/tool/inspectorCertificadosSsl/i18n/fr.ts +214 -0
  113. package/src/tool/inspectorCertificadosSsl/index.ts +33 -0
  114. package/src/tool/inspectorCertificadosSsl/seo.astro +14 -0
  115. package/src/tool/inspectorCertificadosSsl/ui.ts +29 -0
  116. package/src/tool/jsonFormatter/bibliography.astro +14 -0
  117. package/src/tool/jsonFormatter/component.astro +590 -0
  118. package/src/tool/jsonFormatter/i18n/en.ts +156 -0
  119. package/src/tool/jsonFormatter/i18n/es.ts +156 -0
  120. package/src/tool/jsonFormatter/i18n/fr.ts +156 -0
  121. package/src/tool/jsonFormatter/index.ts +34 -0
  122. package/src/tool/jsonFormatter/seo.astro +14 -0
  123. package/src/tool/jsonFormatter/ui.ts +15 -0
  124. package/src/tool/keycode/bibliography.astro +14 -0
  125. package/src/tool/keycode/component.astro +540 -0
  126. package/src/tool/keycode/i18n/en.ts +160 -0
  127. package/src/tool/keycode/i18n/es.ts +160 -0
  128. package/src/tool/keycode/i18n/fr.ts +160 -0
  129. package/src/tool/keycode/index.ts +34 -0
  130. package/src/tool/keycode/seo.astro +14 -0
  131. package/src/tool/keycode/ui.ts +10 -0
  132. package/src/tool/llmCostCalculator/bibliography.astro +14 -0
  133. package/src/tool/llmCostCalculator/component.astro +488 -0
  134. package/src/tool/llmCostCalculator/data.ts +99 -0
  135. package/src/tool/llmCostCalculator/i18n/en.ts +165 -0
  136. package/src/tool/llmCostCalculator/i18n/es.ts +165 -0
  137. package/src/tool/llmCostCalculator/i18n/fr.ts +165 -0
  138. package/src/tool/llmCostCalculator/index.ts +34 -0
  139. package/src/tool/llmCostCalculator/seo.astro +14 -0
  140. package/src/tool/llmCostCalculator/ui.ts +11 -0
  141. package/src/tool/mobileMockupGenerator/app.ts +614 -0
  142. package/src/tool/mobileMockupGenerator/bibliography.astro +14 -0
  143. package/src/tool/mobileMockupGenerator/component.astro +1044 -0
  144. package/src/tool/mobileMockupGenerator/constants.ts +48 -0
  145. package/src/tool/mobileMockupGenerator/i18n/en.ts +121 -0
  146. package/src/tool/mobileMockupGenerator/i18n/es.ts +121 -0
  147. package/src/tool/mobileMockupGenerator/i18n/fr.ts +121 -0
  148. package/src/tool/mobileMockupGenerator/index.ts +33 -0
  149. package/src/tool/mobileMockupGenerator/rendering.ts +189 -0
  150. package/src/tool/mobileMockupGenerator/seo.astro +14 -0
  151. package/src/tool/mobileMockupGenerator/store.ts +145 -0
  152. package/src/tool/mobileMockupGenerator/types.ts +41 -0
  153. package/src/tool/mobileMockupGenerator/ui.ts +48 -0
  154. package/src/tool/musicalTypography/bibliography.astro +14 -0
  155. package/src/tool/musicalTypography/component.astro +480 -0
  156. package/src/tool/musicalTypography/i18n/en.ts +179 -0
  157. package/src/tool/musicalTypography/i18n/es.ts +179 -0
  158. package/src/tool/musicalTypography/i18n/fr.ts +179 -0
  159. package/src/tool/musicalTypography/index.ts +33 -0
  160. package/src/tool/musicalTypography/seo.astro +14 -0
  161. package/src/tool/musicalTypography/ui.ts +25 -0
  162. package/src/tool/placeholderGenerator/bibliography.astro +14 -0
  163. package/src/tool/placeholderGenerator/component.astro +499 -0
  164. package/src/tool/placeholderGenerator/i18n/en.ts +176 -0
  165. package/src/tool/placeholderGenerator/i18n/es.ts +176 -0
  166. package/src/tool/placeholderGenerator/i18n/fr.ts +176 -0
  167. package/src/tool/placeholderGenerator/index.ts +34 -0
  168. package/src/tool/placeholderGenerator/seo.astro +14 -0
  169. package/src/tool/placeholderGenerator/ui.ts +14 -0
  170. package/src/tool/promptLibrary/bibliography.astro +14 -0
  171. package/src/tool/promptLibrary/component.astro +1131 -0
  172. package/src/tool/promptLibrary/controller.ts +49 -0
  173. package/src/tool/promptLibrary/i18n/en.ts +131 -0
  174. package/src/tool/promptLibrary/i18n/es.ts +131 -0
  175. package/src/tool/promptLibrary/i18n/fr.ts +126 -0
  176. package/src/tool/promptLibrary/index.ts +33 -0
  177. package/src/tool/promptLibrary/seo.astro +14 -0
  178. package/src/tool/promptLibrary/ui.ts +29 -0
  179. package/src/tool/readabilityCalculator/bibliography.astro +14 -0
  180. package/src/tool/readabilityCalculator/component.astro +534 -0
  181. package/src/tool/readabilityCalculator/i18n/en.ts +133 -0
  182. package/src/tool/readabilityCalculator/i18n/es.ts +133 -0
  183. package/src/tool/readabilityCalculator/i18n/fr.ts +133 -0
  184. package/src/tool/readabilityCalculator/index.ts +33 -0
  185. package/src/tool/readabilityCalculator/seo.astro +14 -0
  186. package/src/tool/readabilityCalculator/ui.ts +27 -0
  187. package/src/tool/svgSanitizer/bibliography.astro +14 -0
  188. package/src/tool/svgSanitizer/component.astro +554 -0
  189. package/src/tool/svgSanitizer/i18n/en.ts +125 -0
  190. package/src/tool/svgSanitizer/i18n/es.ts +125 -0
  191. package/src/tool/svgSanitizer/i18n/fr.ts +125 -0
  192. package/src/tool/svgSanitizer/index.ts +33 -0
  193. package/src/tool/svgSanitizer/seo.astro +14 -0
  194. package/src/tool/svgSanitizer/ui.ts +18 -0
  195. package/src/tool/svgToCss/bibliography.astro +14 -0
  196. package/src/tool/svgToCss/component.astro +381 -0
  197. package/src/tool/svgToCss/i18n/en.ts +174 -0
  198. package/src/tool/svgToCss/i18n/es.ts +174 -0
  199. package/src/tool/svgToCss/i18n/fr.ts +174 -0
  200. package/src/tool/svgToCss/index.ts +34 -0
  201. package/src/tool/svgToCss/seo.astro +14 -0
  202. package/src/tool/svgToCss/ui.ts +13 -0
  203. package/src/tool/urlCleaner/bibliography.astro +14 -0
  204. package/src/tool/urlCleaner/component.astro +458 -0
  205. package/src/tool/urlCleaner/i18n/en.ts +116 -0
  206. package/src/tool/urlCleaner/i18n/es.ts +116 -0
  207. package/src/tool/urlCleaner/i18n/fr.ts +116 -0
  208. package/src/tool/urlCleaner/index.ts +33 -0
  209. package/src/tool/urlCleaner/seo.astro +14 -0
  210. package/src/tool/urlCleaner/ui.ts +12 -0
  211. package/src/tool/urlEncoderDecoder/bibliography.astro +14 -0
  212. package/src/tool/urlEncoderDecoder/component.astro +291 -0
  213. package/src/tool/urlEncoderDecoder/i18n/en.ts +162 -0
  214. package/src/tool/urlEncoderDecoder/i18n/es.ts +162 -0
  215. package/src/tool/urlEncoderDecoder/i18n/fr.ts +162 -0
  216. package/src/tool/urlEncoderDecoder/index.ts +34 -0
  217. package/src/tool/urlEncoderDecoder/seo.astro +14 -0
  218. package/src/tool/urlEncoderDecoder/ui.ts +11 -0
  219. package/src/tool/utmGenerator/bibliography.astro +14 -0
  220. package/src/tool/utmGenerator/component.astro +270 -0
  221. package/src/tool/utmGenerator/i18n/en.ts +117 -0
  222. package/src/tool/utmGenerator/i18n/es.ts +117 -0
  223. package/src/tool/utmGenerator/i18n/fr.ts +117 -0
  224. package/src/tool/utmGenerator/index.ts +33 -0
  225. package/src/tool/utmGenerator/seo.astro +14 -0
  226. package/src/tool/utmGenerator/ui.ts +13 -0
  227. package/src/tools.ts +27 -0
  228. package/src/types.ts +72 -0
@@ -0,0 +1,488 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { LlmCostCalculatorUI } from './ui';
4
+ import { LLM_MODELS } from './data';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ ui?: Record<string, unknown>;
9
+ }
10
+
11
+ const { ui } = Astro.props;
12
+ const t = (ui ?? {}) as LlmCostCalculatorUI;
13
+ ---
14
+
15
+ <div
16
+ class="llm-root"
17
+ id="llm-root"
18
+ data-i18n={JSON.stringify(t)}
19
+ data-models={JSON.stringify(LLM_MODELS)}
20
+ >
21
+ <div class="llm-glow-wrap">
22
+ <div class="llm-glow"></div>
23
+ <div class="llm-card">
24
+ <div class="llm-grid">
25
+ <div class="llm-controls">
26
+ <div class="llm-field">
27
+ <label class="llm-label" for="llm-model-select">{t.labelModel}</label>
28
+ <select class="llm-select" id="llm-model-select"></select>
29
+ </div>
30
+
31
+ <div class="llm-tokens-grid">
32
+ <div class="llm-field">
33
+ <label class="llm-label" for="llm-input-tokens">{t.labelInputTokens}</label>
34
+ <input class="llm-input" type="number" id="llm-input-tokens" value="1000" min="0" />
35
+ <p class="llm-word-hint">
36
+ ~<span id="llm-input-words">750</span>
37
+ {t.unitWords}
38
+ </p>
39
+ </div>
40
+ <div class="llm-field">
41
+ <label class="llm-label" for="llm-output-tokens">{t.labelOutputTokens}</label>
42
+ <input class="llm-input" type="number" id="llm-output-tokens" value="500" min="0" />
43
+ <p class="llm-word-hint">
44
+ ~<span id="llm-output-words">375</span>
45
+ {t.unitWords}
46
+ </p>
47
+ </div>
48
+ </div>
49
+
50
+ <div class="llm-field">
51
+ <label class="llm-label" for="llm-requests-range">{t.labelRequests}</label>
52
+ <div class="llm-slider-row">
53
+ <input
54
+ class="llm-slider"
55
+ type="range"
56
+ id="llm-requests-range"
57
+ min="1"
58
+ max="100000"
59
+ step="100"
60
+ value="1000"
61
+ />
62
+ <input class="llm-req-num" type="number" id="llm-requests-num" value="1000" min="1" />
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="llm-results">
68
+ <div class="llm-stat-block">
69
+ <h3 class="llm-stat-title">{t.labelCostPerRequest}</h3>
70
+ <div class="llm-cost-val" id="llm-cost-per-req">$0.00</div>
71
+ </div>
72
+
73
+ <div class="llm-divider-wrap">
74
+ <div class="llm-divider-line"></div>
75
+ <span class="llm-divider-label">x <span id="llm-multiplier">1,000</span></span>
76
+ </div>
77
+
78
+ <div class="llm-stat-block">
79
+ <h3 class="llm-total-title">{t.labelTotal}</h3>
80
+ <div class="llm-total-val" id="llm-total-cost">$0.00</div>
81
+ </div>
82
+
83
+ <div class="llm-bar-section">
84
+ <div class="llm-bar-labels">
85
+ <span>{t.labelInput}</span>
86
+ <span>{t.labelOutput}</span>
87
+ </div>
88
+ <div class="llm-bar-track">
89
+ <div class="llm-bar-input" id="llm-bar-input" style="width:50%"></div>
90
+ <div class="llm-bar-output" id="llm-bar-output" style="width:50%"></div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+
99
+ <script>
100
+ type Model = {
101
+ id: string;
102
+ name: string;
103
+ input: number;
104
+ output: number;
105
+ group: string;
106
+ description: string;
107
+ };
108
+ type Refs = {
109
+ select: HTMLSelectElement;
110
+ inputTokens: HTMLInputElement;
111
+ outputTokens: HTMLInputElement;
112
+ requestsRange: HTMLInputElement;
113
+ requestsNum: HTMLInputElement;
114
+ inputWords: HTMLElement;
115
+ outputWords: HTMLElement;
116
+ costPerReq: HTMLElement;
117
+ multiplier: HTMLElement;
118
+ barInput: HTMLElement;
119
+ barOutput: HTMLElement;
120
+ totalCost: HTMLElement;
121
+ };
122
+
123
+ function formatMoney(amount: number): string {
124
+ const digits = amount < 0.01 ? 6 : 4;
125
+ return new Intl.NumberFormat('en-US', {
126
+ style: 'currency',
127
+ currency: 'USD',
128
+ minimumFractionDigits: digits,
129
+ }).format(amount);
130
+ }
131
+
132
+ function estimateWords(tokens: number): number {
133
+ return Math.round(tokens * 0.75);
134
+ }
135
+
136
+ function getGroups(models: Model[]): string[] {
137
+ const seen = new Set<string>();
138
+ models.forEach((m) => seen.add(m.group));
139
+ return Array.from(seen);
140
+ }
141
+
142
+ function buildOption(model: Model): HTMLOptionElement {
143
+ const opt = document.createElement('option');
144
+ opt.value = model.id;
145
+ opt.dataset.input = String(model.input);
146
+ opt.dataset.output = String(model.output);
147
+ opt.textContent = model.name + ' ($' + model.input + '/$' + model.output + ')';
148
+ return opt;
149
+ }
150
+
151
+ function buildOptgroup(group: string, models: Model[]): HTMLOptGroupElement {
152
+ const og = document.createElement('optgroup');
153
+ og.label = group;
154
+ models.filter((m) => m.group === group).forEach((m) => og.appendChild(buildOption(m)));
155
+ return og;
156
+ }
157
+
158
+ function buildSelect(select: HTMLSelectElement, models: Model[]): void {
159
+ const groups = getGroups(models);
160
+ groups.forEach((g) => select.appendChild(buildOptgroup(g, models)));
161
+ }
162
+
163
+ function updateBar(refs: Refs, inputCost: number, outputCost: number, total: number): void {
164
+ if (total <= 0) return;
165
+ refs.barInput.style.width = (inputCost / total) * 100 + '%';
166
+ refs.barOutput.style.width = (outputCost / total) * 100 + '%';
167
+ }
168
+
169
+ function update(refs: Refs): void {
170
+ const opt = refs.select.options[refs.select.selectedIndex];
171
+ const inputPrice = parseFloat(opt?.dataset.input ?? '0');
172
+ const outputPrice = parseFloat(opt?.dataset.output ?? '0');
173
+ const inputs = parseInt(refs.inputTokens.value, 10) || 0;
174
+ const outputs = parseInt(refs.outputTokens.value, 10) || 0;
175
+ const requests = parseInt(refs.requestsNum.value, 10) || 1;
176
+ refs.inputWords.textContent = estimateWords(inputs).toLocaleString();
177
+ refs.outputWords.textContent = estimateWords(outputs).toLocaleString();
178
+ const inputCost = (inputs / 1e6) * inputPrice;
179
+ const outputCost = (outputs / 1e6) * outputPrice;
180
+ const costPerReq = inputCost + outputCost;
181
+ refs.costPerReq.textContent = formatMoney(costPerReq);
182
+ refs.multiplier.textContent = requests.toLocaleString();
183
+ const total = costPerReq * requests;
184
+ refs.totalCost.textContent = new Intl.NumberFormat('en-US', {
185
+ style: 'currency',
186
+ currency: 'USD',
187
+ }).format(total);
188
+ updateBar(refs, inputCost, outputCost, costPerReq);
189
+ }
190
+
191
+ function getRefs(root: HTMLElement): Refs {
192
+ return {
193
+ select: root.querySelector<HTMLSelectElement>('#llm-model-select')!,
194
+ inputTokens: root.querySelector<HTMLInputElement>('#llm-input-tokens')!,
195
+ outputTokens: root.querySelector<HTMLInputElement>('#llm-output-tokens')!,
196
+ requestsRange: root.querySelector<HTMLInputElement>('#llm-requests-range')!,
197
+ requestsNum: root.querySelector<HTMLInputElement>('#llm-requests-num')!,
198
+ inputWords: root.querySelector<HTMLElement>('#llm-input-words')!,
199
+ outputWords: root.querySelector<HTMLElement>('#llm-output-words')!,
200
+ costPerReq: root.querySelector<HTMLElement>('#llm-cost-per-req')!,
201
+ multiplier: root.querySelector<HTMLElement>('#llm-multiplier')!,
202
+ barInput: root.querySelector<HTMLElement>('#llm-bar-input')!,
203
+ barOutput: root.querySelector<HTMLElement>('#llm-bar-output')!,
204
+ totalCost: root.querySelector<HTMLElement>('#llm-total-cost')!,
205
+ };
206
+ }
207
+
208
+ function bindEvents(refs: Refs, onUpdate: () => void): void {
209
+ refs.select.addEventListener('change', onUpdate);
210
+ refs.inputTokens.addEventListener('input', onUpdate);
211
+ refs.outputTokens.addEventListener('input', onUpdate);
212
+ refs.requestsRange.addEventListener('input', () => {
213
+ refs.requestsNum.value = refs.requestsRange.value;
214
+ onUpdate();
215
+ });
216
+ refs.requestsNum.addEventListener('input', () => {
217
+ refs.requestsRange.value = refs.requestsNum.value;
218
+ onUpdate();
219
+ });
220
+ }
221
+
222
+ function init(): void {
223
+ const root = document.getElementById('llm-root');
224
+ if (!root) return;
225
+ const models = JSON.parse(root.dataset.models ?? '[]') as Model[];
226
+ const refs = getRefs(root as HTMLElement);
227
+ buildSelect(refs.select, models);
228
+ const upd = () => update(refs);
229
+ bindEvents(refs, upd);
230
+ upd();
231
+ }
232
+
233
+ init();
234
+ </script>
235
+
236
+ <style>
237
+ .llm-root {
238
+ --llm-card-bg: rgba(255, 255, 255, 0.9);
239
+ --llm-card-border: rgba(15, 23, 42, 0.05);
240
+ --llm-text: #1e293b;
241
+ --llm-muted: #64748b;
242
+ --llm-accent: #6366f1;
243
+ --llm-input-bg: #f8fafc;
244
+ --llm-input-border: #e2e8f0;
245
+ --llm-panel-bg: #f8fafc;
246
+ --llm-panel-border: #f1f5f9;
247
+ --llm-divider: #e2e8f0;
248
+ --llm-total-from: #4f46e5;
249
+ --llm-total-to: #9333ea;
250
+ }
251
+
252
+ :global(.theme-dark) .llm-root {
253
+ --llm-card-bg: rgba(15, 23, 42, 0.9);
254
+ --llm-card-border: rgba(255, 255, 255, 0.1);
255
+ --llm-text: #f1f5f9;
256
+ --llm-muted: #94a3b8;
257
+ --llm-input-bg: #1e293b;
258
+ --llm-input-border: #334155;
259
+ --llm-panel-bg: rgba(30, 41, 59, 0.5);
260
+ --llm-panel-border: rgba(51, 65, 85, 0.5);
261
+ --llm-divider: #334155;
262
+ --llm-total-from: #818cf8;
263
+ --llm-total-to: #c084fc;
264
+ }
265
+
266
+ .llm-glow-wrap {
267
+ position: relative;
268
+ }
269
+
270
+ .llm-glow {
271
+ position: absolute;
272
+ inset: -4px;
273
+ background: linear-gradient(to right, #6366f1, #a855f7, #ec4899);
274
+ border-radius: 1.25rem;
275
+ filter: blur(8px);
276
+ opacity: 0.25;
277
+ transition: opacity 1s;
278
+ }
279
+
280
+ .llm-glow-wrap:hover .llm-glow {
281
+ opacity: 0.5;
282
+ transition-duration: 0.2s;
283
+ }
284
+
285
+ .llm-card {
286
+ position: relative;
287
+ background: var(--llm-card-bg);
288
+ backdrop-filter: blur(12px);
289
+ border-radius: 0.75rem;
290
+ padding: 1.5rem;
291
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
292
+ border: 1px solid var(--llm-card-border);
293
+ color: var(--llm-text);
294
+ }
295
+
296
+ .llm-grid {
297
+ display: grid;
298
+ grid-template-columns: 1fr 1fr;
299
+ gap: 2rem;
300
+ }
301
+
302
+ @media (max-width: 640px) {
303
+ .llm-grid {
304
+ grid-template-columns: 1fr;
305
+ }
306
+ }
307
+
308
+ .llm-controls {
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 1.5rem;
312
+ }
313
+
314
+ .llm-label {
315
+ display: block;
316
+ font-size: 0.875rem;
317
+ font-weight: 500;
318
+ color: var(--llm-muted);
319
+ margin-bottom: 0.5rem;
320
+ }
321
+
322
+ .llm-select,
323
+ .llm-input {
324
+ display: block;
325
+ width: 100%;
326
+ border-radius: 0.5rem;
327
+ border: 1px solid var(--llm-input-border);
328
+ background: var(--llm-input-bg);
329
+ color: var(--llm-text);
330
+ font-size: 0.875rem;
331
+ padding: 0.625rem;
332
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
333
+ box-sizing: border-box;
334
+ }
335
+
336
+ .llm-select:focus,
337
+ .llm-input:focus {
338
+ outline: 2px solid var(--llm-accent);
339
+ outline-offset: 2px;
340
+ border-color: var(--llm-accent);
341
+ }
342
+
343
+ .llm-tokens-grid {
344
+ display: grid;
345
+ grid-template-columns: 1fr 1fr;
346
+ gap: 1rem;
347
+ }
348
+
349
+ .llm-word-hint {
350
+ margin-top: 0.25rem;
351
+ font-size: 0.75rem;
352
+ color: var(--llm-muted);
353
+ }
354
+
355
+ .llm-slider-row {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: 1rem;
359
+ }
360
+
361
+ .llm-slider {
362
+ flex: 1;
363
+ accent-color: var(--llm-accent);
364
+ cursor: pointer;
365
+ }
366
+
367
+ .llm-req-num {
368
+ width: 6rem;
369
+ border-radius: 0.5rem;
370
+ border: 1px solid var(--llm-input-border);
371
+ background: var(--llm-input-bg);
372
+ color: var(--llm-text);
373
+ font-size: 0.875rem;
374
+ padding: 0.5rem;
375
+ text-align: center;
376
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
377
+ }
378
+
379
+ .llm-req-num:focus {
380
+ outline: 2px solid var(--llm-accent);
381
+ outline-offset: 2px;
382
+ }
383
+
384
+ .llm-results {
385
+ display: flex;
386
+ flex-direction: column;
387
+ justify-content: center;
388
+ gap: 1.5rem;
389
+ background: var(--llm-panel-bg);
390
+ border-radius: 0.75rem;
391
+ padding: 1.5rem;
392
+ border: 1px solid var(--llm-panel-border);
393
+ }
394
+
395
+ .llm-stat-title {
396
+ font-size: 0.75rem;
397
+ font-weight: 500;
398
+ color: var(--llm-muted);
399
+ text-transform: uppercase;
400
+ letter-spacing: 0.05em;
401
+ text-align: center;
402
+ margin: 0 0 0.5rem;
403
+ }
404
+
405
+ .llm-cost-val {
406
+ font-size: 1.875rem;
407
+ font-weight: 700;
408
+ color: var(--llm-text);
409
+ text-align: center;
410
+ }
411
+
412
+ .llm-divider-wrap {
413
+ position: relative;
414
+ height: 1px;
415
+ background: var(--llm-divider);
416
+ }
417
+
418
+ .llm-divider-line {
419
+ position: absolute;
420
+ inset: 0;
421
+ background: var(--llm-divider);
422
+ }
423
+
424
+ .llm-divider-label {
425
+ position: absolute;
426
+ left: 50%;
427
+ top: 50%;
428
+ transform: translate(-50%, -50%);
429
+ background: var(--llm-panel-bg);
430
+ padding: 0 0.5rem;
431
+ font-size: 0.75rem;
432
+ color: var(--llm-muted);
433
+ white-space: nowrap;
434
+ }
435
+
436
+ .llm-total-title {
437
+ font-size: 0.75rem;
438
+ font-weight: 500;
439
+ color: var(--llm-accent);
440
+ text-transform: uppercase;
441
+ letter-spacing: 0.05em;
442
+ text-align: center;
443
+ margin: 0 0 0.5rem;
444
+ }
445
+
446
+ .llm-total-val {
447
+ font-size: 3rem;
448
+ font-weight: 900;
449
+ text-align: center;
450
+ background: linear-gradient(to right, var(--llm-total-from), var(--llm-total-to));
451
+ -webkit-background-clip: text;
452
+ -webkit-text-fill-color: transparent;
453
+ background-clip: text;
454
+ }
455
+
456
+ .llm-bar-section {
457
+ display: flex;
458
+ flex-direction: column;
459
+ gap: 0.5rem;
460
+ }
461
+
462
+ .llm-bar-labels {
463
+ display: flex;
464
+ justify-content: space-between;
465
+ font-size: 0.75rem;
466
+ color: var(--llm-muted);
467
+ }
468
+
469
+ .llm-bar-track {
470
+ height: 0.5rem;
471
+ background: var(--llm-divider);
472
+ border-radius: 9999px;
473
+ overflow: hidden;
474
+ display: flex;
475
+ }
476
+
477
+ .llm-bar-input {
478
+ background: #3b82f6;
479
+ height: 100%;
480
+ transition: width 0.5s;
481
+ }
482
+
483
+ .llm-bar-output {
484
+ background: #22c55e;
485
+ height: 100%;
486
+ transition: width 0.5s;
487
+ }
488
+ </style>
@@ -0,0 +1,99 @@
1
+ export interface LLMModel {
2
+ id: string;
3
+ name: string;
4
+ input: number;
5
+ output: number;
6
+ group: string;
7
+ description: string;
8
+ }
9
+
10
+ export const LLM_MODELS: LLMModel[] = [
11
+ {
12
+ id: 'gpt-5.4-pro',
13
+ name: 'GPT-5.4 Pro',
14
+ input: 30.0,
15
+ output: 180.0,
16
+ group: 'OpenAI',
17
+ description: 'Deep reasoning with specialized hardware for critical tasks.',
18
+ },
19
+ {
20
+ id: 'gpt-5.4-standard',
21
+ name: 'GPT-5.4 Standard',
22
+ input: 2.5,
23
+ output: 15.0,
24
+ group: 'OpenAI',
25
+ description: 'The gold standard in intelligence for general-purpose applications.',
26
+ },
27
+ {
28
+ id: 'gpt-5.2-reasoning',
29
+ name: 'GPT-5.2 Reasoning',
30
+ input: 1.75,
31
+ output: 14.0,
32
+ group: 'OpenAI',
33
+ description: 'Optimized for complex logic and structured thinking.',
34
+ },
35
+ {
36
+ id: 'claude-4.6-opus',
37
+ name: 'Claude 4.6 Opus',
38
+ input: 5.0,
39
+ output: 25.0,
40
+ group: 'Anthropic',
41
+ description: 'Leader in linguistic nuance and complex ethical reasoning.',
42
+ },
43
+ {
44
+ id: 'claude-4.6-sonnet',
45
+ name: 'Claude 4.6 Sonnet',
46
+ input: 3.0,
47
+ output: 15.0,
48
+ group: 'Anthropic',
49
+ description: 'Unmatched speed and technical precision for developers.',
50
+ },
51
+ {
52
+ id: 'claude-4.6-haiku',
53
+ name: 'Claude 4.6 Haiku',
54
+ input: 1.0,
55
+ output: 5.0,
56
+ group: 'Anthropic',
57
+ description: 'Sub-second efficiency for massive automation workloads.',
58
+ },
59
+ {
60
+ id: 'gemini-3-pro',
61
+ name: 'Gemini 3 Pro',
62
+ input: 2.0,
63
+ output: 12.0,
64
+ group: 'Google',
65
+ description: 'Multimodal context window of 2M+ tokens.',
66
+ },
67
+ {
68
+ id: 'gemini-3-flash',
69
+ name: 'Gemini 3 Flash',
70
+ input: 0.1,
71
+ output: 0.4,
72
+ group: 'Google',
73
+ description: 'Ultra-fast inference at the lowest market cost.',
74
+ },
75
+ {
76
+ id: 'llama-4-maverick',
77
+ name: 'Llama 4 Maverick (405B)',
78
+ input: 0.15,
79
+ output: 0.6,
80
+ group: 'Meta',
81
+ description: 'Enterprise-grade open source power.',
82
+ },
83
+ {
84
+ id: 'llama-4-scout',
85
+ name: 'Llama 4 Scout (70B)',
86
+ input: 0.08,
87
+ output: 0.3,
88
+ group: 'Meta',
89
+ description: 'Balanced efficiency for local deployments and APIs.',
90
+ },
91
+ {
92
+ id: 'deepseek-v4',
93
+ name: 'DeepSeek-V4',
94
+ input: 0.1,
95
+ output: 0.2,
96
+ group: 'DeepSeek',
97
+ description: 'Extreme optimization for coding and mathematics.',
98
+ },
99
+ ];