@jjlmoya/utils-textiles 1.4.0 → 1.6.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 (181) hide show
  1. package/package.json +59 -58
  2. package/src/category/i18n/de.ts +76 -0
  3. package/src/category/i18n/fr.ts +33 -30
  4. package/src/category/i18n/id.ts +76 -0
  5. package/src/category/i18n/it.ts +76 -0
  6. package/src/category/i18n/ja.ts +76 -0
  7. package/src/category/i18n/ko.ts +76 -0
  8. package/src/category/i18n/nl.ts +76 -0
  9. package/src/category/i18n/pl.ts +76 -0
  10. package/src/category/i18n/pt.ts +76 -0
  11. package/src/category/i18n/ru.ts +76 -0
  12. package/src/category/i18n/sv.ts +76 -0
  13. package/src/category/i18n/tr.ts +76 -0
  14. package/src/category/i18n/zh.ts +76 -0
  15. package/src/category/index.ts +39 -7
  16. package/src/index.ts +1 -1
  17. package/src/tests/i18n_coverage.test.ts +36 -0
  18. package/src/tests/schemas_fulfillment.test.ts +23 -0
  19. package/src/tests/slug_language_code_format.test.ts +23 -0
  20. package/src/tests/slug_uniqueness.test.ts +81 -0
  21. package/src/tests/title_quality.test.ts +55 -0
  22. package/src/tool/burnTest/i18n/de.ts +225 -0
  23. package/src/tool/burnTest/i18n/id.ts +225 -0
  24. package/src/tool/burnTest/i18n/it.ts +225 -0
  25. package/src/tool/burnTest/i18n/ja.ts +225 -0
  26. package/src/tool/burnTest/i18n/ko.ts +225 -0
  27. package/src/tool/burnTest/i18n/nl.ts +225 -0
  28. package/src/tool/burnTest/i18n/pl.ts +225 -0
  29. package/src/tool/burnTest/i18n/pt.ts +225 -0
  30. package/src/tool/burnTest/i18n/ru.ts +225 -0
  31. package/src/tool/burnTest/i18n/sv.ts +225 -0
  32. package/src/tool/burnTest/i18n/tr.ts +225 -0
  33. package/src/tool/burnTest/i18n/zh.ts +225 -0
  34. package/src/tool/burnTest/index.ts +15 -7
  35. package/src/tool/clothingSizeConverter/i18n/de.ts +356 -0
  36. package/src/tool/clothingSizeConverter/i18n/id.ts +356 -0
  37. package/src/tool/clothingSizeConverter/i18n/it.ts +356 -0
  38. package/src/tool/clothingSizeConverter/i18n/ja.ts +356 -0
  39. package/src/tool/clothingSizeConverter/i18n/ko.ts +356 -0
  40. package/src/tool/clothingSizeConverter/i18n/nl.ts +356 -0
  41. package/src/tool/clothingSizeConverter/i18n/pl.ts +356 -0
  42. package/src/tool/clothingSizeConverter/i18n/pt.ts +356 -0
  43. package/src/tool/clothingSizeConverter/i18n/ru.ts +356 -0
  44. package/src/tool/clothingSizeConverter/i18n/sv.ts +356 -0
  45. package/src/tool/clothingSizeConverter/i18n/tr.ts +356 -0
  46. package/src/tool/clothingSizeConverter/i18n/zh.ts +356 -0
  47. package/src/tool/clothingSizeConverter/index.ts +15 -7
  48. package/src/tool/fabricProjectCalculator/i18n/de.ts +250 -0
  49. package/src/tool/fabricProjectCalculator/i18n/id.ts +250 -0
  50. package/src/tool/fabricProjectCalculator/i18n/it.ts +250 -0
  51. package/src/tool/fabricProjectCalculator/i18n/ja.ts +250 -0
  52. package/src/tool/fabricProjectCalculator/i18n/ko.ts +250 -0
  53. package/src/tool/fabricProjectCalculator/i18n/nl.ts +250 -0
  54. package/src/tool/fabricProjectCalculator/i18n/pl.ts +250 -0
  55. package/src/tool/fabricProjectCalculator/i18n/pt.ts +250 -0
  56. package/src/tool/fabricProjectCalculator/i18n/ru.ts +250 -0
  57. package/src/tool/fabricProjectCalculator/i18n/sv.ts +250 -0
  58. package/src/tool/fabricProjectCalculator/i18n/tr.ts +250 -0
  59. package/src/tool/fabricProjectCalculator/i18n/zh.ts +250 -0
  60. package/src/tool/fabricProjectCalculator/index.ts +15 -7
  61. package/src/tool/fabricTruth/i18n/de.ts +282 -0
  62. package/src/tool/fabricTruth/i18n/en.ts +1 -1
  63. package/src/tool/fabricTruth/i18n/fr.ts +1 -1
  64. package/src/tool/fabricTruth/i18n/id.ts +282 -0
  65. package/src/tool/fabricTruth/i18n/it.ts +282 -0
  66. package/src/tool/fabricTruth/i18n/ja.ts +282 -0
  67. package/src/tool/fabricTruth/i18n/ko.ts +282 -0
  68. package/src/tool/fabricTruth/i18n/nl.ts +282 -0
  69. package/src/tool/fabricTruth/i18n/pl.ts +282 -0
  70. package/src/tool/fabricTruth/i18n/pt.ts +282 -0
  71. package/src/tool/fabricTruth/i18n/ru.ts +282 -0
  72. package/src/tool/fabricTruth/i18n/sv.ts +282 -0
  73. package/src/tool/fabricTruth/i18n/tr.ts +282 -0
  74. package/src/tool/fabricTruth/i18n/zh.ts +286 -0
  75. package/src/tool/fabricTruth/index.ts +15 -7
  76. package/src/tool/fiberPrep/i18n/de.ts +225 -0
  77. package/src/tool/fiberPrep/i18n/id.ts +225 -0
  78. package/src/tool/fiberPrep/i18n/it.ts +225 -0
  79. package/src/tool/fiberPrep/i18n/ja.ts +225 -0
  80. package/src/tool/fiberPrep/i18n/ko.ts +225 -0
  81. package/src/tool/fiberPrep/i18n/nl.ts +225 -0
  82. package/src/tool/fiberPrep/i18n/pl.ts +225 -0
  83. package/src/tool/fiberPrep/i18n/pt.ts +225 -0
  84. package/src/tool/fiberPrep/i18n/ru.ts +225 -0
  85. package/src/tool/fiberPrep/i18n/sv.ts +225 -0
  86. package/src/tool/fiberPrep/i18n/tr.ts +225 -0
  87. package/src/tool/fiberPrep/i18n/zh.ts +225 -0
  88. package/src/tool/fiberPrep/index.ts +15 -7
  89. package/src/tool/knittingGauge/i18n/de.ts +234 -0
  90. package/src/tool/knittingGauge/i18n/id.ts +234 -0
  91. package/src/tool/knittingGauge/i18n/it.ts +234 -0
  92. package/src/tool/knittingGauge/i18n/ja.ts +234 -0
  93. package/src/tool/knittingGauge/i18n/ko.ts +234 -0
  94. package/src/tool/knittingGauge/i18n/nl.ts +234 -0
  95. package/src/tool/knittingGauge/i18n/pl.ts +234 -0
  96. package/src/tool/knittingGauge/i18n/pt.ts +234 -0
  97. package/src/tool/knittingGauge/i18n/ru.ts +234 -0
  98. package/src/tool/knittingGauge/i18n/sv.ts +234 -0
  99. package/src/tool/knittingGauge/i18n/tr.ts +234 -0
  100. package/src/tool/knittingGauge/i18n/zh.ts +234 -0
  101. package/src/tool/knittingGauge/index.ts +15 -7
  102. package/src/tool/laundryGuide/i18n/de.ts +275 -0
  103. package/src/tool/laundryGuide/i18n/id.ts +275 -0
  104. package/src/tool/laundryGuide/i18n/it.ts +275 -0
  105. package/src/tool/laundryGuide/i18n/ja.ts +275 -0
  106. package/src/tool/laundryGuide/i18n/ko.ts +275 -0
  107. package/src/tool/laundryGuide/i18n/nl.ts +275 -0
  108. package/src/tool/laundryGuide/i18n/pl.ts +275 -0
  109. package/src/tool/laundryGuide/i18n/pt.ts +275 -0
  110. package/src/tool/laundryGuide/i18n/ru.ts +275 -0
  111. package/src/tool/laundryGuide/i18n/sv.ts +275 -0
  112. package/src/tool/laundryGuide/i18n/tr.ts +275 -0
  113. package/src/tool/laundryGuide/i18n/zh.ts +275 -0
  114. package/src/tool/laundryGuide/index.ts +15 -7
  115. package/src/tool/needleConverter/i18n/de.ts +212 -0
  116. package/src/tool/needleConverter/i18n/id.ts +212 -0
  117. package/src/tool/needleConverter/i18n/it.ts +212 -0
  118. package/src/tool/needleConverter/i18n/ja.ts +212 -0
  119. package/src/tool/needleConverter/i18n/ko.ts +212 -0
  120. package/src/tool/needleConverter/i18n/nl.ts +212 -0
  121. package/src/tool/needleConverter/i18n/pl.ts +212 -0
  122. package/src/tool/needleConverter/i18n/pt.ts +212 -0
  123. package/src/tool/needleConverter/i18n/ru.ts +212 -0
  124. package/src/tool/needleConverter/i18n/sv.ts +212 -0
  125. package/src/tool/needleConverter/i18n/tr.ts +212 -0
  126. package/src/tool/needleConverter/i18n/zh.ts +212 -0
  127. package/src/tool/needleConverter/index.ts +15 -7
  128. package/src/tool/sewingPatternScaler/i18n/de.ts +227 -0
  129. package/src/tool/sewingPatternScaler/i18n/id.ts +227 -0
  130. package/src/tool/sewingPatternScaler/i18n/it.ts +227 -0
  131. package/src/tool/sewingPatternScaler/i18n/ja.ts +227 -0
  132. package/src/tool/sewingPatternScaler/i18n/ko.ts +227 -0
  133. package/src/tool/sewingPatternScaler/i18n/nl.ts +227 -0
  134. package/src/tool/sewingPatternScaler/i18n/pl.ts +227 -0
  135. package/src/tool/sewingPatternScaler/i18n/pt.ts +227 -0
  136. package/src/tool/sewingPatternScaler/i18n/ru.ts +227 -0
  137. package/src/tool/sewingPatternScaler/i18n/sv.ts +227 -0
  138. package/src/tool/sewingPatternScaler/i18n/tr.ts +227 -0
  139. package/src/tool/sewingPatternScaler/i18n/zh.ts +227 -0
  140. package/src/tool/sewingPatternScaler/index.ts +15 -7
  141. package/src/tool/shoeSizeConverter/i18n/de.ts +184 -0
  142. package/src/tool/shoeSizeConverter/i18n/id.ts +184 -0
  143. package/src/tool/shoeSizeConverter/i18n/it.ts +184 -0
  144. package/src/tool/shoeSizeConverter/i18n/ja.ts +184 -0
  145. package/src/tool/shoeSizeConverter/i18n/ko.ts +184 -0
  146. package/src/tool/shoeSizeConverter/i18n/nl.ts +184 -0
  147. package/src/tool/shoeSizeConverter/i18n/pl.ts +184 -0
  148. package/src/tool/shoeSizeConverter/i18n/pt.ts +184 -0
  149. package/src/tool/shoeSizeConverter/i18n/ru.ts +184 -0
  150. package/src/tool/shoeSizeConverter/i18n/sv.ts +184 -0
  151. package/src/tool/shoeSizeConverter/i18n/tr.ts +184 -0
  152. package/src/tool/shoeSizeConverter/i18n/zh.ts +184 -0
  153. package/src/tool/shoeSizeConverter/index.ts +15 -7
  154. package/src/tool/stainChemistry/i18n/de.ts +300 -0
  155. package/src/tool/stainChemistry/i18n/id.ts +300 -0
  156. package/src/tool/stainChemistry/i18n/it.ts +300 -0
  157. package/src/tool/stainChemistry/i18n/ja.ts +300 -0
  158. package/src/tool/stainChemistry/i18n/ko.ts +297 -0
  159. package/src/tool/stainChemistry/i18n/nl.ts +300 -0
  160. package/src/tool/stainChemistry/i18n/pl.ts +300 -0
  161. package/src/tool/stainChemistry/i18n/pt.ts +300 -0
  162. package/src/tool/stainChemistry/i18n/ru.ts +300 -0
  163. package/src/tool/stainChemistry/i18n/sv.ts +300 -0
  164. package/src/tool/stainChemistry/i18n/tr.ts +300 -0
  165. package/src/tool/stainChemistry/i18n/zh.ts +295 -0
  166. package/src/tool/stainChemistry/index.ts +15 -7
  167. package/src/tool/yarnCalculator/i18n/de.ts +310 -0
  168. package/src/tool/yarnCalculator/i18n/id.ts +310 -0
  169. package/src/tool/yarnCalculator/i18n/it.ts +310 -0
  170. package/src/tool/yarnCalculator/i18n/ja.ts +310 -0
  171. package/src/tool/yarnCalculator/i18n/ko.ts +310 -0
  172. package/src/tool/yarnCalculator/i18n/nl.ts +310 -0
  173. package/src/tool/yarnCalculator/i18n/pl.ts +310 -0
  174. package/src/tool/yarnCalculator/i18n/pt.ts +310 -0
  175. package/src/tool/yarnCalculator/i18n/ru.ts +310 -0
  176. package/src/tool/yarnCalculator/i18n/sv.ts +310 -0
  177. package/src/tool/yarnCalculator/i18n/tr.ts +310 -0
  178. package/src/tool/yarnCalculator/i18n/zh.ts +309 -0
  179. package/src/tool/yarnCalculator/index.ts +15 -7
  180. package/src/tools.ts +2 -0
  181. package/src/types.ts +1 -1
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import type { ToolLocaleContent } from '../types';
4
+
5
+ describe('Schemas Fulfillment Validation', () => {
6
+ ALL_TOOLS.forEach((tool) => {
7
+ describe(`Tool: ${tool.entry.id}`, () => {
8
+ Object.keys(tool.entry.i18n).forEach((locale) => {
9
+ it(`Locale: ${locale} should have faqSchema, appSchema and howToSchema`, async () => {
10
+ const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
11
+ if (!loader) return;
12
+ const content = (await loader()) as ToolLocaleContent;
13
+
14
+ const schemaTypes = content.schemas.map((s: any) => s['@type']);
15
+
16
+ expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing FAQPage schema`).toContain('FAQPage');
17
+ expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing SoftwareApplication schema`).toContain('SoftwareApplication');
18
+ expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing HowTo schema`).toContain('HowTo');
19
+ });
20
+ });
21
+ });
22
+ });
23
+ });
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import type { ToolLocaleContent } from '../types';
4
+
5
+ describe('Slug Language Code Format Validation', () => {
6
+ ALL_TOOLS.forEach((tool) => {
7
+ describe(`Tool: ${tool.entry.id}`, () => {
8
+ it('slug should not end with 2-letter language codes like -ja, -ru, -ko', async () => {
9
+ const locales = Object.keys(tool.entry.i18n);
10
+
11
+ for (const locale of locales) {
12
+ const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
13
+ const content = (await loader?.()) as ToolLocaleContent;
14
+
15
+ expect(
16
+ content.slug,
17
+ `Tool "${tool.entry.id}" locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
18
+ ).not.toMatch(/-(?!ye$)[a-z]{2}$/);
19
+ }
20
+ });
21
+ });
22
+ });
23
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import type { ToolLocaleContent } from '../types';
4
+
5
+ const sharingLocales = ['ja', 'ko', 'zh'];
6
+
7
+ interface ValidateParams {
8
+ toolId: string;
9
+ locale: string;
10
+ content: ToolLocaleContent;
11
+ enSlug: string;
12
+ slugs: Map<string, string>;
13
+ }
14
+
15
+ const validateLocaleSlug = ({
16
+ toolId,
17
+ locale,
18
+ content,
19
+ enSlug,
20
+ slugs,
21
+ }: ValidateParams) => {
22
+ expect(
23
+ content.slug,
24
+ `Tool "${toolId}" locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
25
+ ).toMatch(/^[a-z0-9-]+$/);
26
+
27
+ if (locale === 'en') {
28
+ return;
29
+ }
30
+
31
+ if (sharingLocales.includes(locale)) {
32
+ expect(
33
+ content.slug,
34
+ `Tool "${toolId}" locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
35
+ ).toBe(enSlug);
36
+ } else {
37
+ expect(
38
+ content.slug,
39
+ `Tool "${toolId}" locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propia idioma`,
40
+ ).not.toBe(enSlug);
41
+
42
+ if (slugs.has(content.slug)) {
43
+ const previousLocale = slugs.get(content.slug);
44
+ expect(
45
+ false,
46
+ `Tool "${toolId}" locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propia idioma`,
47
+ ).toBe(true);
48
+ }
49
+ slugs.set(content.slug, locale);
50
+ }
51
+ };
52
+
53
+ describe('Slug Localization and Uniqueness Validation', () => {
54
+ ALL_TOOLS.forEach((tool) => {
55
+ describe(`Tool: ${tool.entry.id}`, () => {
56
+ it('every locale should have a unique, translated slug', async () => {
57
+ const slugs = new Map<string, string>();
58
+ const locales = Object.keys(tool.entry.i18n);
59
+
60
+ let enSlug = '';
61
+ if (locales.includes('en')) {
62
+ const enLoader = tool.entry.i18n['en' as keyof typeof tool.entry.i18n];
63
+ const enContent = (await enLoader?.()) as ToolLocaleContent;
64
+ enSlug = enContent.slug;
65
+ }
66
+
67
+ for (const locale of locales) {
68
+ const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
69
+ const content = (await loader?.()) as ToolLocaleContent;
70
+ validateLocaleSlug({
71
+ toolId: tool.entry.id,
72
+ locale,
73
+ content,
74
+ enSlug,
75
+ slugs,
76
+ });
77
+ }
78
+ });
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, it } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ function getFiles(dir: string, ext: string[]): string[] {
6
+ const results: string[] = [];
7
+ if (!fs.existsSync(dir)) return results;
8
+ const list = fs.readdirSync(dir);
9
+ for (const file of list) {
10
+ const fullPath = path.join(dir, file);
11
+ const stat = fs.statSync(fullPath);
12
+ if (stat && stat.isDirectory()) {
13
+ results.push(...getFiles(fullPath, ext));
14
+ } else if (ext.some((e) => file.endsWith(e))) {
15
+ results.push(fullPath);
16
+ }
17
+ }
18
+ return results;
19
+ }
20
+
21
+ const SRC_DIR = path.join(process.cwd(), 'src');
22
+
23
+ describe('Project Titles - Separator Validation', () => {
24
+ const files = [
25
+ ...getFiles(path.join(SRC_DIR, 'tool'), ['.ts']),
26
+ ...getFiles(path.join(SRC_DIR, 'category'), ['.ts']),
27
+ ].filter(f => f.includes('i18n'));
28
+
29
+ it.each(files)('Verify that titles in %s do not contain | or -', (filePath) => {
30
+ const content = fs.readFileSync(filePath, 'utf-8');
31
+ const relativePath = path.relative(process.cwd(), filePath);
32
+
33
+ const titlePatterns = [
34
+ /const\s+title\s*=\s*['"]([^'"]+)['"]/g,
35
+ /title\s*:\s*['"]([^'"]+)['"]/g,
36
+ ];
37
+
38
+ const findings: string[] = [];
39
+
40
+ for (const pattern of titlePatterns) {
41
+ let match;
42
+ while ((match = pattern.exec(content)) !== null) {
43
+ const title = match[1];
44
+ if (title.includes('|') || title.includes('-')) {
45
+ findings.push(title);
46
+ }
47
+ }
48
+ }
49
+
50
+ if (findings.length > 0) {
51
+ const list = findings.map((f) => ` - "${f}"`).join('\n');
52
+ throw new Error(`Forbidden separators (| or -) found in titles in ${relativePath}:\n${list}`);
53
+ }
54
+ });
55
+ });
@@ -0,0 +1,225 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { BurnTestUI } from '../ui';
4
+
5
+ const slug = 'textilien-brennprobe';
6
+ const title = 'Textile Brennprobe';
7
+ const description =
8
+ 'Identifizieren Sie die tatsächliche Zusammensetzung jedes Stoffes durch Analyse seines Brennverhaltens. Unterscheiden Sie Baumwolle, Wolle, Seide und Synthetik mit wissenschaftlicher Genauigkeit.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Ist es sicher, eine Brennprobe zu Hause durchzuführen?',
13
+ answer:
14
+ 'Ja, sofern Sie extreme Vorsichtsmaßnahmen treffen: Verwenden Sie eine Metallpinzette, arbeiten Sie auf einer feuerfesten Unterlage (Keramik oder Metall), halten Sie Wasser bereit und führen Sie den Test an einem gut belüfteten Ort durch. Sie benötigen nur eine kleine Probe von 1x1 cm oder einige Fäden.',
15
+ },
16
+ {
17
+ question: 'Wie riecht Baumwolle beim Verbrennen?',
18
+ answer:
19
+ 'Baumwolle ist reine Zellulose. Beim Verbrennen riecht sie genau wie verbranntes Papier oder trockene Blätter. Die resultierende Asche ist grau, sehr fein und zerfällt bei Berührung.',
20
+ },
21
+ {
22
+ question: 'Was bedeutet eine harte, schwarze Perle am Ende der Faser?',
23
+ answer:
24
+ 'Dies ist das eindeutige Zeichen für eine synthetische Faser (Kunststoff) wie Polyester oder Nylon. Beim Schmelzen verklumpt das Polymer zu einer Kunststoffperle, die im Gegensatz zur Asche von Naturfasern nicht zerbröckelt.',
25
+ },
26
+ {
27
+ question: 'Warum riecht Wolle beim Verbrennen schlecht?',
28
+ answer:
29
+ 'Wolle und Seide sind tierische Proteine, die Schwefel enthalten. Wenn sie mit Feuer in Berührung kommen, verströmen sie einen stechenden Geruch nach verbranntem Haar oder verbrannten Federn. Dies ist der einfachste Weg, sie von Pflanzenfasern zu unterscheiden.',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: 'Eine repräsentative Probe entnehmen',
36
+ text: 'Schneiden Sie ein kleines Quadrat aus dem Stoff aus oder, noch besser, ziehen Sie einige Fäden sowohl aus der Kette (vertikal) als auch aus dem Schuss (horizontal).',
37
+ },
38
+ {
39
+ name: 'Annäherung an die Flamme beobachten',
40
+ text: 'Bringen Sie die Faser langsam in die Nähe des Feuers, ohne es zu berühren. Beobachten Sie, ob sie schrumpft, schmilzt oder sofort Feuer fängt.',
41
+ },
42
+ {
43
+ name: 'Flammentyp und Rauch analysieren',
44
+ text: 'Achten Sie darauf, ob die Flamme beständig ist oder von selbst erlischt, und ob der Rauch weiß, schwarz oder nicht vorhanden ist.',
45
+ },
46
+ {
47
+ name: 'Rückstand und Geruch bewerten',
48
+ text: 'Berühren Sie nach dem Abkühlen die resultierende Asche oder Perle und nehmen Sie den Geruch wahr (Papier, Haar oder chemisch), um die Faserfamilie zu bestätigen.',
49
+ },
50
+ ];
51
+
52
+ const faqSchema: WithContext<FAQPage> = {
53
+ '@context': 'https://schema.org',
54
+ '@type': 'FAQPage',
55
+ mainEntity: faqData.map((item) => ({
56
+ '@type': 'Question',
57
+ name: item.question,
58
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
59
+ })),
60
+ };
61
+
62
+ const howToSchema: WithContext<HowTo> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'HowTo',
65
+ name: title,
66
+ description,
67
+ step: howToData.map((step) => ({
68
+ '@type': 'HowToStep',
69
+ name: step.name,
70
+ text: step.text,
71
+ })),
72
+ };
73
+
74
+ const appSchema: WithContext<SoftwareApplication> = {
75
+ '@context': 'https://schema.org',
76
+ '@type': 'SoftwareApplication',
77
+ name: title,
78
+ description,
79
+ applicationCategory: 'UtilityApplication',
80
+ operatingSystem: 'All',
81
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
82
+ inLanguage: 'de',
83
+ };
84
+
85
+ const flame = {
86
+ cellulosic: 'Brennt SCHNELL und HELL (gelbe Flamme). Schmilzt nicht. Brennt nach dem Entfernen weiter.',
87
+ protein: 'Brennt LANGSAM, knistert und SCHRUMPFT von der Flamme weg. Erlischt beim Entfernen von selbst.',
88
+ synthetic: 'SCHMILZT und zieht sich schnell zusammen, tropft wie geschmolzener Kunststoff. Rußige Flamme.',
89
+ };
90
+
91
+ const odor = {
92
+ paper: 'Verbranntes Papier, Holz, trockene Blätter.',
93
+ hair: 'Verbranntes Haar, verbranntes Horn, Federn.',
94
+ chemical_sweet: 'AROMATISCH chemisch (süß, fruchtig).',
95
+ chemical_fishy: 'SAUER chemisch (Fisch, gebratenes Fleisch).',
96
+ vegetable: 'Gekochtes Gemüse (Sellerie).',
97
+ };
98
+
99
+ const residue = {
100
+ ash_soft: 'Feine GRAUE/SCHWARZE Asche, weich und flüchtig (zerfällt bei Berührung).',
101
+ ash_shape: 'GRAUE Asche behält die Faserform bei (zerreiblich).',
102
+ bead_crushable: 'Hohle/spröde schwarze Masse (leicht mit den Fingern zu zerdrücken).',
103
+ bead_hard: 'HARTE und gläserne Perle. Unmöglich mit den Fingern zu zerbrechen.',
104
+ bead_irregular: 'HARTE und unregelmäßige schwarze Masse.',
105
+ };
106
+
107
+ const smoke = {
108
+ white_gray: 'Weiß / Hellgrau.',
109
+ gray: 'Mittelgrau.',
110
+ black: 'Dichter dunkler SCHWARZER Rauch.',
111
+ };
112
+
113
+ export const content: ToolLocaleContent<BurnTestUI> = {
114
+ slug,
115
+ title,
116
+ description,
117
+ faqTitle: 'Häufig gestellte Fragen',
118
+ faq: faqData,
119
+ bibliographyTitle: 'Bibliographie',
120
+ bibliography: [
121
+ {
122
+ name: 'AATCC TM20: Fiber Analysis - Qualitative',
123
+ url: 'https://members.aatcc.org/store/tm20/485/',
124
+ },
125
+ {
126
+ name: 'Textile Fibers: Identification and Testing - NIST',
127
+ url: 'https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular41e3.pdf',
128
+ },
129
+ {
130
+ name: 'Burn Test Chart for Fiber Identification - Fabric Mart',
131
+ url: 'https://fabricmartfabrics.com/pages/burn-test-chart',
132
+ },
133
+ ],
134
+ howTo: howToData,
135
+ schemas: [faqSchema, howToSchema, appSchema],
136
+ seo: [
137
+ {
138
+ type: 'title',
139
+ text: 'Die forensische Wissenschaft des Fadens',
140
+ level: 2,
141
+ },
142
+ {
143
+ type: 'paragraph',
144
+ html: 'Die Brennprobe ist die schnellste und präziseste analytische Methode, um natürliche Polymere von synthetischen zu unterscheiden, ohne auf ein Elektronenmikroskop zurückgreifen zu müssen. Wenn eine Faser Hitze ausgesetzt wird, offenbart ihr molekulares Verhalten ihren Ursprung: von pflanzlicher Zellulose bis hin zu tierischen Proteinen oder Erdölderivaten.',
145
+ },
146
+ {
147
+ type: 'card',
148
+ title: 'Zellulosefasern',
149
+ icon: 'mdi:leaf',
150
+ html: '<strong>Baumwolle</strong> und <strong>Leinen</strong> brennen schnell, ohne zu schmelzen. Sie riechen nach verbranntem Papier, da sie aus Holz oder Pflanzen stammen, und hinterlassen eine flüchtige graue Asche, die beim Pusten verweht.',
151
+ },
152
+ {
153
+ type: 'card',
154
+ title: 'Proteinfasern',
155
+ icon: 'mdi:sheep',
156
+ html: '<strong>Wolle</strong> und <strong>Seide</strong> ziehen sich von der Flamme weg. Ihr charakteristischer Geruch nach verbranntem Haar deutet auf das Vorhandensein von Schwefel und Keratin hin. Der Rückstand ist eine schwarze Perle, die bei Berührung zerfällt.',
157
+ },
158
+ {
159
+ type: 'card',
160
+ title: 'Synthetikfasern',
161
+ icon: 'mdi:bottle-soda-classic',
162
+ html: '<strong>Polyester</strong> und <strong>Nylon</strong> sind im Wesentlichen Kunststoffe. Sie schmelzen und bilden einen heißen Tropfen, der zu einer gläsernen Perle aushärtet, die unmöglich zu zerbrechen ist, bei stechendem chemischem Geruch.',
163
+ },
164
+ {
165
+ type: 'title',
166
+ text: 'Chemische Evidenzmatrix',
167
+ level: 2,
168
+ },
169
+ {
170
+ type: 'paragraph',
171
+ html: 'Eine präzise Analyse erfordert den Abgleich von mindestens drei Beweisstücken. Der Geruch ist der zuverlässigste Indikator zur Identifizierung von Mischgeweben: Wenn ein „Leinen“-Stoff ein leichtes Kunststoffaroma verströmt und harte Rückstände hinterlässt, deutet dies auf eine nicht deklarierte Polyestermischung hin.',
172
+ },
173
+ {
174
+ type: 'tip',
175
+ title: 'Labor Sicherheitsprotokoll',
176
+ html: '<strong>Kritische Belüftung:</strong> Synthetische Fasern setzen beim Verbrennen giftige Gase frei. Führen Sie diesen Test niemals in geschlossenen Räumen durch.<br><strong>Sofortige Löschung:</strong> Halten Sie immer einen Behälter mit Wasser bereit. Einige synthetische Fasern können schmelzen und an der Haut haften bleiben.',
177
+ },
178
+ ],
179
+ ui: {
180
+ investigationLabel: 'UNTERSUCHUNG',
181
+ stepPrefix: 'SCHRITT',
182
+ stepCompleted: 'ANALYSE ABGESCHLOSSEN',
183
+ resetLabel: 'NEUSTART',
184
+ positiveId: 'POSITIVE IDENTIFIZIERUNG',
185
+ flameLabel: 'Flammenverhalten',
186
+ odorLabel: 'Geruchsspur',
187
+ residueLabel: 'Rückstand',
188
+ identifiedAs: 'Probe positiv identifiziert als',
189
+ safetyTitle: 'Sicherheitsprotokoll',
190
+ safetyText:
191
+ 'Führen Sie diesen Test an einem belüfteten Ort auf einer feuerfesten Unterlage durch. Verwenden Sie Metallpinzetten und halten Sie Wasser bereit.',
192
+ questions: {
193
+ flame: 'Verhalten in der Flamme?',
194
+ odor: 'Charakteristischer Geruch?',
195
+ residue: 'Endgültiger Rückstand?',
196
+ smoke: 'Verhalten des Rauchs?',
197
+ },
198
+ fiberData: {
199
+ cotton: { name: 'Baumwolle', icon: 'mdi:tshirt-crew', color: '#60A5FA', description: 'Natürliche Pflanzenfaser, weich und atmungsaktiv. Der König der Basics.' },
200
+ linen: { name: 'Leinen', icon: 'mdi:leaf', color: '#A7F3D0', description: 'Sehr starke Pflanzenfaser, kühl und charakteristisch knittrig.' },
201
+ wool: { name: 'Wolle / Merino', icon: 'mdi:sheep', color: '#FCD34D', description: 'Tierische Proteinfaser, hervorragender Wärmeisolator und elastisch.' },
202
+ silk: { name: 'Seide', icon: 'mdi:ticket-percent', color: '#F472B6', description: 'Tierische Endlosfaser. Natürlicher Glanz und trockener Griff.' },
203
+ cashmere: { name: 'Kaschmir', icon: 'mdi:crown', color: '#F59E0B', description: 'Luxuriöse Kaschmirziegenfaser. Extrem weich, leicht und warm.' },
204
+ mohair: { name: 'Mohair', icon: 'mdi:shimmer', color: '#14B8A6', description: 'Die „Diamantfaser“ der Angora-Ziege. Natürlicher Glanz, sehr widerstandsfähig.' },
205
+ angora: { name: 'Angora', icon: 'mdi:rabbit', color: '#F9FAFB', description: 'Haar des Angora-Kaninchens. Bekannt für seinen flauschigen „Halo“-Effekt und extreme Leichtigkeit.' },
206
+ alpaca: { name: 'Alpaka', icon: 'mdi:image-filter-hdr', color: '#9F7AEA', description: 'Schatz der Anden. Hohle, thermische, hypoallergene und seidige Faser.' },
207
+ polyester: { name: 'Polyester', icon: 'mdi:oil', color: '#94A3B8', description: 'Synthetische Faser auf Erdölbasis. Widerstandsfähig, knitterfrei, nicht atmungsaktiv.' },
208
+ nylon: { name: 'Nylon (Polyamid)', icon: 'mdi:parachute', color: '#2DD4BF', description: 'Hochwiderstandsfähige Kunstfaser für Zugkraft und Abrieb.' },
209
+ acrylic: { name: 'Acryl', icon: 'mdi:resize', color: '#450A0A', description: 'Synthetische Wollimitation. Weich, warm, neigt zu Pilling.' },
210
+ },
211
+ burnTestData: [
212
+ { fiberId: 'cotton', flame: flame.cellulosic, odor: odor.paper, smoke: smoke.white_gray, residue: residue.ash_soft },
213
+ { fiberId: 'linen', flame: flame.cellulosic, odor: odor.paper, smoke: smoke.white_gray, residue: residue.ash_shape },
214
+ { fiberId: 'wool', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
215
+ { fiberId: 'silk', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
216
+ { fiberId: 'cashmere', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
217
+ { fiberId: 'mohair', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
218
+ { fiberId: 'angora', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
219
+ { fiberId: 'alpaca', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
220
+ { fiberId: 'polyester', flame: flame.synthetic, odor: odor.chemical_sweet, smoke: smoke.black, residue: residue.bead_hard },
221
+ { fiberId: 'nylon', flame: flame.synthetic, odor: odor.vegetable, smoke: smoke.white_gray, residue: residue.bead_hard },
222
+ { fiberId: 'acrylic', flame: flame.synthetic, odor: odor.chemical_fishy, smoke: smoke.black, residue: residue.bead_irregular },
223
+ ],
224
+ },
225
+ };
@@ -0,0 +1,225 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { BurnTestUI } from '../ui';
4
+
5
+ const slug = 'uji-bakar-tekstil';
6
+ const title = 'Uji Bakar Tekstil';
7
+ const description =
8
+ 'Identifikasi komposisi asli kain apa pun dengan menganalisis perilaku pembakarannya. Bedakan katun, wol, sutra, dan sintetik secara ilmiah.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Apakah aman melakukan uji bakar di rumah?',
13
+ answer:
14
+ 'Ya, selama Anda melakukan tindakan pencegahan ekstrem: gunakan pinset logam, kerjakan di atas permukaan tahan api (keramik atau logam), sediakan air di dekat Anda, dan lakukan pengujian di area berventilasi. Anda hanya memerlukan sampel kecil berukuran 1x1 cm atau beberapa helai benang.',
15
+ },
16
+ {
17
+ question: 'Bagaimana bau katun saat dibakar?',
18
+ answer:
19
+ 'Katun adalah selulosa murni. Saat dibakar, baunya persis seperti kertas atau daun kering yang terbakar. Abu yang dihasilkan berwarna abu-abu, sangat halus, dan hancur saat disentuh.',
20
+ },
21
+ {
22
+ question: 'Apa arti bulatan hitam keras di ujung serat?',
23
+ answer:
24
+ 'Itu adalah tanda tak terbantahkan dari serat sintetis (plastik), seperti poliester atau nilon. Saat meleleh, polimer akan menggumpal menjadi bulatan plastik yang tidak hancur, berbeda dengan abu dari serat alami.',
25
+ },
26
+ {
27
+ question: 'Mengapa wol berbau tidak sedap saat dibakar?',
28
+ answer:
29
+ 'Wol dan sutra adalah protein hewani yang mengandung belerang. Saat terkena api, mereka memancarkan aroma tajam rambut atau bulu yang terbakar. Ini adalah cara termudah untuk membedakannya dari serat tumbuhan.',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: 'Ekstrak sampel yang representatif',
36
+ text: 'Potong kotak kecil dari kain atau, lebih baik lagi, tarik beberapa helai benang dari lungsin (vertikal) dan pakan (horizontal).',
37
+ },
38
+ {
39
+ name: 'Amati pendekatan ke nyala api',
40
+ text: 'Dekatkan serat perlahan ke nyala api tanpa menyentuhnya. Amati apakah serat mengkerut, meleleh, atau langsung terbakar.',
41
+ },
42
+ {
43
+ name: 'Analisis jenis nyala api dan asap',
44
+ text: 'Perhatikan apakah nyala api stabil atau padam sendiri, dan apakah asapnya berwarna putih, hitam, atau tidak ada.',
45
+ },
46
+ {
47
+ name: 'Evaluasi residu dan bau',
48
+ text: 'Setelah dingin, sentuh abu atau bulatan yang dihasilkan dan amati baunya (kertas, rambut, atau kimia) untuk memastikan keluarga serat.',
49
+ },
50
+ ];
51
+
52
+ const faqSchema: WithContext<FAQPage> = {
53
+ '@context': 'https://schema.org',
54
+ '@type': 'FAQPage',
55
+ mainEntity: faqData.map((item) => ({
56
+ '@type': 'Question',
57
+ name: item.question,
58
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
59
+ })),
60
+ };
61
+
62
+ const howToSchema: WithContext<HowTo> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'HowTo',
65
+ name: title,
66
+ description,
67
+ step: howToData.map((step) => ({
68
+ '@type': 'HowToStep',
69
+ name: step.name,
70
+ text: step.text,
71
+ })),
72
+ };
73
+
74
+ const appSchema: WithContext<SoftwareApplication> = {
75
+ '@context': 'https://schema.org',
76
+ '@type': 'SoftwareApplication',
77
+ name: title,
78
+ description,
79
+ applicationCategory: 'UtilityApplication',
80
+ operatingSystem: 'All',
81
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
82
+ inLanguage: 'id',
83
+ };
84
+
85
+ const flame = {
86
+ cellulosic: 'Terbakar CEPAT dan TERANG (nyala kuning). Tidak meleleh. Terus terbakar saat dijauhkan.',
87
+ protein: 'Terbakar LAMBAT, meletup-letup, dan MENGKERUT menjauhi nyala api. Padam sendiri saat dijauhkan.',
88
+ synthetic: 'MELELEH dan kontraksi cepat, menetes seperti plastik cair. Nyala api berjelaga.',
89
+ };
90
+
91
+ const odor = {
92
+ paper: 'Kertas terbakar, kayu, daun kering.',
93
+ hair: 'Rambut terbakar, tanduk terbakar, bulu.',
94
+ chemical_sweet: 'Kimia AROMATIK (manis, buah).',
95
+ chemical_fishy: 'Kimia ASAM (ikan, daging panggang).',
96
+ vegetable: 'Sayuran rebus (seledri).',
97
+ };
98
+
99
+ const residue = {
100
+ ash_soft: 'Abu ABU-ABU/HITAM halus, lembut dan mudah terbang (hancur saat disentuh).',
101
+ ash_shape: 'Abu ABU-ABU mempertahankan bentuk serat (rapuh).',
102
+ bead_crushable: 'Massa hitam berongga/rapuh (mudah hancur dengan jari).',
103
+ bead_hard: 'Bulatan KERAS dan seperti kaca. Mustahil dipecahkan dengan jari.',
104
+ bead_irregular: 'Massa hitam KERAS dan tidak beraturan.',
105
+ };
106
+
107
+ const smoke = {
108
+ white_gray: 'Putih / Abu-abu terang.',
109
+ gray: 'Abu-abu sedang.',
110
+ black: 'HITAM pekat dan gelap.',
111
+ };
112
+
113
+ export const content: ToolLocaleContent<BurnTestUI> = {
114
+ slug,
115
+ title,
116
+ description,
117
+ faqTitle: 'Pertanyaan yang Sering Diajukan',
118
+ faq: faqData,
119
+ bibliographyTitle: 'Bibliografi',
120
+ bibliography: [
121
+ {
122
+ name: 'AATCC TM20: Fiber Analysis - Qualitative',
123
+ url: 'https://members.aatcc.org/store/tm20/485/',
124
+ },
125
+ {
126
+ name: 'Textile Fibers: Identification and Testing - NIST',
127
+ url: 'https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular41e3.pdf',
128
+ },
129
+ {
130
+ name: 'Burn Test Chart for Fiber Identification - Fabric Mart',
131
+ url: 'https://fabricmartfabrics.com/pages/burn-test-chart',
132
+ },
133
+ ],
134
+ howTo: howToData,
135
+ schemas: [faqSchema, howToSchema, appSchema],
136
+ seo: [
137
+ {
138
+ type: 'title',
139
+ text: 'Sains Forensik pada Filamen',
140
+ level: 2,
141
+ },
142
+ {
143
+ type: 'paragraph',
144
+ html: 'Uji bakar adalah metode analitis tercepat dan paling akurat untuk membedakan polimer alami dari sintetis tanpa memerlukan mikroskop elektron. Dengan memaparkan serat ke panas, perilaku molekulnya mengungkapkan asal-usulnya: dari selulosa tumbuhan hingga protein hewani atau turunan minyak bumi.',
145
+ },
146
+ {
147
+ type: 'card',
148
+ title: 'Serat Selulosa',
149
+ icon: 'mdi:leaf',
150
+ html: '<strong>Katun</strong> dan <strong>Linen</strong> terbakar cepat tanpa meleleh. Baunya seperti kertas terbakar karena berasal dari kayu atau tumbuhan, meninggalkan abu abu-abu yang mudah terbang saat ditiup.',
151
+ },
152
+ {
153
+ type: 'card',
154
+ title: 'Serat Protein',
155
+ icon: 'mdi:sheep',
156
+ html: '<strong>Wol</strong> dan <strong>Sutra</strong> menjauh dari nyala api. Bau khas rambut terbakar menunjukkan adanya belerang dan keratin. Residunya berupa butiran hitam yang hancur saat disentuh.',
157
+ },
158
+ {
159
+ type: 'card',
160
+ title: 'Serat Sintetis',
161
+ icon: 'mdi:bottle-soda-classic',
162
+ html: '<strong>Poliester</strong> dan <strong>Nilon</strong> pada dasarnya adalah plastik. Mereka meleleh membentuk tetesan panas yang mengeras menjadi butiran kaca yang mustahil dipecahkan, dengan bau kimia yang tajam.',
163
+ },
164
+ {
165
+ type: 'title',
166
+ text: 'Matriks Bukti Kimia',
167
+ level: 2,
168
+ },
169
+ {
170
+ type: 'paragraph',
171
+ html: 'Analisis yang tepat memerlukan penyilangan setidaknya tiga bukti. Bau adalah indikator paling andal untuk mengidentifikasi campuran: jika kain "linen" mengeluarkan sedikit aroma plastik dan meninggalkan residu keras, itu menunjukkan campuran poliester yang tidak dinyatakan.',
172
+ },
173
+ {
174
+ type: 'tip',
175
+ title: 'Protokol Keamanan Laboratorium',
176
+ html: '<strong>Ventilasi kritis:</strong> Serat sintetis melepaskan gas beracun saat terbakar. Jangan pernah melakukan tes ini di ruang tertutup.<br><strong>Pemadaman segera:</strong> Selalu sediakan wadah berisi air di dekat Anda. Beberapa serat sintetis dapat meleleh dan menempel pada kulit.',
177
+ },
178
+ ],
179
+ ui: {
180
+ investigationLabel: 'INVESTIGASI',
181
+ stepPrefix: 'LANGKAH',
182
+ stepCompleted: 'ANALISIS SELESAI',
183
+ resetLabel: 'MULAI ULANG',
184
+ positiveId: 'IDENTIFIKASI POSITIF',
185
+ flameLabel: 'Perilaku Api',
186
+ odorLabel: 'Jejak Penciuman',
187
+ residueLabel: 'Residu',
188
+ identifiedAs: 'Sampel diidentifikasi positif sebagai',
189
+ safetyTitle: 'Protokol Keamanan',
190
+ safetyText:
191
+ 'Lakukan pengujian ini di area berventilasi, di atas permukaan tahan api. Gunakan pinset logam dan sediakan air di dekat Anda.',
192
+ questions: {
193
+ flame: 'Perilaku dalam api?',
194
+ odor: 'Bau khas?',
195
+ residue: 'Residu akhir?',
196
+ smoke: 'Jenis asap?',
197
+ },
198
+ fiberData: {
199
+ cotton: { name: 'Katun', icon: 'mdi:tshirt-crew', color: '#60A5FA', description: 'Serat nabati alami, lembut dan sejuk. Raja dari kebutuhan dasar.' },
200
+ linen: { name: 'Linen', icon: 'mdi:leaf', color: '#A7F3D0', description: 'Serat nabati yang sangat kuat, dingin dan berkerut khas.' },
201
+ wool: { name: 'Wol / Merino', icon: 'mdi:sheep', color: '#FCD34D', description: 'Serat protein hewani, isolator termal yang sangat baik dan elastis.' },
202
+ silk: { name: 'Sutra', icon: 'mdi:ticket-percent', color: '#F472B6', description: 'Serat filamen kontinu hewani. Kilau alami dan sentuhan kering.' },
203
+ cashmere: { name: 'Cashmere', icon: 'mdi:crown', color: '#F59E0B', description: 'Serat kambing Kashmir yang mewah. Sangat lembut, ringan, dan hangat.' },
204
+ mohair: { name: 'Mohair', icon: 'mdi:shimmer', color: '#14B8A6', description: ' "Serat berlian" dari kambing Angora. Kilau alami, sangat tahan lama.' },
205
+ angora: { name: 'Angora', icon: 'mdi:rabbit', color: '#F9FAFB', description: 'Rambut kelinci Angora. Dikenal karena efek "halo" yang halus dan sangat ringan.' },
206
+ alpaca: { name: 'Alpaka', icon: 'mdi:image-filter-hdr', color: '#9F7AEA', description: 'Harta karun dari Andes. Serat berongga, termal, hipoalergenik, dan halus.' },
207
+ polyester: { name: 'Poliester', icon: 'mdi:oil', color: '#94A3B8', description: 'Serat sintetis berbasis minyak bumi. Tahan lama, bebas kerut, tidak menyerap keringat.' },
208
+ nylon: { name: 'Nilon (Poliamida)', icon: 'mdi:parachute', color: '#2DD4BF', description: 'Serat sintetis yang sangat tahan terhadap tarikan dan abrasi.' },
209
+ acrylic: { name: 'Akrilik', icon: 'mdi:resize', color: '#450A0A', description: 'Imitasi wol sintetis. Lembut, hangat, mudah berbulu (pilling).' },
210
+ },
211
+ burnTestData: [
212
+ { fiberId: 'cotton', flame: flame.cellulosic, odor: odor.paper, smoke: smoke.white_gray, residue: residue.ash_soft },
213
+ { fiberId: 'linen', flame: flame.cellulosic, odor: odor.paper, smoke: smoke.white_gray, residue: residue.ash_shape },
214
+ { fiberId: 'wool', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
215
+ { fiberId: 'silk', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
216
+ { fiberId: 'cashmere', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
217
+ { fiberId: 'mohair', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
218
+ { fiberId: 'angora', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
219
+ { fiberId: 'alpaca', flame: flame.protein, odor: odor.hair, smoke: smoke.gray, residue: residue.bead_crushable },
220
+ { fiberId: 'polyester', flame: flame.synthetic, odor: odor.chemical_sweet, smoke: smoke.black, residue: residue.bead_hard },
221
+ { fiberId: 'nylon', flame: flame.synthetic, odor: odor.vegetable, smoke: smoke.white_gray, residue: residue.bead_hard },
222
+ { fiberId: 'acrylic', flame: flame.synthetic, odor: odor.chemical_fishy, smoke: smoke.black, residue: residue.bead_irregular },
223
+ ],
224
+ },
225
+ };