@jjlmoya/utils-finance 1.1.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 (133) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +159 -0
  3. package/src/category/i18n/es.ts +159 -0
  4. package/src/category/i18n/fr.ts +159 -0
  5. package/src/category/index.ts +24 -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 +32 -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/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +39 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +23 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/compoundInterest/bibliography.astro +14 -0
  25. package/src/tool/compoundInterest/component.astro +697 -0
  26. package/src/tool/compoundInterest/i18n/en.ts +212 -0
  27. package/src/tool/compoundInterest/i18n/es.ts +212 -0
  28. package/src/tool/compoundInterest/i18n/fr.ts +212 -0
  29. package/src/tool/compoundInterest/index.ts +34 -0
  30. package/src/tool/compoundInterest/logic.ts +40 -0
  31. package/src/tool/compoundInterest/seo.astro +14 -0
  32. package/src/tool/compoundInterest/ui.ts +19 -0
  33. package/src/tool/courtFeeCalculator/bibliography.astro +14 -0
  34. package/src/tool/courtFeeCalculator/component.astro +427 -0
  35. package/src/tool/courtFeeCalculator/i18n/en.ts +268 -0
  36. package/src/tool/courtFeeCalculator/i18n/es.ts +268 -0
  37. package/src/tool/courtFeeCalculator/i18n/fr.ts +268 -0
  38. package/src/tool/courtFeeCalculator/index.ts +33 -0
  39. package/src/tool/courtFeeCalculator/logic.ts +86 -0
  40. package/src/tool/courtFeeCalculator/seo.astro +14 -0
  41. package/src/tool/courtFeeCalculator/ui.ts +31 -0
  42. package/src/tool/debtSnowball/bibliography.astro +14 -0
  43. package/src/tool/debtSnowball/component.astro +1052 -0
  44. package/src/tool/debtSnowball/i18n/en.ts +288 -0
  45. package/src/tool/debtSnowball/i18n/es.ts +288 -0
  46. package/src/tool/debtSnowball/i18n/fr.ts +288 -0
  47. package/src/tool/debtSnowball/index.ts +37 -0
  48. package/src/tool/debtSnowball/logic.ts +156 -0
  49. package/src/tool/debtSnowball/seo.astro +14 -0
  50. package/src/tool/debtSnowball/ui.ts +49 -0
  51. package/src/tool/fireCalculator/bibliography.astro +14 -0
  52. package/src/tool/fireCalculator/component.astro +689 -0
  53. package/src/tool/fireCalculator/i18n/en.ts +261 -0
  54. package/src/tool/fireCalculator/i18n/es.ts +261 -0
  55. package/src/tool/fireCalculator/i18n/fr.ts +261 -0
  56. package/src/tool/fireCalculator/index.ts +33 -0
  57. package/src/tool/fireCalculator/logic.ts +108 -0
  58. package/src/tool/fireCalculator/seo.astro +14 -0
  59. package/src/tool/fireCalculator/ui.ts +33 -0
  60. package/src/tool/ibanBicSwiftConverter/bibliography.astro +14 -0
  61. package/src/tool/ibanBicSwiftConverter/component.astro +581 -0
  62. package/src/tool/ibanBicSwiftConverter/i18n/en.ts +167 -0
  63. package/src/tool/ibanBicSwiftConverter/i18n/es.ts +216 -0
  64. package/src/tool/ibanBicSwiftConverter/i18n/fr.ts +167 -0
  65. package/src/tool/ibanBicSwiftConverter/index.ts +33 -0
  66. package/src/tool/ibanBicSwiftConverter/logic.ts +101 -0
  67. package/src/tool/ibanBicSwiftConverter/seo.astro +14 -0
  68. package/src/tool/ibanBicSwiftConverter/ui.ts +17 -0
  69. package/src/tool/inflation/bibliography.astro +14 -0
  70. package/src/tool/inflation/component.astro +400 -0
  71. package/src/tool/inflation/i18n/en.ts +301 -0
  72. package/src/tool/inflation/i18n/es.ts +301 -0
  73. package/src/tool/inflation/i18n/fr.ts +301 -0
  74. package/src/tool/inflation/index.ts +34 -0
  75. package/src/tool/inflation/logic.ts +92 -0
  76. package/src/tool/inflation/seo.astro +14 -0
  77. package/src/tool/inflation/ui.ts +17 -0
  78. package/src/tool/lateInterest/bibliography.astro +14 -0
  79. package/src/tool/lateInterest/component.astro +655 -0
  80. package/src/tool/lateInterest/i18n/en.ts +255 -0
  81. package/src/tool/lateInterest/i18n/es.ts +224 -0
  82. package/src/tool/lateInterest/i18n/fr.ts +224 -0
  83. package/src/tool/lateInterest/index.ts +34 -0
  84. package/src/tool/lateInterest/logic.ts +89 -0
  85. package/src/tool/lateInterest/seo.astro +14 -0
  86. package/src/tool/lateInterest/ui.ts +33 -0
  87. package/src/tool/legalInterestRate/bibliography.astro +14 -0
  88. package/src/tool/legalInterestRate/component.astro +650 -0
  89. package/src/tool/legalInterestRate/i18n/en.ts +159 -0
  90. package/src/tool/legalInterestRate/i18n/es.ts +159 -0
  91. package/src/tool/legalInterestRate/i18n/fr.ts +159 -0
  92. package/src/tool/legalInterestRate/index.ts +33 -0
  93. package/src/tool/legalInterestRate/logic.ts +29 -0
  94. package/src/tool/legalInterestRate/seo.astro +14 -0
  95. package/src/tool/legalInterestRate/ui.ts +23 -0
  96. package/src/tool/lotteryOptimizer/bibliography.astro +14 -0
  97. package/src/tool/lotteryOptimizer/component.astro +863 -0
  98. package/src/tool/lotteryOptimizer/i18n/en.ts +188 -0
  99. package/src/tool/lotteryOptimizer/i18n/es.ts +281 -0
  100. package/src/tool/lotteryOptimizer/i18n/fr.ts +188 -0
  101. package/src/tool/lotteryOptimizer/index.ts +33 -0
  102. package/src/tool/lotteryOptimizer/logic.ts +133 -0
  103. package/src/tool/lotteryOptimizer/seo.astro +14 -0
  104. package/src/tool/lotteryOptimizer/ui.ts +33 -0
  105. package/src/tool/mortgage/bibliography.astro +14 -0
  106. package/src/tool/mortgage/component.astro +1030 -0
  107. package/src/tool/mortgage/i18n/en.ts +227 -0
  108. package/src/tool/mortgage/i18n/es.ts +227 -0
  109. package/src/tool/mortgage/i18n/fr.ts +227 -0
  110. package/src/tool/mortgage/index.ts +34 -0
  111. package/src/tool/mortgage/logic.ts +118 -0
  112. package/src/tool/mortgage/seo.astro +14 -0
  113. package/src/tool/mortgage/ui.ts +35 -0
  114. package/src/tool/percentageCalculator/bibliography.astro +14 -0
  115. package/src/tool/percentageCalculator/component.astro +640 -0
  116. package/src/tool/percentageCalculator/i18n/en.ts +233 -0
  117. package/src/tool/percentageCalculator/i18n/es.ts +233 -0
  118. package/src/tool/percentageCalculator/i18n/fr.ts +233 -0
  119. package/src/tool/percentageCalculator/index.ts +34 -0
  120. package/src/tool/percentageCalculator/logic.ts +48 -0
  121. package/src/tool/percentageCalculator/seo.astro +14 -0
  122. package/src/tool/percentageCalculator/ui.ts +30 -0
  123. package/src/tool/rentIncreaseCalculator/bibliography.astro +14 -0
  124. package/src/tool/rentIncreaseCalculator/component.astro +587 -0
  125. package/src/tool/rentIncreaseCalculator/i18n/en.ts +159 -0
  126. package/src/tool/rentIncreaseCalculator/i18n/es.ts +220 -0
  127. package/src/tool/rentIncreaseCalculator/i18n/fr.ts +159 -0
  128. package/src/tool/rentIncreaseCalculator/index.ts +33 -0
  129. package/src/tool/rentIncreaseCalculator/logic.ts +43 -0
  130. package/src/tool/rentIncreaseCalculator/seo.astro +14 -0
  131. package/src/tool/rentIncreaseCalculator/ui.ts +19 -0
  132. package/src/tools.ts +16 -0
  133. package/src/types.ts +74 -0
