@jjlmoya/utils-textiles 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 +60 -0
  2. package/src/category/i18n/en.ts +76 -0
  3. package/src/category/i18n/es.ts +76 -0
  4. package/src/category/i18n/fr.ts +73 -0
  5. package/src/category/index.ts +17 -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 +253 -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/seo_length.test.ts +22 -0
  21. package/src/tests/title_separators.test.ts +40 -0
  22. package/src/tests/tool_validation.test.ts +17 -0
  23. package/src/tool/burnTest/bibliography.astro +34 -0
  24. package/src/tool/burnTest/component.astro +602 -0
  25. package/src/tool/burnTest/i18n/en.ts +225 -0
  26. package/src/tool/burnTest/i18n/es.ts +225 -0
  27. package/src/tool/burnTest/i18n/fr.ts +225 -0
  28. package/src/tool/burnTest/index.ts +34 -0
  29. package/src/tool/burnTest/logic.ts +3 -0
  30. package/src/tool/burnTest/seo.astro +40 -0
  31. package/src/tool/burnTest/ui.ts +36 -0
  32. package/src/tool/clothingSizeConverter/bibliography.astro +34 -0
  33. package/src/tool/clothingSizeConverter/component.astro +1226 -0
  34. package/src/tool/clothingSizeConverter/i18n/en.ts +356 -0
  35. package/src/tool/clothingSizeConverter/i18n/es.ts +356 -0
  36. package/src/tool/clothingSizeConverter/i18n/fr.ts +356 -0
  37. package/src/tool/clothingSizeConverter/index.ts +34 -0
  38. package/src/tool/clothingSizeConverter/logic.ts +30 -0
  39. package/src/tool/clothingSizeConverter/seo.astro +40 -0
  40. package/src/tool/clothingSizeConverter/ui.ts +70 -0
  41. package/src/tool/fabricProjectCalculator/bibliography.astro +19 -0
  42. package/src/tool/fabricProjectCalculator/component.astro +914 -0
  43. package/src/tool/fabricProjectCalculator/i18n/en.ts +250 -0
  44. package/src/tool/fabricProjectCalculator/i18n/es.ts +250 -0
  45. package/src/tool/fabricProjectCalculator/i18n/fr.ts +250 -0
  46. package/src/tool/fabricProjectCalculator/index.ts +34 -0
  47. package/src/tool/fabricProjectCalculator/logic.ts +89 -0
  48. package/src/tool/fabricProjectCalculator/seo.astro +40 -0
  49. package/src/tool/fabricProjectCalculator/ui.ts +57 -0
  50. package/src/tool/fabricTruth/bibliography.astro +40 -0
  51. package/src/tool/fabricTruth/component.astro +708 -0
  52. package/src/tool/fabricTruth/i18n/en.ts +282 -0
  53. package/src/tool/fabricTruth/i18n/es.ts +316 -0
  54. package/src/tool/fabricTruth/i18n/fr.ts +282 -0
  55. package/src/tool/fabricTruth/index.ts +34 -0
  56. package/src/tool/fabricTruth/logic.ts +91 -0
  57. package/src/tool/fabricTruth/seo.astro +40 -0
  58. package/src/tool/fabricTruth/ui.ts +28 -0
  59. package/src/tool/fiberPrep/bibliography.astro +33 -0
  60. package/src/tool/fiberPrep/component.astro +987 -0
  61. package/src/tool/fiberPrep/i18n/en.ts +225 -0
  62. package/src/tool/fiberPrep/i18n/es.ts +225 -0
  63. package/src/tool/fiberPrep/i18n/fr.ts +225 -0
  64. package/src/tool/fiberPrep/index.ts +34 -0
  65. package/src/tool/fiberPrep/logic.ts +36 -0
  66. package/src/tool/fiberPrep/seo.astro +40 -0
  67. package/src/tool/fiberPrep/ui.ts +38 -0
  68. package/src/tool/knittingGauge/bibliography.astro +14 -0
  69. package/src/tool/knittingGauge/component.astro +828 -0
  70. package/src/tool/knittingGauge/i18n/en.ts +234 -0
  71. package/src/tool/knittingGauge/i18n/es.ts +234 -0
  72. package/src/tool/knittingGauge/i18n/fr.ts +234 -0
  73. package/src/tool/knittingGauge/index.ts +34 -0
  74. package/src/tool/knittingGauge/logic.ts +28 -0
  75. package/src/tool/knittingGauge/seo.astro +40 -0
  76. package/src/tool/knittingGauge/ui.ts +41 -0
  77. package/src/tool/laundryGuide/bibliography.astro +33 -0
  78. package/src/tool/laundryGuide/component.astro +486 -0
  79. package/src/tool/laundryGuide/data-en.ts +166 -0
  80. package/src/tool/laundryGuide/data-es.ts +166 -0
  81. package/src/tool/laundryGuide/data-fr.ts +103 -0
  82. package/src/tool/laundryGuide/i18n/en.ts +275 -0
  83. package/src/tool/laundryGuide/i18n/es.ts +275 -0
  84. package/src/tool/laundryGuide/i18n/fr.ts +291 -0
  85. package/src/tool/laundryGuide/index.ts +29 -0
  86. package/src/tool/laundryGuide/logic.ts +19 -0
  87. package/src/tool/laundryGuide/seo.astro +40 -0
  88. package/src/tool/needleConverter/bibliography.astro +14 -0
  89. package/src/tool/needleConverter/component.astro +518 -0
  90. package/src/tool/needleConverter/i18n/en.ts +217 -0
  91. package/src/tool/needleConverter/i18n/es.ts +217 -0
  92. package/src/tool/needleConverter/i18n/fr.ts +217 -0
  93. package/src/tool/needleConverter/index.ts +34 -0
  94. package/src/tool/needleConverter/logic.ts +31 -0
  95. package/src/tool/needleConverter/seo.astro +15 -0
  96. package/src/tool/needleConverter/ui.ts +17 -0
  97. package/src/tool/sewingPatternScaler/bibliography.astro +14 -0
  98. package/src/tool/sewingPatternScaler/component.astro +550 -0
  99. package/src/tool/sewingPatternScaler/i18n/en.ts +196 -0
  100. package/src/tool/sewingPatternScaler/i18n/es.ts +231 -0
  101. package/src/tool/sewingPatternScaler/i18n/fr.ts +187 -0
  102. package/src/tool/sewingPatternScaler/index.ts +34 -0
  103. package/src/tool/sewingPatternScaler/logic.ts +35 -0
  104. package/src/tool/sewingPatternScaler/seo.astro +49 -0
  105. package/src/tool/sewingPatternScaler/ui.ts +29 -0
  106. package/src/tool/shoeSizeConverter/bibliography.astro +34 -0
  107. package/src/tool/shoeSizeConverter/component.astro +437 -0
  108. package/src/tool/shoeSizeConverter/i18n/en.ts +188 -0
  109. package/src/tool/shoeSizeConverter/i18n/es.ts +188 -0
  110. package/src/tool/shoeSizeConverter/i18n/fr.ts +188 -0
  111. package/src/tool/shoeSizeConverter/index.ts +34 -0
  112. package/src/tool/shoeSizeConverter/logic.ts +8 -0
  113. package/src/tool/shoeSizeConverter/seo.astro +40 -0
  114. package/src/tool/shoeSizeConverter/ui.ts +21 -0
  115. package/src/tool/stainChemistry/bibliography.astro +40 -0
  116. package/src/tool/stainChemistry/component.astro +717 -0
  117. package/src/tool/stainChemistry/i18n/en.ts +303 -0
  118. package/src/tool/stainChemistry/i18n/es.ts +300 -0
  119. package/src/tool/stainChemistry/i18n/fr.ts +310 -0
  120. package/src/tool/stainChemistry/index.ts +34 -0
  121. package/src/tool/stainChemistry/logic.ts +11 -0
  122. package/src/tool/stainChemistry/seo.astro +40 -0
  123. package/src/tool/stainChemistry/ui.ts +51 -0
  124. package/src/tool/yarnCalculator/bibliography.astro +19 -0
  125. package/src/tool/yarnCalculator/component.astro +792 -0
  126. package/src/tool/yarnCalculator/i18n/en.ts +310 -0
  127. package/src/tool/yarnCalculator/i18n/es.ts +310 -0
  128. package/src/tool/yarnCalculator/i18n/fr.ts +310 -0
  129. package/src/tool/yarnCalculator/index.ts +34 -0
  130. package/src/tool/yarnCalculator/logic.ts +84 -0
  131. package/src/tool/yarnCalculator/seo.astro +14 -0
  132. package/src/tool/yarnCalculator/ui.ts +42 -0
  133. package/src/tools.ts +16 -0
  134. package/src/types.ts +72 -0
