@jjlmoya/utils-textiles 1.20.0 → 1.22.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.
- package/package.json +1 -1
- package/src/category/i18n/de.ts +76 -76
- package/src/category/i18n/en.ts +76 -76
- package/src/category/i18n/es.ts +76 -76
- package/src/category/i18n/fr.ts +76 -76
- package/src/category/i18n/id.ts +76 -76
- package/src/category/i18n/it.ts +76 -76
- package/src/category/i18n/ja.ts +76 -76
- package/src/category/i18n/ko.ts +76 -76
- package/src/category/i18n/nl.ts +76 -76
- package/src/category/i18n/pl.ts +76 -76
- package/src/category/i18n/pt.ts +76 -76
- package/src/category/i18n/ru.ts +76 -76
- package/src/category/i18n/sv.ts +76 -76
- package/src/category/i18n/zh.ts +76 -76
- package/src/category/index.ts +2 -0
- package/src/category/seo.astro +1 -1
- package/src/components/PreviewNavSidebar.astro +1 -1
- package/src/components/PreviewToolbar.astro +1 -1
- package/src/data.ts +1 -1
- package/src/entries.ts +4 -1
- package/src/env.d.ts +1 -1
- package/src/index.ts +1 -1
- package/src/layouts/PreviewLayout.astro +113 -113
- package/src/pages/index.astro +1 -1
- package/src/tests/faq_count.test.ts +19 -19
- package/src/tests/i18n_coverage.test.ts +4 -2
- package/src/tests/locale_spelling.test.ts +44 -0
- package/src/tests/mocks/astro_mock.js +1 -1
- package/src/tests/no_en_dash.test.ts +70 -70
- package/src/tests/no_h1_in_components.test.ts +1 -1
- package/src/tests/seo_length.test.ts +22 -22
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/spanish_leakage.test.ts +175 -153
- package/src/tests/tool_validation.test.ts +17 -17
- package/src/tool/burnTest/i18n/es.ts +211 -211
- package/src/tool/burnTest/i18n/fr.ts +211 -211
- package/src/tool/burnTest/i18n/ja.ts +211 -211
- package/src/tool/burnTest/i18n/ko.ts +211 -211
- package/src/tool/burnTest/i18n/pl.ts +211 -211
- package/src/tool/burnTest/i18n/ru.ts +211 -211
- package/src/tool/burnTest/i18n/sv.ts +211 -211
- package/src/tool/burnTest/i18n/zh.ts +211 -211
- package/src/tool/burnTest/seo.astro +15 -15
- package/src/tool/clothingSizeConverter/i18n/de.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/en.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/fr.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/ja.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/ko.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/nl.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/pl.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/ru.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/sv.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/tr.ts +348 -348
- package/src/tool/clothingSizeConverter/i18n/zh.ts +348 -348
- package/src/tool/clothingSizeConverter/seo.astro +15 -15
- package/src/tool/embroideryStitchPricingEstimator/bibliography.astro +6 -0
- package/src/tool/embroideryStitchPricingEstimator/bibliography.ts +12 -0
- package/src/tool/embroideryStitchPricingEstimator/component.astro +111 -0
- package/src/tool/embroideryStitchPricingEstimator/controller.ts +111 -0
- package/src/tool/embroideryStitchPricingEstimator/dom-views.ts +94 -0
- package/src/tool/embroideryStitchPricingEstimator/embroidery-stitch-count-pricing-estimator.css +438 -0
- package/src/tool/embroideryStitchPricingEstimator/entry.ts +29 -0
- package/src/tool/embroideryStitchPricingEstimator/evaluator.ts +22 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/de.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/en.ts +194 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/es.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/fr.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/id.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/it.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/ja.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/ko.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/nl.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/pl.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/pt.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/ru.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/sv.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/tr.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/i18n/zh.ts +38 -0
- package/src/tool/embroideryStitchPricingEstimator/index.ts +11 -0
- package/src/tool/embroideryStitchPricingEstimator/logic.test.ts +40 -0
- package/src/tool/embroideryStitchPricingEstimator/logic.ts +86 -0
- package/src/tool/embroideryStitchPricingEstimator/seo.astro +15 -0
- package/src/tool/embroideryStitchPricingEstimator/storage.ts +20 -0
- package/src/tool/embroideryStitchPricingEstimator/ui.ts +53 -0
- package/src/tool/fabricProjectCalculator/i18n/de.ts +236 -236
- package/src/tool/fabricProjectCalculator/i18n/ja.ts +236 -236
- package/src/tool/fabricProjectCalculator/i18n/ko.ts +236 -236
- package/src/tool/fabricProjectCalculator/i18n/nl.ts +236 -236
- package/src/tool/fabricProjectCalculator/i18n/pl.ts +236 -236
- package/src/tool/fabricProjectCalculator/i18n/ru.ts +236 -236
- package/src/tool/fabricProjectCalculator/i18n/sv.ts +236 -236
- package/src/tool/fabricProjectCalculator/i18n/tr.ts +236 -236
- package/src/tool/fabricProjectCalculator/i18n/zh.ts +236 -236
- package/src/tool/fabricProjectCalculator/seo.astro +15 -15
- package/src/tool/fabricTruth/i18n/de.ts +268 -268
- package/src/tool/fabricTruth/i18n/en.ts +268 -268
- package/src/tool/fabricTruth/i18n/es.ts +302 -302
- package/src/tool/fabricTruth/i18n/fr.ts +268 -268
- package/src/tool/fabricTruth/i18n/id.ts +268 -268
- package/src/tool/fabricTruth/i18n/it.ts +268 -268
- package/src/tool/fabricTruth/i18n/ja.ts +268 -268
- package/src/tool/fabricTruth/i18n/ko.ts +268 -268
- package/src/tool/fabricTruth/i18n/nl.ts +268 -268
- package/src/tool/fabricTruth/i18n/pl.ts +268 -268
- package/src/tool/fabricTruth/i18n/pt.ts +268 -268
- package/src/tool/fabricTruth/i18n/ru.ts +268 -268
- package/src/tool/fabricTruth/i18n/sv.ts +268 -268
- package/src/tool/fabricTruth/i18n/tr.ts +268 -268
- package/src/tool/fabricTruth/i18n/zh.ts +272 -272
- package/src/tool/fabricTruth/seo.astro +15 -15
- package/src/tool/fiberPrep/i18n/de.ts +215 -215
- package/src/tool/fiberPrep/i18n/fr.ts +215 -215
- package/src/tool/fiberPrep/i18n/ja.ts +215 -215
- package/src/tool/fiberPrep/i18n/ko.ts +215 -215
- package/src/tool/fiberPrep/i18n/nl.ts +215 -215
- package/src/tool/fiberPrep/i18n/pl.ts +215 -215
- package/src/tool/fiberPrep/i18n/ru.ts +215 -215
- package/src/tool/fiberPrep/i18n/sv.ts +215 -215
- package/src/tool/fiberPrep/i18n/tr.ts +215 -215
- package/src/tool/fiberPrep/i18n/zh.ts +215 -215
- package/src/tool/fiberPrep/seo.astro +15 -15
- package/src/tool/knittingGauge/i18n/de.ts +224 -224
- package/src/tool/knittingGauge/i18n/ja.ts +224 -224
- package/src/tool/knittingGauge/i18n/ko.ts +224 -224
- package/src/tool/knittingGauge/i18n/pl.ts +224 -224
- package/src/tool/knittingGauge/i18n/ru.ts +224 -224
- package/src/tool/knittingGauge/i18n/tr.ts +224 -224
- package/src/tool/knittingGauge/i18n/zh.ts +224 -224
- package/src/tool/knittingGauge/seo.astro +15 -15
- package/src/tool/laundryGuide/i18n/de.ts +271 -271
- package/src/tool/laundryGuide/i18n/en.ts +271 -271
- package/src/tool/laundryGuide/i18n/es.ts +271 -271
- package/src/tool/laundryGuide/i18n/fr.ts +255 -255
- package/src/tool/laundryGuide/i18n/id.ts +271 -271
- package/src/tool/laundryGuide/i18n/it.ts +271 -271
- package/src/tool/laundryGuide/i18n/ja.ts +271 -271
- package/src/tool/laundryGuide/i18n/ko.ts +271 -271
- package/src/tool/laundryGuide/i18n/nl.ts +271 -271
- package/src/tool/laundryGuide/i18n/pl.ts +271 -271
- package/src/tool/laundryGuide/i18n/pt.ts +271 -271
- package/src/tool/laundryGuide/i18n/ru.ts +271 -271
- package/src/tool/laundryGuide/i18n/sv.ts +271 -271
- package/src/tool/laundryGuide/i18n/tr.ts +271 -271
- package/src/tool/laundryGuide/i18n/zh.ts +271 -271
- package/src/tool/laundryGuide/seo.astro +15 -15
- package/src/tool/needleConverter/i18n/de.ts +202 -202
- package/src/tool/needleConverter/i18n/es.ts +207 -207
- package/src/tool/needleConverter/i18n/fr.ts +207 -207
- package/src/tool/needleConverter/i18n/ja.ts +202 -202
- package/src/tool/needleConverter/i18n/ko.ts +202 -202
- package/src/tool/needleConverter/i18n/nl.ts +202 -202
- package/src/tool/needleConverter/i18n/pl.ts +202 -202
- package/src/tool/needleConverter/i18n/ru.ts +202 -202
- package/src/tool/needleConverter/i18n/sv.ts +202 -202
- package/src/tool/needleConverter/i18n/tr.ts +202 -202
- package/src/tool/needleConverter/i18n/zh.ts +202 -202
- package/src/tool/needleConverter/seo.astro +15 -15
- package/src/tool/sewingPatternScaler/component.astro +236 -236
- package/src/tool/sewingPatternScaler/i18n/fr.ts +170 -170
- package/src/tool/sewingPatternScaler/i18n/ja.ts +114 -114
- package/src/tool/sewingPatternScaler/i18n/ko.ts +114 -114
- package/src/tool/sewingPatternScaler/i18n/nl.ts +114 -114
- package/src/tool/sewingPatternScaler/i18n/pl.ts +114 -114
- package/src/tool/sewingPatternScaler/i18n/ru.ts +114 -114
- package/src/tool/sewingPatternScaler/i18n/sv.ts +114 -114
- package/src/tool/sewingPatternScaler/i18n/tr.ts +114 -114
- package/src/tool/sewingPatternScaler/i18n/zh.ts +114 -114
- package/src/tool/sewingPatternScaler/seo.astro +15 -15
- package/src/tool/shoeSizeConverter/i18n/fr.ts +183 -183
- package/src/tool/shoeSizeConverter/i18n/ja.ts +179 -179
- package/src/tool/shoeSizeConverter/i18n/ko.ts +179 -179
- package/src/tool/shoeSizeConverter/i18n/nl.ts +179 -179
- package/src/tool/shoeSizeConverter/i18n/pl.ts +179 -179
- package/src/tool/shoeSizeConverter/i18n/ru.ts +179 -179
- package/src/tool/shoeSizeConverter/i18n/sv.ts +179 -179
- package/src/tool/shoeSizeConverter/i18n/tr.ts +179 -179
- package/src/tool/shoeSizeConverter/i18n/zh.ts +179 -179
- package/src/tool/shoeSizeConverter/seo.astro +15 -15
- package/src/tool/stainChemistry/bibliography.ts +16 -16
- package/src/tool/stainChemistry/i18n/de.ts +286 -286
- package/src/tool/stainChemistry/i18n/en.ts +289 -289
- package/src/tool/stainChemistry/i18n/es.ts +286 -286
- package/src/tool/stainChemistry/i18n/fr.ts +296 -296
- package/src/tool/stainChemistry/i18n/id.ts +286 -286
- package/src/tool/stainChemistry/i18n/it.ts +286 -286
- package/src/tool/stainChemistry/i18n/ja.ts +286 -286
- package/src/tool/stainChemistry/i18n/ko.ts +283 -283
- package/src/tool/stainChemistry/i18n/nl.ts +286 -286
- package/src/tool/stainChemistry/i18n/pl.ts +286 -286
- package/src/tool/stainChemistry/i18n/pt.ts +286 -286
- package/src/tool/stainChemistry/i18n/ru.ts +286 -286
- package/src/tool/stainChemistry/i18n/sv.ts +286 -286
- package/src/tool/stainChemistry/i18n/tr.ts +286 -286
- package/src/tool/stainChemistry/i18n/zh.ts +281 -281
- package/src/tool/stainChemistry/seo.astro +15 -15
- package/src/tool/yarnCalculator/i18n/de.ts +296 -296
- package/src/tool/yarnCalculator/i18n/es.ts +296 -296
- package/src/tool/yarnCalculator/i18n/fr.ts +296 -296
- package/src/tool/yarnCalculator/i18n/ja.ts +296 -296
- package/src/tool/yarnCalculator/i18n/ko.ts +296 -296
- package/src/tool/yarnCalculator/i18n/nl.ts +296 -296
- package/src/tool/yarnCalculator/i18n/pl.ts +296 -296
- package/src/tool/yarnCalculator/i18n/ru.ts +296 -296
- package/src/tool/yarnCalculator/i18n/sv.ts +296 -296
- package/src/tool/yarnCalculator/i18n/tr.ts +296 -296
- package/src/tool/yarnCalculator/i18n/zh.ts +295 -295
- package/src/tool/yarnCalculator/seo.astro +15 -15
- package/src/tools.ts +2 -2
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
-
import { clothingSizeConverter } from './index';
|
|
4
|
-
import type { KnownLocale } from '../../types';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
locale?: KnownLocale;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { locale = 'es' } = Astro.props;
|
|
11
|
-
const content = await clothingSizeConverter.i18n[locale]?.();
|
|
12
|
-
if (!content) return null;
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { clothingSizeConverter } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await clothingSizeConverter.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BibliographyEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const bibliography: BibliographyEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
name: 'Wilcom - Stitch count and scaling',
|
|
6
|
+
url: 'https://help.wilcom.com/portal/en/kb/wilcom-international/embroidery-web-api/faqs/articles/stitch-count-scaling',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'Madeira - Stitch density guide',
|
|
10
|
+
url: 'https://www.madeirausa.com/_resources/common/userfiles/file/Resources/TH.Reads/Density.pdf',
|
|
11
|
+
},
|
|
12
|
+
];
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { EmbroideryStitchPricingEstimatorUI } from './ui';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
ui?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { ui = {} } = Astro.props;
|
|
9
|
+
const toolUI = ui as unknown as EmbroideryStitchPricingEstimatorUI;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<div class="embroider-tool" id="embroidery-stitch-pricing-estimator">
|
|
13
|
+
<div class="embroider-intro">
|
|
14
|
+
<span class="embroider-eyebrow">{toolUI.stageLabel}</span>
|
|
15
|
+
<p>{toolUI.intro}</p>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="embroider-workbench">
|
|
18
|
+
<section class="embroider-stage" aria-label={toolUI.stageLabel}>
|
|
19
|
+
<div class="embroider-stage-header">
|
|
20
|
+
<span>{toolUI.canvasAlt}</span>
|
|
21
|
+
<span class="embroider-status" id="embroider-status-badge">INPUT NEEDED</span>
|
|
22
|
+
</div>
|
|
23
|
+
<svg id="embroider-canvas" class="embroider-canvas" viewBox="0 0 400 200" role="img" aria-label={toolUI.canvasAlt}></svg>
|
|
24
|
+
<div class="embroider-stage-caption">
|
|
25
|
+
<span id="embroider-result-status">{toolUI.emptyResult}</span>
|
|
26
|
+
<span id="embroider-notice" class="embroider-notice" hidden></span>
|
|
27
|
+
</div>
|
|
28
|
+
</section>
|
|
29
|
+
<section class="embroider-controls" aria-label={toolUI.controlsLabel}>
|
|
30
|
+
<div class="embroider-control-heading">
|
|
31
|
+
<span class="embroider-eyebrow">{toolUI.controlsLabel}</span>
|
|
32
|
+
<button class="embroider-reset" id="embroider-reset" type="button">{toolUI.reset}</button>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="embroider-grid">
|
|
35
|
+
<label class="embroider-field">
|
|
36
|
+
<span>{toolUI.widthLabel}</span>
|
|
37
|
+
<input id="embroider-width" class="embroider-input" type="number" min="0.1" step="0.1" inputmode="decimal" />
|
|
38
|
+
<small>{toolUI.widthHint}</small>
|
|
39
|
+
</label>
|
|
40
|
+
<label class="embroider-field">
|
|
41
|
+
<span>{toolUI.heightLabel}</span>
|
|
42
|
+
<input id="embroider-height" class="embroider-input" type="number" min="0.1" step="0.1" inputmode="decimal" />
|
|
43
|
+
<small>{toolUI.heightHint}</small>
|
|
44
|
+
</label>
|
|
45
|
+
<label class="embroider-field">
|
|
46
|
+
<span>{toolUI.densityLabel}</span>
|
|
47
|
+
<input id="embroider-density" class="embroider-input" type="number" min="0.1" max="20" step="0.1" inputmode="decimal" />
|
|
48
|
+
<small>{toolUI.densityHint}</small>
|
|
49
|
+
</label>
|
|
50
|
+
<label class="embroider-field">
|
|
51
|
+
<span>{toolUI.coloursLabel}</span>
|
|
52
|
+
<input id="embroider-colours" class="embroider-input" type="number" min="1" max="30" step="1" inputmode="numeric" />
|
|
53
|
+
<small>{toolUI.coloursHint}</small>
|
|
54
|
+
</label>
|
|
55
|
+
<label class="embroider-field">
|
|
56
|
+
<span>{toolUI.speedLabel}</span>
|
|
57
|
+
<input id="embroider-speed" class="embroider-input" type="number" min="1" step="50" inputmode="numeric" />
|
|
58
|
+
<small>{toolUI.speedHint}</small>
|
|
59
|
+
</label>
|
|
60
|
+
<label class="embroider-field">
|
|
61
|
+
<span>{toolUI.rateLabel}</span>
|
|
62
|
+
<input id="embroider-rate" class="embroider-input" type="number" min="0.01" step="0.5" inputmode="decimal" />
|
|
63
|
+
<small>{toolUI.rateHint}</small>
|
|
64
|
+
</label>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="embroider-select">
|
|
67
|
+
<span class="embroider-field-label">{toolUI.stitchLabel}</span>
|
|
68
|
+
<button id="embroider-stitch-trigger" class="embroider-select-trigger" type="button" aria-expanded="false" aria-controls="embroider-stitch-menu">{toolUI.selectStitch}</button>
|
|
69
|
+
<div id="embroider-stitch-menu" class="embroider-select-menu" hidden role="listbox">
|
|
70
|
+
<button type="button" data-stitch-option="running" data-note={toolUI.stitchRunningNote} role="option">{toolUI.stitchRunning}</button>
|
|
71
|
+
<button type="button" data-stitch-option="satin" data-note={toolUI.stitchSatinNote} role="option">{toolUI.stitchSatin}</button>
|
|
72
|
+
<button type="button" data-stitch-option="fill" data-note={toolUI.stitchFillNote} role="option">{toolUI.stitchFill}</button>
|
|
73
|
+
<button type="button" data-stitch-option="tatami" data-note={toolUI.stitchTatamiNote} role="option">{toolUI.stitchTatami}</button>
|
|
74
|
+
</div>
|
|
75
|
+
<small id="embroider-stitch-note">{toolUI.stitchSatinNote}</small>
|
|
76
|
+
</div>
|
|
77
|
+
</section>
|
|
78
|
+
</div>
|
|
79
|
+
<section class="embroider-result" aria-label={toolUI.resultLabel}>
|
|
80
|
+
<div class="embroider-result-main">
|
|
81
|
+
<span class="embroider-eyebrow">{toolUI.resultLabel}</span>
|
|
82
|
+
<strong id="embroider-price">--</strong>
|
|
83
|
+
<span>{toolUI.priceUnit}</span>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="embroider-result-metrics">
|
|
86
|
+
<div><span>{toolUI.effectiveLabel}</span><strong id="embroider-effective">--</strong></div>
|
|
87
|
+
<div><span>{toolUI.productionLabel}</span><strong id="embroider-duration">--</strong></div>
|
|
88
|
+
</div>
|
|
89
|
+
<button id="embroider-copy" class="embroider-copy" type="button">{toolUI.copySummary}</button>
|
|
90
|
+
</section>
|
|
91
|
+
<section class="embroider-breakdown">
|
|
92
|
+
<div><span>{toolUI.areaLabel}</span><strong id="embroider-area">--</strong></div>
|
|
93
|
+
<div><span>{toolUI.stitchTimeLabel}</span><strong id="embroider-stitch-time">--</strong></div>
|
|
94
|
+
<div><span>{toolUI.setupLabel}</span><strong id="embroider-setup-time">--</strong></div>
|
|
95
|
+
<div><span>{toolUI.changesLabel}</span><strong id="embroider-colour-changes">--</strong></div>
|
|
96
|
+
</section>
|
|
97
|
+
<section class="embroider-explainer">
|
|
98
|
+
<div><h3>{toolUI.formulaTitle}</h3><p>{toolUI.formulaText}</p></div>
|
|
99
|
+
<div><h3>{toolUI.noteTitle}</h3><p>{toolUI.noteText}</p></div>
|
|
100
|
+
</section>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<script id="embroidery-tool-data" type="application/json" is:inline set:html={JSON.stringify(toolUI)}></script>
|
|
104
|
+
|
|
105
|
+
<script>
|
|
106
|
+
import { mountEmbroideryEstimator } from './controller';
|
|
107
|
+
import type { EmbroideryStitchPricingEstimatorUI } from './ui';
|
|
108
|
+
|
|
109
|
+
const data = document.getElementById('embroidery-tool-data');
|
|
110
|
+
if (data?.textContent) mountEmbroideryEstimator(JSON.parse(data.textContent) as EmbroideryStitchPricingEstimatorUI);
|
|
111
|
+
</script>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { evaluateEstimate } from './evaluator';
|
|
2
|
+
import { calculateEmbroidery, DEFAULT_INPUT, type EmbroideryInput, type StitchType } from './logic';
|
|
3
|
+
import { copyableSummary, renderEmbroidery } from './dom-views';
|
|
4
|
+
import { loadEmbroideryInput, saveEmbroideryInput } from './storage';
|
|
5
|
+
import type { EmbroideryStitchPricingEstimatorUI } from './ui';
|
|
6
|
+
|
|
7
|
+
function numberValue(id: string, fallback: number): number {
|
|
8
|
+
const element = document.getElementById(id) as HTMLInputElement | null;
|
|
9
|
+
return Number(element?.value ?? fallback);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readInput(): EmbroideryInput {
|
|
13
|
+
const selected = document.querySelector('[data-stitch-option].is-selected') as HTMLElement | null;
|
|
14
|
+
return {
|
|
15
|
+
widthCm: numberValue('embroider-width', DEFAULT_INPUT.widthCm),
|
|
16
|
+
heightCm: numberValue('embroider-height', DEFAULT_INPUT.heightCm),
|
|
17
|
+
density: numberValue('embroider-density', DEFAULT_INPUT.density),
|
|
18
|
+
stitchType: (selected?.dataset.stitchOption as StitchType) || DEFAULT_INPUT.stitchType,
|
|
19
|
+
colours: numberValue('embroider-colours', DEFAULT_INPUT.colours),
|
|
20
|
+
speed: numberValue('embroider-speed', DEFAULT_INPUT.speed),
|
|
21
|
+
hourlyRate: numberValue('embroider-rate', DEFAULT_INPUT.hourlyRate),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeInput(input: EmbroideryInput): void {
|
|
26
|
+
const values: Record<string, number> = {
|
|
27
|
+
'embroider-width': input.widthCm,
|
|
28
|
+
'embroider-height': input.heightCm,
|
|
29
|
+
'embroider-density': input.density,
|
|
30
|
+
'embroider-colours': input.colours,
|
|
31
|
+
'embroider-speed': input.speed,
|
|
32
|
+
'embroider-rate': input.hourlyRate,
|
|
33
|
+
};
|
|
34
|
+
Object.entries(values).forEach(([id, value]) => {
|
|
35
|
+
const element = document.getElementById(id) as HTMLInputElement | null;
|
|
36
|
+
if (element) element.value = String(value);
|
|
37
|
+
});
|
|
38
|
+
selectStitch(input.stitchType);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function selectStitch(value: StitchType): void {
|
|
42
|
+
document.querySelectorAll<HTMLElement>('[data-stitch-option]').forEach((option) => {
|
|
43
|
+
const active = option.dataset.stitchOption === value;
|
|
44
|
+
option.classList.toggle('is-selected', active);
|
|
45
|
+
option.setAttribute('aria-selected', String(active));
|
|
46
|
+
});
|
|
47
|
+
const selected = document.querySelector(`[data-stitch-option="${value}"]`) as HTMLElement | null;
|
|
48
|
+
const trigger = document.getElementById('embroider-stitch-trigger');
|
|
49
|
+
if (trigger && selected) trigger.textContent = selected.textContent;
|
|
50
|
+
const note = document.getElementById('embroider-stitch-note');
|
|
51
|
+
if (note && selected?.dataset.note) note.textContent = selected.dataset.note;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function update(ui: EmbroideryStitchPricingEstimatorUI): { input: EmbroideryInput; calculation: ReturnType<typeof calculateEmbroidery> } {
|
|
55
|
+
const input = readInput();
|
|
56
|
+
const calculation = calculateEmbroidery(input);
|
|
57
|
+
const evaluation = evaluateEstimate(calculation, { invalid: ui.invalidMessage, highVolume: ui.warningHighVolume, longRun: ui.warningLongRun });
|
|
58
|
+
renderEmbroidery({ input, calculation, evaluation, ui });
|
|
59
|
+
saveEmbroideryInput(input);
|
|
60
|
+
return { input, calculation };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function wireSelect(ui: EmbroideryStitchPricingEstimatorUI, refresh: () => void): void {
|
|
64
|
+
const trigger = document.getElementById('embroider-stitch-trigger');
|
|
65
|
+
const menu = document.getElementById('embroider-stitch-menu');
|
|
66
|
+
trigger?.addEventListener('click', () => {
|
|
67
|
+
if (menu) menu.hidden = !menu.hidden;
|
|
68
|
+
trigger.setAttribute('aria-expanded', String(menu ? !menu.hidden : false));
|
|
69
|
+
});
|
|
70
|
+
document.querySelectorAll<HTMLElement>('[data-stitch-option]').forEach((option) => option.addEventListener('click', () => {
|
|
71
|
+
selectStitch(option.dataset.stitchOption as StitchType);
|
|
72
|
+
if (menu) menu.hidden = true;
|
|
73
|
+
if (trigger) trigger.setAttribute('aria-expanded', 'false');
|
|
74
|
+
refresh();
|
|
75
|
+
}));
|
|
76
|
+
document.addEventListener('click', (event) => {
|
|
77
|
+
if (!(event.target as HTMLElement).closest('.embroider-select')) {
|
|
78
|
+
if (menu) menu.hidden = true;
|
|
79
|
+
trigger?.setAttribute('aria-expanded', 'false');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
const note = document.getElementById('embroider-stitch-note');
|
|
83
|
+
document.querySelectorAll<HTMLElement>('[data-stitch-option]').forEach((option) => option.addEventListener('mouseenter', () => {
|
|
84
|
+
if (note) note.textContent = option.dataset.note || ui.stitchSatinNote;
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function wireCopy(ui: EmbroideryStitchPricingEstimatorUI, refresh: () => ReturnType<typeof update>): void {
|
|
89
|
+
document.getElementById('embroider-copy')?.addEventListener('click', async () => {
|
|
90
|
+
const state = refresh();
|
|
91
|
+
try {
|
|
92
|
+
await navigator.clipboard.writeText(copyableSummary({ input: state.input, calculation: state.calculation, evaluation: evaluateEstimate(state.calculation, { invalid: ui.invalidMessage, highVolume: ui.warningHighVolume, longRun: ui.warningLongRun }), ui }));
|
|
93
|
+
const button = document.getElementById('embroider-copy');
|
|
94
|
+
if (button) {
|
|
95
|
+
button.textContent = ui.copied;
|
|
96
|
+
window.setTimeout(() => { button.textContent = ui.copySummary; }, 1600);
|
|
97
|
+
}
|
|
98
|
+
} catch {}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function mountEmbroideryEstimator(ui: EmbroideryStitchPricingEstimatorUI): void {
|
|
103
|
+
const saved = loadEmbroideryInput(DEFAULT_INPUT);
|
|
104
|
+
writeInput(saved);
|
|
105
|
+
const refresh = () => update(ui);
|
|
106
|
+
document.querySelectorAll<HTMLInputElement>('.embroider-input').forEach((input) => input.addEventListener('input', refresh));
|
|
107
|
+
document.getElementById('embroider-reset')?.addEventListener('click', () => { writeInput(DEFAULT_INPUT); refresh(); });
|
|
108
|
+
wireSelect(ui, refresh);
|
|
109
|
+
wireCopy(ui, refresh);
|
|
110
|
+
refresh();
|
|
111
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { EmbroideryCalculation, EmbroideryInput } from './logic';
|
|
2
|
+
import { formatMinutes, formatNumber } from './logic';
|
|
3
|
+
import type { EmbroideryStitchPricingEstimatorUI } from './ui';
|
|
4
|
+
import type { EstimateEvaluation } from './evaluator';
|
|
5
|
+
|
|
6
|
+
interface RenderContext {
|
|
7
|
+
input: EmbroideryInput;
|
|
8
|
+
calculation: EmbroideryCalculation | null;
|
|
9
|
+
evaluation: EstimateEvaluation;
|
|
10
|
+
ui: EmbroideryStitchPricingEstimatorUI;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function setText(id: string, value: string): void {
|
|
14
|
+
const element = document.getElementById(id);
|
|
15
|
+
if (element) element.textContent = value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeSvgElement<K extends keyof SVGElementTagNameMap>(tag: K, attrs: Record<string, string>): SVGElementTagNameMap[K] {
|
|
19
|
+
const element = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
|
20
|
+
Object.entries(attrs).forEach(([key, value]) => element.setAttribute(key, value));
|
|
21
|
+
return element;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderThreadLines(svg: SVGSVGElement, input: EmbroideryInput): void {
|
|
25
|
+
const lineCount = Math.min(18, Math.max(5, Math.round(input.density * 2)));
|
|
26
|
+
for (let index = 0; index < lineCount; index += 1) {
|
|
27
|
+
const y = 38 + (index * 124) / Math.max(1, lineCount - 1);
|
|
28
|
+
const path = makeSvgElement('path', {
|
|
29
|
+
d: `M 45 ${y} C 95 ${y - 20} 145 ${y + 20} 195 ${y} S 295 ${y - 20} 355 ${y}`,
|
|
30
|
+
class: 'embroider-thread',
|
|
31
|
+
'stroke-width': `${1.2 + input.density / 10}`,
|
|
32
|
+
});
|
|
33
|
+
svg.appendChild(path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderCanvas(input: EmbroideryInput): void {
|
|
38
|
+
const svg = document.getElementById('embroider-canvas') as SVGSVGElement | null;
|
|
39
|
+
if (!svg) return;
|
|
40
|
+
while (svg.firstChild) svg.removeChild(svg.firstChild);
|
|
41
|
+
svg.appendChild(makeSvgElement('circle', { cx: '200', cy: '100', r: '160', class: 'embroider-hoop-shadow' }));
|
|
42
|
+
svg.appendChild(makeSvgElement('circle', { cx: '200', cy: '100', r: '150', class: 'embroider-hoop' }));
|
|
43
|
+
svg.appendChild(makeSvgElement('rect', { x: '42', y: '22', width: '316', height: '156', rx: '14', class: 'embroider-cloth' }));
|
|
44
|
+
renderThreadLines(svg, input);
|
|
45
|
+
svg.appendChild(makeSvgElement('circle', { cx: '200', cy: '100', r: '8', class: 'embroider-needle' }));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderValues(calculation: EmbroideryCalculation | null, ui: EmbroideryStitchPricingEstimatorUI): void {
|
|
49
|
+
if (!calculation) {
|
|
50
|
+
setText('embroider-result-status', ui.emptyResult);
|
|
51
|
+
setText('embroider-effective', '--');
|
|
52
|
+
setText('embroider-duration', '--');
|
|
53
|
+
setText('embroider-price', '--');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setText('embroider-result-status', ui.estimateBadge);
|
|
57
|
+
setText('embroider-effective', `${formatNumber(calculation.effectiveStitches)} ${ui.stitchesUnit}`);
|
|
58
|
+
setText('embroider-duration', formatMinutes(calculation.totalMinutes));
|
|
59
|
+
setText('embroider-price', calculation.basePrice.toFixed(2));
|
|
60
|
+
setText('embroider-area', `${formatNumber(calculation.areaCm2, 1)} cm2`);
|
|
61
|
+
setText('embroider-stitch-time', formatMinutes(calculation.stitchMinutes));
|
|
62
|
+
setText('embroider-setup-time', formatMinutes(calculation.setupMinutes));
|
|
63
|
+
setText('embroider-colour-changes', formatNumber(calculation.colourChanges));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function renderEvaluation(evaluation: EstimateEvaluation): void {
|
|
67
|
+
const badge = document.getElementById('embroider-status-badge');
|
|
68
|
+
const notice = document.getElementById('embroider-notice');
|
|
69
|
+
if (badge) {
|
|
70
|
+
badge.textContent = getStatusLabel(evaluation.status);
|
|
71
|
+
badge.dataset.status = evaluation.status;
|
|
72
|
+
}
|
|
73
|
+
if (notice) {
|
|
74
|
+
notice.textContent = evaluation.message;
|
|
75
|
+
notice.hidden = !evaluation.message;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getStatusLabel(status: EstimateEvaluation['status']): string {
|
|
80
|
+
if (status === 'ready') return 'READY';
|
|
81
|
+
if (status === 'attention') return 'CHECK';
|
|
82
|
+
return 'INPUT NEEDED';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function renderEmbroidery(ctx: RenderContext): void {
|
|
86
|
+
renderCanvas(ctx.input);
|
|
87
|
+
renderValues(ctx.calculation, ctx.ui);
|
|
88
|
+
renderEvaluation(ctx.evaluation);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function copyableSummary(ctx: RenderContext): string {
|
|
92
|
+
if (!ctx.calculation) return ctx.ui.invalidMessage;
|
|
93
|
+
return `${ctx.ui.resultLabel}: ${formatNumber(ctx.calculation.effectiveStitches)} ${ctx.ui.stitchesUnit}, ${formatMinutes(ctx.calculation.totalMinutes)}, ${ctx.calculation.basePrice.toFixed(2)} ${ctx.ui.priceUnit}`;
|
|
94
|
+
}
|