@@ -0,0 +1,640 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+
4
+ interface Props {
5
+ locale?: KnownLocale;
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const { ui } = Astro.props;
10
+ ---
11
+
12
+ <div class="pc-wrapper">
13
+ <div class="pc-card">
14
+ <div class="pc-tabs">
15
+ <button class="pc-tab pc-tab-active" data-case="1">{ui.labelCase1Title}</button>
16
+ <button class="pc-tab" data-case="2">{ui.labelCase2Title}</button>
17
+ <button class="pc-tab" data-case="3">{ui.labelCase3Title}</button>
18
+ <button class="pc-tab" data-case="4">{ui.labelCase4Title}</button>
19
+ </div>
20
+
21
+ <div class="pc-content">
22
+ <div class="pc-case pc-case-active" data-case="1">
23
+ <h3 class="pc-case-title">{ui.labelCase1Question}</h3>
24
+ <div class="pc-inputs">
25
+ <div class="pc-input-field">
26
+ <label for="case1-x">{ui.labelCase1Placeholder1}</label>
27
+ <div class="pc-input-container">
28
+ <input
29
+ type="number"
30
+ id="case1-x"
31
+ class="pc-input"
32
+ data-case="1"
33
+ data-field="x"
34
+ />
35
+ <span class="pc-unit">{ui.percentSymbol}</span>
36
+ </div>
37
+ </div>
38
+ <div class="pc-input-field">
39
+ <label for="case1-y">{ui.labelCase1Placeholder2}</label>
40
+ <div class="pc-input-container">
41
+ <input
42
+ type="number"
43
+ id="case1-y"
44
+ class="pc-input"
45
+ data-case="1"
46
+ data-field="y"
47
+ />
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <div class="pc-result-display">
52
+ <div class="pc-result-label">{ui.labelResult}</div>
53
+ <div class="pc-result-value" data-case="1" data-result="percent-of-number">0</div>
54
+ <button class="pc-copy-btn" data-case="1" data-result="percent-of-number" title={ui.labelCopyTooltip}>
55
+ Copy
56
+ </button>
57
+ </div>
58
+ <div class="pc-formula">{ui.labelCase1Formula}</div>
59
+ </div>
60
+
61
+ <div class="pc-case" data-case="2">
62
+ <h3 class="pc-case-title">{ui.labelCase2Question}</h3>
63
+ <div class="pc-inputs">
64
+ <div class="pc-input-field">
65
+ <label for="case2-x">{ui.labelCase2Placeholder1}</label>
66
+ <div class="pc-input-container">
67
+ <input
68
+ type="number"
69
+ id="case2-x"
70
+ class="pc-input"
71
+ data-case="2"
72
+ data-field="x"
73
+ />
74
+ </div>
75
+ </div>
76
+ <div class="pc-input-field">
77
+ <label for="case2-y">{ui.labelCase2Placeholder2}</label>
78
+ <div class="pc-input-container">
79
+ <input
80
+ type="number"
81
+ id="case2-y"
82
+ class="pc-input"
83
+ data-case="2"
84
+ data-field="y"
85
+ />
86
+ </div>
87
+ </div>
88
+ </div>
89
+ <div class="pc-result-display">
90
+ <div class="pc-result-label">{ui.labelResult}</div>
91
+ <div class="pc-result-value" data-case="2" data-result="percentage-of">
92
+ 0<span class="pc-result-unit">{ui.percentSymbol}</span>
93
+ </div>
94
+ <button class="pc-copy-btn" data-case="2" data-result="percentage-of" title={ui.labelCopyTooltip}>
95
+ Copy
96
+ </button>
97
+ </div>
98
+ <div class="pc-formula">{ui.labelCase2Formula}</div>
99
+ </div>
100
+
101
+ <div class="pc-case" data-case="3">
102
+ <h3 class="pc-case-title">{ui.labelCase3Question}</h3>
103
+ <div class="pc-inputs">
104
+ <div class="pc-input-field">
105
+ <label for="case3-v1">{ui.labelCase3Placeholder1}</label>
106
+ <div class="pc-input-container">
107
+ <input
108
+ type="number"
109
+ id="case3-v1"
110
+ class="pc-input"
111
+ data-case="3"
112
+ data-field="v1"
113
+ />
114
+ </div>
115
+ </div>
116
+ <div class="pc-input-field">
117
+ <label for="case3-v2">{ui.labelCase3Placeholder2}</label>
118
+ <div class="pc-input-container">
119
+ <input
120
+ type="number"
121
+ id="case3-v2"
122
+ class="pc-input"
123
+ data-case="3"
124
+ data-field="v2"
125
+ />
126
+ </div>
127
+ </div>
128
+ </div>
129
+ <div class="pc-result-display">
130
+ <div class="pc-result-label">{ui.labelDifference}</div>
131
+ <div class="pc-result-value" data-case="3" data-result="percentage-difference">
132
+ 0<span class="pc-result-unit">{ui.percentSymbol}</span>
133
+ </div>
134
+ <button class="pc-copy-btn" data-case="3" data-result="percentage-difference" title={ui.labelCopyTooltip}>
135
+ Copy
136
+ </button>
137
+ </div>
138
+ <div class="pc-formula">{ui.labelCase3Formula}</div>
139
+ </div>
140
+
141
+ <div class="pc-case" data-case="4">
142
+ <h3 class="pc-case-title">{ui.labelCase4Question}</h3>
143
+ <div class="pc-inputs">
144
+ <div class="pc-input-field">
145
+ <label for="case4-value">{ui.labelCase4Placeholder1}</label>
146
+ <div class="pc-input-container">
147
+ <input
148
+ type="number"
149
+ id="case4-value"
150
+ class="pc-input"
151
+ data-case="4"
152
+ data-field="value"
153
+ />
154
+ </div>
155
+ </div>
156
+ <div class="pc-input-field">
157
+ <label for="case4-percent">{ui.labelCase4Placeholder2}</label>
158
+ <div class="pc-input-container">
159
+ <input
160
+ type="number"
161
+ id="case4-percent"
162
+ class="pc-input"
163
+ data-case="4"
164
+ data-field="percent"
165
+ />
166
+ <span class="pc-unit">{ui.percentSymbol}</span>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ <div class="pc-operation-buttons">
171
+ <button class="pc-op-btn pc-op-add" data-case="4" data-operation="add">
172
+ {ui.labelCase4AddLabel}
173
+ </button>
174
+ <button class="pc-op-btn pc-op-subtract" data-case="4" data-operation="subtract">
175
+ {ui.labelCase4SubtractLabel}
176
+ </button>
177
+ </div>
178
+ <div class="pc-result-display">
179
+ <div class="pc-result-label">{ui.labelResult}</div>
180
+ <div class="pc-result-value" data-case="4" data-result="add-percentage">0</div>
181
+ <button class="pc-copy-btn" data-case="4" data-result="add-percentage" title={ui.labelCopyTooltip}>
182
+ Copy
183
+ </button>
184
+ </div>
185
+ <div class="pc-formula">{ui.labelCase4Formula}</div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
+ <style is:global>
192
+ :root {
193
+ --pc-primary: #4b5563;
194
+ --pc-primary-light: #f1f3f5;
195
+ --pc-accent: #2c7a7b;
196
+ --pc-success: #2d6a4f;
197
+ --pc-danger: #9d4456;
198
+ --pc-text: #1a202c;
199
+ --pc-text-light: #4a5568;
200
+ --pc-border: #d2d6dc;
201
+ --pc-bg: #fff;
202
+ }
203
+
204
+ @media (prefers-color-scheme: dark) {
205
+ :root {
206
+ --pc-primary: #cbd5e1;
207
+ --pc-primary-light: #334155;
208
+ --pc-accent: #7dd3c0;
209
+ --pc-text: #f1f5f9;
210
+ --pc-text-light: #cbd5e1;
211
+ --pc-border: #475569;
212
+ --pc-bg: #1e293b;
213
+ }
214
+ }
215
+
216
+ .theme-dark {
217
+ --pc-primary: #cbd5e1;
218
+ --pc-primary-light: #334155;
219
+ --pc-accent: #7dd3c0;
220
+ --pc-text: #f1f5f9;
221
+ --pc-text-light: #cbd5e1;
222
+ --pc-border: #475569;
223
+ --pc-bg: #1e293b;
224
+ }
225
+
226
+ [data-theme='dark'] {
227
+ --pc-primary: #cbd5e1;
228
+ --pc-primary-light: #334155;
229
+ --pc-accent: #7dd3c0;
230
+ --pc-text: #f1f5f9;
231
+ --pc-text-light: #cbd5e1;
232
+ --pc-border: #475569;
233
+ --pc-bg: #1e293b;
234
+ }
235
+ </style>
236
+
237
+ <style>
238
+ .pc-wrapper {
239
+ max-width: 700px;
240
+ margin: 0 auto;
241
+ padding: 1.5rem;
242
+ }
243
+
244
+ .pc-card {
245
+ background: var(--pc-bg);
246
+ border: 1px solid var(--pc-border);
247
+ border-radius: 12px;
248
+ padding: 1.5rem;
249
+ }
250
+
251
+ .pc-tabs {
252
+ display: grid;
253
+ grid-template-columns: repeat(4, 1fr);
254
+ gap: 0.35rem;
255
+ margin-bottom: 1.5rem;
256
+ background: var(--pc-primary-light);
257
+ padding: 0.35rem;
258
+ border-radius: 8px;
259
+ }
260
+
261
+ .pc-tab {
262
+ padding: 0.6rem 0.75rem;
263
+ border: none;
264
+ border-radius: 6px;
265
+ background: transparent;
266
+ color: var(--pc-text-light);
267
+ font-weight: 600;
268
+ font-size: 0.8rem;
269
+ cursor: pointer;
270
+ transition: all 0.2s;
271
+ }
272
+
273
+ .pc-tab:hover {
274
+ background: var(--pc-accent);
275
+ color: white;
276
+ }
277
+
278
+ .pc-tab-active {
279
+ background: var(--pc-accent);
280
+ color: white;
281
+ }
282
+
283
+ .pc-content {
284
+ position: relative;
285
+ }
286
+
287
+ .pc-case {
288
+ display: none;
289
+ animation: pc-fade-in 0.25s ease-out;
290
+ }
291
+
292
+ .pc-case-active {
293
+ display: block;
294
+ }
295
+
296
+ @keyframes pc-fade-in {
297
+ from {
298
+ opacity: 0;
299
+ }
300
+ to {
301
+ opacity: 1;
302
+ }
303
+ }
304
+
305
+ .pc-case-title {
306
+ font-size: 1.1rem;
307
+ font-weight: 700;
308
+ color: var(--pc-text);
309
+ margin-bottom: 1.2rem;
310
+ }
311
+
312
+ .pc-inputs {
313
+ display: grid;
314
+ grid-template-columns: 1fr 1fr;
315
+ gap: 1rem;
316
+ margin-bottom: 1.2rem;
317
+ }
318
+
319
+ .pc-input-field {
320
+ display: flex;
321
+ flex-direction: column;
322
+ }
323
+
324
+ .pc-input-field label {
325
+ font-size: 0.8rem;
326
+ font-weight: 600;
327
+ color: var(--pc-text);
328
+ margin-bottom: 0.3rem;
329
+ }
330
+
331
+ .pc-input-container {
332
+ position: relative;
333
+ display: flex;
334
+ align-items: center;
335
+ }
336
+
337
+ .pc-input {
338
+ width: 100%;
339
+ padding: 0.65rem 0.8rem;
340
+ border: 1px solid var(--pc-border);
341
+ border-radius: 6px;
342
+ font-size: 0.95rem;
343
+ background: var(--pc-bg);
344
+ color: var(--pc-text);
345
+ transition: all 0.2s;
346
+ }
347
+
348
+ .pc-input:focus {
349
+ outline: none;
350
+ border-color: var(--pc-accent);
351
+ box-shadow: 0 0 0 2px var(--pc-primary-light);
352
+ }
353
+
354
+ .pc-unit {
355
+ position: absolute;
356
+ right: 0.8rem;
357
+ font-weight: 700;
358
+ font-size: 0.9rem;
359
+ color: var(--pc-accent);
360
+ pointer-events: none;
361
+ }
362
+
363
+ .pc-operation-buttons {
364
+ display: grid;
365
+ grid-template-columns: 1fr 1fr;
366
+ gap: 0.8rem;
367
+ margin-bottom: 1.2rem;
368
+ }
369
+
370
+ .pc-op-btn {
371
+ padding: 0.75rem;
372
+ border: none;
373
+ border-radius: 6px;
374
+ font-weight: 600;
375
+ font-size: 0.9rem;
376
+ cursor: pointer;
377
+ transition: all 0.2s;
378
+ color: white;
379
+ }
380
+
381
+ .pc-op-add {
382
+ background: var(--pc-success);
383
+ }
384
+
385
+ .pc-op-add:hover {
386
+ background: #1b4332;
387
+ }
388
+
389
+ .pc-op-subtract {
390
+ background: var(--pc-danger);
391
+ }
392
+
393
+ .pc-op-subtract:hover {
394
+ background: #6d3a43;
395
+ }
396
+
397
+ .pc-result-display {
398
+ background: var(--pc-primary-light);
399
+ border: 1px solid var(--pc-border);
400
+ border-radius: 8px;
401
+ padding: 1rem;
402
+ margin-bottom: 1rem;
403
+ display: flex;
404
+ flex-direction: column;
405
+ align-items: center;
406
+ gap: 0.6rem;
407
+ position: relative;
408
+ }
409
+
410
+ .pc-result-label {
411
+ font-size: 0.75rem;
412
+ font-weight: 600;
413
+ color: var(--pc-text-light);
414
+ text-transform: uppercase;
415
+ letter-spacing: 0.3px;
416
+ }
417
+
418
+ .pc-result-value {
419
+ font-size: 2.5rem;
420
+ font-weight: 800;
421
+ color: var(--pc-accent);
422
+ display: flex;
423
+ align-items: baseline;
424
+ gap: 0.3rem;
425
+ }
426
+
427
+ .pc-result-unit {
428
+ font-size: 1.2rem;
429
+ font-weight: 700;
430
+ color: var(--pc-accent);
431
+ }
432
+
433
+ .pc-copy-btn {
434
+ position: absolute;
435
+ top: 0.75rem;
436
+ right: 0.75rem;
437
+ padding: 0.5rem 0.8rem;
438
+ border: none;
439
+ border-radius: 5px;
440
+ background: var(--pc-accent);
441
+ color: white;
442
+ font-weight: 600;
443
+ font-size: 0.75rem;
444
+ cursor: pointer;
445
+ transition: all 0.2s;
446
+ }
447
+
448
+ .pc-copy-btn:hover {
449
+ background: #1f6f6f;
450
+ transform: scale(1.05);
451
+ }
452
+
453
+ .pc-copy-btn:active {
454
+ transform: scale(0.95);
455
+ }
456
+
457
+ .pc-formula {
458
+ background: var(--pc-bg);
459
+ border: 1px solid var(--pc-border);
460
+ padding: 0.75rem;
461
+ border-radius: 6px;
462
+ font-size: 0.8rem;
463
+ color: var(--pc-text-light);
464
+ border-left: 3px solid var(--pc-accent);
465
+ font-weight: 500;
466
+ }
467
+
468
+ .pc-toast {
469
+ position: fixed;
470
+ bottom: 1.5rem;
471
+ right: 1.5rem;
472
+ background: var(--pc-accent);
473
+ color: white;
474
+ padding: 0.85rem 1.2rem;
475
+ border-radius: 6px;
476
+ font-weight: 600;
477
+ font-size: 0.9rem;
478
+ z-index: 1000;
479
+ animation: pc-slide-in 0.3s ease-out;
480
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
481
+ }
482
+
483
+ @keyframes pc-slide-in {
484
+ from {
485
+ transform: translateX(400px);
486
+ opacity: 0;
487
+ }
488
+ to {
489
+ transform: translateX(0);
490
+ opacity: 1;
491
+ }
492
+ }
493
+
494
+ @media (max-width: 640px) {
495
+ .pc-wrapper {
496
+ padding: 1rem;
497
+ }
498
+
499
+ .pc-card {
500
+ padding: 1rem;
501
+ }
502
+
503
+ .pc-tabs {
504
+ grid-template-columns: repeat(2, 1fr);
505
+ gap: 0.25rem;
506
+ padding: 0.25rem;
507
+ }
508
+
509
+ .pc-tab {
510
+ font-size: 0.7rem;
511
+ padding: 0.5rem;
512
+ }
513
+
514
+ .pc-result-value {
515
+ font-size: 1.8rem;
516
+ }
517
+
518
+ .pc-result-unit {
519
+ font-size: 0.9rem;
520
+ }
521
+
522
+ .pc-inputs {
523
+ grid-template-columns: 1fr;
524
+ gap: 0.8rem;
525
+ }
526
+ }
527
+ </style>
528
+
529
+ <script>
530
+ import { PercentageCalculator } from './logic';
531
+
532
+ function showToast(message: string) {
533
+ const existing = document.querySelector('.pc-toast');
534
+ if (existing) existing.remove();
535
+
536
+ const toast = document.createElement('div');
537
+ toast.className = 'pc-toast';
538
+ toast.textContent = message;
539
+ document.body.appendChild(toast);
540
+
541
+ setTimeout(() => toast.remove(), 2000);
542
+ }
543
+
544
+ function switchTab(caseNum: string) {
545
+ document.querySelectorAll('.pc-case').forEach((c) => c.classList.remove('pc-case-active'));
546
+ document.querySelectorAll('.pc-tab').forEach((t) => t.classList.remove('pc-tab-active'));
547
+
548
+ const activeCase = document.querySelector(`[data-case="${caseNum}"].pc-case`);
549
+ const activeTab = document.querySelector(`.pc-tab[data-case="${caseNum}"]`);
550
+
551
+ if (activeCase) activeCase.classList.add('pc-case-active');
552
+ if (activeTab) activeTab.classList.add('pc-tab-active');
553
+ }
554
+
555
+ function formatNumber(value: number): string {
556
+ return PercentageCalculator.formatNumber(value);
557
+ }
558
+
559
+ function updateCase1() {
560
+ const x = parseFloat((document.getElementById('case1-x') as HTMLInputElement)?.value || '0');
561
+ const y = parseFloat((document.getElementById('case1-y') as HTMLInputElement)?.value || '0');
562
+ const result = document.querySelector('[data-case="1"][data-result="percent-of-number"]');
563
+ if (result) {
564
+ const value = PercentageCalculator.calculatePercentOfNumber(x, y);
565
+ result.textContent = formatNumber(value);
566
+ }
567
+ }
568
+
569
+ function updateCase2() {
570
+ const x = parseFloat((document.getElementById('case2-x') as HTMLInputElement)?.value || '0');
571
+ const y = parseFloat((document.getElementById('case2-y') as HTMLInputElement)?.value || '0');
572
+ const result = document.querySelector('[data-case="2"][data-result="percentage-of"]');
573
+ if (result) {
574
+ const value = PercentageCalculator.calculateWhatPercentageIs(x, y);
575
+ const displayValue = result as HTMLElement;
576
+ displayValue.textContent = formatNumber(value);
577
+ displayValue.innerHTML = `${formatNumber(value)}<span class="pc-result-unit">%</span>`;
578
+ }
579
+ }
580
+
581
+ function updateCase3() {
582
+ const v1 = parseFloat((document.getElementById('case3-v1') as HTMLInputElement)?.value || '0');
583
+ const v2 = parseFloat((document.getElementById('case3-v2') as HTMLInputElement)?.value || '0');
584
+ const result = document.querySelector('[data-case="3"][data-result="percentage-difference"]');
585
+ if (result) {
586
+ const value = PercentageCalculator.calculatePercentageDifference(v1, v2);
587
+ const displayValue = result as HTMLElement;
588
+ displayValue.innerHTML = `${formatNumber(value)}<span class="pc-result-unit">%</span>`;
589
+ }
590
+ }
591
+
592
+ function updateCase4() {
593
+ const value = parseFloat((document.getElementById('case4-value') as HTMLInputElement)?.value || '0');
594
+ const percent = parseFloat((document.getElementById('case4-percent') as HTMLInputElement)?.value || '0');
595
+ const result = document.querySelector('[data-case="4"][data-result="add-percentage"]');
596
+ if (result) {
597
+ const addValue = PercentageCalculator.calculateAddPercentage(value, percent);
598
+ result.textContent = formatNumber(addValue);
599
+ }
600
+ }
601
+
602
+ function updateAllResults() {
603
+ updateCase1();
604
+ updateCase2();
605
+ updateCase3();
606
+ updateCase4();
607
+ }
608
+
609
+ document.querySelectorAll('.pc-input').forEach((input) => {
610
+ input.addEventListener('input', updateAllResults);
611
+ });
612
+
613
+ document.querySelectorAll('.pc-tab').forEach((tab) => {
614
+ tab.addEventListener('click', () => {
615
+ const caseNum = tab.getAttribute('data-case');
616
+ if (caseNum) switchTab(caseNum);
617
+ });
618
+ });
619
+
620
+ document.querySelectorAll('.pc-copy-btn-big').forEach((btn) => {
621
+ btn.addEventListener('click', async (e) => {
622
+ e.preventDefault();
623
+ const button = btn as HTMLElement;
624
+ const resultElement = document.querySelector(
625
+ `[data-case="${button.dataset.case}"][data-result="${button.dataset.result}"]`
626
+ );
627
+ if (resultElement) {
628
+ const text = resultElement.textContent?.replace('%', '') || '';
629
+ try {
630
+ await navigator.clipboard.writeText(text);
631
+ showToast('Copied!');
632
+ } catch {
633
+ showToast('Failed to copy');
634
+ }
635
+ }
636
+ });
637
+ });
638
+
639
+ updateAllResults();
640
+ </script>