@@ -0,0 +1,708 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { FabricTruthUI } from './ui';
4
+
5
+ interface Props {
6
+ ui: FabricTruthUI;
7
+ }
8
+
9
+ const { ui } = Astro.props;
10
+
11
+ const fiberData = ui?.fiberData ?? {};
12
+ const fibers = Object.entries(fiberData)
13
+ .map(([id, data]) => ({
14
+ id,
15
+ name: data.name || id
16
+ }))
17
+ .sort((a, b) => a.name.localeCompare(b.name));
18
+ ---
19
+
20
+ <div class="fabric-truth-container" id="fabric-truth-root">
21
+ <div class="card-wrapper">
22
+ <div class="composition-section">
23
+ <h2 class="section-title">
24
+ <Icon name="mdi:tag-text-outline" class="title-icon" />
25
+ {ui.composition}
26
+ </h2>
27
+ <p class="section-description">{ui.description}</p>
28
+
29
+ <div id="composition-rows" class="rows-container"></div>
30
+
31
+ <div class="controls-footer">
32
+ <button id="add-row-btn" class="btn-add">
33
+ <Icon name="mdi:plus" class="btn-icon" />
34
+ {ui.addFiber}
35
+ </button>
36
+ <div id="total-indicator" class="total-badge">
37
+ {ui.total}: <span id="total-val">0</span>%
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="results-section">
43
+ <div id="empty-state" class="empty-state">
44
+ <Icon name="mdi:alert-circle-outline" class="empty-icon" />
45
+ <p id="validation-msg"></p>
46
+ <p id="total-helper" class="empty-helper"></p>
47
+ </div>
48
+
49
+ <div id="result-content" class="result-content hidden">
50
+ <div id="verdict-card" class="verdict-card">
51
+ <div class="verdict-content">
52
+ <span class="verdict-meta">{ui.verdict}</span>
53
+ <h3 id="verdict-label" class="verdict-label">Calidad Natural</h3>
54
+ <p id="verdict-description" class="verdict-desc">Prenda transpirable y duradera.</p>
55
+ </div>
56
+ <div class="verdict-icon">
57
+ <Icon name="mdi:check-decagram" />
58
+ </div>
59
+ </div>
60
+
61
+ <div class="stats-grid">
62
+ <div class="stat-card">
63
+ <div class="stat-header">
64
+ <Icon name="mdi:wind-power" />
65
+ <span>{ui.breathability}</span>
66
+ </div>
67
+ <div class="stat-bar">
68
+ <div id="stat-breathability" class="stat-fill"></div>
69
+ </div>
70
+ <div id="stat-val-breathability" class="stat-value">0/10</div>
71
+ </div>
72
+
73
+ <div class="stat-card">
74
+ <div class="stat-header">
75
+ <Icon name="mdi:shield-check" />
76
+ <span>{ui.durability}</span>
77
+ </div>
78
+ <div class="stat-bar">
79
+ <div id="stat-durability" class="stat-fill"></div>
80
+ </div>
81
+ <div id="stat-val-durability" class="stat-value">0/10</div>
82
+ </div>
83
+
84
+ <div class="stat-card">
85
+ <div class="stat-header">
86
+ <Icon name="mdi:fire-circle" />
87
+ <span>{ui.warmth}</span>
88
+ </div>
89
+ <div class="stat-bar">
90
+ <div id="stat-warmth" class="stat-fill"></div>
91
+ </div>
92
+ <div id="stat-val-warmth" class="stat-value">0/10</div>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="care-warning">
97
+ <Icon name="mdi:alert-circle" />
98
+ <div>
99
+ <h4>{ui.careWarning}</h4>
100
+ <p id="care-warning">Sigue siempre las instrucciones de la etiqueta.</p>
101
+ </div>
102
+ </div>
103
+
104
+ <p class="disclaimer" id="disclaimer">{ui.disclaimer}</p>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+
110
+ <datalist id="perc-list">
111
+ <option value="100">100%</option>
112
+ <option value="95">95%</option>
113
+ <option value="80">80%</option>
114
+ <option value="50">50%</option>
115
+ <option value="30">30%</option>
116
+ <option value="10">10%</option>
117
+ <option value="5">5%</option>
118
+ <option value="2">2%</option>
119
+ </datalist>
120
+
121
+ <template id="fiber-row-template">
122
+ <div class="fiber-row">
123
+ <select class="fiber-select">
124
+ {fibers.map((f) => <option value={f.id}>{f.name}</option>)}
125
+ </select>
126
+ <div class="fiber-perc-wrapper">
127
+ <input type="number" min="0" max="100" step="1" placeholder="0" list="perc-list" class="fiber-perc" />
128
+ <span class="perc-symbol">%</span>
129
+ </div>
130
+ <button class="remove-row-btn">
131
+ <Icon name="mdi:close-circle-outline" />
132
+ </button>
133
+ </div>
134
+ </template>
135
+
136
+ <script define:vars={{ ui }} is:inline>
137
+ window.__fabricUI = ui;
138
+ </script>
139
+
140
+ <script>
141
+ import { FabricEngine } from './logic';
142
+ import type { CompositionRow } from './logic';
143
+ import type { FabricTruthUI } from './ui';
144
+
145
+ const COLORS: Record<string, string> = {
146
+ red: 'color-red',
147
+ amber: 'color-amber',
148
+ yellow: 'color-yellow',
149
+ emerald: 'color-emerald',
150
+ blue: 'color-blue',
151
+ indigo: 'color-indigo',
152
+ gray: 'color-gray',
153
+ };
154
+
155
+ function renderStats(averages: { b: number; d: number; w: number }) {
156
+ (['breathability', 'durability', 'warmth'] as const).forEach((key) => {
157
+ const val = averages[key[0] as 'b' | 'd' | 'w'];
158
+ const bar = document.getElementById(`stat-${key}`);
159
+ const text = document.getElementById(`stat-val-${key}`);
160
+ if (bar) bar.style.width = `${val * 10}%`;
161
+ if (text) text.textContent = `${val.toFixed(1)}/10`;
162
+ });
163
+ }
164
+
165
+ function renderVerdict(composition: CompositionRow[], uiData: FabricTruthUI) {
166
+ const verdictLabel = document.getElementById('verdict-label');
167
+ const verdictDescription = document.getElementById('verdict-description');
168
+ const verdictCard = document.getElementById('verdict-card');
169
+ const careWarning = document.getElementById('care-warning');
170
+
171
+ const { fiberData, verdicts } = uiData;
172
+ const careWarnings = uiData.careWarnings as Record<string, string>;
173
+ const verdict = FabricEngine.calculateVerdict(composition, fiberData, verdicts);
174
+ const averages = FabricEngine.getAverages(composition, fiberData);
175
+ const warning = FabricEngine.getCareWarning(composition, careWarnings);
176
+
177
+ if (verdictLabel) verdictLabel.textContent = verdict.label;
178
+ if (verdictDescription) verdictDescription.textContent = verdict.description;
179
+ if (verdictCard) verdictCard.className = `verdict-card ${COLORS[verdict.color] ?? COLORS.gray}`;
180
+ if (careWarning) careWarning.textContent = warning;
181
+ renderStats(averages);
182
+ }
183
+
184
+ function updateDisplay(composition: CompositionRow[], total: number, uiData: FabricTruthUI) {
185
+ const emptyState = document.getElementById('empty-state');
186
+ const resultContent = document.getElementById('result-content');
187
+ const validationMsg = document.getElementById('validation-msg');
188
+
189
+ if (total !== 100) {
190
+ emptyState?.classList.remove('hidden');
191
+ resultContent?.classList.add('hidden');
192
+ if (validationMsg) validationMsg.textContent = uiData.validationError;
193
+ return;
194
+ }
195
+
196
+ emptyState?.classList.add('hidden');
197
+ resultContent?.classList.remove('hidden');
198
+ renderVerdict(composition, uiData);
199
+ }
200
+
201
+ function updateTotalBadge(total: number) {
202
+ const totalVal = document.getElementById('total-val');
203
+ const totalIndicator = document.getElementById('total-indicator');
204
+ if (totalVal) totalVal.textContent = total.toString();
205
+ if (!totalIndicator) return;
206
+ totalIndicator.className = 'total-badge';
207
+ if (total === 100) totalIndicator.classList.add('total-complete');
208
+ if (total > 100) totalIndicator.classList.add('total-error');
209
+ }
210
+
211
+ function updateAnalysis() {
212
+ const uiData = (window as unknown as { __fabricUI: FabricTruthUI }).__fabricUI;
213
+ const container = document.getElementById('composition-rows');
214
+ const totalHelper = document.getElementById('total-helper');
215
+
216
+ const rows = container?.querySelectorAll('.fiber-row');
217
+ if (!rows) return;
218
+
219
+ const composition: CompositionRow[] = Array.from(rows).map((row) => ({
220
+ fiberId: (row.querySelector('.fiber-select') as HTMLSelectElement).value,
221
+ percentage: parseFloat((row.querySelector('.fiber-perc') as HTMLInputElement).value) || 0,
222
+ })).filter((c) => c.percentage > 0);
223
+
224
+ const total = composition.reduce((acc, c) => acc + c.percentage, 0);
225
+
226
+ updateTotalBadge(total);
227
+ if (totalHelper) totalHelper.textContent = total === 100 ? '' : `${uiData.actualTotal}: ${total}%`;
228
+ updateDisplay(composition, total, uiData);
229
+ }
230
+
231
+ function addRow() {
232
+ const container = document.getElementById('composition-rows');
233
+ const template = document.getElementById('fiber-row-template') as HTMLTemplateElement;
234
+ if (!template || !container) return;
235
+
236
+ const currentTotal = Array.from(container.querySelectorAll('.fiber-row'))
237
+ .reduce((acc, r) => acc + (parseFloat((r.querySelector('.fiber-perc') as HTMLInputElement).value) || 0), 0);
238
+
239
+ const row = (template.content.cloneNode(true) as HTMLElement).querySelector('.fiber-row') as HTMLElement;
240
+ const select = row.querySelector('.fiber-select') as HTMLSelectElement;
241
+ const input = row.querySelector('.fiber-perc') as HTMLInputElement;
242
+
243
+ if (container.children.length === 0) {
244
+ select.value = 'cotton';
245
+ input.value = '100';
246
+ } else {
247
+ input.value = Math.max(0, 100 - currentTotal).toString();
248
+ }
249
+
250
+ [select, input].forEach((el) => el.addEventListener('input', updateAnalysis));
251
+ row.querySelector('.remove-row-btn')?.addEventListener('click', () => {
252
+ row.remove();
253
+ updateAnalysis();
254
+ });
255
+
256
+ container.appendChild(row);
257
+ updateAnalysis();
258
+ }
259
+
260
+ function initFabricTruth() {
261
+ const root = document.getElementById('fabric-truth-root');
262
+ if (!root) return;
263
+ const addBtn = document.getElementById('add-row-btn');
264
+ addBtn?.addEventListener('click', addRow);
265
+ const container = document.getElementById('composition-rows');
266
+ if (container && container.children.length === 0) addRow();
267
+ }
268
+
269
+ function startInit() {
270
+ if (document.readyState === 'loading') {
271
+ document.addEventListener('DOMContentLoaded', initFabricTruth);
272
+ } else {
273
+ setTimeout(initFabricTruth, 100);
274
+ }
275
+ document.addEventListener('astro:page-load', initFabricTruth);
276
+ }
277
+
278
+ startInit();
279
+ </script>
280
+
281
+ <style>
282
+ .fabric-truth-container {
283
+ width: 100%;
284
+ padding: 1rem;
285
+ }
286
+
287
+ .card-wrapper {
288
+ background: var(--bg-surface);
289
+ border: 1px solid var(--border-color);
290
+ border-radius: 1.5rem;
291
+ overflow: hidden;
292
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
293
+ }
294
+
295
+ .composition-section {
296
+ padding: 2rem;
297
+ border-bottom: 1px solid var(--border-color);
298
+ }
299
+
300
+ .section-title {
301
+ font-size: 1.5rem;
302
+ font-weight: bold;
303
+ margin: 0 0 0.5rem;
304
+ display: flex;
305
+ align-items: center;
306
+ gap: 0.5rem;
307
+ color: var(--text-main);
308
+ }
309
+
310
+ .title-icon {
311
+ width: 1.5rem;
312
+ height: 1.5rem;
313
+ color: #6366f1;
314
+ }
315
+
316
+ .section-description {
317
+ color: var(--text-muted);
318
+ margin: 0 0 1.5rem;
319
+ font-size: 0.95rem;
320
+ }
321
+
322
+ .rows-container {
323
+ display: flex;
324
+ flex-direction: column;
325
+ gap: 1rem;
326
+ margin-bottom: 1.5rem;
327
+ }
328
+
329
+ .fiber-row {
330
+ display: flex;
331
+ gap: 0.75rem;
332
+ align-items: center;
333
+ animation: slide-in 0.3s ease;
334
+ }
335
+
336
+ @keyframes slide-in {
337
+ from {
338
+ opacity: 0;
339
+ transform: translateX(-10px);
340
+ }
341
+ to {
342
+ opacity: 1;
343
+ transform: translateX(0);
344
+ }
345
+ }
346
+
347
+ .fiber-select {
348
+ flex: 1;
349
+ min-width: 150px;
350
+ background: var(--bg-page);
351
+ border: 1px solid var(--border-color);
352
+ border-radius: 0.75rem;
353
+ padding: 0.75rem;
354
+ color: var(--text-main);
355
+ font-weight: 500;
356
+ cursor: pointer;
357
+ }
358
+
359
+ .fiber-select:focus {
360
+ outline: none;
361
+ border-color: #6366f1;
362
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
363
+ }
364
+
365
+ .fiber-perc-wrapper {
366
+ position: relative;
367
+ width: 120px;
368
+ }
369
+
370
+ .fiber-perc {
371
+ width: 100%;
372
+ background: var(--bg-page);
373
+ border: 1px solid var(--border-color);
374
+ border-radius: 0.75rem;
375
+ padding: 0.75rem 2.5rem 0.75rem 0.75rem;
376
+ color: var(--text-main);
377
+ font-weight: 500;
378
+ }
379
+
380
+ .fiber-perc:focus {
381
+ outline: none;
382
+ border-color: #6366f1;
383
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
384
+ }
385
+
386
+ .perc-symbol {
387
+ position: absolute;
388
+ right: 0.75rem;
389
+ top: 50%;
390
+ transform: translateY(-50%);
391
+ color: var(--text-muted);
392
+ font-weight: bold;
393
+ pointer-events: none;
394
+ }
395
+
396
+ .remove-row-btn {
397
+ background: none;
398
+ border: none;
399
+ color: var(--text-muted);
400
+ cursor: pointer;
401
+ padding: 0.5rem;
402
+ display: flex;
403
+ align-items: center;
404
+ transition: color 0.2s;
405
+ }
406
+
407
+ .remove-row-btn:hover {
408
+ color: #ef4444;
409
+ }
410
+
411
+ .controls-footer {
412
+ display: flex;
413
+ justify-content: space-between;
414
+ align-items: center;
415
+ padding-top: 1rem;
416
+ border-top: 1px solid var(--border-color);
417
+ gap: 1rem;
418
+ flex-wrap: wrap;
419
+ }
420
+
421
+ .btn-add {
422
+ display: flex;
423
+ align-items: center;
424
+ gap: 0.5rem;
425
+ padding: 0.75rem 1.5rem;
426
+ background: var(--bg-page);
427
+ border: 1px solid var(--border-color);
428
+ border-radius: 0.75rem;
429
+ color: var(--text-main);
430
+ font-weight: 500;
431
+ cursor: pointer;
432
+ transition: all 0.2s;
433
+ }
434
+
435
+ .btn-add:hover {
436
+ background: var(--bg-surface);
437
+ border-color: #6366f1;
438
+ }
439
+
440
+ .btn-icon {
441
+ width: 1.25rem;
442
+ height: 1.25rem;
443
+ }
444
+
445
+ .total-badge {
446
+ padding: 0.75rem 1.5rem;
447
+ border-radius: 0.75rem;
448
+ font-weight: 600;
449
+ background: var(--bg-page);
450
+ color: var(--text-muted);
451
+ border: 1px solid var(--border-color);
452
+ transition: all 0.2s;
453
+ }
454
+
455
+ :global(.total-badge.total-complete) {
456
+ background: rgba(16, 185, 129, 0.1);
457
+ color: #10b981;
458
+ border-color: #10b981;
459
+ }
460
+
461
+ :global(.total-badge.total-error) {
462
+ background: rgba(239, 68, 68, 0.1);
463
+ color: #ef4444;
464
+ border-color: #ef4444;
465
+ animation: pulse 0.5s;
466
+ }
467
+
468
+ @keyframes pulse {
469
+ 0%, 100% { opacity: 1; }
470
+ 50% { opacity: 0.7; }
471
+ }
472
+
473
+ .results-section {
474
+ padding: 2rem;
475
+ background: var(--bg-page);
476
+ }
477
+
478
+ .empty-state {
479
+ text-align: center;
480
+ padding: 2rem;
481
+ }
482
+
483
+ .empty-icon {
484
+ width: 4rem;
485
+ height: 4rem;
486
+ margin: 0 auto 1rem;
487
+ color: var(--border-color);
488
+ }
489
+
490
+ .empty-state p {
491
+ margin: 0.5rem 0;
492
+ color: var(--text-muted);
493
+ font-size: 0.95rem;
494
+ }
495
+
496
+ .empty-helper {
497
+ font-size: 0.85rem;
498
+ font-style: italic;
499
+ color: var(--text-muted);
500
+ margin-top: 0.25rem;
501
+ }
502
+
503
+ .result-content {
504
+ display: flex;
505
+ flex-direction: column;
506
+ gap: 1.5rem;
507
+ animation: fade-in 0.5s ease;
508
+ }
509
+
510
+ :global(.result-content.hidden) {
511
+ display: none;
512
+ }
513
+
514
+ :global(.empty-state.hidden) {
515
+ display: none;
516
+ }
517
+
518
+ @keyframes fade-in {
519
+ from { opacity: 0; }
520
+ to { opacity: 1; }
521
+ }
522
+
523
+ .verdict-card {
524
+ padding: 1.5rem;
525
+ border-radius: 1rem;
526
+ border-left: 4px solid;
527
+ display: flex;
528
+ justify-content: space-between;
529
+ align-items: center;
530
+ transition: all 0.3s;
531
+ min-height: 100px;
532
+ }
533
+
534
+ :global(.verdict-card.color-red) {
535
+ background: rgba(239, 68, 68, 0.1);
536
+ color: #ef4444;
537
+ border-left-color: #dc2626;
538
+ }
539
+
540
+ :global(.verdict-card.color-amber) {
541
+ background: rgba(251, 146, 60, 0.1);
542
+ color: #f97316;
543
+ border-left-color: #ea580c;
544
+ }
545
+
546
+ :global(.verdict-card.color-yellow) {
547
+ background: rgba(234, 179, 8, 0.1);
548
+ color: #ca8a04;
549
+ border-left-color: #b45309;
550
+ }
551
+
552
+ :global(.verdict-card.color-emerald) {
553
+ background: rgba(16, 185, 129, 0.1);
554
+ color: #059669;
555
+ border-left-color: #047857;
556
+ }
557
+
558
+ :global(.verdict-card.color-blue) {
559
+ background: rgba(59, 130, 246, 0.1);
560
+ color: #2563eb;
561
+ border-left-color: #1d4ed8;
562
+ }
563
+
564
+ :global(.verdict-card.color-indigo) {
565
+ background: rgba(99, 102, 241, 0.1);
566
+ color: #4f46e5;
567
+ border-left-color: #4338ca;
568
+ }
569
+
570
+ :global(.verdict-card.color-gray) {
571
+ background: rgba(107, 114, 128, 0.1);
572
+ color: #6b7280;
573
+ border-left-color: #4b5563;
574
+ }
575
+
576
+ .verdict-content {
577
+ flex: 1;
578
+ }
579
+
580
+ .verdict-meta {
581
+ font-size: 0.75rem;
582
+ font-weight: 700;
583
+ text-transform: uppercase;
584
+ letter-spacing: 0.05em;
585
+ opacity: 0.7;
586
+ }
587
+
588
+ .verdict-label {
589
+ font-size: 1.75rem;
590
+ font-weight: 900;
591
+ margin: 0.5rem 0;
592
+ }
593
+
594
+ .verdict-desc {
595
+ font-size: 1rem;
596
+ margin: 0;
597
+ opacity: 0.9;
598
+ }
599
+
600
+ .verdict-icon {
601
+ width: 3rem;
602
+ height: 3rem;
603
+ display: flex;
604
+ align-items: center;
605
+ justify-content: center;
606
+ background: rgba(255, 255, 255, 0.2);
607
+ border-radius: 50%;
608
+ }
609
+
610
+ .stats-grid {
611
+ display: grid;
612
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
613
+ gap: 1rem;
614
+ }
615
+
616
+ .stat-card {
617
+ background: var(--bg-surface);
618
+ padding: 1rem;
619
+ border-radius: 0.75rem;
620
+ border: 1px solid var(--border-color);
621
+ }
622
+
623
+ .stat-header {
624
+ display: flex;
625
+ align-items: center;
626
+ gap: 0.5rem;
627
+ margin-bottom: 0.75rem;
628
+ font-size: 0.75rem;
629
+ font-weight: 700;
630
+ text-transform: uppercase;
631
+ letter-spacing: 0.05em;
632
+ color: var(--text-muted);
633
+ }
634
+
635
+ .stat-bar {
636
+ width: 100%;
637
+ height: 6px;
638
+ background: var(--border-color);
639
+ border-radius: 99px;
640
+ overflow: hidden;
641
+ margin-bottom: 0.5rem;
642
+ }
643
+
644
+ .stat-fill {
645
+ height: 100%;
646
+ background: currentcolor;
647
+ transition: width 1s ease;
648
+ width: 0%;
649
+ }
650
+
651
+ .stat-card:nth-child(1) .stat-fill {
652
+ background: #10b981;
653
+ }
654
+
655
+ .stat-card:nth-child(2) .stat-fill {
656
+ background: #3b82f6;
657
+ }
658
+
659
+ .stat-card:nth-child(3) .stat-fill {
660
+ background: #f97316;
661
+ }
662
+
663
+ .stat-value {
664
+ font-size: 0.85rem;
665
+ font-weight: 600;
666
+ text-align: right;
667
+ color: var(--text-muted);
668
+ }
669
+
670
+ .care-warning {
671
+ background: rgba(251, 191, 36, 0.1);
672
+ border: 1px solid rgba(251, 146, 60, 0.3);
673
+ padding: 1rem;
674
+ border-radius: 0.75rem;
675
+ display: flex;
676
+ gap: 0.75rem;
677
+ color: #b45309;
678
+ }
679
+
680
+ .care-warning svg {
681
+ width: 1.5rem;
682
+ height: 1.5rem;
683
+ flex-shrink: 0;
684
+ margin-top: 0.1rem;
685
+ }
686
+
687
+ .care-warning h4 {
688
+ font-weight: 700;
689
+ font-size: 0.85rem;
690
+ margin: 0 0 0.25rem;
691
+ text-transform: uppercase;
692
+ letter-spacing: 0.05em;
693
+ }
694
+
695
+ .care-warning p {
696
+ font-size: 0.9rem;
697
+ margin: 0;
698
+ }
699
+
700
+ .disclaimer {
701
+ text-align: center;
702
+ font-size: 0.75rem;
703
+ text-transform: uppercase;
704
+ letter-spacing: 0.05em;
705
+ color: var(--text-muted);
706
+ margin: 0;
707
+ }
708
+ </style>