@jjlmoya/utils-tools 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 (134) hide show
  1. package/package.json +63 -0
  2. package/src/category/i18n/en.ts +172 -0
  3. package/src/category/i18n/es.ts +172 -0
  4. package/src/category/i18n/fr.ts +172 -0
  5. package/src/category/index.ts +23 -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 +90 -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 +42 -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 +56 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/date-diff-calculator/bibliography.astro +14 -0
  25. package/src/tool/date-diff-calculator/component.astro +370 -0
  26. package/src/tool/date-diff-calculator/i18n/en.ts +132 -0
  27. package/src/tool/date-diff-calculator/i18n/es.ts +132 -0
  28. package/src/tool/date-diff-calculator/i18n/fr.ts +132 -0
  29. package/src/tool/date-diff-calculator/index.ts +22 -0
  30. package/src/tool/date-diff-calculator/seo.astro +14 -0
  31. package/src/tool/date-diff-calculator/ui.ts +17 -0
  32. package/src/tool/drive-direct-link/bibliography.astro +14 -0
  33. package/src/tool/drive-direct-link/component.astro +280 -0
  34. package/src/tool/drive-direct-link/i18n/en.ts +118 -0
  35. package/src/tool/drive-direct-link/i18n/es.ts +118 -0
  36. package/src/tool/drive-direct-link/i18n/fr.ts +118 -0
  37. package/src/tool/drive-direct-link/index.ts +22 -0
  38. package/src/tool/drive-direct-link/seo.astro +14 -0
  39. package/src/tool/drive-direct-link/ui.ts +10 -0
  40. package/src/tool/email-list-cleaner/bibliography.astro +14 -0
  41. package/src/tool/email-list-cleaner/component.astro +375 -0
  42. package/src/tool/email-list-cleaner/i18n/en.ts +140 -0
  43. package/src/tool/email-list-cleaner/i18n/es.ts +140 -0
  44. package/src/tool/email-list-cleaner/i18n/fr.ts +140 -0
  45. package/src/tool/email-list-cleaner/index.ts +22 -0
  46. package/src/tool/email-list-cleaner/seo.astro +14 -0
  47. package/src/tool/email-list-cleaner/ui.ts +15 -0
  48. package/src/tool/env-badge-spain/bibliography.astro +14 -0
  49. package/src/tool/env-badge-spain/component.astro +303 -0
  50. package/src/tool/env-badge-spain/components/BadgeForm.astro +243 -0
  51. package/src/tool/env-badge-spain/components/BadgeResult.astro +151 -0
  52. package/src/tool/env-badge-spain/i18n/en.ts +153 -0
  53. package/src/tool/env-badge-spain/i18n/es.ts +153 -0
  54. package/src/tool/env-badge-spain/i18n/fr.ts +153 -0
  55. package/src/tool/env-badge-spain/index.ts +22 -0
  56. package/src/tool/env-badge-spain/seo.astro +14 -0
  57. package/src/tool/env-badge-spain/ui.ts +53 -0
  58. package/src/tool/morse-beacon/bibliography.astro +14 -0
  59. package/src/tool/morse-beacon/component.astro +534 -0
  60. package/src/tool/morse-beacon/i18n/en.ts +157 -0
  61. package/src/tool/morse-beacon/i18n/es.ts +157 -0
  62. package/src/tool/morse-beacon/i18n/fr.ts +157 -0
  63. package/src/tool/morse-beacon/index.ts +22 -0
  64. package/src/tool/morse-beacon/logic/MorseEngine.ts +124 -0
  65. package/src/tool/morse-beacon/seo.astro +14 -0
  66. package/src/tool/morse-beacon/ui.ts +18 -0
  67. package/src/tool/password-generator/bibliography.astro +14 -0
  68. package/src/tool/password-generator/component.astro +259 -0
  69. package/src/tool/password-generator/components/Config.astro +227 -0
  70. package/src/tool/password-generator/components/Display.astro +147 -0
  71. package/src/tool/password-generator/components/Strength.astro +70 -0
  72. package/src/tool/password-generator/i18n/en.ts +166 -0
  73. package/src/tool/password-generator/i18n/es.ts +166 -0
  74. package/src/tool/password-generator/i18n/fr.ts +166 -0
  75. package/src/tool/password-generator/index.ts +22 -0
  76. package/src/tool/password-generator/seo.astro +14 -0
  77. package/src/tool/password-generator/ui.ts +16 -0
  78. package/src/tool/routes/bibliography.astro +14 -0
  79. package/src/tool/routes/component.astro +543 -0
  80. package/src/tool/routes/i18n/en.ts +157 -0
  81. package/src/tool/routes/i18n/es.ts +157 -0
  82. package/src/tool/routes/i18n/fr.ts +157 -0
  83. package/src/tool/routes/index.ts +22 -0
  84. package/src/tool/routes/logic/GeocodingService.ts +60 -0
  85. package/src/tool/routes/logic/RouteManager.ts +192 -0
  86. package/src/tool/routes/logic/RouteService.ts +66 -0
  87. package/src/tool/routes/seo.astro +14 -0
  88. package/src/tool/routes/ui.ts +16 -0
  89. package/src/tool/rule-of-three/bibliography.astro +14 -0
  90. package/src/tool/rule-of-three/component.astro +369 -0
  91. package/src/tool/rule-of-three/i18n/en.ts +171 -0
  92. package/src/tool/rule-of-three/i18n/es.ts +171 -0
  93. package/src/tool/rule-of-three/i18n/fr.ts +171 -0
  94. package/src/tool/rule-of-three/index.ts +22 -0
  95. package/src/tool/rule-of-three/seo.astro +14 -0
  96. package/src/tool/rule-of-three/ui.ts +13 -0
  97. package/src/tool/seo-content-optimizer/bibliography.astro +14 -0
  98. package/src/tool/seo-content-optimizer/component.astro +552 -0
  99. package/src/tool/seo-content-optimizer/i18n/en.ts +136 -0
  100. package/src/tool/seo-content-optimizer/i18n/es.ts +136 -0
  101. package/src/tool/seo-content-optimizer/i18n/fr.ts +136 -0
  102. package/src/tool/seo-content-optimizer/index.ts +22 -0
  103. package/src/tool/seo-content-optimizer/seo.astro +14 -0
  104. package/src/tool/seo-content-optimizer/ui.ts +29 -0
  105. package/src/tool/speed-reader/bibliography.astro +14 -0
  106. package/src/tool/speed-reader/component.astro +586 -0
  107. package/src/tool/speed-reader/i18n/en.ts +152 -0
  108. package/src/tool/speed-reader/i18n/es.ts +152 -0
  109. package/src/tool/speed-reader/i18n/fr.ts +152 -0
  110. package/src/tool/speed-reader/index.ts +22 -0
  111. package/src/tool/speed-reader/logic/RSVPEngine.ts +106 -0
  112. package/src/tool/speed-reader/seo.astro +14 -0
  113. package/src/tool/speed-reader/ui.ts +14 -0
  114. package/src/tool/text-pixel-calculator/bibliography.astro +14 -0
  115. package/src/tool/text-pixel-calculator/component.astro +315 -0
  116. package/src/tool/text-pixel-calculator/components/Editor.astro +240 -0
  117. package/src/tool/text-pixel-calculator/components/Preview.astro +155 -0
  118. package/src/tool/text-pixel-calculator/components/Stats.astro +87 -0
  119. package/src/tool/text-pixel-calculator/i18n/en.ts +133 -0
  120. package/src/tool/text-pixel-calculator/i18n/es.ts +133 -0
  121. package/src/tool/text-pixel-calculator/i18n/fr.ts +133 -0
  122. package/src/tool/text-pixel-calculator/index.ts +22 -0
  123. package/src/tool/text-pixel-calculator/seo.astro +14 -0
  124. package/src/tool/text-pixel-calculator/ui.ts +15 -0
  125. package/src/tool/whatsapp-link/bibliography.astro +14 -0
  126. package/src/tool/whatsapp-link/component.astro +455 -0
  127. package/src/tool/whatsapp-link/i18n/en.ts +128 -0
  128. package/src/tool/whatsapp-link/i18n/es.ts +128 -0
  129. package/src/tool/whatsapp-link/i18n/fr.ts +128 -0
  130. package/src/tool/whatsapp-link/index.ts +22 -0
  131. package/src/tool/whatsapp-link/seo.astro +14 -0
  132. package/src/tool/whatsapp-link/ui.ts +15 -0
  133. package/src/tools.ts +15 -0
  134. package/src/types.ts +72 -0
