@jjlmoya/utils-health 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 (155) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +60 -0
  3. package/src/category/i18n/es.ts +60 -0
  4. package/src/category/i18n/fr.ts +60 -0
  5. package/src/category/index.ts +22 -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 +28 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +36 -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 +22 -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/binauralTuner/bibliography.astro +14 -0
  25. package/src/tool/binauralTuner/component.astro +687 -0
  26. package/src/tool/binauralTuner/i18n/en.ts +187 -0
  27. package/src/tool/binauralTuner/i18n/es.ts +187 -0
  28. package/src/tool/binauralTuner/i18n/fr.ts +187 -0
  29. package/src/tool/binauralTuner/index.ts +27 -0
  30. package/src/tool/binauralTuner/seo.astro +14 -0
  31. package/src/tool/binauralTuner/ui.ts +18 -0
  32. package/src/tool/bloodUnitConverter/bibliography.astro +14 -0
  33. package/src/tool/bloodUnitConverter/component.astro +915 -0
  34. package/src/tool/bloodUnitConverter/i18n/en.ts +227 -0
  35. package/src/tool/bloodUnitConverter/i18n/es.ts +250 -0
  36. package/src/tool/bloodUnitConverter/i18n/fr.ts +218 -0
  37. package/src/tool/bloodUnitConverter/index.ts +27 -0
  38. package/src/tool/bloodUnitConverter/seo.astro +14 -0
  39. package/src/tool/bloodUnitConverter/ui.ts +38 -0
  40. package/src/tool/bmiCalculator/bibliography.astro +14 -0
  41. package/src/tool/bmiCalculator/component.astro +415 -0
  42. package/src/tool/bmiCalculator/i18n/en.ts +217 -0
  43. package/src/tool/bmiCalculator/i18n/es.ts +221 -0
  44. package/src/tool/bmiCalculator/i18n/fr.ts +217 -0
  45. package/src/tool/bmiCalculator/index.ts +27 -0
  46. package/src/tool/bmiCalculator/seo.astro +14 -0
  47. package/src/tool/bmiCalculator/ui.ts +21 -0
  48. package/src/tool/breathingVisualizer/bibliography.astro +14 -0
  49. package/src/tool/breathingVisualizer/component.astro +636 -0
  50. package/src/tool/breathingVisualizer/i18n/en.ts +206 -0
  51. package/src/tool/breathingVisualizer/i18n/es.ts +206 -0
  52. package/src/tool/breathingVisualizer/i18n/fr.ts +206 -0
  53. package/src/tool/breathingVisualizer/index.ts +27 -0
  54. package/src/tool/breathingVisualizer/seo.astro +14 -0
  55. package/src/tool/breathingVisualizer/ui.ts +31 -0
  56. package/src/tool/caffeineTracker/bibliography.astro +14 -0
  57. package/src/tool/caffeineTracker/component.astro +1210 -0
  58. package/src/tool/caffeineTracker/i18n/en.ts +198 -0
  59. package/src/tool/caffeineTracker/i18n/es.ts +198 -0
  60. package/src/tool/caffeineTracker/i18n/fr.ts +198 -0
  61. package/src/tool/caffeineTracker/index.ts +27 -0
  62. package/src/tool/caffeineTracker/logic.ts +31 -0
  63. package/src/tool/caffeineTracker/seo.astro +14 -0
  64. package/src/tool/caffeineTracker/ui.ts +36 -0
  65. package/src/tool/daltonismSimulator/bibliography.astro +14 -0
  66. package/src/tool/daltonismSimulator/component.astro +383 -0
  67. package/src/tool/daltonismSimulator/i18n/en.ts +188 -0
  68. package/src/tool/daltonismSimulator/i18n/es.ts +218 -0
  69. package/src/tool/daltonismSimulator/i18n/fr.ts +168 -0
  70. package/src/tool/daltonismSimulator/index.ts +27 -0
  71. package/src/tool/daltonismSimulator/seo.astro +14 -0
  72. package/src/tool/daltonismSimulator/ui.ts +20 -0
  73. package/src/tool/digestionStopwatch/bibliography.astro +14 -0
  74. package/src/tool/digestionStopwatch/component.astro +627 -0
  75. package/src/tool/digestionStopwatch/i18n/en.ts +173 -0
  76. package/src/tool/digestionStopwatch/i18n/es.ts +173 -0
  77. package/src/tool/digestionStopwatch/i18n/fr.ts +173 -0
  78. package/src/tool/digestionStopwatch/index.ts +27 -0
  79. package/src/tool/digestionStopwatch/logic.ts +63 -0
  80. package/src/tool/digestionStopwatch/seo.astro +14 -0
  81. package/src/tool/digestionStopwatch/ui.ts +20 -0
  82. package/src/tool/epworthSleepinessScale/bibliography.astro +14 -0
  83. package/src/tool/epworthSleepinessScale/component.astro +528 -0
  84. package/src/tool/epworthSleepinessScale/i18n/en.ts +217 -0
  85. package/src/tool/epworthSleepinessScale/i18n/es.ts +217 -0
  86. package/src/tool/epworthSleepinessScale/i18n/fr.ts +217 -0
  87. package/src/tool/epworthSleepinessScale/index.ts +27 -0
  88. package/src/tool/epworthSleepinessScale/seo.astro +14 -0
  89. package/src/tool/epworthSleepinessScale/ui.ts +27 -0
  90. package/src/tool/hydrationCalculator/bibliography.astro +14 -0
  91. package/src/tool/hydrationCalculator/component.astro +694 -0
  92. package/src/tool/hydrationCalculator/i18n/en.ts +217 -0
  93. package/src/tool/hydrationCalculator/i18n/es.ts +222 -0
  94. package/src/tool/hydrationCalculator/i18n/fr.ts +199 -0
  95. package/src/tool/hydrationCalculator/index.ts +27 -0
  96. package/src/tool/hydrationCalculator/seo.astro +14 -0
  97. package/src/tool/hydrationCalculator/ui.ts +28 -0
  98. package/src/tool/pelliRobsonTest/bibliography.astro +14 -0
  99. package/src/tool/pelliRobsonTest/component.astro +653 -0
  100. package/src/tool/pelliRobsonTest/i18n/en.ts +205 -0
  101. package/src/tool/pelliRobsonTest/i18n/es.ts +205 -0
  102. package/src/tool/pelliRobsonTest/i18n/fr.ts +205 -0
  103. package/src/tool/pelliRobsonTest/index.ts +27 -0
  104. package/src/tool/pelliRobsonTest/seo.astro +14 -0
  105. package/src/tool/pelliRobsonTest/ui.ts +21 -0
  106. package/src/tool/peripheralVisionTrainer/bibliography.astro +14 -0
  107. package/src/tool/peripheralVisionTrainer/component.astro +678 -0
  108. package/src/tool/peripheralVisionTrainer/i18n/en.ts +224 -0
  109. package/src/tool/peripheralVisionTrainer/i18n/es.ts +224 -0
  110. package/src/tool/peripheralVisionTrainer/i18n/fr.ts +211 -0
  111. package/src/tool/peripheralVisionTrainer/index.ts +27 -0
  112. package/src/tool/peripheralVisionTrainer/seo.astro +14 -0
  113. package/src/tool/peripheralVisionTrainer/ui.ts +26 -0
  114. package/src/tool/readingDistanceCalculator/bibliography.astro +14 -0
  115. package/src/tool/readingDistanceCalculator/component.astro +588 -0
  116. package/src/tool/readingDistanceCalculator/i18n/en.ts +202 -0
  117. package/src/tool/readingDistanceCalculator/i18n/es.ts +215 -0
  118. package/src/tool/readingDistanceCalculator/i18n/fr.ts +193 -0
  119. package/src/tool/readingDistanceCalculator/index.ts +31 -0
  120. package/src/tool/readingDistanceCalculator/seo.astro +14 -0
  121. package/src/tool/readingDistanceCalculator/ui.ts +18 -0
  122. package/src/tool/screenDecompressionTime/bibliography.astro +14 -0
  123. package/src/tool/screenDecompressionTime/component.astro +671 -0
  124. package/src/tool/screenDecompressionTime/i18n/en.ts +225 -0
  125. package/src/tool/screenDecompressionTime/i18n/es.ts +247 -0
  126. package/src/tool/screenDecompressionTime/i18n/fr.ts +225 -0
  127. package/src/tool/screenDecompressionTime/index.ts +27 -0
  128. package/src/tool/screenDecompressionTime/seo.astro +14 -0
  129. package/src/tool/screenDecompressionTime/ui.ts +32 -0
  130. package/src/tool/tinnitusReliever/bibliography.astro +14 -0
  131. package/src/tool/tinnitusReliever/component.astro +581 -0
  132. package/src/tool/tinnitusReliever/i18n/en.ts +161 -0
  133. package/src/tool/tinnitusReliever/i18n/es.ts +161 -0
  134. package/src/tool/tinnitusReliever/i18n/fr.ts +161 -0
  135. package/src/tool/tinnitusReliever/index.ts +27 -0
  136. package/src/tool/tinnitusReliever/seo.astro +14 -0
  137. package/src/tool/tinnitusReliever/ui.ts +9 -0
  138. package/src/tool/ubeCalculator/bibliography.astro +14 -0
  139. package/src/tool/ubeCalculator/component.astro +683 -0
  140. package/src/tool/ubeCalculator/i18n/en.ts +200 -0
  141. package/src/tool/ubeCalculator/i18n/es.ts +200 -0
  142. package/src/tool/ubeCalculator/i18n/fr.ts +196 -0
  143. package/src/tool/ubeCalculator/index.ts +27 -0
  144. package/src/tool/ubeCalculator/seo.astro +14 -0
  145. package/src/tool/ubeCalculator/ui.ts +26 -0
  146. package/src/tool/waterPurifier/bibliography.astro +14 -0
  147. package/src/tool/waterPurifier/component.astro +628 -0
  148. package/src/tool/waterPurifier/i18n/en.ts +167 -0
  149. package/src/tool/waterPurifier/i18n/es.ts +167 -0
  150. package/src/tool/waterPurifier/i18n/fr.ts +167 -0
  151. package/src/tool/waterPurifier/index.ts +27 -0
  152. package/src/tool/waterPurifier/seo.astro +14 -0
  153. package/src/tool/waterPurifier/ui.ts +18 -0
  154. package/src/tools.ts +19 -0
  155. package/src/types.ts +72 -0
