@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,17 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import { textilesCategory } from '../data';
4
+
5
+ describe('Tool Validation Suite', () => {
6
+ describe('Library Registration', () => {
7
+ it('should have 9 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(12);
9
+ });
10
+
11
+ it('textilesCategory should be defined', () => {
12
+ expect(textilesCategory).toBeDefined();
13
+ expect(textilesCategory.i18n).toBeDefined();
14
+ });
15
+ });
16
+ });
17
+
@@ -0,0 +1,34 @@
1
+ ---
2
+ ---
3
+ <div class="bibliography">
4
+ <h3>References</h3>
5
+ <ul>
6
+ <li><a href="https://members.aatcc.org/store/tm20/485/" target="_blank">AATCC TM20: Fiber Analysis - Qualitative</a></li>
7
+ <li><a href="https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular41e3.pdf" target="_blank">Textile Fibers: Identification and Testing - NIST</a></li>
8
+ <li><a href="https://fabricmartfabrics.com/pages/burn-test-chart" target="_blank">Burn Test Chart for Fiber Identification - Fabric Mart</a></li>
9
+ </ul>
10
+ </div>
11
+ <style>
12
+ .bibliography {
13
+ padding: 1rem 0;
14
+ }
15
+ .bibliography h3 {
16
+ font-size: 1.1rem;
17
+ font-weight: 600;
18
+ margin-bottom: 1rem;
19
+ }
20
+ .bibliography ul {
21
+ list-style: none;
22
+ padding: 0;
23
+ }
24
+ .bibliography li {
25
+ margin-bottom: 0.5rem;
26
+ }
27
+ .bibliography a {
28
+ color: #6366f1;
29
+ text-decoration: none;
30
+ }
31
+ .bibliography a:hover {
32
+ text-decoration: underline;
33
+ }
34
+ </style>
@@ -0,0 +1,602 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { BurnTestUI } from './ui';
4
+
5
+ interface Props {
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const { ui = {} } = Astro.props;
10
+ const burnUI = ui as BurnTestUI;
11
+ const fibers = Object.entries(burnUI.fiberData ?? {});
12
+ ---
13
+
14
+ <div class="burn-wizard" id="burn-wizard-root">
15
+ <div class="icon-cache" aria-hidden="true">
16
+ {fibers.map(([id, fiber]) => (
17
+ <div id={`icon-cache-${id}`}>
18
+ <Icon name={fiber.icon} />
19
+ </div>
20
+ ))}
21
+ </div>
22
+
23
+ <div class="wizard-card">
24
+ <div class="wizard-header">
25
+ <div class="header-left">
26
+ <div class="header-badge">
27
+ <Icon name="mdi:test-tube" />
28
+ </div>
29
+ <div class="header-info">
30
+ <span class="investigation-label">{burnUI.investigationLabel ?? 'INVESTIGATION'}</span>
31
+ <span class="step-indicator" id="step-indicator">...</span>
32
+ </div>
33
+ </div>
34
+ <button class="reset-btn hidden" id="reset-btn">
35
+ <Icon name="mdi:refresh" />
36
+ <span>{burnUI.resetLabel ?? 'RESET'}</span>
37
+ </button>
38
+ </div>
39
+
40
+ <div class="wizard-content">
41
+ <div id="question-view" class="question-view">
42
+ <h3 id="question-title" class="question-title">...</h3>
43
+ <div class="options-grid" id="options-grid"></div>
44
+ </div>
45
+
46
+ <div id="result-view" class="result-view hidden">
47
+ <div class="result-icon-wrap">
48
+ <div class="result-icon-container" id="result-icon-container">
49
+ <Icon name="mdi:microscope" />
50
+ </div>
51
+ </div>
52
+ <div class="result-meta">
53
+ <span class="positive-id">{burnUI.positiveId ?? 'POSITIVE IDENTIFICATION'}</span>
54
+ <h2 id="result-name" class="result-name">...</h2>
55
+ </div>
56
+ <div class="result-desc" id="result-desc"></div>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="progress-track">
61
+ <div class="progress-fill" id="progress-bar" style="width: 0%"></div>
62
+ </div>
63
+ </div>
64
+
65
+ <div class="safety-note">
66
+ <div class="safety-icon">
67
+ <Icon name="mdi:shield-alert" />
68
+ </div>
69
+ <div class="safety-text">
70
+ <h5 class="safety-title">{burnUI.safetyTitle ?? 'Safety Protocol'}</h5>
71
+ <p>{burnUI.safetyText ?? ''}</p>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <script define:vars={{ ui }} is:inline>
77
+ window.__burnTestUI = ui;
78
+ </script>
79
+
80
+ <script>
81
+ import type { BurnTestUI, BurnTestEntry, BurnTestFiber } from './ui';
82
+
83
+ type BurnTestStep = 'flame' | 'odor' | 'residue' | 'smoke';
84
+ const STEPS: BurnTestStep[] = ['flame', 'odor', 'residue', 'smoke'];
85
+
86
+ const rawUI = (window as unknown as { __burnTestUI: BurnTestUI }).__burnTestUI;
87
+ const { fiberData, burnTestData, questions, stepPrefix, stepCompleted } = rawUI;
88
+ const { flameLabel, odorLabel, residueLabel, identifiedAs } = rawUI;
89
+
90
+ let candidates: BurnTestEntry[] = [...burnTestData];
91
+
92
+ function getEl(id: string): HTMLElement | null {
93
+ return document.getElementById(id);
94
+ }
95
+
96
+ function getNextDiscriminator(): BurnTestStep | null {
97
+ if (candidates.length === burnTestData.length) return STEPS[0];
98
+ return STEPS.find((s) => new Set(candidates.map((c) => c[s])).size > 1) ?? null;
99
+ }
100
+
101
+ function updateProgress(pct: number) {
102
+ const bar = getEl('progress-bar');
103
+ if (bar) bar.style.width = `${pct}%`;
104
+ }
105
+
106
+ function animateTitle(disc: BurnTestStep) {
107
+ const el = getEl('question-title');
108
+ if (!el) return;
109
+ el.style.opacity = '0';
110
+ setTimeout(() => {
111
+ el.textContent = questions[disc];
112
+ el.style.opacity = '1';
113
+ }, 300);
114
+ }
115
+
116
+ function makeOptionBtn(opt: string, index: number, disc: BurnTestStep): HTMLButtonElement {
117
+ const btn = document.createElement('button');
118
+ btn.className = 'option-btn';
119
+ btn.style.animationDelay = `${index * 0.1}s`;
120
+ btn.textContent = opt;
121
+ btn.onclick = () => selectOption(disc, opt);
122
+ return btn;
123
+ }
124
+
125
+ function buildOptions(disc: BurnTestStep) {
126
+ const grid = getEl('options-grid');
127
+ if (!grid) return;
128
+ grid.innerHTML = '';
129
+ const opts = [...new Set(candidates.map((c) => c[disc]))];
130
+ opts.forEach((opt, i) => grid.appendChild(makeOptionBtn(opt, i, disc)));
131
+ }
132
+
133
+ function buildResultDetail(result: BurnTestEntry): string {
134
+ const fiber: BurnTestFiber | undefined = fiberData[result.fiberId];
135
+ const name = fiber?.name ?? result.fiberId;
136
+ return `<div class="result-details">
137
+ <p class="result-id-text">${identifiedAs} <strong>${name}</strong>.</p>
138
+ <div class="result-attrs">
139
+ <div class="attr-row attr-flame">
140
+ <span class="attr-label">${flameLabel}</span>
141
+ <span class="attr-value">${result.flame}</span>
142
+ </div>
143
+ <div class="attr-row attr-odor">
144
+ <span class="attr-label">${odorLabel}</span>
145
+ <span class="attr-value">${result.odor}</span>
146
+ </div>
147
+ <div class="attr-row attr-residue">
148
+ <span class="attr-label">${residueLabel}</span>
149
+ <span class="attr-value">${result.residue}</span>
150
+ </div>
151
+ </div>
152
+ </div>`;
153
+ }
154
+
155
+ function showResultIcon(fiberId: string) {
156
+ const cached = getEl(`icon-cache-${fiberId}`);
157
+ const container = getEl('result-icon-container');
158
+ if (!cached || !container) return;
159
+ container.innerHTML = cached.innerHTML;
160
+ const svg = container.querySelector('svg');
161
+ if (svg) {
162
+ svg.style.width = '1em';
163
+ svg.style.height = '1em';
164
+ }
165
+ }
166
+
167
+ function showResult(result: BurnTestEntry) {
168
+ const fiber = fiberData[result.fiberId];
169
+ if (!fiber) return;
170
+ getEl('question-view')?.classList.add('hidden');
171
+ getEl('result-view')?.classList.remove('hidden');
172
+ getEl('reset-btn')?.classList.remove('hidden');
173
+ updateProgress(100);
174
+ const nameEl = getEl('result-name');
175
+ if (nameEl) nameEl.textContent = fiber.name;
176
+ const descEl = getEl('result-desc');
177
+ if (descEl) descEl.innerHTML = buildResultDetail(result);
178
+ const indEl = getEl('step-indicator');
179
+ if (indEl) indEl.textContent = stepCompleted;
180
+ showResultIcon(result.fiberId);
181
+ }
182
+
183
+ function renderStep() {
184
+ if (candidates.length === 1) { showResult(candidates[0]); return; }
185
+ const disc = getNextDiscriminator();
186
+ if (!disc) { showResult(candidates[0]); return; }
187
+ animateTitle(disc);
188
+ const indEl = getEl('step-indicator');
189
+ if (indEl) indEl.textContent = `${stepPrefix} ${STEPS.indexOf(disc) + 1}`;
190
+ buildOptions(disc);
191
+ getEl('reset-btn')?.classList.toggle('hidden', candidates.length === burnTestData.length);
192
+ updateProgress((STEPS.indexOf(disc) / STEPS.length) * 100);
193
+ }
194
+
195
+ function selectOption(attr: BurnTestStep, value: string) {
196
+ candidates = candidates.filter((c) => c[attr] === value);
197
+ renderStep();
198
+ }
199
+
200
+ function init() {
201
+ candidates = [...burnTestData];
202
+ getEl('reset-btn')?.classList.add('hidden');
203
+ getEl('result-view')?.classList.add('hidden');
204
+ getEl('question-view')?.classList.remove('hidden');
205
+ renderStep();
206
+ updateProgress(0);
207
+ }
208
+
209
+ getEl('reset-btn')?.addEventListener('click', init);
210
+
211
+ function startInit() {
212
+ if (document.readyState === 'loading') {
213
+ document.addEventListener('DOMContentLoaded', init);
214
+ } else {
215
+ setTimeout(init, 100);
216
+ }
217
+ document.addEventListener('astro:page-load', init);
218
+ }
219
+
220
+ startInit();
221
+ </script>
222
+
223
+ <style>
224
+ .burn-wizard {
225
+ width: 100%;
226
+ padding: 1rem;
227
+ }
228
+
229
+ .icon-cache {
230
+ display: none;
231
+ }
232
+
233
+ .wizard-card {
234
+ background: var(--bg-surface);
235
+ border: 1px solid var(--border-color);
236
+ border-radius: 1.5rem;
237
+ overflow: hidden;
238
+ min-height: 400px;
239
+ display: flex;
240
+ flex-direction: column;
241
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
242
+ }
243
+
244
+ .theme-dark .wizard-card {
245
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
246
+ }
247
+
248
+ .wizard-header {
249
+ display: flex;
250
+ justify-content: space-between;
251
+ align-items: center;
252
+ padding: 1rem 1.5rem;
253
+ background: var(--bg-page);
254
+ border-bottom: 1px solid var(--border-color);
255
+ }
256
+
257
+ .header-left {
258
+ display: flex;
259
+ align-items: center;
260
+ gap: 0.75rem;
261
+ }
262
+
263
+ .header-badge {
264
+ width: 2rem;
265
+ height: 2rem;
266
+ background: linear-gradient(135deg, #6366f1, #7c3aed);
267
+ border-radius: 0.5rem;
268
+ display: flex;
269
+ align-items: center;
270
+ justify-content: center;
271
+ color: white;
272
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
273
+ }
274
+
275
+ .header-badge svg {
276
+ width: 1rem;
277
+ height: 1rem;
278
+ }
279
+
280
+ .header-info {
281
+ display: flex;
282
+ flex-direction: column;
283
+ }
284
+
285
+ .investigation-label {
286
+ font-size: 0.55rem;
287
+ font-weight: 900;
288
+ text-transform: uppercase;
289
+ letter-spacing: 0.15em;
290
+ color: var(--text-muted);
291
+ }
292
+
293
+ .step-indicator {
294
+ font-size: 0.625rem;
295
+ font-weight: 700;
296
+ color: var(--text-main);
297
+ }
298
+
299
+ .reset-btn {
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 0.5rem;
303
+ padding: 0.375rem 0.75rem;
304
+ border-radius: 9999px;
305
+ background: var(--bg-surface);
306
+ border: 1px solid var(--border-color);
307
+ color: var(--text-muted);
308
+ font-size: 0.625rem;
309
+ font-weight: 700;
310
+ cursor: pointer;
311
+ transition: all 0.2s;
312
+ }
313
+
314
+ .reset-btn:hover {
315
+ background: rgba(249, 115, 22, 0.05);
316
+ color: #f97316;
317
+ border-color: #f97316;
318
+ }
319
+
320
+ .reset-btn svg {
321
+ width: 1rem;
322
+ height: 1rem;
323
+ transition: transform 0.5s;
324
+ }
325
+
326
+ .reset-btn:hover svg {
327
+ transform: rotate(180deg);
328
+ }
329
+
330
+ .reset-btn.hidden {
331
+ display: none;
332
+ }
333
+
334
+ .wizard-content {
335
+ flex: 1;
336
+ padding: 1.5rem;
337
+ display: flex;
338
+ align-items: center;
339
+ justify-content: center;
340
+ }
341
+
342
+ .question-view {
343
+ width: 100%;
344
+ }
345
+
346
+ .question-view.hidden {
347
+ display: none;
348
+ }
349
+
350
+ .question-title {
351
+ font-size: 1.5rem;
352
+ font-weight: 900;
353
+ color: var(--text-main);
354
+ margin: 0 0 1.5rem;
355
+ transition: opacity 0.3s ease;
356
+ }
357
+
358
+ .options-grid {
359
+ display: grid;
360
+ grid-template-columns: 1fr 1fr;
361
+ gap: 0.75rem;
362
+ }
363
+
364
+ :global(.option-btn) {
365
+ text-align: left;
366
+ padding: 1rem;
367
+ background: var(--bg-page);
368
+ border: 1px solid var(--border-color);
369
+ border-radius: 0.75rem;
370
+ color: var(--text-muted);
371
+ font-size: 0.875rem;
372
+ font-weight: 500;
373
+ cursor: pointer;
374
+ transition: all 0.3s;
375
+ animation: fade-in-up 0.5s ease-out forwards;
376
+ opacity: 0;
377
+ line-height: 1.4;
378
+ }
379
+
380
+ :global(.option-btn:hover) {
381
+ border-color: #6366f1;
382
+ background: rgba(99, 102, 241, 0.05);
383
+ color: var(--text-main);
384
+ transform: translateY(-2px);
385
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
386
+ }
387
+
388
+ .result-view {
389
+ width: 100%;
390
+ max-width: 520px;
391
+ text-align: center;
392
+ display: flex;
393
+ flex-direction: column;
394
+ align-items: center;
395
+ gap: 1.5rem;
396
+ animation: fade-in 0.5s ease;
397
+ }
398
+
399
+ .result-view.hidden {
400
+ display: none;
401
+ }
402
+
403
+ .result-icon-wrap {
404
+ position: relative;
405
+ }
406
+
407
+ .result-icon-container {
408
+ width: 6rem;
409
+ height: 6rem;
410
+ background: linear-gradient(135deg, #6366f1, #8b5cf6, #ec4899);
411
+ border-radius: 1.5rem;
412
+ display: flex;
413
+ align-items: center;
414
+ justify-content: center;
415
+ font-size: 2.5rem;
416
+ color: white;
417
+ box-shadow: 0 25px 50px rgba(99, 102, 241, 0.4);
418
+ position: relative;
419
+ z-index: 1;
420
+ }
421
+
422
+ .result-meta {
423
+ display: flex;
424
+ flex-direction: column;
425
+ gap: 0.5rem;
426
+ align-items: center;
427
+ }
428
+
429
+ .positive-id {
430
+ display: inline-block;
431
+ padding: 0.25rem 0.75rem;
432
+ border-radius: 9999px;
433
+ background: rgba(16, 185, 129, 0.08);
434
+ color: #10b981;
435
+ font-size: 0.55rem;
436
+ font-weight: 900;
437
+ text-transform: uppercase;
438
+ letter-spacing: 0.2em;
439
+ border: 1px solid rgba(16, 185, 129, 0.2);
440
+ }
441
+
442
+ .result-name {
443
+ font-size: 2.5rem;
444
+ font-weight: 900;
445
+ color: var(--text-main);
446
+ margin: 0;
447
+ }
448
+
449
+ .result-desc {
450
+ background: var(--bg-page);
451
+ border: 1px solid var(--border-color);
452
+ border-radius: 1rem;
453
+ padding: 1.5rem;
454
+ width: 100%;
455
+ text-align: left;
456
+ }
457
+
458
+ :global(.result-details) {
459
+ display: flex;
460
+ flex-direction: column;
461
+ gap: 1rem;
462
+ }
463
+
464
+ :global(.result-id-text) {
465
+ font-size: 0.875rem;
466
+ font-weight: 500;
467
+ color: var(--text-muted);
468
+ border-bottom: 1px solid var(--border-color);
469
+ padding-bottom: 1rem;
470
+ margin: 0;
471
+ }
472
+
473
+ :global(.result-attrs) {
474
+ display: flex;
475
+ flex-direction: column;
476
+ gap: 0.75rem;
477
+ }
478
+
479
+ :global(.attr-row) {
480
+ display: flex;
481
+ flex-direction: column;
482
+ padding-left: 1rem;
483
+ border-left: 2px solid var(--border-color);
484
+ }
485
+
486
+ :global(.attr-flame) {
487
+ border-left-color: #f97316;
488
+ }
489
+
490
+ :global(.attr-odor) {
491
+ border-left-color: #6366f1;
492
+ }
493
+
494
+ :global(.attr-residue) {
495
+ border-left-color: #94a3b8;
496
+ }
497
+
498
+ :global(.attr-label) {
499
+ font-size: 0.6rem;
500
+ font-weight: 900;
501
+ text-transform: uppercase;
502
+ letter-spacing: 0.05em;
503
+ color: var(--text-muted);
504
+ margin-bottom: 0.2rem;
505
+ }
506
+
507
+ :global(.attr-value) {
508
+ font-size: 0.875rem;
509
+ font-weight: 600;
510
+ color: var(--text-main);
511
+ }
512
+
513
+ .progress-track {
514
+ height: 0.25rem;
515
+ background: var(--bg-page);
516
+ }
517
+
518
+ .progress-fill {
519
+ height: 100%;
520
+ background: linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899);
521
+ transition: width 0.7s ease-out;
522
+ box-shadow: 0 0 10px rgba(99, 102, 241, 0.5);
523
+ }
524
+
525
+ .safety-note {
526
+ margin-top: 1.5rem;
527
+ padding: 1rem 1.5rem;
528
+ background: var(--bg-surface);
529
+ border: 1px solid var(--border-color);
530
+ border-left: 4px solid #ef4444;
531
+ border-radius: 0.75rem;
532
+ display: flex;
533
+ gap: 1rem;
534
+ align-items: center;
535
+ }
536
+
537
+ .safety-icon {
538
+ background: rgba(239, 68, 68, 0.08);
539
+ color: #ef4444;
540
+ padding: 0.5rem;
541
+ border-radius: 0.5rem;
542
+ flex-shrink: 0;
543
+ display: flex;
544
+ align-items: center;
545
+ justify-content: center;
546
+ }
547
+
548
+ .safety-icon svg {
549
+ width: 1.25rem;
550
+ height: 1.25rem;
551
+ }
552
+
553
+ .safety-title {
554
+ font-size: 0.625rem;
555
+ font-weight: 900;
556
+ text-transform: uppercase;
557
+ letter-spacing: 0.1em;
558
+ color: #ef4444;
559
+ margin: 0 0 0.25rem;
560
+ }
561
+
562
+ .safety-text p {
563
+ margin: 0;
564
+ font-size: 0.75rem;
565
+ color: var(--text-muted);
566
+ line-height: 1.5;
567
+ }
568
+
569
+ @keyframes fade-in-up {
570
+ from {
571
+ opacity: 0;
572
+ transform: translateY(10px);
573
+ }
574
+ to {
575
+ opacity: 1;
576
+ transform: translateY(0);
577
+ }
578
+ }
579
+
580
+ @keyframes fade-in {
581
+ from {
582
+ opacity: 0;
583
+ }
584
+ to {
585
+ opacity: 1;
586
+ }
587
+ }
588
+
589
+ @media (max-width: 640px) {
590
+ .options-grid {
591
+ grid-template-columns: 1fr;
592
+ }
593
+
594
+ .result-name {
595
+ font-size: 1.75rem;
596
+ }
597
+
598
+ .wizard-content {
599
+ padding: 1rem;
600
+ }
601
+ }
602
+ </style>