@@ -0,0 +1,303 @@
1
+ ---
2
+ import type { EnvBadgeSpainUI } from './ui';
3
+ import BadgeForm from './components/BadgeForm.astro';
4
+ import BadgeResult from './components/BadgeResult.astro';
5
+
6
+ interface Props {
7
+ ui?: Record<string, unknown>;
8
+ }
9
+
10
+ const { ui } = Astro.props;
11
+ const t = (ui ?? {}) as EnvBadgeSpainUI;
12
+ ---
13
+
14
+ <div class="deb-root" data-ui={JSON.stringify(t)}>
15
+ <div class="deb-main-container">
16
+ <BadgeForm t={t} />
17
+ <BadgeResult t={t} />
18
+ </div>
19
+ </div>
20
+
21
+ <style>
22
+ .deb-root {
23
+ --deb-bg: #f8fafc;
24
+ --deb-card-bg: #fff;
25
+ --deb-card-border: #e2e8f0;
26
+ --deb-text-main: #1e293b;
27
+ --deb-text-muted: #64748b;
28
+ --deb-text-sub: #475569;
29
+ --deb-label-border: #e2e8f0;
30
+ --deb-option-bg: #f1f5f9;
31
+ --deb-option-border: #e2e8f0;
32
+ --deb-option-hover: #e2e8f0;
33
+ --deb-result-bg: #fff;
34
+
35
+ width: 100%;
36
+ max-width: 720px;
37
+ margin: 0 auto;
38
+ }
39
+
40
+ :global(.theme-dark) .deb-root {
41
+ --deb-bg: #0f172a;
42
+ --deb-card-bg: #1e293b;
43
+ --deb-card-border: rgba(255, 255, 255, 0.08);
44
+ --deb-text-main: #f8fafc;
45
+ --deb-text-muted: #94a3b8;
46
+ --deb-text-sub: #cbd5e1;
47
+ --deb-label-border: rgba(255, 255, 255, 0.1);
48
+ --deb-option-bg: rgba(15, 23, 42, 0.5);
49
+ --deb-option-border: rgba(255, 255, 255, 0.1);
50
+ --deb-option-hover: rgba(255, 255, 255, 0.05);
51
+ --deb-result-bg: rgba(15, 23, 42, 0.3);
52
+ }
53
+
54
+ .deb-main-container {
55
+ background: var(--deb-card-bg);
56
+ border: 1px solid var(--deb-card-border);
57
+ border-radius: 2rem;
58
+ padding: 2.5rem;
59
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
60
+ }
61
+
62
+ @media (max-width: 640px) {
63
+ .deb-main-container {
64
+ padding: 1.5rem;
65
+ border-radius: 1.5rem;
66
+ }
67
+ }
68
+
69
+ :global(.deb-badge-display h3) {
70
+ font-size: 1.5rem;
71
+ margin: 0;
72
+ color: var(--deb-text-main);
73
+ font-weight: 800;
74
+ }
75
+
76
+ :global(.deb-replica) {
77
+ width: 200px;
78
+ height: 200px;
79
+ border-radius: 50%;
80
+ border: 6px solid #111;
81
+ position: relative;
82
+ display: flex;
83
+ flex-direction: column;
84
+ justify-content: center;
85
+ align-items: center;
86
+ overflow: hidden;
87
+ background: #fff;
88
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
89
+ }
90
+
91
+ :global(.deb-replica)::after {
92
+ content: "DGT";
93
+ position: absolute;
94
+ top: 25px;
95
+ right: 35px;
96
+ font-size: 1.125rem;
97
+ font-weight: 900;
98
+ color: #000;
99
+ opacity: 0.6;
100
+ letter-spacing: -1px;
101
+ z-index: 10;
102
+ }
103
+
104
+ :global(.deb-big-letter) {
105
+ font-size: 7rem;
106
+ font-weight: 900;
107
+ line-height: 1;
108
+ z-index: 2;
109
+ margin-top: 15px;
110
+ }
111
+
112
+ :global(.deb-sub-text) {
113
+ font-size: 1rem;
114
+ font-weight: 800;
115
+ text-transform: uppercase;
116
+ z-index: 2;
117
+ margin-top: -5px;
118
+ }
119
+
120
+ :global(.deb-replica-0) {
121
+ background: #005aa3;
122
+ color: #fff;
123
+ }
124
+
125
+ :global(.deb-replica-0)::after {
126
+ color: #fff;
127
+ opacity: 0.9;
128
+ }
129
+
130
+ :global(.deb-replica-eco) {
131
+ background: linear-gradient(90deg, #00a455 50%, #005aa3 50%);
132
+ color: #fff;
133
+ }
134
+
135
+ :global(.deb-replica-eco)::after {
136
+ color: #fff;
137
+ opacity: 0.9;
138
+ }
139
+
140
+ :global(.deb-replica-eco) :global(.deb-big-letter) {
141
+ font-size: 5rem;
142
+ margin-top: 25px;
143
+ }
144
+
145
+ :global(.deb-replica-c) {
146
+ background: #00a455;
147
+ color: #000;
148
+ }
149
+
150
+ :global(.deb-replica-c) :global(.deb-sub-text) { display: none; }
151
+
152
+ :global(.deb-replica-b) {
153
+ background: #ffdf00;
154
+ color: #000;
155
+ }
156
+
157
+ :global(.deb-replica-b) :global(.deb-sub-text) { display: none; }
158
+
159
+ :global(.deb-replica-a) {
160
+ width: 200px;
161
+ height: 120px;
162
+ border-radius: 1rem;
163
+ border: 6px solid #dc2626;
164
+ background: #fff;
165
+ color: #dc2626;
166
+ display: flex;
167
+ flex-direction: column;
168
+ justify-content: center;
169
+ align-items: center;
170
+ }
171
+
172
+ :global(.deb-replica-a) :global(.deb-big-letter) {
173
+ font-size: 4rem;
174
+ margin-top: 0;
175
+ }
176
+ </style>
177
+
178
+ <script>
179
+ import type { EnvBadgeSpainUI } from './ui';
180
+
181
+ const root = document.querySelector('.deb-root') as HTMLElement;
182
+ const t = JSON.parse(root?.dataset.ui ?? '{}') as EnvBadgeSpainUI;
183
+
184
+ const form = document.getElementById('deb-form') as HTMLFormElement;
185
+ const resultBox = document.getElementById('deb-result') as HTMLDivElement;
186
+ const badgeDisplay = document.getElementById('deb-badge-display') as HTMLDivElement;
187
+ const resultDesc = document.getElementById('deb-result-desc') as HTMLParagraphElement;
188
+ const resetBtn = document.getElementById('deb-reset') as HTMLButtonElement;
189
+
190
+ type BadgeKey = 'ZERO' | 'ECO' | 'C' | 'B' | 'A';
191
+
192
+ interface BadgeData {
193
+ label: string;
194
+ letter: string;
195
+ sub: string;
196
+ desc: string;
197
+ cssClass: string;
198
+ }
199
+
200
+ const BADGES: Record<BadgeKey, BadgeData> = {
201
+ ZERO: { label: t.badge0Label, letter: t.badge0Letter, sub: t.badge0Sub, desc: t.badge0Desc, cssClass: 'deb-replica-0' },
202
+ ECO: { label: t.badgeEcoLabel, letter: t.badgeEcoLetter, sub: '', desc: t.badgeEcoDesc, cssClass: 'deb-replica-eco' },
203
+ C: { label: t.badgeCLabel, letter: t.badgeCLetter, sub: '', desc: t.badgeCDesc, cssClass: 'deb-replica-c' },
204
+ B: { label: t.badgeBLabel, letter: t.badgeBLetter, sub: '', desc: t.badgeBDesc, cssClass: 'deb-replica-b' },
205
+ A: { label: t.badgeALabel, letter: t.badgeALetter, sub: t.badgeASub, desc: t.badgeADesc, cssClass: 'deb-replica-a' },
206
+ };
207
+
208
+ function calculateBadge(vType: string, fType: string, year: string): BadgeKey {
209
+ if (fType === 'electric' || fType === 'phev_high') return 'ZERO';
210
+ if (fType === 'phev_low' || fType === 'hybrid_gas') return 'ECO';
211
+
212
+ if (vType === 'moto') {
213
+ if (year === 'pre2000' || year === '2000-2005') return 'B';
214
+ return 'C';
215
+ }
216
+
217
+ if (fType === 'gasoline') {
218
+ if (year === 'pre2000') return 'A';
219
+ if (year === '2000-2005') return 'B';
220
+ return 'C';
221
+ }
222
+
223
+ if (fType === 'diesel') {
224
+ if (year === 'pre2000' || year === '2000-2005') return 'A';
225
+ if (year === '2006-2013') return 'B';
226
+ return 'C';
227
+ }
228
+
229
+ return 'A';
230
+ }
231
+
232
+ function getBadgeHTML(key: BadgeKey): string {
233
+ const d = BADGES[key];
234
+ if (key === 'A') {
235
+ return `<div class="deb-replica-a"><span class="deb-big-letter">${d.letter}</span><span class="deb-sub-text">${d.sub}</span></div><h3>${d.label}</h3>`;
236
+ }
237
+ return `<div class="deb-replica ${d.cssClass}"><span class="deb-big-letter">${d.letter}</span><span class="deb-sub-text">${d.sub}</span></div><h3>${d.label}</h3>`;
238
+ }
239
+
240
+ function showError(id: string, show: boolean): void {
241
+ const err = document.getElementById(`deb-err-${id}`);
242
+ if (err) err.style.opacity = show ? '1' : '0';
243
+ form.querySelectorAll<HTMLElement>(`.deb-option[data-group="${id}"]`).forEach((c) => {
244
+ if (show) c.style.borderColor = '#ef4444';
245
+ else c.style.borderColor = '';
246
+ });
247
+ }
248
+
249
+ form.querySelectorAll<HTMLButtonElement>('.deb-option').forEach((card) => {
250
+ card.addEventListener('click', () => {
251
+ const group = card.dataset.group;
252
+ const val = card.dataset.val;
253
+ if (!group || !val) return;
254
+
255
+ form.querySelectorAll<HTMLElement>(`.deb-option[data-group="${group}"]`).forEach((c) => {
256
+ c.classList.remove('deb-selected');
257
+ });
258
+ card.classList.add('deb-selected');
259
+
260
+ const hidden = document.getElementById(group) as HTMLInputElement | null;
261
+ if (hidden) {
262
+ hidden.value = val;
263
+ showError(group, false);
264
+ }
265
+ });
266
+ });
267
+
268
+ form.addEventListener('submit', (e) => {
269
+ e.preventDefault();
270
+
271
+ const vehicleType = document.getElementById('vehicleType') as HTMLInputElement;
272
+ const fuelType = document.getElementById('fuelType') as HTMLInputElement;
273
+ const registrationYear = document.getElementById('registrationYear') as HTMLInputElement;
274
+
275
+ let valid = true;
276
+ if (!vehicleType.value) { showError('vehicleType', true); valid = false; }
277
+ if (!fuelType.value) { showError('fuelType', true); valid = false; }
278
+ if (!registrationYear.value) { showError('registrationYear', true); valid = false; }
279
+
280
+ if (!valid) return;
281
+
282
+ const key = calculateBadge(vehicleType.value, fuelType.value, registrationYear.value);
283
+ badgeDisplay.innerHTML = getBadgeHTML(key);
284
+ resultDesc.textContent = BADGES[key].desc;
285
+
286
+ form.style.display = 'none';
287
+ resultBox.style.display = 'flex';
288
+ });
289
+
290
+ resetBtn.addEventListener('click', () => {
291
+ form.querySelectorAll<HTMLElement>('.deb-option').forEach((c) => {
292
+ c.classList.remove('deb-selected');
293
+ c.style.borderColor = '';
294
+ });
295
+ form.querySelectorAll<HTMLInputElement>('input[type="hidden"]').forEach((inp) => {
296
+ inp.value = '';
297
+ });
298
+ ['vehicleType', 'fuelType', 'registrationYear'].forEach((id) => showError(id, false));
299
+
300
+ form.style.display = 'flex';
301
+ resultBox.style.display = 'none';
302
+ });
303
+ </script>
@@ -0,0 +1,243 @@
1
+ ---
2
+ interface Props {
3
+ t: Record<string, string>;
4
+ }
5
+
6
+ const { t } = Astro.props;
7
+ ---
8
+
9
+ <form class="deb-form" id="deb-form" novalidate>
10
+ <div class="deb-group">
11
+ <label class="deb-step-label">{t.step1Label}</label>
12
+ <div class="deb-grid deb-grid-3">
13
+ <button type="button" class="deb-option deb-vehicle" data-group="vehicleType" data-val="car">
14
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 17H3a2 2 0 0 1-2-2V9l3.5-6h11L19 9h2v6a2 2 0 0 1-2 2h-2"/><circle cx="7.5" cy="17" r="2.5"/><circle cx="16.5" cy="17" r="2.5"/></svg>
15
+ <span>{t.vehicleCar}</span>
16
+ </button>
17
+ <button type="button" class="deb-option deb-vehicle" data-group="vehicleType" data-val="moto">
18
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5.5" cy="17.5" r="3"/><circle cx="18.5" cy="17.5" r="3"/><path d="M15 6h-3l-2 7h9"/><path d="M9 6l4 4"/></svg>
19
+ <span>{t.vehicleMoto}</span>
20
+ </button>
21
+ <button type="button" class="deb-option deb-vehicle" data-group="vehicleType" data-val="heavy">
22
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="3" width="15" height="13" rx="1"/><path d="M16 8h4l3 5v3h-7V8z"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
23
+ <span>{t.vehicleHeavy}</span>
24
+ </button>
25
+ </div>
26
+ <input type="hidden" id="vehicleType" />
27
+ <div class="deb-error" id="deb-err-vehicleType">{t.errVehicle}</div>
28
+ </div>
29
+
30
+ <div class="deb-group">
31
+ <label class="deb-step-label">{t.step2Label}</label>
32
+ <div class="deb-grid deb-grid-3">
33
+ <button type="button" class="deb-option deb-fuel" data-group="fuelType" data-val="electric">
34
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
35
+ <span>{t.fuelElectric}</span>
36
+ </button>
37
+ <button type="button" class="deb-option deb-fuel" data-group="fuelType" data-val="phev_high">
38
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22v-6"/><path d="M9 11V7a3 3 0 0 1 6 0v4"/><rect x="5" y="11" width="14" height="8" rx="2"/></svg>
39
+ <span>{t.fuelPhevHigh}</span>
40
+ </button>
41
+ <button type="button" class="deb-option deb-fuel" data-group="fuelType" data-val="phev_low">
42
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22v-6"/><path d="M9 11V8a3 3 0 0 1 5.12-2.12"/><rect x="5" y="11" width="14" height="8" rx="2"/></svg>
43
+ <span>{t.fuelPhevLow}</span>
44
+ </button>
45
+ <button type="button" class="deb-option deb-fuel" data-group="fuelType" data-val="hybrid_gas">
46
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 22c1.25-1.25 2.5-2.5 3.5-3.5C7.5 15 12 6 20 2c0 8-5 12-9 13.5-1.5.5-3 1-4 2L2 22z"/></svg>
47
+ <span>{t.fuelHybridGas}</span>
48
+ </button>
49
+ <button type="button" class="deb-option deb-fuel" data-group="fuelType" data-val="gasoline">
50
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 22V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v17"/><path d="M3 10h12"/><path d="M15 7l4 3v9a1 1 0 0 1-1 1h-3"/></svg>
51
+ <span>{t.fuelGasoline}</span>
52
+ </button>
53
+ <button type="button" class="deb-option deb-fuel" data-group="fuelType" data-val="diesel">
54
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"/></svg>
55
+ <span>{t.fuelDiesel}</span>
56
+ </button>
57
+ </div>
58
+ <input type="hidden" id="fuelType" />
59
+ <div class="deb-error" id="deb-err-fuelType">{t.errFuel}</div>
60
+ </div>
61
+
62
+ <div class="deb-group">
63
+ <label class="deb-step-label">{t.step3Label}</label>
64
+ <div class="deb-grid deb-grid-4">
65
+ <button type="button" class="deb-option deb-year" data-group="registrationYear" data-val="pre2000">
66
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
67
+ <span>{t.yearPre2000}</span>
68
+ </button>
69
+ <button type="button" class="deb-option deb-year" data-group="registrationYear" data-val="2000-2005">
70
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
71
+ <span>{t.year20002005}</span>
72
+ </button>
73
+ <button type="button" class="deb-option deb-year" data-group="registrationYear" data-val="2006-2013">
74
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><path d="M8 14h4l-2 2 2 2H8"/></svg>
75
+ <span>{t.year20062013}</span>
76
+ </button>
77
+ <button type="button" class="deb-option deb-year" data-group="registrationYear" data-val="post2014">
78
+ <svg class="deb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><polyline points="9 15 11 17 15 13"/></svg>
79
+ <span>{t.yearPost2014}</span>
80
+ </button>
81
+ </div>
82
+ <input type="hidden" id="registrationYear" />
83
+ <div class="deb-error" id="deb-err-registrationYear">{t.errYear}</div>
84
+ <p class="deb-help">{t.helpText}</p>
85
+ </div>
86
+
87
+ <button type="submit" class="deb-submit">
88
+ {t.submitBtn}
89
+ <svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14m-7-7l7 7-7 7"/></svg>
90
+ </button>
91
+ </form>
92
+
93
+ <style>
94
+ .deb-form {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 2rem;
98
+ }
99
+
100
+ .deb-group {
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 1rem;
104
+ }
105
+
106
+ .deb-step-label {
107
+ font-size: 0.875rem;
108
+ font-weight: 800;
109
+ color: var(--deb-text-muted);
110
+ text-transform: uppercase;
111
+ letter-spacing: 0.1em;
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 0.5rem;
115
+ }
116
+
117
+ .deb-step-label::after {
118
+ content: "";
119
+ flex: 1;
120
+ height: 1px;
121
+ background: var(--deb-label-border);
122
+ }
123
+
124
+ .deb-grid {
125
+ display: grid;
126
+ gap: 0.75rem;
127
+ }
128
+
129
+ .deb-grid-3 { grid-template-columns: repeat(3, 1fr); }
130
+ .deb-grid-4 { grid-template-columns: repeat(4, 1fr); }
131
+
132
+ .deb-option {
133
+ border-radius: 1rem;
134
+ padding: 1rem 0.5rem;
135
+ display: flex;
136
+ flex-direction: column;
137
+ align-items: center;
138
+ gap: 0.75rem;
139
+ background: var(--deb-option-bg);
140
+ border: 2px solid var(--deb-option-border);
141
+ cursor: pointer;
142
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
143
+ color: var(--deb-text-main);
144
+ }
145
+
146
+ .deb-icon {
147
+ width: 2.25rem;
148
+ height: 2.25rem;
149
+ stroke: currentcolor;
150
+ opacity: 0.7;
151
+ transition: transform 0.2s;
152
+ }
153
+
154
+ .deb-option span {
155
+ font-size: 0.8125rem;
156
+ font-weight: 700;
157
+ line-height: 1.2;
158
+ }
159
+
160
+ .deb-option:hover {
161
+ transform: translateY(-2px);
162
+ background: var(--deb-option-hover);
163
+ border-color: var(--deb-accent);
164
+ }
165
+
166
+ .deb-option:hover .deb-icon {
167
+ transform: scale(1.1);
168
+ opacity: 1;
169
+ }
170
+
171
+ :global(.deb-selected).deb-option {
172
+ background: var(--deb-accent-bg);
173
+ border-color: var(--deb-accent);
174
+ color: #fff;
175
+ box-shadow: 0 8px 16px -4px var(--deb-accent-shadow);
176
+ }
177
+
178
+ :global(.deb-selected) .deb-icon { opacity: 1; }
179
+
180
+ .deb-vehicle {
181
+ --deb-accent: #0ea5e9;
182
+ --deb-accent-bg: #0ea5e9;
183
+ --deb-accent-shadow: rgba(14, 165, 233, 0.4);
184
+ }
185
+
186
+ .deb-fuel {
187
+ --deb-accent: #10b981;
188
+ --deb-accent-bg: #10b981;
189
+ --deb-accent-shadow: rgba(16, 185, 129, 0.4);
190
+ }
191
+
192
+ .deb-year {
193
+ --deb-accent: #8b5cf6;
194
+ --deb-accent-bg: #8b5cf6;
195
+ --deb-accent-shadow: rgba(139, 92, 246, 0.4);
196
+ }
197
+
198
+ .deb-error {
199
+ color: #ef4444;
200
+ font-size: 0.75rem;
201
+ font-weight: 700;
202
+ opacity: 0;
203
+ transition: opacity 0.2s;
204
+ margin-top: -0.5rem;
205
+ }
206
+
207
+ .deb-help {
208
+ font-size: 0.75rem;
209
+ color: var(--deb-text-muted);
210
+ line-height: 1.5;
211
+ font-style: italic;
212
+ }
213
+
214
+ .deb-submit {
215
+ width: 100%;
216
+ padding: 1.25rem;
217
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
218
+ color: #fff;
219
+ border: none;
220
+ border-radius: 1.25rem;
221
+ font-weight: 800;
222
+ font-size: 1.125rem;
223
+ cursor: pointer;
224
+ transition: all 0.3s ease;
225
+ text-transform: uppercase;
226
+ letter-spacing: 0.05em;
227
+ display: flex;
228
+ justify-content: center;
229
+ align-items: center;
230
+ gap: 0.75rem;
231
+ box-shadow: 0 10px 15px -3px rgba(217, 119, 6, 0.3);
232
+ }
233
+
234
+ .deb-submit:hover {
235
+ transform: translateY(-2px);
236
+ box-shadow: 0 20px 25px -5px rgba(217, 119, 6, 0.4);
237
+ filter: brightness(1.1);
238
+ }
239
+
240
+ @media (max-width: 480px) {
241
+ .deb-grid-3, .deb-grid-4 { grid-template-columns: repeat(2, 1fr); }
242
+ }
243
+ </style>
@@ -0,0 +1,151 @@
1
+ ---
2
+ interface Props {
3
+ t: Record<string, string>;
4
+ }
5
+
6
+ const { t } = Astro.props;
7
+ ---
8
+
9
+ <div class="deb-result" id="deb-result" style="display: none">
10
+ <div class="deb-result-card">
11
+ <h2 class="deb-result-title">{t.resultTitle}</h2>
12
+
13
+ <div class="deb-badge-container">
14
+ <div id="deb-badge-display" class="deb-badge-display"></div>
15
+ </div>
16
+
17
+ <div class="deb-result-info">
18
+ <p class="deb-result-desc" id="deb-result-desc"></p>
19
+ <div class="deb-warning-box">
20
+ <svg
21
+ width="20"
22
+ height="20"
23
+ fill="none"
24
+ viewBox="0 0 24 24"
25
+ stroke="#f59e0b"
26
+ stroke-width="2"
27
+ ><path
28
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
29
+ ></path></svg
30
+ >
31
+ <p class="deb-result-warn">{t.warningText}</p>
32
+ </div>
33
+ </div>
34
+
35
+ <button type="button" class="deb-reset-btn" id="deb-reset">
36
+ <svg
37
+ width="18"
38
+ height="18"
39
+ fill="none"
40
+ viewBox="0 0 24 24"
41
+ stroke="currentColor"
42
+ stroke-width="2.5"
43
+ ><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8m0 0V3m0 5h5"
44
+ ></path></svg
45
+ >
46
+ {t.resetBtn}
47
+ </button>
48
+ </div>
49
+ </div>
50
+
51
+ <style>
52
+ .deb-result {
53
+ display: none;
54
+ flex-direction: column;
55
+ animation: deb-slide-up 0.5s cubic-bezier(0.16, 1, 0.3, 1);
56
+ }
57
+
58
+ .deb-result-card {
59
+ background: var(--deb-result-bg);
60
+ border-radius: 1.5rem;
61
+ padding: 2.5rem;
62
+ display: flex;
63
+ flex-direction: column;
64
+ align-items: center;
65
+ text-align: center;
66
+ border: 1px solid var(--deb-card-border);
67
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
68
+ }
69
+
70
+ .deb-result-title {
71
+ font-size: 1.25rem;
72
+ font-weight: 800;
73
+ color: var(--deb-text-muted);
74
+ text-transform: uppercase;
75
+ letter-spacing: 0.1em;
76
+ margin-bottom: 2rem;
77
+ }
78
+
79
+ .deb-badge-container {
80
+ margin-bottom: 2rem;
81
+ filter: drop-shadow(0 15px 30px rgba(0, 0, 0, 0.1));
82
+ }
83
+
84
+ .deb-result-info {
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 1.5rem;
88
+ max-width: 640px;
89
+ width: 100%;
90
+ }
91
+
92
+ .deb-result-desc {
93
+ font-size: 1.125rem;
94
+ font-weight: 600;
95
+ line-height: 1.6;
96
+ color: var(--deb-text-main);
97
+ }
98
+
99
+ .deb-warning-box {
100
+ display: flex;
101
+ gap: 1rem;
102
+ padding: 1rem;
103
+ background: rgba(245, 158, 11, 0.08);
104
+ border-radius: 1rem;
105
+ border: 1px solid rgba(245, 158, 11, 0.2);
106
+ text-align: left;
107
+ }
108
+
109
+ .deb-result-warn {
110
+ font-size: 0.8125rem;
111
+ color: #b45309;
112
+ font-weight: 500;
113
+ line-height: 1.5;
114
+ }
115
+
116
+ :global(.theme-dark) .deb-result-warn {
117
+ color: #fbbf24;
118
+ }
119
+
120
+ .deb-reset-btn {
121
+ margin-top: 2.5rem;
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 0.75rem;
125
+ padding: 0.875rem 2rem;
126
+ background: var(--deb-option-bg);
127
+ border: 1px solid var(--deb-option-border);
128
+ border-radius: 100px;
129
+ color: var(--deb-text-main);
130
+ font-weight: 700;
131
+ cursor: pointer;
132
+ transition: all 0.2s;
133
+ }
134
+
135
+ .deb-reset-btn:hover {
136
+ background: var(--deb-option-hover);
137
+ transform: scale(1.05);
138
+ }
139
+
140
+ @keyframes deb-slide-up {
141
+ from {
142
+ opacity: 0;
143
+ transform: translateY(20px);
144
+ }
145
+
146
+ to {
147
+ opacity: 1;
148
+ transform: translateY(0);
149
+ }
150
+ }
151
+ </style>