@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,587 @@
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="ric-container" data-icon-legal="mdi:shield-check" data-icon-copy="mdi:content-copy">
13
+ <div class="ric-card">
14
+ <header class="ric-header">
15
+ <h3>{ui.labelTitle}</h3>
16
+ <p>{ui.labelDescription}</p>
17
+ </header>
18
+
19
+ <div class="ric-layout">
20
+ <div class="ric-inputs-section">
21
+ <div class="ric-input-group">
22
+ <label for="current-rent">
23
+ {ui.labelCurrentRent}
24
+ </label>
25
+ <div class="ric-input-wrapper">
26
+ <input
27
+ type="number"
28
+ id="current-rent"
29
+ placeholder={ui.labelCurrentRentPlaceholder}
30
+ min="0"
31
+ step="0.01"
32
+ value="800"
33
+ />
34
+ <span class="ric-input-unit">{ui.currencySymbol}/mes</span>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="ric-input-group">
39
+ <label for="increment-type">
40
+ {ui.labelIncrementType}
41
+ </label>
42
+ <div class="ric-input-wrapper">
43
+ <select id="increment-type">
44
+ <option value="legal" selected>{ui.labelLegalLimit}</option>
45
+ <option value="manual">{ui.labelManualIncrement}</option>
46
+ </select>
47
+ </div>
48
+ </div>
49
+
50
+ <div id="manual-input-container" class="ric-input-group ric-hidden">
51
+ <label for="manual-percentage">
52
+ {ui.labelManualIncrement}
53
+ </label>
54
+ <div class="ric-input-wrapper">
55
+ <input
56
+ type="number"
57
+ id="manual-percentage"
58
+ placeholder={ui.labelManualPercentagePlaceholder}
59
+ step="0.1"
60
+ value="3"
61
+ />
62
+ <span class="ric-input-unit">%</span>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="ric-results-section ric-hidden" id="results-area">
68
+ <div class="ric-result-header">
69
+ <span class="ric-result-label">{ui.labelNewRentEstimate}</span>
70
+ <div class="ric-result-main-value" id="val-new-rent">0,00 {ui.currencySymbol}</div>
71
+ </div>
72
+
73
+ <div class="ric-comparison-bar">
74
+ <div class="ric-bar-base">
75
+ <div class="ric-bar-fill" id="increase-bar-fill"></div>
76
+ </div>
77
+ <div class="ric-bar-labels">
78
+ <span id="label-old-rent">0,00 {ui.currencySymbol}</span>
79
+ <span id="label-new-rent">0,00 {ui.currencySymbol}</span>
80
+ </div>
81
+ </div>
82
+
83
+ <div class="ric-summary-details">
84
+ <div class="ric-detail-item">
85
+ <span class="ric-detail-label">{ui.labelMonthlyIncrease}</span>
86
+ <span class="ric-detail-value" id="val-monthly-increase">+0,00 {ui.currencySymbol}</span>
87
+ </div>
88
+ <div class="ric-detail-item">
89
+ <span class="ric-detail-label">{ui.labelYearlyExtra}</span>
90
+ <span class="ric-detail-value ric-highlight" id="val-yearly-increase">+0,00 {ui.currencySymbol}</span>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="ric-info-footer">
97
+ <div class="ric-legal-badge" data-icon="legal">
98
+ <span>{ui.labelLegalBadge}</span>
99
+ </div>
100
+
101
+ <button class="ric-action-btn" id="copy-summary">
102
+ <span data-icon="copy"></span>
103
+ <span>{ui.labelCopyButton}</span>
104
+ </button>
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ <style>
110
+ :root {
111
+ --ric-bg-card: #fff;
112
+ --ric-border-card: #eef2f6;
113
+ --ric-shadow-card: 0 4px 6px -1px rgba(0, 0, 0, 0.02), 0 20px 40px -8px rgba(0, 0, 0, 0.05);
114
+ --ric-header-bg: linear-gradient(90deg, #10b981, #14b8a6);
115
+ --ric-text-title: #0f172a;
116
+ --ric-text-secondary: #64748b;
117
+ --ric-text-primary: #1e293b;
118
+ --ric-bg-input-section: #f8fafc;
119
+ --ric-border-input-section: #f1f5f9;
120
+ --ric-bg-input: #fff;
121
+ --ric-text-input: #1e293b;
122
+ --ric-border-input: transparent;
123
+ --ric-focus-border: #10b981;
124
+ --ric-focus-ring: rgba(16, 185, 129, 0.15);
125
+ --ric-icon-color: #10b981;
126
+ --ric-bg-result: #fff;
127
+ --ric-border-result: #eef2f6;
128
+ --ric-text-result-value: #0f172a;
129
+ --ric-text-result-label: #94a3b8;
130
+ --ric-bg-bar: #f1f5f9;
131
+ --ric-bg-bar-fill: #10b981;
132
+ --ric-text-bar: #64748b;
133
+ --ric-bg-badge: #d1fae5;
134
+ --ric-text-badge: #059669;
135
+ --ric-bg-button: #0f172a;
136
+ --ric-text-button: #fff;
137
+ --ric-bg-button-hover: #1e293b;
138
+ --ric-highlight-color: #10b981;
139
+ }
140
+
141
+ :global(.theme-dark) {
142
+ --ric-bg-card: #0f172a;
143
+ --ric-border-card: rgba(255, 255, 255, 0.05);
144
+ --ric-text-title: #f8fafc;
145
+ --ric-text-secondary: #94a3b8;
146
+ --ric-text-primary: #f8fafc;
147
+ --ric-bg-input-section: rgba(30, 41, 59, 0.5);
148
+ --ric-border-input-section: rgba(255, 255, 255, 0.05);
149
+ --ric-bg-input: #1e293b;
150
+ --ric-text-input: #f8fafc;
151
+ --ric-border-input: #334155;
152
+ --ric-focus-border: #10b981;
153
+ --ric-focus-ring: rgba(16, 185, 129, 0.15);
154
+ --ric-icon-color: #34d399;
155
+ --ric-bg-result: #1e293b;
156
+ --ric-border-result: rgba(255, 255, 255, 0.05);
157
+ --ric-text-result-value: #f8fafc;
158
+ --ric-text-result-label: #94a3b8;
159
+ --ric-bg-bar: #1e293b;
160
+ --ric-bg-bar-fill: #10b981;
161
+ --ric-text-bar: #cbd5e1;
162
+ --ric-bg-badge: rgba(16, 185, 129, 0.1);
163
+ --ric-text-badge: #6ee7b7;
164
+ --ric-bg-button: #10b981;
165
+ --ric-text-button: #fff;
166
+ --ric-bg-button-hover: #059669;
167
+ --ric-highlight-color: #34d399;
168
+ }
169
+
170
+ :global([data-theme='dark']) {
171
+ --ric-bg-card: #0f172a;
172
+ --ric-border-card: rgba(255, 255, 255, 0.05);
173
+ --ric-text-title: #f8fafc;
174
+ --ric-text-secondary: #94a3b8;
175
+ --ric-text-primary: #f8fafc;
176
+ --ric-bg-input-section: rgba(30, 41, 59, 0.5);
177
+ --ric-border-input-section: rgba(255, 255, 255, 0.05);
178
+ --ric-bg-input: #1e293b;
179
+ --ric-text-input: #f8fafc;
180
+ --ric-border-input: #334155;
181
+ --ric-focus-border: #10b981;
182
+ --ric-focus-ring: rgba(16, 185, 129, 0.15);
183
+ --ric-icon-color: #34d399;
184
+ --ric-bg-result: #1e293b;
185
+ --ric-border-result: rgba(255, 255, 255, 0.05);
186
+ --ric-text-result-value: #f8fafc;
187
+ --ric-text-result-label: #94a3b8;
188
+ --ric-bg-bar: #1e293b;
189
+ --ric-bg-bar-fill: #10b981;
190
+ --ric-text-bar: #cbd5e1;
191
+ --ric-bg-badge: rgba(16, 185, 129, 0.1);
192
+ --ric-text-badge: #6ee7b7;
193
+ --ric-bg-button: #10b981;
194
+ --ric-text-button: #fff;
195
+ --ric-bg-button-hover: #059669;
196
+ --ric-highlight-color: #34d399;
197
+ }
198
+
199
+ .ric-container {
200
+ width: 100%;
201
+ max-width: 900px;
202
+ margin: 0 auto;
203
+ perspective: 1000px;
204
+ padding: 2rem 1rem;
205
+ }
206
+
207
+ .ric-card {
208
+ background: var(--ric-bg-card);
209
+ border: 1px solid var(--ric-border-card);
210
+ border-radius: 32px;
211
+ padding: 3rem;
212
+ box-shadow: var(--ric-shadow-card);
213
+ position: relative;
214
+ overflow: hidden;
215
+ color-scheme: light;
216
+ }
217
+
218
+ :global(.theme-dark) .ric-card,
219
+ :global([data-theme='dark']) .ric-card {
220
+ color-scheme: dark;
221
+ }
222
+
223
+ .ric-card::before {
224
+ content: '';
225
+ position: absolute;
226
+ top: 0;
227
+ left: 0;
228
+ right: 0;
229
+ height: 6px;
230
+ background: var(--ric-header-bg);
231
+ }
232
+
233
+ .ric-header {
234
+ text-align: left;
235
+ margin-bottom: 3rem;
236
+ }
237
+
238
+ .ric-header h3 {
239
+ font-size: 2.25rem;
240
+ font-weight: 950;
241
+ color: var(--ric-text-title);
242
+ letter-spacing: -0.04em;
243
+ margin-bottom: 0.75rem;
244
+ line-height: 1;
245
+ }
246
+
247
+ .ric-header p {
248
+ color: var(--ric-text-secondary);
249
+ font-size: 1.1rem;
250
+ max-width: 500px;
251
+ }
252
+
253
+ .ric-layout {
254
+ display: grid;
255
+ grid-template-columns: 1fr 1fr;
256
+ gap: 2rem;
257
+ margin-bottom: 2rem;
258
+ }
259
+
260
+ .ric-inputs-section {
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: 1.5rem;
264
+ }
265
+
266
+ .ric-input-group {
267
+ display: flex;
268
+ flex-direction: column;
269
+ gap: 0.75rem;
270
+ }
271
+
272
+ .ric-input-group label {
273
+ font-size: 0.875rem;
274
+ font-weight: 600;
275
+ color: var(--ric-text-primary);
276
+ text-transform: uppercase;
277
+ letter-spacing: 0.05em;
278
+ }
279
+
280
+ .ric-input-wrapper {
281
+ position: relative;
282
+ display: flex;
283
+ align-items: center;
284
+ }
285
+
286
+ .ric-input-wrapper input,
287
+ .ric-input-wrapper select {
288
+ width: 100%;
289
+ padding: 0.875rem 1rem;
290
+ padding-left: 1rem;
291
+ border: 1px solid var(--ric-border-input);
292
+ border-radius: 12px;
293
+ background: var(--ric-bg-input);
294
+ color: var(--ric-text-input);
295
+ font-size: 1rem;
296
+ transition: all 0.2s;
297
+ }
298
+
299
+ .ric-input-wrapper input:focus,
300
+ .ric-input-wrapper select:focus {
301
+ outline: none;
302
+ border-color: var(--ric-focus-border);
303
+ box-shadow: 0 0 0 3px var(--ric-focus-ring);
304
+ }
305
+
306
+ .ric-input-unit {
307
+ position: absolute;
308
+ right: 1rem;
309
+ font-size: 0.875rem;
310
+ font-weight: 600;
311
+ color: var(--ric-text-secondary);
312
+ pointer-events: none;
313
+ }
314
+
315
+ .ric-results-section {
316
+ display: flex;
317
+ flex-direction: column;
318
+ gap: 1.5rem;
319
+ }
320
+
321
+ .ric-result-header {
322
+ display: flex;
323
+ flex-direction: column;
324
+ gap: 0.5rem;
325
+ }
326
+
327
+ .ric-result-label {
328
+ font-size: 0.875rem;
329
+ font-weight: 600;
330
+ color: var(--ric-text-result-label);
331
+ text-transform: uppercase;
332
+ letter-spacing: 0.05em;
333
+ }
334
+
335
+ .ric-result-main-value {
336
+ font-size: 2.5rem;
337
+ font-weight: 900;
338
+ color: var(--ric-text-result-value);
339
+ letter-spacing: -0.02em;
340
+ overflow-wrap: break-word;
341
+ }
342
+
343
+ .ric-comparison-bar {
344
+ display: flex;
345
+ flex-direction: column;
346
+ gap: 0.75rem;
347
+ }
348
+
349
+ .ric-bar-base {
350
+ width: 100%;
351
+ height: 24px;
352
+ background: var(--ric-bg-bar);
353
+ border-radius: 12px;
354
+ overflow: hidden;
355
+ }
356
+
357
+ .ric-bar-fill {
358
+ height: 100%;
359
+ background: var(--ric-bg-bar-fill);
360
+ transition: width 0.3s ease;
361
+ }
362
+
363
+ .ric-bar-labels {
364
+ display: flex;
365
+ justify-content: space-between;
366
+ font-size: 0.875rem;
367
+ font-weight: 600;
368
+ color: var(--ric-text-bar);
369
+ }
370
+
371
+ .ric-summary-details {
372
+ display: grid;
373
+ grid-template-columns: 1fr 1fr;
374
+ gap: 1rem;
375
+ }
376
+
377
+ .ric-detail-item {
378
+ display: flex;
379
+ flex-direction: column;
380
+ gap: 0.5rem;
381
+ padding: 1rem;
382
+ background: var(--ric-bg-result);
383
+ border: 1px solid var(--ric-border-result);
384
+ border-radius: 12px;
385
+ }
386
+
387
+ .ric-detail-label {
388
+ font-size: 0.75rem;
389
+ font-weight: 700;
390
+ color: var(--ric-text-result-label);
391
+ text-transform: uppercase;
392
+ }
393
+
394
+ .ric-detail-value {
395
+ font-size: 1.25rem;
396
+ font-weight: 800;
397
+ color: var(--ric-text-result-value);
398
+ }
399
+
400
+ .ric-highlight {
401
+ color: var(--ric-highlight-color);
402
+ }
403
+
404
+ .ric-info-footer {
405
+ display: flex;
406
+ justify-content: space-between;
407
+ align-items: center;
408
+ padding-top: 2rem;
409
+ border-top: 1px solid var(--ric-border-card);
410
+ gap: 1rem;
411
+ }
412
+
413
+ .ric-legal-badge {
414
+ display: flex;
415
+ align-items: center;
416
+ gap: 0.75rem;
417
+ padding: 0.875rem 1.25rem;
418
+ background: var(--ric-bg-badge);
419
+ color: var(--ric-text-badge);
420
+ border-radius: 12px;
421
+ font-size: 0.875rem;
422
+ font-weight: 600;
423
+ }
424
+
425
+ .ric-action-btn {
426
+ display: flex;
427
+ align-items: center;
428
+ gap: 0.75rem;
429
+ padding: 0.875rem 1.5rem;
430
+ background: var(--ric-bg-button);
431
+ color: var(--ric-text-button);
432
+ border: none;
433
+ border-radius: 12px;
434
+ font-size: 0.875rem;
435
+ font-weight: 600;
436
+ cursor: pointer;
437
+ transition: all 0.2s;
438
+ }
439
+
440
+ .ric-action-btn:hover {
441
+ background: var(--ric-bg-button-hover);
442
+ transform: translateY(-2px);
443
+ }
444
+
445
+ :global(.ric-results-section.ric-hidden) {
446
+ display: none;
447
+ }
448
+
449
+ :global(.ric-input-group.ric-hidden) {
450
+ display: none;
451
+ }
452
+
453
+ @media (max-width: 768px) {
454
+ .ric-layout {
455
+ grid-template-columns: 1fr;
456
+ }
457
+
458
+ .ric-header h3 {
459
+ font-size: 1.75rem;
460
+ }
461
+
462
+ .ric-result-main-value {
463
+ font-size: 2rem;
464
+ }
465
+
466
+ .ric-info-footer {
467
+ flex-direction: column;
468
+ }
469
+
470
+ .ric-action-btn {
471
+ width: 100%;
472
+ justify-content: center;
473
+ }
474
+
475
+ .ric-legal-badge {
476
+ width: 100%;
477
+ justify-content: center;
478
+ }
479
+ }
480
+ </style>
481
+
482
+ <script>
483
+ import { RentIncreaseCalculator } from './logic';
484
+
485
+ const rentInput = document.getElementById('current-rent') as HTMLInputElement | null;
486
+ const typeSelect = document.getElementById('increment-type') as HTMLSelectElement | null;
487
+ const manualContainer = document.getElementById('manual-input-container') as HTMLDivElement | null;
488
+ const manualInput = document.getElementById('manual-percentage') as HTMLInputElement | null;
489
+ const resultsArea = document.getElementById('results-area') as HTMLDivElement | null;
490
+
491
+ const newRentDisplay = document.getElementById('val-new-rent');
492
+ const labelOldRent = document.getElementById('label-old-rent');
493
+ const labelNewRent = document.getElementById('label-new-rent');
494
+ const monthlyDisplay = document.getElementById('val-monthly-increase');
495
+ const yearlyDisplay = document.getElementById('val-yearly-increase');
496
+ const barFill = document.getElementById('increase-bar-fill');
497
+ const copyBtn = document.getElementById('copy-summary');
498
+
499
+ const currencyCodeAttr = document.querySelector('.ric-container')?.getAttribute('data-currency-code') || 'EUR';
500
+ const currencyLocaleAttr = document.querySelector('.ric-container')?.getAttribute('data-currency-locale') || 'es-ES';
501
+
502
+ function formatCurrency(val: number): string {
503
+ return new Intl.NumberFormat(currencyLocaleAttr, {
504
+ style: 'currency',
505
+ currency: currencyCodeAttr,
506
+ }).format(val);
507
+ }
508
+
509
+ function getPercentage(): number {
510
+ if (!typeSelect || !manualInput) return 3;
511
+ if (typeSelect.value === 'legal') {
512
+ manualContainer?.classList.add('ric-hidden');
513
+ return 3;
514
+ }
515
+ manualContainer?.classList.remove('ric-hidden');
516
+ return parseFloat(manualInput.value) || 0;
517
+ }
518
+
519
+ function updateResultsDisplay(calc: ReturnType<typeof RentIncreaseCalculator.calculate>) {
520
+ if (newRentDisplay) newRentDisplay.textContent = formatCurrency(calc.newRent);
521
+ if (labelOldRent) labelOldRent.textContent = formatCurrency(calc.currentRent);
522
+ if (labelNewRent) labelNewRent.textContent = formatCurrency(calc.newRent);
523
+ if (monthlyDisplay) monthlyDisplay.textContent = `+${formatCurrency(calc.monthlyIncrease)}`;
524
+ if (yearlyDisplay) yearlyDisplay.textContent = `+${formatCurrency(calc.yearlyIncrease)}`;
525
+ if (barFill) barFill.style.width = `${calc.barRatio}%`;
526
+ }
527
+
528
+ function showPulseAnimation() {
529
+ newRentDisplay?.classList.add('ric-pulse');
530
+ setTimeout(() => newRentDisplay?.classList.remove('ric-pulse'), 500);
531
+ }
532
+
533
+ function calculate() {
534
+ if (!rentInput || !typeSelect || !manualInput) return;
535
+
536
+ const currentRent = parseFloat(rentInput.value) || 0;
537
+ const percentage = getPercentage();
538
+
539
+ if (!RentIncreaseCalculator.validateInputs(currentRent, percentage)) {
540
+ resultsArea?.classList.add('ric-hidden');
541
+ return;
542
+ }
543
+
544
+ const calc = RentIncreaseCalculator.calculate(currentRent, percentage);
545
+ resultsArea?.classList.remove('ric-hidden');
546
+ updateResultsDisplay(calc);
547
+ showPulseAnimation();
548
+ }
549
+
550
+ [rentInput, typeSelect, manualInput].forEach((el) => {
551
+ el?.addEventListener('input', calculate);
552
+ el?.addEventListener('change', calculate);
553
+ });
554
+
555
+ function getCopyPercentage(): { percentage: number; isLegal: boolean } {
556
+ const percentage = typeSelect?.value === 'legal' ? 3 : parseFloat(manualInput?.value || '0');
557
+ return { percentage, isLegal: typeSelect?.value === 'legal' };
558
+ }
559
+
560
+ function updateButtonFeedback(originalText: string | undefined) {
561
+ const btnSpan = copyBtn?.querySelector('span:last-child');
562
+ if (!btnSpan || !copyBtn) return;
563
+ btnSpan.textContent = document.querySelector('.ric-container')?.getAttribute('data-copy-success') || 'Copied!';
564
+ copyBtn.classList.add('ric-copied');
565
+ setTimeout(() => {
566
+ if (btnSpan) btnSpan.textContent = originalText;
567
+ copyBtn?.classList.remove('ric-copied');
568
+ }, 2000);
569
+ }
570
+
571
+ function handleCopyClick() {
572
+ if (!rentInput || !typeSelect) return;
573
+
574
+ const currentRent = parseFloat(rentInput.value) || 0;
575
+ const { percentage, isLegal } = getCopyPercentage();
576
+
577
+ const report = RentIncreaseCalculator.generateReport(currentRent, percentage, isLegal, formatCurrency);
578
+ navigator.clipboard.writeText(report);
579
+
580
+ const btnSpan = copyBtn?.querySelector('span:last-child');
581
+ updateButtonFeedback(btnSpan?.textContent);
582
+ }
583
+
584
+ copyBtn?.addEventListener('click', handleCopyClick);
585
+
586
+ calculate();
587
+ </script>