@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,581 @@
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="ibs-container" data-icon-bank="mdi:bank-outline" data-icon-card="mdi:credit-card-chip-outline" data-icon-sync="mdi:sync" data-icon-copy="mdi:content-copy" data-icon-check="mdi:check" data-icon-alert="mdi:alert-circle" data-icon-bank-check="mdi:bank-check">
13
+ <div class="ibs-card">
14
+ <header class="ibs-header">
15
+ <h3>{ui.labelTitle}</h3>
16
+ <p>{ui.labelDescription}</p>
17
+ </header>
18
+
19
+ <div class="ibs-input-section">
20
+ <div class="ibs-input-group">
21
+ <label for="iban-input">
22
+ {ui.labelInputIBAN}
23
+ </label>
24
+ <div class="ibs-input-wrapper">
25
+ <input
26
+ type="text"
27
+ id="iban-input"
28
+ placeholder={ui.labelInputPlaceholder}
29
+ autocomplete="off"
30
+ maxlength="34"
31
+ />
32
+ <span class="ibs-input-icon" data-icon="card"></span>
33
+ </div>
34
+ </div>
35
+
36
+ <button id="btn-convert" class="ibs-btn-convert">
37
+ <span data-icon="sync"></span> {ui.labelButtonConvert}
38
+ </button>
39
+ </div>
40
+
41
+ <div id="results-area" class="ibs-results-section ibs-hidden">
42
+ <div class="ibs-result-card">
43
+ <span class="ibs-result-label">{ui.labelBIC}</span>
44
+ <div class="ibs-result-value" id="res-bic">-</div>
45
+ <button class="ibs-btn-copy-result" data-copy="res-bic" title={ui.labelCopyButton}>
46
+ <span data-icon="copy"></span>
47
+ </button>
48
+ </div>
49
+
50
+ <div class="ibs-result-card">
51
+ <span class="ibs-result-label">{ui.labelCountry}</span>
52
+ <div class="ibs-result-value" id="res-country">-</div>
53
+ </div>
54
+
55
+ <div class="ibs-bank-info" id="bank-info-box">
56
+ <div class="ibs-bank-logo-placeholder" data-icon="bank">
57
+ </div>
58
+ <div class="ibs-bank-details">
59
+ <div class="ibs-bank-name-label">{ui.labelBank}</div>
60
+ <div class="ibs-bank-name-value" id="res-bank-name">-</div>
61
+ </div>
62
+ </div>
63
+
64
+ <div id="iban-error" class="ibs-error-message ibs-hidden">
65
+ <span data-icon="alert"></span> {ui.labelError}
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <style>
72
+ :root {
73
+ --ibs-bg-card: #fff;
74
+ --ibs-border-card: #eef2f6;
75
+ --ibs-shadow-card: 0 4px 6px -1px rgba(0, 0, 0, 0.02), 0 20px 40px -8px rgba(0, 0, 0, 0.05);
76
+ --ibs-header-bg: linear-gradient(90deg, #6366f1, #a855f7);
77
+ --ibs-text-title: #0f172a;
78
+ --ibs-text-secondary: #64748b;
79
+ --ibs-text-primary: #1e293b;
80
+ --ibs-bg-input-section: #f8fafc;
81
+ --ibs-border-input-section: #f1f5f9;
82
+ --ibs-bg-input: #fff;
83
+ --ibs-text-input: #1e293b;
84
+ --ibs-border-input: transparent;
85
+ --ibs-focus-border: #6366f1;
86
+ --ibs-focus-ring: rgba(99, 102, 241, 0.15);
87
+ --ibs-icon-color: #6366f1;
88
+ --ibs-bg-button: #0f172a;
89
+ --ibs-text-button: #fff;
90
+ --ibs-bg-button-hover: #1e293b;
91
+ --ibs-bg-result: #fff;
92
+ --ibs-border-result: #eef2f6;
93
+ --ibs-text-result-value: #0f172a;
94
+ --ibs-text-result-label: #94a3b8;
95
+ --ibs-bg-bank-info: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
96
+ --ibs-border-bank-info: #e2e8f0;
97
+ --ibs-bg-error: #fef2f2;
98
+ --ibs-border-error: #fee2e2;
99
+ --ibs-text-error: #dc2626;
100
+ --ibs-bg-copy-btn: #f1f5f9;
101
+ --ibs-text-copy-btn: #64748b;
102
+ }
103
+
104
+ :global(.theme-dark) {
105
+ --ibs-bg-card: #0f172a;
106
+ --ibs-border-card: rgba(255, 255, 255, 0.05);
107
+ --ibs-text-title: #f8fafc;
108
+ --ibs-text-secondary: #94a3b8;
109
+ --ibs-text-primary: #f8fafc;
110
+ --ibs-bg-input-section: rgba(30, 41, 59, 0.5);
111
+ --ibs-border-input-section: rgba(255, 255, 255, 0.05);
112
+ --ibs-bg-input: #1e293b;
113
+ --ibs-text-input: #f8fafc;
114
+ --ibs-border-input: #334155;
115
+ --ibs-focus-border: #6366f1;
116
+ --ibs-focus-ring: rgba(99, 102, 241, 0.15);
117
+ --ibs-icon-color: #60a5fa;
118
+ --ibs-bg-button: #6366f1;
119
+ --ibs-text-button: #fff;
120
+ --ibs-bg-button-hover: #4f46e5;
121
+ --ibs-bg-result: #1e293b;
122
+ --ibs-border-result: rgba(255, 255, 255, 0.05);
123
+ --ibs-text-result-value: #f8fafc;
124
+ --ibs-text-result-label: #94a3b8;
125
+ --ibs-bg-bank-info: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
126
+ --ibs-border-bank-info: rgba(255, 255, 255, 0.05);
127
+ --ibs-bg-error: rgba(220, 38, 38, 0.1);
128
+ --ibs-border-error: rgba(220, 38, 38, 0.2);
129
+ --ibs-text-error: #fca5a5;
130
+ --ibs-bg-copy-btn: #0f172a;
131
+ --ibs-text-copy-btn: #94a3b8;
132
+ }
133
+
134
+ :global([data-theme='dark']) {
135
+ --ibs-bg-card: #0f172a;
136
+ --ibs-border-card: rgba(255, 255, 255, 0.05);
137
+ --ibs-text-title: #f8fafc;
138
+ --ibs-text-secondary: #94a3b8;
139
+ --ibs-text-primary: #f8fafc;
140
+ --ibs-bg-input-section: rgba(30, 41, 59, 0.5);
141
+ --ibs-border-input-section: rgba(255, 255, 255, 0.05);
142
+ --ibs-bg-input: #1e293b;
143
+ --ibs-text-input: #f8fafc;
144
+ --ibs-border-input: #334155;
145
+ --ibs-focus-border: #6366f1;
146
+ --ibs-focus-ring: rgba(99, 102, 241, 0.15);
147
+ --ibs-icon-color: #60a5fa;
148
+ --ibs-bg-button: #6366f1;
149
+ --ibs-text-button: #fff;
150
+ --ibs-bg-button-hover: #4f46e5;
151
+ --ibs-bg-result: #1e293b;
152
+ --ibs-border-result: rgba(255, 255, 255, 0.05);
153
+ --ibs-text-result-value: #f8fafc;
154
+ --ibs-text-result-label: #94a3b8;
155
+ --ibs-bg-bank-info: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
156
+ --ibs-border-bank-info: rgba(255, 255, 255, 0.05);
157
+ --ibs-bg-error: rgba(220, 38, 38, 0.1);
158
+ --ibs-border-error: rgba(220, 38, 38, 0.2);
159
+ --ibs-text-error: #fca5a5;
160
+ --ibs-bg-copy-btn: #0f172a;
161
+ --ibs-text-copy-btn: #94a3b8;
162
+ }
163
+
164
+ .ibs-container {
165
+ width: 100%;
166
+ max-width: 900px;
167
+ margin: 0 auto;
168
+ perspective: 1000px;
169
+ padding: 2rem 1rem;
170
+ }
171
+
172
+ .ibs-card {
173
+ background: var(--ibs-bg-card);
174
+ border: 1px solid var(--ibs-border-card);
175
+ border-radius: 32px;
176
+ padding: 3rem;
177
+ box-shadow: var(--ibs-shadow-card);
178
+ position: relative;
179
+ overflow: hidden;
180
+ color-scheme: light;
181
+ }
182
+
183
+ :global(.theme-dark) .ibs-card,
184
+ :global([data-theme='dark']) .ibs-card {
185
+ color-scheme: dark;
186
+ }
187
+
188
+ .ibs-card::before {
189
+ content: '';
190
+ position: absolute;
191
+ top: 0;
192
+ left: 0;
193
+ right: 0;
194
+ height: 6px;
195
+ background: var(--ibs-header-bg);
196
+ }
197
+
198
+ .ibs-header {
199
+ text-align: left;
200
+ margin-bottom: 3rem;
201
+ }
202
+
203
+ .ibs-header h3 {
204
+ font-size: 2.25rem;
205
+ font-weight: 950;
206
+ color: var(--ibs-text-title);
207
+ letter-spacing: -0.04em;
208
+ margin-bottom: 0.75rem;
209
+ line-height: 1;
210
+ }
211
+
212
+ .ibs-header p {
213
+ color: var(--ibs-text-secondary);
214
+ font-size: 1.1rem;
215
+ max-width: 500px;
216
+ line-height: 1.6;
217
+ }
218
+
219
+ .ibs-input-section {
220
+ display: flex;
221
+ flex-direction: column;
222
+ gap: 2rem;
223
+ background: var(--ibs-bg-input-section);
224
+ padding: 2rem;
225
+ border-radius: 24px;
226
+ border: 1px solid var(--ibs-border-input-section);
227
+ }
228
+
229
+ .ibs-input-group {
230
+ display: flex;
231
+ flex-direction: column;
232
+ gap: 1rem;
233
+ }
234
+
235
+ .ibs-input-group label {
236
+ font-size: 0.75rem;
237
+ font-weight: 800;
238
+ color: var(--ibs-text-secondary);
239
+ text-transform: uppercase;
240
+ letter-spacing: 0.1em;
241
+ }
242
+
243
+ .ibs-input-wrapper {
244
+ position: relative;
245
+ display: flex;
246
+ align-items: center;
247
+ background: var(--ibs-bg-input);
248
+ border-radius: 20px;
249
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03);
250
+ border: 2px solid var(--ibs-border-input);
251
+ transition: all 0.3s ease;
252
+ }
253
+
254
+ .ibs-input-wrapper:focus-within {
255
+ border-color: var(--ibs-focus-border);
256
+ box-shadow: 0 0 0 4px var(--ibs-focus-ring);
257
+ }
258
+
259
+ .ibs-input-wrapper input {
260
+ width: 100%;
261
+ padding: 1.5rem;
262
+ border: none;
263
+ background: transparent;
264
+ font-size: 1.5rem;
265
+ font-weight: 800;
266
+ color: var(--ibs-text-input);
267
+ outline: none;
268
+ letter-spacing: 0.02em;
269
+ }
270
+
271
+ .ibs-input-wrapper input::placeholder {
272
+ color: var(--ibs-text-secondary);
273
+ opacity: 0.5;
274
+ }
275
+
276
+ .ibs-input-icon {
277
+ margin-right: 1.5rem;
278
+ font-size: 1.75rem;
279
+ opacity: 0.6;
280
+ flex-shrink: 0;
281
+ }
282
+
283
+ .ibs-btn-convert {
284
+ width: 100%;
285
+ padding: 1.25rem;
286
+ background: var(--ibs-bg-button);
287
+ color: var(--ibs-text-button);
288
+ border: none;
289
+ border-radius: 20px;
290
+ font-size: 1.1rem;
291
+ font-weight: 800;
292
+ cursor: pointer;
293
+ transition: all 0.3s ease;
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: center;
297
+ gap: 0.75rem;
298
+ }
299
+
300
+ .ibs-btn-convert:hover {
301
+ background: var(--ibs-bg-button-hover);
302
+ transform: translateY(-2px);
303
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
304
+ }
305
+
306
+ .ibs-results-section {
307
+ margin-top: 3rem;
308
+ display: grid;
309
+ grid-template-columns: repeat(2, 1fr);
310
+ gap: 1.5rem;
311
+ animation: ibs-slide-up 0.5s ease-out;
312
+ }
313
+
314
+ :global(.ibs-results-section.ibs-hidden) {
315
+ display: none;
316
+ }
317
+
318
+ :global(.ibs-error-message.ibs-hidden) {
319
+ display: none;
320
+ }
321
+
322
+ :global(.ibs-btn-copy-success span[data-icon]) {
323
+ background-color: #10b981;
324
+ }
325
+
326
+ @keyframes ibs-slide-up {
327
+ from {
328
+ opacity: 0;
329
+ transform: translateY(20px);
330
+ }
331
+ to {
332
+ opacity: 1;
333
+ transform: translateY(0);
334
+ }
335
+ }
336
+
337
+ .ibs-result-card {
338
+ background: var(--ibs-bg-result);
339
+ border: 1px solid var(--ibs-border-result);
340
+ padding: 1.5rem 2rem;
341
+ border-radius: 24px;
342
+ display: flex;
343
+ flex-direction: column;
344
+ justify-content: center;
345
+ gap: 0.5rem;
346
+ position: relative;
347
+ transition: all 0.3s ease;
348
+ }
349
+
350
+ .ibs-result-card:hover {
351
+ border-color: var(--ibs-border-card);
352
+ box-shadow: 0 12px 24px -10px rgba(0, 0, 0, 0.05);
353
+ }
354
+
355
+ .ibs-result-label {
356
+ font-size: 0.7rem;
357
+ font-weight: 800;
358
+ color: var(--ibs-text-result-label);
359
+ text-transform: uppercase;
360
+ letter-spacing: 0.08em;
361
+ }
362
+
363
+ .ibs-result-value {
364
+ font-size: 1.5rem;
365
+ font-weight: 900;
366
+ color: var(--ibs-text-result-value);
367
+ letter-spacing: -0.02em;
368
+ overflow-wrap: break-word;
369
+ }
370
+
371
+ .ibs-btn-copy-result {
372
+ position: absolute;
373
+ top: 1rem;
374
+ right: 1rem;
375
+ cursor: pointer;
376
+ background: var(--ibs-bg-copy-btn);
377
+ border: none;
378
+ padding: 8px;
379
+ border-radius: 10px;
380
+ color: var(--ibs-text-copy-btn);
381
+ transition: all 0.2s;
382
+ display: flex;
383
+ font-size: 1.2rem;
384
+ }
385
+
386
+ .ibs-btn-copy-result:hover {
387
+ background: var(--ibs-focus-border);
388
+ color: #fff;
389
+ transform: scale(1.1);
390
+ }
391
+
392
+ .ibs-btn-copy-success {
393
+ background: #10b981;
394
+ }
395
+
396
+ .ibs-bank-info {
397
+ grid-column: span 2;
398
+ background: var(--ibs-bg-bank-info);
399
+ padding: 2rem;
400
+ border-radius: 24px;
401
+ display: flex;
402
+ align-items: center;
403
+ gap: 1.5rem;
404
+ border: 1px solid var(--ibs-border-bank-info);
405
+ }
406
+
407
+ .ibs-bank-logo-placeholder {
408
+ width: 64px;
409
+ height: 64px;
410
+ background: var(--ibs-bg-input);
411
+ border-radius: 20px;
412
+ display: flex;
413
+ align-items: center;
414
+ justify-content: center;
415
+ border: 1px solid var(--ibs-border-result);
416
+ font-size: 2rem;
417
+ box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.04);
418
+ flex-shrink: 0;
419
+ }
420
+
421
+ .ibs-bank-details {
422
+ flex: 1;
423
+ }
424
+
425
+ .ibs-bank-name-label {
426
+ font-size: 0.75rem;
427
+ font-weight: 800;
428
+ color: var(--ibs-text-secondary);
429
+ text-transform: uppercase;
430
+ margin-bottom: 0.25rem;
431
+ }
432
+
433
+ .ibs-bank-name-value {
434
+ font-size: 1.5rem;
435
+ font-weight: 900;
436
+ color: var(--ibs-text-result-value);
437
+ letter-spacing: -0.03em;
438
+ }
439
+
440
+ .ibs-error-message {
441
+ grid-column: span 2;
442
+ background: var(--ibs-bg-error);
443
+ border: 1px solid var(--ibs-border-error);
444
+ padding: 1.25rem;
445
+ border-radius: 20px;
446
+ color: var(--ibs-text-error);
447
+ font-size: 1rem;
448
+ font-weight: 700;
449
+ display: flex;
450
+ align-items: center;
451
+ gap: 0.75rem;
452
+ }
453
+
454
+ @media (max-width: 768px) {
455
+ .ibs-card {
456
+ padding: 2rem;
457
+ }
458
+
459
+ .ibs-header h3 {
460
+ font-size: 1.75rem;
461
+ }
462
+
463
+ .ibs-input-wrapper input {
464
+ font-size: 1.2rem;
465
+ }
466
+ }
467
+
468
+ @media (max-width: 640px) {
469
+ .ibs-results-section {
470
+ grid-template-columns: 1fr;
471
+ }
472
+
473
+ .ibs-bank-info {
474
+ grid-column: span 1;
475
+ flex-direction: column;
476
+ text-align: center;
477
+ gap: 1rem;
478
+ }
479
+
480
+ .ibs-error-message {
481
+ grid-column: span 1;
482
+ }
483
+ }
484
+ </style>
485
+
486
+ <script>
487
+ import { IBANConverter } from './logic';
488
+
489
+ const principalInput = document.getElementById("iban-input") as HTMLInputElement | null;
490
+ const btn = document.getElementById("btn-convert");
491
+ const resultsArea = document.getElementById("results-area");
492
+ const resBic = document.getElementById("res-bic");
493
+ const resCountry = document.getElementById("res-country");
494
+ const resBank = document.getElementById("res-bank-name");
495
+ const errorMsg = document.getElementById("iban-error");
496
+ const bankInfoBox = document.getElementById("bank-info-box");
497
+
498
+ function handleInput() {
499
+ if (!principalInput) return;
500
+
501
+ const value = principalInput.value.toUpperCase().replace(/[^A-Z0-9]/g, "");
502
+ let formatted = "";
503
+ for (let i = 0; i < value.length; i++) {
504
+ if (i > 0 && i % 4 === 0) formatted += " ";
505
+ formatted += value[i];
506
+ }
507
+ principalInput.value = formatted;
508
+
509
+ errorMsg?.classList.add("ibs-hidden");
510
+ if (value.length === 0) {
511
+ resultsArea?.classList.add("ibs-hidden");
512
+ }
513
+ }
514
+
515
+ function displayInvalidResult() {
516
+ errorMsg?.classList.remove("ibs-hidden");
517
+ bankInfoBox?.classList.add("ibs-hidden");
518
+ if (resBic) resBic.innerText = "-";
519
+ if (resCountry) resCountry.innerText = "Inválido";
520
+ if (resBank) resBank.innerText = "IBAN No Válido";
521
+ }
522
+
523
+ function displayBankInfo(result: ReturnType<typeof IBANConverter.validate>) {
524
+ if (result.bankData) {
525
+ if (resBic) resBic.innerText = result.bankData.bic;
526
+ if (resBank) resBank.innerText = result.bankData.name;
527
+ } else {
528
+ const message = result.countryCode !== "ES" ? "Banco fuera de España (Datos limitados)" : "Entidad no identificada";
529
+ if (resBic) resBic.innerText = "BIC Desconocido";
530
+ if (resBank) resBank.innerText = message;
531
+ }
532
+ }
533
+
534
+ function displayValidResult(result: ReturnType<typeof IBANConverter.validate>) {
535
+ errorMsg?.classList.add("ibs-hidden");
536
+ bankInfoBox?.classList.remove("ibs-hidden");
537
+
538
+ if (resCountry) resCountry.innerText = `${result.countryCode} (Formato OK)`;
539
+ displayBankInfo(result);
540
+ }
541
+
542
+ function handleCalculate() {
543
+ if (!principalInput) return;
544
+
545
+ const val = principalInput.value || "";
546
+ if (val.length === 0) {
547
+ resultsArea?.classList.add("ibs-hidden");
548
+ return;
549
+ }
550
+
551
+ const result = IBANConverter.validate(val);
552
+ resultsArea?.classList.remove("ibs-hidden");
553
+
554
+ if (!result.isValid) {
555
+ displayInvalidResult();
556
+ return;
557
+ }
558
+
559
+ displayValidResult(result);
560
+ }
561
+
562
+ principalInput?.addEventListener("input", handleInput);
563
+ principalInput?.addEventListener("keydown", (e) => {
564
+ if (e.key === "Enter") handleCalculate();
565
+ });
566
+
567
+ btn?.addEventListener("click", handleCalculate);
568
+
569
+ document.querySelectorAll(".ibs-btn-copy-result").forEach((copyBtn) => {
570
+ copyBtn.addEventListener("click", () => {
571
+ const targetId = copyBtn.getAttribute("data-copy");
572
+ const text = document.getElementById(targetId!)?.innerText;
573
+ if (text && text !== "-") {
574
+ navigator.clipboard.writeText(text);
575
+ const originalClass = copyBtn.className;
576
+ copyBtn.className = "ibs-btn-copy-result ibs-btn-copy-success";
577
+ setTimeout(() => (copyBtn.className = originalClass), 2000);
578
+ }
579
+ });
580
+ });
581
+ </script>