@@ -0,0 +1,588 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { KnownLocale } from '../../types';
4
+ import type { ReadingDistanceCalculatorUI } from './ui';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ ui?: Record<string, unknown>;
9
+ }
10
+
11
+ const ui = (Astro.props.ui ?? {}) as ReadingDistanceCalculatorUI;
12
+ ---
13
+
14
+ <div class="rdc" data-ui={JSON.stringify(ui)}>
15
+ <div class="rdc__card">
16
+
17
+
18
+ <div class="rdc__section rdc__section--banner">
19
+ <div class="rdc__banner">
20
+ <span class="rdc__banner-icon"><Icon name="mdi:eye-refresh" /></span>
21
+ <p class="rdc__banner-text" data-key="bannerText">{ui.bannerText}</p>
22
+ </div>
23
+ </div>
24
+
25
+
26
+ <div class="rdc__section rdc__section--bordered">
27
+ <div class="rdc__preview-controls">
28
+ <button id="rdc-dec" class="rdc__btn-circle" aria-label="Decrease font size">
29
+ <Icon name="mdi:minus" />
30
+ </button>
31
+ <div class="rdc__zoom-indicator">
32
+ <small class="rdc__zoom-label" data-key="zoomLabel">{ui.zoomLabel}</small>
33
+ <span class="rdc__zoom-value" id="rdc-font-display">16</span>
34
+ </div>
35
+ <button id="rdc-inc" class="rdc__btn-circle" aria-label="Increase font size">
36
+ <Icon name="mdi:plus" />
37
+ </button>
38
+ </div>
39
+
40
+ <div class="rdc__preview-box">
41
+ <p class="rdc__preview-text" id="rdc-preview" data-key="previewText">{ui.previewText}</p>
42
+ </div>
43
+
44
+ <div class="rdc__slider-wrap">
45
+ <input
46
+ type="range"
47
+ id="rdc-slider"
48
+ class="rdc__slider"
49
+ min="8"
50
+ max="60"
51
+ value="16"
52
+ step="1"
53
+ />
54
+ </div>
55
+ </div>
56
+
57
+
58
+ <div class="rdc__section rdc__section--bordered rdc__section--result">
59
+ <div class="rdc__result-grid">
60
+ <div class="rdc__result-main">
61
+ <span class="rdc__result-label" data-key="resultLabel">{ui.resultLabel}</span>
62
+ <div class="rdc__result-value">
63
+ <span class="rdc__result-number" id="rdc-distance">45</span>
64
+ <span class="rdc__result-unit" data-key="resultUnit">{ui.resultUnit}</span>
65
+ </div>
66
+ <span class="rdc__result-footer" data-key="resultFooter">{ui.resultFooter}</span>
67
+ </div>
68
+
69
+ <div class="rdc__status" id="rdc-status" data-state="ideal">
70
+ <div class="rdc__status-icon" id="rdc-status-icon">
71
+ <Icon name="mdi:check-circle-outline" />
72
+ </div>
73
+ <div class="rdc__status-text">
74
+ <p class="rdc__status-main" id="rdc-status-text">{ui.statusIdeal}</p>
75
+ <small class="rdc__status-sub" id="rdc-status-sub">{ui.statusIdealDesc}</small>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+
81
+
82
+ <div class="rdc__section rdc__section--bordered rdc__section--concepts">
83
+ <div class="rdc__concepts-grid">
84
+ <div class="rdc__concept">
85
+ <h4 class="rdc__concept-title">
86
+ <span class="rdc__concept-icon"><Icon name="mdi:relation-many-to-many" /></span>
87
+ <span data-key="conceptTitle1">{ui.conceptTitle1}</span>
88
+ </h4>
89
+ <p class="rdc__concept-text" data-key="conceptText1">{ui.conceptText1}</p>
90
+ </div>
91
+ <div class="rdc__concept rdc__concept--highlight">
92
+ <h4 class="rdc__concept-title">
93
+ <span class="rdc__concept-icon"><Icon name="mdi:lightbulb-on-outline" /></span>
94
+ <span data-key="conceptTitle2">{ui.conceptTitle2}</span>
95
+ </h4>
96
+ <p class="rdc__concept-text" data-key="conceptText2">{ui.conceptText2}</p>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ </div>
102
+
103
+
104
+ <div class="rdc__icon-cache" aria-hidden="true">
105
+ <span id="rdc-icon-check"><Icon name="mdi:check-circle-outline" /></span>
106
+ <span id="rdc-icon-alert"><Icon name="mdi:alert-circle-outline" /></span>
107
+ <span id="rdc-icon-shield"><Icon name="mdi:shield-check-outline" /></span>
108
+ </div>
109
+ </div>
110
+
111
+ <style>
112
+ .rdc {
113
+ --rdc-primary: #3b82f6;
114
+ --rdc-secondary: #2563eb;
115
+ --rdc-accent: #6366f1;
116
+ --rdc-bg: #fff;
117
+ --rdc-border: rgba(59, 130, 246, 0.15);
118
+ --rdc-divider: rgba(59, 130, 246, 0.1);
119
+ --rdc-surface: #f8fafc;
120
+ --rdc-text: #1e293b;
121
+ --rdc-text-muted: #64748b;
122
+ --rdc-banner-bg: #eff6ff;
123
+ --rdc-banner-color: #1e40af;
124
+ --rdc-preview-bg: #fff;
125
+ --rdc-preview-border: #cbd5e1;
126
+ --rdc-concept-bg: #fff;
127
+ --rdc-concept-border: rgba(0,0,0,0.05);
128
+ --rdc-concept-highlight-bg: #f0fdf4;
129
+ --rdc-concept-highlight-border: #bbf7d0;
130
+ --rdc-concept-highlight-title: #166534;
131
+
132
+ width: 100%;
133
+ max-width: 640px;
134
+ margin: 0 auto;
135
+ }
136
+
137
+ :global(.theme-dark) .rdc {
138
+ --rdc-bg: #0f172a;
139
+ --rdc-border: rgba(99, 102, 241, 0.2);
140
+ --rdc-divider: rgba(99, 102, 241, 0.12);
141
+ --rdc-surface: #1e293b;
142
+ --rdc-text: #f1f5f9;
143
+ --rdc-text-muted: #94a3b8;
144
+ --rdc-banner-bg: rgba(30, 64, 175, 0.2);
145
+ --rdc-banner-color: #93c5fd;
146
+ --rdc-preview-bg: #020617;
147
+ --rdc-preview-border: #334155;
148
+ --rdc-concept-bg: #1e293b;
149
+ --rdc-concept-border: rgba(255,255,255,0.06);
150
+ --rdc-concept-highlight-bg: rgba(20, 83, 45, 0.2);
151
+ --rdc-concept-highlight-border: #166534;
152
+ --rdc-concept-highlight-title: #4ade80;
153
+ }
154
+
155
+ .rdc__card {
156
+ background: var(--rdc-bg);
157
+ border: 2px solid var(--rdc-border);
158
+ border-radius: 2rem;
159
+ box-shadow: 0 20px 60px rgba(59, 130, 246, 0.08);
160
+ overflow: hidden;
161
+ }
162
+
163
+ .rdc__section {
164
+ padding: 2rem;
165
+ }
166
+
167
+ .rdc__section--bordered {
168
+ border-top: 1px solid var(--rdc-divider);
169
+ }
170
+
171
+
172
+ .rdc__banner {
173
+ background: var(--rdc-banner-bg);
174
+ border-radius: 1rem;
175
+ padding: 1rem 1.25rem;
176
+ display: flex;
177
+ align-items: flex-start;
178
+ gap: 0.75rem;
179
+ color: var(--rdc-banner-color);
180
+ }
181
+
182
+ .rdc__banner-icon {
183
+ flex-shrink: 0;
184
+ width: 1.25rem;
185
+ height: 1.25rem;
186
+ margin-top: 0.1rem;
187
+ }
188
+
189
+ .rdc__banner-icon svg {
190
+ width: 100%;
191
+ height: 100%;
192
+ }
193
+
194
+ .rdc__banner-text {
195
+ margin: 0;
196
+ font-size: 0.875rem;
197
+ font-weight: 500;
198
+ line-height: 1.5;
199
+ }
200
+
201
+
202
+ .rdc__preview-controls {
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ gap: 2rem;
207
+ margin-bottom: 1.25rem;
208
+ }
209
+
210
+ .rdc__btn-circle {
211
+ width: 2.5rem;
212
+ height: 2.5rem;
213
+ border-radius: 50%;
214
+ border: none;
215
+ background: var(--rdc-surface);
216
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
217
+ cursor: pointer;
218
+ color: var(--rdc-primary);
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ transition: transform 0.15s, box-shadow 0.15s;
223
+ }
224
+
225
+ .rdc__btn-circle svg {
226
+ width: 1.25rem;
227
+ height: 1.25rem;
228
+ }
229
+
230
+ .rdc__btn-circle:hover {
231
+ transform: translateY(-2px);
232
+ box-shadow: 0 6px 16px rgba(0,0,0,0.12);
233
+ }
234
+
235
+ .rdc__btn-circle:active {
236
+ transform: scale(0.94);
237
+ }
238
+
239
+ .rdc__zoom-indicator {
240
+ display: flex;
241
+ flex-direction: column;
242
+ align-items: center;
243
+ gap: 0.1rem;
244
+ }
245
+
246
+ .rdc__zoom-label {
247
+ font-size: 0.7rem;
248
+ font-weight: 700;
249
+ letter-spacing: 0.08em;
250
+ color: var(--rdc-text-muted);
251
+ text-transform: uppercase;
252
+ }
253
+
254
+ .rdc__zoom-value {
255
+ font-size: 3rem;
256
+ font-weight: 900;
257
+ line-height: 1;
258
+ color: var(--rdc-text);
259
+ }
260
+
261
+
262
+ .rdc__preview-box {
263
+ background: var(--rdc-preview-bg);
264
+ border: 1.5px dashed var(--rdc-preview-border);
265
+ border-radius: 1rem;
266
+ min-height: 6rem;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ padding: 1rem;
271
+ margin-bottom: 1.25rem;
272
+ }
273
+
274
+ .rdc__preview-text {
275
+ margin: 0;
276
+ text-align: center;
277
+ line-height: 1.2;
278
+ color: var(--rdc-text);
279
+ transition: font-size 0.15s ease-out;
280
+ }
281
+
282
+
283
+ .rdc__slider-wrap {
284
+ width: 100%;
285
+ }
286
+
287
+ .rdc__slider {
288
+ width: 100%;
289
+ height: 6px;
290
+ border-radius: 10px;
291
+ appearance: none;
292
+ background: linear-gradient(to right, var(--rdc-primary) 0%, var(--rdc-primary) 22%, #e2e8f0 22%);
293
+ cursor: pointer;
294
+ outline: none;
295
+ }
296
+
297
+ :global(.theme-dark) .rdc__slider {
298
+ background: linear-gradient(to right, var(--rdc-primary) 0%, var(--rdc-primary) 22%, #1e293b 22%);
299
+ }
300
+
301
+ .rdc__slider::-webkit-slider-thumb {
302
+ appearance: none;
303
+ width: 1.25rem;
304
+ height: 1.25rem;
305
+ background: var(--rdc-primary);
306
+ border-radius: 50%;
307
+ box-shadow: 0 0 0 4px var(--rdc-bg);
308
+ border: 1.5px solid rgba(0,0,0,0.08);
309
+ cursor: pointer;
310
+ }
311
+
312
+
313
+ .rdc__section--result {
314
+ background: var(--rdc-surface);
315
+ }
316
+
317
+ .rdc__result-grid {
318
+ display: flex;
319
+ flex-direction: column;
320
+ gap: 1rem;
321
+ }
322
+
323
+ .rdc__result-main {
324
+ background: linear-gradient(135deg, var(--rdc-primary) 0%, var(--rdc-accent) 100%);
325
+ border-radius: 1.25rem;
326
+ padding: 1.5rem;
327
+ color: #fff;
328
+ text-align: center;
329
+ display: flex;
330
+ flex-direction: column;
331
+ align-items: center;
332
+ gap: 0.5rem;
333
+ }
334
+
335
+ .rdc__result-label {
336
+ font-size: 0.7rem;
337
+ font-weight: 800;
338
+ letter-spacing: 0.1em;
339
+ text-transform: uppercase;
340
+ opacity: 0.9;
341
+ }
342
+
343
+ .rdc__result-value {
344
+ display: flex;
345
+ align-items: baseline;
346
+ gap: 0.25rem;
347
+ }
348
+
349
+ .rdc__result-number {
350
+ font-size: 5rem;
351
+ font-weight: 900;
352
+ line-height: 1;
353
+ }
354
+
355
+ .rdc__result-unit {
356
+ font-size: 1.5rem;
357
+ font-weight: 700;
358
+ opacity: 0.8;
359
+ }
360
+
361
+ .rdc__result-footer {
362
+ font-size: 0.7rem;
363
+ font-weight: 600;
364
+ text-transform: uppercase;
365
+ letter-spacing: 0.04em;
366
+ opacity: 0.75;
367
+ }
368
+
369
+
370
+ .rdc__status {
371
+ background: var(--rdc-concept-bg);
372
+ border: 1.5px solid var(--rdc-concept-border);
373
+ border-radius: 1rem;
374
+ padding: 1rem 1.25rem;
375
+ display: flex;
376
+ align-items: center;
377
+ gap: 0.875rem;
378
+ transition: background 0.3s, border-color 0.3s;
379
+ }
380
+
381
+ :global(.rdc__status[data-state="warning"]) .rdc__status-icon {
382
+ background: #fee2e2;
383
+ color: #991b1b;
384
+ }
385
+
386
+ :global(.rdc__status[data-state="ideal"]) .rdc__status-icon {
387
+ background: #dcfce7;
388
+ color: #166534;
389
+ }
390
+
391
+ :global(.rdc__status[data-state="safe"]) .rdc__status-icon {
392
+ background: #dbeafe;
393
+ color: #1e40af;
394
+ }
395
+
396
+ .rdc__status-icon {
397
+ width: 2.5rem;
398
+ height: 2.5rem;
399
+ flex-shrink: 0;
400
+ border-radius: 50%;
401
+ display: flex;
402
+ align-items: center;
403
+ justify-content: center;
404
+ transition: background 0.3s, color 0.3s;
405
+ }
406
+
407
+ .rdc__status-icon svg {
408
+ width: 1.25rem;
409
+ height: 1.25rem;
410
+ }
411
+
412
+ .rdc__status-main {
413
+ margin: 0;
414
+ font-size: 0.9rem;
415
+ font-weight: 700;
416
+ color: var(--rdc-text);
417
+ }
418
+
419
+ .rdc__status-sub {
420
+ font-size: 0.8rem;
421
+ color: var(--rdc-text-muted);
422
+ }
423
+
424
+
425
+ .rdc__section--concepts {
426
+ padding-top: 1.5rem;
427
+ padding-bottom: 1.5rem;
428
+ }
429
+
430
+ .rdc__concepts-grid {
431
+ display: grid;
432
+ grid-template-columns: 1fr;
433
+ gap: 1rem;
434
+ }
435
+
436
+ @media (min-width: 480px) {
437
+ .rdc__concepts-grid {
438
+ grid-template-columns: 1fr 1fr;
439
+ }
440
+
441
+ .rdc__result-grid {
442
+ flex-direction: row;
443
+ align-items: stretch;
444
+ }
445
+
446
+ .rdc__result-main {
447
+ flex: 1.4;
448
+ }
449
+
450
+ .rdc__status {
451
+ flex: 1;
452
+ flex-direction: column;
453
+ align-items: flex-start;
454
+ justify-content: center;
455
+ }
456
+ }
457
+
458
+ .rdc__concept {
459
+ background: var(--rdc-concept-bg);
460
+ border: 1.5px solid var(--rdc-concept-border);
461
+ border-radius: 1rem;
462
+ padding: 1.25rem;
463
+ }
464
+
465
+ .rdc__concept--highlight {
466
+ background: var(--rdc-concept-highlight-bg);
467
+ border-color: var(--rdc-concept-highlight-border);
468
+ }
469
+
470
+ .rdc__concept-title {
471
+ display: flex;
472
+ align-items: center;
473
+ gap: 0.5rem;
474
+ margin: 0 0 0.625rem;
475
+ font-size: 0.95rem;
476
+ font-weight: 800;
477
+ color: var(--rdc-text);
478
+ }
479
+
480
+ .rdc__concept--highlight .rdc__concept-title {
481
+ color: var(--rdc-concept-highlight-title);
482
+ }
483
+
484
+ .rdc__concept-icon {
485
+ width: 1.1rem;
486
+ height: 1.1rem;
487
+ display: flex;
488
+ align-items: center;
489
+ }
490
+
491
+ .rdc__concept-icon svg {
492
+ width: 100%;
493
+ height: 100%;
494
+ }
495
+
496
+ .rdc__concept-text {
497
+ margin: 0;
498
+ font-size: 0.875rem;
499
+ line-height: 1.6;
500
+ color: var(--rdc-text-muted);
501
+ }
502
+
503
+
504
+ .rdc__icon-cache {
505
+ display: none;
506
+ }
507
+ </style>
508
+
509
+ <script>
510
+ // eslint-disable-next-line complexity, max-lines-per-function
511
+ function initRdc(container: Element) {
512
+ const ui = JSON.parse((container as HTMLElement).dataset.ui ?? '{}') as Record<string, string>;
513
+
514
+ const slider = container.querySelector<HTMLInputElement>('#rdc-slider');
515
+ const incBtn = container.querySelector<HTMLButtonElement>('#rdc-inc');
516
+ const decBtn = container.querySelector<HTMLButtonElement>('#rdc-dec');
517
+ const fontDisplay = container.querySelector<HTMLElement>('#rdc-font-display');
518
+ const preview = container.querySelector<HTMLElement>('#rdc-preview');
519
+ const distanceEl = container.querySelector<HTMLElement>('#rdc-distance');
520
+ const statusEl = container.querySelector<HTMLElement>('#rdc-status');
521
+ const statusIcon = container.querySelector<HTMLElement>('#rdc-status-icon');
522
+ const statusText = container.querySelector<HTMLElement>('#rdc-status-text');
523
+ const statusSub = container.querySelector<HTMLElement>('#rdc-status-sub');
524
+
525
+ const iconCheck = container.querySelector<HTMLElement>('#rdc-icon-check');
526
+ const iconAlert = container.querySelector<HTMLElement>('#rdc-icon-alert');
527
+ const iconShield = container.querySelector<HTMLElement>('#rdc-icon-shield');
528
+
529
+ if (!slider || !fontDisplay || !preview || !distanceEl || !statusEl || !statusIcon || !statusText || !statusSub) return;
530
+
531
+ function updateSliderTrack(px: number) {
532
+ if (!slider) return;
533
+ const pct = ((px - 8) / (60 - 8)) * 100;
534
+ slider.style.background = `linear-gradient(to right, var(--rdc-primary) 0%, var(--rdc-primary) ${pct}%, var(--rdc-preview-border) ${pct}%)`;
535
+ }
536
+
537
+ // eslint-disable-next-line complexity
538
+ function getStatus(px: number): { state: string; iconHtml: string; mainText: string; subText: string } {
539
+ const statuses: Record<string, { state: string; iconHtml: string; mainText: string; subText: string }> = {
540
+ warning: { state: 'warning', iconHtml: iconAlert?.innerHTML ?? '', mainText: ui.statusWarning ?? '', subText: ui.statusWarningDesc ?? '' },
541
+ safe: { state: 'safe', iconHtml: iconShield?.innerHTML ?? '', mainText: ui.statusSafe ?? '', subText: ui.statusSafeDesc ?? '' },
542
+ ideal: { state: 'ideal', iconHtml: iconCheck?.innerHTML ?? '', mainText: ui.statusIdeal ?? '', subText: ui.statusIdealDesc ?? '' },
543
+ };
544
+ let statusKey = 'ideal';
545
+ if (px < 14) statusKey = 'warning';
546
+ else if (px > 24) statusKey = 'safe';
547
+ return statuses[statusKey];
548
+ }
549
+
550
+ function update() {
551
+ const px = parseInt(slider!.value);
552
+
553
+ fontDisplay!.textContent = String(px);
554
+ preview!.style.fontSize = `${px}px`;
555
+ distanceEl!.textContent = String(Math.round(px * 2.8));
556
+
557
+ updateSliderTrack(px);
558
+
559
+ const status = getStatus(px);
560
+ statusEl!.setAttribute('data-state', status.state);
561
+ statusIcon!.innerHTML = status.iconHtml;
562
+ statusText!.textContent = status.mainText;
563
+ statusSub!.textContent = status.subText;
564
+ }
565
+
566
+ slider.addEventListener('input', update);
567
+
568
+ incBtn?.addEventListener('click', () => {
569
+ const v = parseInt(slider.value);
570
+ if (v < 60) {
571
+ slider.value = String(v + 1);
572
+ update();
573
+ }
574
+ });
575
+
576
+ decBtn?.addEventListener('click', () => {
577
+ const v = parseInt(slider.value);
578
+ if (v > 8) {
579
+ slider.value = String(v - 1);
580
+ update();
581
+ }
582
+ });
583
+
584
+ update();
585
+ }
586
+
587
+ document.querySelectorAll('.rdc').forEach(initRdc);
588
+ </script>