@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,1226 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { ClothingSizeConverterUI } from './ui';
4
+
5
+ interface Props {
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const { ui = {} } = Astro.props;
10
+ const sizeUI = ui as ClothingSizeConverterUI;
11
+
12
+ const fitLabels: Record<string, string> = {
13
+ small: sizeUI.fitSmall ?? 'Small',
14
+ regular: sizeUI.fitRegular ?? 'Regular',
15
+ large: sizeUI.fitLarge ?? 'Large',
16
+ };
17
+
18
+ const brands = [
19
+ { brand: 'Zara', fit: 'regular' },
20
+ { brand: 'H&M', fit: 'large' },
21
+ { brand: 'ASOS', fit: 'regular' },
22
+ { brand: 'Uniqlo', fit: 'small' },
23
+ { brand: 'Nike', fit: 'small' },
24
+ { brand: 'Adidas', fit: 'regular' },
25
+ ] as const;
26
+ ---
27
+
28
+ <div class="clothing-converter">
29
+ <div class="converter-card">
30
+
31
+ <div class="selector-section">
32
+ <div class="selector-group">
33
+ <span class="selector-label">{sizeUI.categoryLabel ?? 'Category'}</span>
34
+ <div class="btn-group">
35
+ <button class="cat-btn active" data-category="shirts">
36
+ <Icon name="mdi:tshirt-crew" />
37
+ <span>{sizeUI.categoryShirts ?? 'Shirts'}</span>
38
+ </button>
39
+ <button class="cat-btn" data-category="pants">
40
+ <Icon name="mdi:human-handsdown" />
41
+ <span>{sizeUI.categoryPants ?? 'Pants'}</span>
42
+ </button>
43
+ <button class="cat-btn" data-category="dresses">
44
+ <Icon name="mdi:human-female" />
45
+ <span>{sizeUI.categoryDresses ?? 'Dresses'}</span>
46
+ </button>
47
+ <button class="cat-btn" data-category="jackets">
48
+ <Icon name="mdi:hanger" />
49
+ <span>{sizeUI.categoryJackets ?? 'Jackets'}</span>
50
+ </button>
51
+ </div>
52
+ </div>
53
+
54
+ <div class="selector-group">
55
+ <span class="selector-label">{sizeUI.genderLabel ?? 'Gender'}</span>
56
+ <div class="btn-group">
57
+ <button class="gender-btn active" data-gender="men">
58
+ <Icon name="mdi:gender-male" />
59
+ <span>{sizeUI.genderMen ?? 'Men'}</span>
60
+ </button>
61
+ <button class="gender-btn" data-gender="women">
62
+ <Icon name="mdi:gender-female" />
63
+ <span>{sizeUI.genderWomen ?? 'Women'}</span>
64
+ </button>
65
+ <button class="gender-btn" data-gender="unisex">
66
+ <Icon name="mdi:gender-male-female" />
67
+ <span>{sizeUI.genderUnisex ?? 'Unisex'}</span>
68
+ </button>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="tabs">
74
+ <button class="tab-btn active" data-tab="by-size">
75
+ <Icon name="mdi:ruler" />
76
+ {sizeUI.tabBySize ?? 'By Size'}
77
+ </button>
78
+ <button class="tab-btn" data-tab="by-measurements">
79
+ <Icon name="mdi:tape-measure" />
80
+ {sizeUI.tabByMeasurements ?? 'By Measurements'}
81
+ </button>
82
+ </div>
83
+
84
+ <div class="tab-content active" id="by-size-tab">
85
+ <div class="control-row">
86
+ <div class="field-group">
87
+ <label class="field-label" for="region-select">{sizeUI.regionLabel ?? 'Size System'}</label>
88
+ <div class="select-wrap">
89
+ <select id="region-select" class="region-select">
90
+ <option value="EU">{sizeUI.regionEU ?? 'Europe (EU)'}</option>
91
+ <option value="US">{sizeUI.regionUS ?? 'United States (US)'}</option>
92
+ <option value="UK">{sizeUI.regionUK ?? 'United Kingdom (UK)'}</option>
93
+ <option value="IT">{sizeUI.regionIT ?? 'Italy (IT)'}</option>
94
+ </select>
95
+ </div>
96
+ </div>
97
+ <div class="field-group">
98
+ <label class="field-label" for="size-select">{sizeUI.sizeLabel ?? 'Select Size'}</label>
99
+ <div class="select-wrap">
100
+ <select id="size-select" class="size-select"></select>
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ <div class="results-grid">
106
+ <div class="result-card">
107
+ <span class="result-label" id="label-1">-</span>
108
+ <span class="result-value" id="val-1">-</span>
109
+ </div>
110
+ <div class="result-card">
111
+ <span class="result-label" id="label-2">-</span>
112
+ <span class="result-value" id="val-2">-</span>
113
+ </div>
114
+ <div class="result-card">
115
+ <span class="result-label" id="label-3">-</span>
116
+ <span class="result-value" id="val-3">-</span>
117
+ </div>
118
+ </div>
119
+
120
+ <div class="quick-grid-section">
121
+ <h3 class="section-title">{sizeUI.quickGridTitle ?? 'Quick Reference (EU)'}</h3>
122
+ <div class="size-grid" id="quick-grid"></div>
123
+ </div>
124
+ </div>
125
+
126
+ <div class="tab-content" id="by-measurements-tab">
127
+ <div class="measurements-layout">
128
+ <div class="measurements-inputs">
129
+ <h3 class="measurements-section-title">{sizeUI.measurementsTitle ?? 'Enter Your Measurements'}</h3>
130
+ <div class="measure-field">
131
+ <label class="measure-label" for="chest-input">{sizeUI.chestLabel ?? 'Chest (cm)'}</label>
132
+ <input type="number" id="chest-input" class="measure-input"
133
+ placeholder={sizeUI.chestPlaceholder ?? 'E.g. 95'} min="50" max="150" />
134
+ </div>
135
+ <div class="measure-field">
136
+ <label class="measure-label" for="waist-input">{sizeUI.waistLabel ?? 'Waist (cm)'}</label>
137
+ <input type="number" id="waist-input" class="measure-input"
138
+ placeholder={sizeUI.waistPlaceholder ?? 'E.g. 85'} min="40" max="140" />
139
+ </div>
140
+ <div class="measure-field">
141
+ <label class="measure-label" for="hip-input">{sizeUI.hipLabel ?? 'Hip (cm)'}</label>
142
+ <input type="number" id="hip-input" class="measure-input"
143
+ placeholder={sizeUI.hipPlaceholder ?? 'E.g. 95'} min="50" max="150" />
144
+ </div>
145
+ <button class="btn-recommend" id="recommend-btn">
146
+ <Icon name="mdi:lightbulb-on" />
147
+ {sizeUI.recommendBtnLabel ?? 'Recommend Size'}
148
+ </button>
149
+ </div>
150
+
151
+ <div class="tips-card">
152
+ <h4 class="tips-title">
153
+ <Icon name="mdi:lightbulb-outline" />
154
+ {sizeUI.tipsTitle ?? 'Measurement Tips'}
155
+ </h4>
156
+ <div class="tips-grid">
157
+ <div class="tip-item">
158
+ <Icon name="mdi:tshirt-crew" />
159
+ <span>{sizeUI.tipOverClothes ?? 'Over clothes'}</span>
160
+ </div>
161
+ <div class="tip-item">
162
+ <Icon name="mdi:arrow-expand-horizontal" />
163
+ <span>{sizeUI.tipStraight ?? 'Straight and parallel'}</span>
164
+ </div>
165
+ <div class="tip-item">
166
+ <Icon name="mdi:minus" />
167
+ <span>{sizeUI.tipNoTight ?? 'Without pulling tight'}</span>
168
+ </div>
169
+ <div class="tip-item">
170
+ <Icon name="mdi:weather-sunset" />
171
+ <span>{sizeUI.tipEndOfDay ?? 'At the end of the day'}</span>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+
177
+ <div class="recommendation-box" id="recommendation-box">
178
+ <div class="recommend-header">
179
+ <Icon name="mdi:check-circle" />
180
+ <h4>{sizeUI.recommendationTitle ?? 'Recommended Size'}</h4>
181
+ </div>
182
+ <p id="recommendation-text"></p>
183
+ <div id="size-details"></div>
184
+ </div>
185
+ </div>
186
+
187
+ <div class="fit-guide">
188
+ <h3 class="section-title">{sizeUI.fitGuideTitle ?? 'Brand Fit Guide'}</h3>
189
+ <div class="fit-grid">
190
+ {brands.map(({ brand, fit }) => (
191
+ <button class="fit-card" data-brand={brand} data-fit={fit}>
192
+ <span class="fit-brand">{brand}</span>
193
+ <span class="fit-size-label">{fitLabels[fit]}</span>
194
+ </button>
195
+ ))}
196
+ </div>
197
+ <div class="fit-notification" id="fit-notification"></div>
198
+ <p class="fit-info" id="fit-info-text">
199
+ <Icon name="mdi:information-outline" />
200
+ {sizeUI.fitInfoDefault ?? 'Click a brand for specific recommendations.'}
201
+ </p>
202
+ </div>
203
+
204
+ <div class="table-section">
205
+ <h3 class="section-title">{sizeUI.tableTitle ?? 'Full Conversion Table'}</h3>
206
+ <div class="table-wrap">
207
+ <table class="conv-table">
208
+ <thead>
209
+ <tr>
210
+ <th>EU</th>
211
+ <th>US</th>
212
+ <th>UK</th>
213
+ <th>IT</th>
214
+ </tr>
215
+ </thead>
216
+ <tbody id="table-body"></tbody>
217
+ </table>
218
+ </div>
219
+ </div>
220
+
221
+ <div class="info-banner">
222
+ <Icon name="mdi:lightbulb-on" />
223
+ <p>
224
+ <strong>{sizeUI.infoBannerTitle ?? 'Pro Tip:'}</strong>
225
+ {' '}{sizeUI.infoBannerText ?? ''}
226
+ </p>
227
+ </div>
228
+
229
+ </div>
230
+ </div>
231
+
232
+ <script define:vars={{ ui }} is:inline>
233
+ window.__clothingSizeUI = ui;
234
+ </script>
235
+
236
+ <script>
237
+ import type { ClothingSizeConverterUI } from './ui';
238
+ import type { SizeData, ClothingRegion, MeasurementRange } from './logic';
239
+ import { findBestSize } from './logic';
240
+
241
+ const rawUI = (window as unknown as { __clothingSizeUI: ClothingSizeConverterUI }).__clothingSizeUI;
242
+
243
+ const dataMap: Record<string, Record<string, SizeData[]>> = {
244
+ shirts: rawUI.shirtsData,
245
+ pants: rawUI.pantsData,
246
+ dresses: rawUI.dressesData,
247
+ jackets: rawUI.jacketsData,
248
+ };
249
+
250
+ const regionLabels: Record<ClothingRegion, string> = {
251
+ EU: rawUI.labelEU,
252
+ US: rawUI.labelUS,
253
+ UK: rawUI.labelUK,
254
+ IT: rawUI.labelIT,
255
+ };
256
+
257
+ let currentCategory = 'shirts';
258
+ let currentGender = 'men';
259
+
260
+ function getEl(id: string): HTMLElement | null {
261
+ return document.getElementById(id);
262
+ }
263
+
264
+ function getCurrentData(): SizeData[] {
265
+ return dataMap[currentCategory]?.[currentGender] ?? [];
266
+ }
267
+
268
+ function populateSizeSelect(data: SizeData[], region: string): void {
269
+ const sel = document.getElementById('size-select') as HTMLSelectElement | null;
270
+ if (!sel) return;
271
+ sel.innerHTML = '';
272
+ for (const item of data) {
273
+ const opt = document.createElement('option');
274
+ opt.value = item[region];
275
+ opt.textContent = item[region];
276
+ sel.appendChild(opt);
277
+ }
278
+ }
279
+
280
+ function makeGridCell(item: SizeData): HTMLDivElement {
281
+ const cell = document.createElement('div');
282
+ cell.className = 'size-cell';
283
+ cell.textContent = item.EU;
284
+ cell.addEventListener('click', () => {
285
+ const rs = document.getElementById('region-select') as HTMLSelectElement | null;
286
+ const ss = document.getElementById('size-select') as HTMLSelectElement | null;
287
+ if (rs) rs.value = 'EU';
288
+ if (ss) ss.value = item.EU;
289
+ calculate();
290
+ });
291
+ return cell;
292
+ }
293
+
294
+ function renderQuickGrid(data: SizeData[]): void {
295
+ const grid = getEl('quick-grid');
296
+ if (!grid) return;
297
+ grid.innerHTML = '';
298
+ for (const item of data) grid.appendChild(makeGridCell(item));
299
+ }
300
+
301
+ function makeTableRow(item: SizeData): HTMLTableRowElement {
302
+ const row = document.createElement('tr');
303
+ row.innerHTML = `<td class="eu-cell">${item.EU}</td><td>${item.US}</td><td>${item.UK}</td><td>${item.IT}</td>`;
304
+ return row;
305
+ }
306
+
307
+ function renderTable(data: SizeData[]): void {
308
+ const tbody = getEl('table-body');
309
+ if (!tbody) return;
310
+ tbody.innerHTML = '';
311
+ for (const item of data) tbody.appendChild(makeTableRow(item));
312
+ }
313
+
314
+ function updateResults(match: SizeData, region: string): void {
315
+ const others = (['EU', 'US', 'UK', 'IT'] as ClothingRegion[]).filter((r) => r !== region);
316
+ others.forEach((r, i) => {
317
+ const valEl = getEl(`val-${i + 1}`);
318
+ const labelEl = getEl(`label-${i + 1}`);
319
+ if (valEl) valEl.textContent = match[r];
320
+ if (labelEl) labelEl.textContent = regionLabels[r];
321
+ });
322
+ }
323
+
324
+ function updateActiveState(match: SizeData): void {
325
+ document.querySelectorAll('.size-cell').forEach((cell) => {
326
+ cell.classList.toggle('active', cell.textContent === match.EU);
327
+ });
328
+ document.querySelectorAll('.eu-cell').forEach((cell) => {
329
+ cell.classList.toggle('active', cell.textContent === match.EU);
330
+ });
331
+ }
332
+
333
+ function calculate(): void {
334
+ const regionSel = document.getElementById('region-select') as HTMLSelectElement | null;
335
+ const sizeSel = document.getElementById('size-select') as HTMLSelectElement | null;
336
+ const region = regionSel?.value ?? 'EU';
337
+ const val = sizeSel?.value;
338
+ if (!val) return;
339
+ const data = getCurrentData();
340
+ const match = data.find((item) => item[region] === val);
341
+ if (!match) return;
342
+ updateActiveState(match);
343
+ updateResults(match, region);
344
+ }
345
+
346
+ function updateControls(): void {
347
+ const regionSel = document.getElementById('region-select') as HTMLSelectElement | null;
348
+ const region = regionSel?.value ?? 'EU';
349
+ const data = getCurrentData();
350
+ populateSizeSelect(data, region);
351
+ renderQuickGrid(data);
352
+ renderTable(data);
353
+ calculate();
354
+ }
355
+
356
+ function getInputVal(id: string): number | null {
357
+ const el = document.getElementById(id) as HTMLInputElement | null;
358
+ const v = el?.value;
359
+ return v ? parseFloat(v) : null;
360
+ }
361
+
362
+ function getMeasurements(): { chest: number; waist: number; hip: number } | null {
363
+ const chest = getInputVal('chest-input');
364
+ const waist = getInputVal('waist-input');
365
+ const hip = getInputVal('hip-input');
366
+ if (chest === null || waist === null || hip === null) return null;
367
+ return { chest, waist, hip };
368
+ }
369
+
370
+ function showRecommendationBox(html: string, detailsHTML: string): void {
371
+ const box = getEl('recommendation-box');
372
+ const text = getEl('recommendation-text');
373
+ const details = getEl('size-details');
374
+ if (box) box.style.display = 'block';
375
+ if (text) text.innerHTML = html;
376
+ if (details) details.innerHTML = detailsHTML;
377
+ }
378
+
379
+ function buildDetailsHTML(sizeData: SizeData): string {
380
+ return (
381
+ '<div class="recommend-details">' +
382
+ `<div class="recommend-item"><span class="rec-label">EU</span><strong>${sizeData.EU}</strong></div>` +
383
+ `<div class="recommend-item"><span class="rec-label">US</span><strong>${sizeData.US}</strong></div>` +
384
+ `<div class="recommend-item"><span class="rec-label">UK</span><strong>${sizeData.UK}</strong></div>` +
385
+ '</div>'
386
+ );
387
+ }
388
+
389
+ function buildRecommendHTML(best: MeasurementRange, chest: number, waist: number, hip: number): string {
390
+ const measured = `${rawUI.measuredBy} ${rawUI.chestShort} ${chest}cm, ${rawUI.waistShort} ${waist}cm, ${rawUI.hipShort} ${hip}cm`;
391
+ return `<strong class="rec-size-label">${rawUI.recommendedSizePrefix} <em class="rec-size">${best.size}</em></strong><span class="rec-info">${measured}</span>`;
392
+ }
393
+
394
+ function recommendSize(): void {
395
+ const m = getMeasurements();
396
+ if (!m) {
397
+ showRecommendationBox(`<span class="rec-error">${rawUI.recommendationError}</span>`, '');
398
+ return;
399
+ }
400
+ const ranges: MeasurementRange[] = rawUI.measurementRanges[currentCategory] ?? [];
401
+ if (!ranges.length) {
402
+ showRecommendationBox(`<span class="rec-error">${rawUI.recommendationNoData}</span>`, '');
403
+ return;
404
+ }
405
+ const best = findBestSize(m.chest, m.waist, m.hip, ranges);
406
+ const sizeData = getCurrentData().find((item) => item.EU === best.size);
407
+ showRecommendationBox(
408
+ buildRecommendHTML(best, m.chest, m.waist, m.hip),
409
+ sizeData ? buildDetailsHTML(sizeData) : '',
410
+ );
411
+ }
412
+
413
+ function getFitTypeLabel(fit: string): string {
414
+ if (fit === 'small') return rawUI.fitNotifSmall;
415
+ if (fit === 'large') return rawUI.fitNotifLarge;
416
+ return rawUI.fitNotifRegular;
417
+ }
418
+
419
+ function getFitAdvice(fit: string): string {
420
+ if (fit === 'small') return rawUI.fitAdviceSmall;
421
+ if (fit === 'large') return rawUI.fitAdviceLarge;
422
+ return rawUI.fitAdviceRegular;
423
+ }
424
+
425
+ function showFitNotification(brand: string, fit: string): void {
426
+ const notif = getEl('fit-notification');
427
+ const infoText = getEl('fit-info-text');
428
+ if (!notif) return;
429
+ notif.innerHTML = `<strong>${brand}</strong>${rawUI.fitNotif1}<strong>${getFitTypeLabel(fit)}</strong>. ${rawUI.fitNotif2} <em>${getFitAdvice(fit)}</em> ${rawUI.fitNotif3}`;
430
+ notif.style.display = 'block';
431
+ if (infoText) infoText.textContent = `${brand} ${rawUI.fitSelectedSuffix}`;
432
+ }
433
+
434
+ function attachCategoryListeners(): void {
435
+ const catBtns = document.querySelectorAll('[data-category]');
436
+ catBtns.forEach((btn) => {
437
+ btn.addEventListener('click', () => {
438
+ catBtns.forEach((b) => b.classList.remove('active'));
439
+ btn.classList.add('active');
440
+ currentCategory = (btn as HTMLElement).dataset.category ?? 'shirts';
441
+ currentGender = 'men';
442
+ document.querySelectorAll('[data-gender]').forEach((b, i) => b.classList.toggle('active', i === 0));
443
+ updateControls();
444
+ });
445
+ });
446
+ }
447
+
448
+ function attachGenderListeners(): void {
449
+ document.querySelectorAll('[data-gender]').forEach((btn) => {
450
+ btn.addEventListener('click', () => {
451
+ document.querySelectorAll('[data-gender]').forEach((b) => b.classList.remove('active'));
452
+ btn.classList.add('active');
453
+ currentGender = (btn as HTMLElement).dataset.gender ?? 'men';
454
+ updateControls();
455
+ });
456
+ });
457
+ }
458
+
459
+ function attachTabListeners(): void {
460
+ document.querySelectorAll('[data-tab]').forEach((btn) => {
461
+ btn.addEventListener('click', () => {
462
+ const tab = (btn as HTMLElement).dataset.tab;
463
+ document.querySelectorAll('[data-tab]').forEach((b) => b.classList.remove('active'));
464
+ document.querySelectorAll('.tab-content').forEach((c) => c.classList.remove('active'));
465
+ btn.classList.add('active');
466
+ if (tab) getEl(`${tab}-tab`)?.classList.add('active');
467
+ });
468
+ });
469
+ }
470
+
471
+ function attachFitListeners(): void {
472
+ document.querySelectorAll('[data-brand]').forEach((card) => {
473
+ card.addEventListener('click', () => {
474
+ document.querySelectorAll('[data-brand]').forEach((c) => c.classList.remove('active'));
475
+ card.classList.add('active');
476
+ const brand = (card as HTMLElement).dataset.brand ?? '';
477
+ const fit = (card as HTMLElement).dataset.fit ?? 'regular';
478
+ showFitNotification(brand, fit);
479
+ });
480
+ });
481
+ }
482
+
483
+ function init(): void {
484
+ attachCategoryListeners();
485
+ attachGenderListeners();
486
+ attachTabListeners();
487
+ const regionSel = document.getElementById('region-select') as HTMLSelectElement | null;
488
+ const sizeSel = document.getElementById('size-select') as HTMLSelectElement | null;
489
+ regionSel?.addEventListener('change', updateControls);
490
+ sizeSel?.addEventListener('change', calculate);
491
+ getEl('recommend-btn')?.addEventListener('click', recommendSize);
492
+ attachFitListeners();
493
+ updateControls();
494
+ }
495
+
496
+ function startInit(): void {
497
+ if (document.readyState === 'loading') {
498
+ document.addEventListener('DOMContentLoaded', init);
499
+ } else {
500
+ setTimeout(init, 100);
501
+ }
502
+ document.addEventListener('astro:page-load', init);
503
+ }
504
+
505
+ startInit();
506
+ </script>
507
+
508
+ <style>
509
+ .clothing-converter {
510
+ max-width: 900px;
511
+ margin: 0 auto;
512
+ padding: 1rem 1rem 4rem;
513
+ }
514
+
515
+ .converter-card {
516
+ background: var(--bg-surface);
517
+ border: 1px solid var(--border-color);
518
+ border-radius: 2.5rem;
519
+ padding: 2.5rem;
520
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.05);
521
+ display: flex;
522
+ flex-direction: column;
523
+ gap: 2.5rem;
524
+ }
525
+
526
+ .theme-dark .converter-card {
527
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
528
+ }
529
+
530
+ .selector-section {
531
+ display: flex;
532
+ flex-direction: column;
533
+ gap: 1.5rem;
534
+ }
535
+
536
+ .selector-group {
537
+ display: flex;
538
+ flex-direction: column;
539
+ gap: 0.75rem;
540
+ }
541
+
542
+ .selector-label {
543
+ font-size: 0.75rem;
544
+ font-weight: 800;
545
+ color: var(--text-muted);
546
+ text-transform: uppercase;
547
+ letter-spacing: 0.1em;
548
+ }
549
+
550
+ .btn-group {
551
+ display: grid;
552
+ grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
553
+ gap: 0.75rem;
554
+ }
555
+
556
+ .cat-btn,
557
+ .gender-btn {
558
+ padding: 0.875rem;
559
+ border-radius: 1rem;
560
+ border: 2px solid transparent;
561
+ background: var(--bg-page);
562
+ cursor: pointer;
563
+ transition: all 0.3s;
564
+ display: flex;
565
+ flex-direction: column;
566
+ align-items: center;
567
+ gap: 0.5rem;
568
+ color: var(--text-muted);
569
+ font-weight: 600;
570
+ font-size: 0.875rem;
571
+ }
572
+
573
+ .cat-btn svg,
574
+ .gender-btn svg {
575
+ width: 1.5rem;
576
+ height: 1.5rem;
577
+ }
578
+
579
+ .cat-btn.active,
580
+ .gender-btn.active {
581
+ background: #3b82f6;
582
+ border-color: #1e40af;
583
+ color: white;
584
+ box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3);
585
+ transform: translateY(-2px);
586
+ }
587
+
588
+ .cat-btn:hover,
589
+ .gender-btn:hover {
590
+ border-color: #3b82f6;
591
+ }
592
+
593
+ .tabs {
594
+ display: flex;
595
+ gap: 0.5rem;
596
+ border-bottom: 2px solid var(--border-color);
597
+ }
598
+
599
+ .tab-btn {
600
+ padding: 0.875rem 1.5rem;
601
+ border: none;
602
+ background: transparent;
603
+ cursor: pointer;
604
+ color: var(--text-muted);
605
+ font-weight: 600;
606
+ border-bottom: 3px solid transparent;
607
+ margin-bottom: -2px;
608
+ display: flex;
609
+ gap: 0.5rem;
610
+ align-items: center;
611
+ transition: color 0.2s;
612
+ }
613
+
614
+ .tab-btn svg {
615
+ width: 1.1rem;
616
+ height: 1.1rem;
617
+ }
618
+
619
+ .tab-btn.active {
620
+ color: #3b82f6;
621
+ border-bottom-color: #3b82f6;
622
+ }
623
+
624
+ .tab-content {
625
+ display: none;
626
+ }
627
+
628
+ .tab-content.active {
629
+ display: flex;
630
+ flex-direction: column;
631
+ gap: 2rem;
632
+ animation: tab-fade-in 0.3s ease;
633
+ }
634
+
635
+ @keyframes tab-fade-in {
636
+ from {
637
+ opacity: 0;
638
+ transform: translateY(8px);
639
+ }
640
+ to {
641
+ opacity: 1;
642
+ transform: translateY(0);
643
+ }
644
+ }
645
+
646
+ .control-row {
647
+ display: grid;
648
+ grid-template-columns: 1fr 1fr;
649
+ gap: 1.5rem;
650
+ }
651
+
652
+ .field-group {
653
+ display: flex;
654
+ flex-direction: column;
655
+ gap: 0.5rem;
656
+ }
657
+
658
+ .field-label {
659
+ font-size: 0.75rem;
660
+ font-weight: 800;
661
+ color: var(--text-muted);
662
+ text-transform: uppercase;
663
+ letter-spacing: 0.1em;
664
+ }
665
+
666
+ .select-wrap {
667
+ position: relative;
668
+ }
669
+
670
+ .select-wrap::after {
671
+ content: '▾';
672
+ position: absolute;
673
+ right: 1.25rem;
674
+ top: 50%;
675
+ transform: translateY(-50%);
676
+ pointer-events: none;
677
+ color: var(--text-muted);
678
+ }
679
+
680
+ .region-select,
681
+ .size-select {
682
+ appearance: none;
683
+ width: 100%;
684
+ background: var(--bg-page);
685
+ border: 1.5px solid var(--border-color);
686
+ border-radius: 1rem;
687
+ padding: 0.875rem 2.75rem 0.875rem 1.25rem;
688
+ font-size: 1rem;
689
+ font-weight: 600;
690
+ color: var(--text-main);
691
+ outline: none;
692
+ cursor: pointer;
693
+ transition: border-color 0.2s;
694
+ }
695
+
696
+ .region-select:focus,
697
+ .size-select:focus {
698
+ border-color: #3b82f6;
699
+ }
700
+
701
+ .results-grid {
702
+ display: grid;
703
+ grid-template-columns: repeat(3, 1fr);
704
+ gap: 1rem;
705
+ }
706
+
707
+ .result-card {
708
+ background: var(--bg-page);
709
+ border: 1px solid var(--border-color);
710
+ border-radius: 1.25rem;
711
+ padding: 1.5rem;
712
+ display: flex;
713
+ flex-direction: column;
714
+ align-items: center;
715
+ gap: 0.5rem;
716
+ transition: all 0.3s;
717
+ }
718
+
719
+ .result-card:hover {
720
+ transform: translateY(-2px);
721
+ border-color: #3b82f6;
722
+ }
723
+
724
+ .result-label {
725
+ font-size: 0.7rem;
726
+ font-weight: 800;
727
+ color: var(--text-muted);
728
+ text-transform: uppercase;
729
+ letter-spacing: 0.05em;
730
+ }
731
+
732
+ .result-value {
733
+ font-size: 2.5rem;
734
+ font-weight: 900;
735
+ color: #3b82f6;
736
+ line-height: 1;
737
+ }
738
+
739
+ .quick-grid-section {
740
+ border-top: 1px solid var(--border-color);
741
+ padding-top: 1.5rem;
742
+ }
743
+
744
+ .section-title {
745
+ font-size: 0.75rem;
746
+ font-weight: 800;
747
+ color: var(--text-muted);
748
+ text-transform: uppercase;
749
+ letter-spacing: 0.05em;
750
+ margin: 0 0 1rem;
751
+ }
752
+
753
+ .size-grid {
754
+ display: grid;
755
+ grid-template-columns: repeat(auto-fill, minmax(65px, 1fr));
756
+ gap: 0.75rem;
757
+ }
758
+
759
+ :global(.size-cell) {
760
+ aspect-ratio: 1;
761
+ display: flex;
762
+ align-items: center;
763
+ justify-content: center;
764
+ border-radius: 0.75rem;
765
+ background: var(--bg-surface);
766
+ border: 1.5px solid var(--border-color);
767
+ font-size: 0.875rem;
768
+ font-weight: 700;
769
+ color: var(--text-muted);
770
+ cursor: pointer;
771
+ transition: all 0.2s;
772
+ }
773
+
774
+ :global(.size-cell:hover) {
775
+ border-color: #3b82f6;
776
+ color: #3b82f6;
777
+ transform: scale(1.05);
778
+ }
779
+
780
+ :global(.size-cell.active) {
781
+ background: #3b82f6;
782
+ border-color: #1e40af;
783
+ color: white;
784
+ box-shadow: 0 5px 15px rgba(59, 130, 246, 0.35);
785
+ transform: scale(1.05);
786
+ }
787
+
788
+ .measurements-layout {
789
+ display: grid;
790
+ grid-template-columns: 1fr 1fr;
791
+ gap: 2rem;
792
+ }
793
+
794
+ .measurements-inputs {
795
+ display: flex;
796
+ flex-direction: column;
797
+ gap: 1.25rem;
798
+ }
799
+
800
+ .measurements-section-title {
801
+ font-size: 1.1rem;
802
+ font-weight: 800;
803
+ color: var(--text-main);
804
+ margin: 0;
805
+ }
806
+
807
+ .measure-field {
808
+ display: flex;
809
+ flex-direction: column;
810
+ gap: 0.5rem;
811
+ }
812
+
813
+ .measure-label {
814
+ font-size: 0.75rem;
815
+ font-weight: 700;
816
+ color: var(--text-muted);
817
+ text-transform: uppercase;
818
+ letter-spacing: 0.05em;
819
+ }
820
+
821
+ .measure-input {
822
+ width: 100%;
823
+ background: var(--bg-page);
824
+ border: 1.5px solid var(--border-color);
825
+ border-radius: 1rem;
826
+ padding: 0.875rem 1.25rem;
827
+ font-size: 1rem;
828
+ font-weight: 600;
829
+ color: var(--text-main);
830
+ outline: none;
831
+ transition: border-color 0.2s;
832
+ }
833
+
834
+ .measure-input:focus {
835
+ border-color: #3b82f6;
836
+ }
837
+
838
+ .btn-recommend {
839
+ padding: 1rem;
840
+ background: #10b981;
841
+ color: white;
842
+ border: none;
843
+ border-radius: 1rem;
844
+ font-weight: 700;
845
+ cursor: pointer;
846
+ transition: all 0.3s;
847
+ display: flex;
848
+ align-items: center;
849
+ justify-content: center;
850
+ gap: 0.75rem;
851
+ font-size: 1rem;
852
+ }
853
+
854
+ .btn-recommend:hover {
855
+ transform: translateY(-2px);
856
+ box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3);
857
+ }
858
+
859
+ .btn-recommend svg {
860
+ width: 1.25rem;
861
+ height: 1.25rem;
862
+ }
863
+
864
+ .tips-card {
865
+ background: rgba(59, 130, 246, 0.05);
866
+ border: 1px solid rgba(59, 130, 246, 0.2);
867
+ border-radius: 1.25rem;
868
+ padding: 1.5rem;
869
+ color: #1e40af;
870
+ }
871
+
872
+ .theme-dark .tips-card {
873
+ background: rgba(59, 130, 246, 0.08);
874
+ color: #93c5fd;
875
+ }
876
+
877
+ .tips-title {
878
+ display: flex;
879
+ align-items: center;
880
+ gap: 0.5rem;
881
+ font-size: 0.9rem;
882
+ font-weight: 700;
883
+ margin: 0 0 1rem;
884
+ }
885
+
886
+ .tips-title svg {
887
+ width: 1.1rem;
888
+ height: 1.1rem;
889
+ flex-shrink: 0;
890
+ }
891
+
892
+ .tips-grid {
893
+ display: grid;
894
+ grid-template-columns: 1fr 1fr;
895
+ gap: 0.75rem;
896
+ }
897
+
898
+ .tip-item {
899
+ display: flex;
900
+ align-items: center;
901
+ gap: 0.75rem;
902
+ padding: 0.75rem;
903
+ background: rgba(59, 130, 246, 0.08);
904
+ border-radius: 0.75rem;
905
+ font-size: 0.8rem;
906
+ font-weight: 600;
907
+ }
908
+
909
+ .theme-dark .tip-item {
910
+ background: rgba(59, 130, 246, 0.12);
911
+ }
912
+
913
+ .tip-item svg {
914
+ width: 1rem;
915
+ height: 1rem;
916
+ flex-shrink: 0;
917
+ color: #3b82f6;
918
+ }
919
+
920
+ .recommendation-box {
921
+ display: none;
922
+ background: #ecfdf5;
923
+ border: 2px solid #10b981;
924
+ border-radius: 1.25rem;
925
+ padding: 1.5rem;
926
+ color: #047857;
927
+ animation: slide-down 0.3s ease;
928
+ }
929
+
930
+ .theme-dark .recommendation-box {
931
+ background: rgba(6, 78, 59, 0.3);
932
+ color: #6ee7b7;
933
+ }
934
+
935
+ @keyframes slide-down {
936
+ from {
937
+ opacity: 0;
938
+ transform: translateY(-8px);
939
+ }
940
+ to {
941
+ opacity: 1;
942
+ transform: translateY(0);
943
+ }
944
+ }
945
+
946
+ .recommend-header {
947
+ display: flex;
948
+ align-items: center;
949
+ gap: 0.75rem;
950
+ margin-bottom: 1rem;
951
+ }
952
+
953
+ .recommend-header svg {
954
+ width: 1.5rem;
955
+ height: 1.5rem;
956
+ flex-shrink: 0;
957
+ }
958
+
959
+ .recommend-header h4 {
960
+ margin: 0;
961
+ font-size: 1rem;
962
+ font-weight: 700;
963
+ }
964
+
965
+ :global(.rec-size-label) {
966
+ display: block;
967
+ font-size: 1rem;
968
+ margin-bottom: 0.5rem;
969
+ }
970
+
971
+ :global(.rec-size) {
972
+ color: #10b981;
973
+ font-style: normal;
974
+ font-size: 1.2em;
975
+ }
976
+
977
+ :global(.rec-info) {
978
+ display: block;
979
+ font-size: 0.875rem;
980
+ opacity: 0.85;
981
+ }
982
+
983
+ :global(.rec-error) {
984
+ color: #dc2626;
985
+ }
986
+
987
+ :global(.recommend-details) {
988
+ display: grid;
989
+ grid-template-columns: repeat(3, 1fr);
990
+ gap: 0.75rem;
991
+ margin-top: 1rem;
992
+ padding-top: 1rem;
993
+ border-top: 1px solid rgba(16, 185, 129, 0.3);
994
+ }
995
+
996
+ :global(.recommend-item) {
997
+ text-align: center;
998
+ display: flex;
999
+ flex-direction: column;
1000
+ gap: 0.25rem;
1001
+ }
1002
+
1003
+ :global(.rec-label) {
1004
+ font-size: 0.7rem;
1005
+ text-transform: uppercase;
1006
+ opacity: 0.7;
1007
+ }
1008
+
1009
+ :global(.recommend-item strong) {
1010
+ font-weight: 700;
1011
+ color: #10b981;
1012
+ }
1013
+
1014
+ .fit-guide {
1015
+ border-top: 1px solid var(--border-color);
1016
+ padding-top: 1.5rem;
1017
+ display: flex;
1018
+ flex-direction: column;
1019
+ gap: 1.25rem;
1020
+ }
1021
+
1022
+ .fit-grid {
1023
+ display: grid;
1024
+ grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
1025
+ gap: 1rem;
1026
+ }
1027
+
1028
+ .fit-card {
1029
+ background: var(--bg-page);
1030
+ border: 2px solid var(--border-color);
1031
+ border-radius: 1rem;
1032
+ padding: 1.25rem;
1033
+ display: flex;
1034
+ flex-direction: column;
1035
+ align-items: center;
1036
+ gap: 0.5rem;
1037
+ cursor: pointer;
1038
+ transition: all 0.3s;
1039
+ color: inherit;
1040
+ text-align: center;
1041
+ }
1042
+
1043
+ .fit-card:hover {
1044
+ border-color: #3b82f6;
1045
+ background: rgba(59, 130, 246, 0.04);
1046
+ transform: translateY(-2px);
1047
+ }
1048
+
1049
+ .fit-card.active {
1050
+ border-color: #0284c7;
1051
+ background: rgba(2, 132, 199, 0.08);
1052
+ box-shadow: 0 4px 12px rgba(2, 132, 199, 0.15);
1053
+ }
1054
+
1055
+ .theme-dark .fit-card.active {
1056
+ background: rgba(2, 132, 199, 0.12);
1057
+ }
1058
+
1059
+ .fit-brand {
1060
+ font-weight: 700;
1061
+ color: var(--text-main);
1062
+ font-size: 0.95rem;
1063
+ }
1064
+
1065
+ .fit-size-label {
1066
+ font-size: 0.75rem;
1067
+ color: var(--text-muted);
1068
+ font-weight: 600;
1069
+ }
1070
+
1071
+ .fit-notification {
1072
+ display: none;
1073
+ padding: 1.25rem;
1074
+ background: rgba(3, 105, 161, 0.06);
1075
+ border: 2px solid #0284c7;
1076
+ border-radius: 1rem;
1077
+ color: #0369a1;
1078
+ font-size: 0.875rem;
1079
+ line-height: 1.5;
1080
+ }
1081
+
1082
+ .theme-dark .fit-notification {
1083
+ background: rgba(3, 105, 161, 0.1);
1084
+ color: #7dd3fc;
1085
+ }
1086
+
1087
+ .fit-info {
1088
+ display: flex;
1089
+ align-items: center;
1090
+ gap: 0.5rem;
1091
+ font-size: 0.85rem;
1092
+ color: var(--text-muted);
1093
+ margin: 0;
1094
+ }
1095
+
1096
+ .fit-info svg {
1097
+ width: 1rem;
1098
+ height: 1rem;
1099
+ flex-shrink: 0;
1100
+ }
1101
+
1102
+ .table-section {
1103
+ border-top: 1px solid var(--border-color);
1104
+ padding-top: 1.5rem;
1105
+ }
1106
+
1107
+ .table-wrap {
1108
+ overflow-x: auto;
1109
+ border-radius: 1.25rem;
1110
+ border: 1px solid var(--border-color);
1111
+ margin-top: 1rem;
1112
+ }
1113
+
1114
+ .conv-table {
1115
+ width: 100%;
1116
+ border-collapse: collapse;
1117
+ background: var(--bg-surface);
1118
+ }
1119
+
1120
+ .conv-table thead {
1121
+ background: var(--bg-page);
1122
+ border-bottom: 2px solid var(--border-color);
1123
+ }
1124
+
1125
+ .conv-table th {
1126
+ padding: 1rem 1.25rem;
1127
+ text-align: left;
1128
+ font-weight: 700;
1129
+ color: var(--text-main);
1130
+ font-size: 0.875rem;
1131
+ text-transform: uppercase;
1132
+ letter-spacing: 0.05em;
1133
+ }
1134
+
1135
+ .conv-table td {
1136
+ padding: 1rem 1.25rem;
1137
+ border-bottom: 1px solid var(--border-color);
1138
+ color: var(--text-muted);
1139
+ font-weight: 500;
1140
+ }
1141
+
1142
+ .conv-table tbody tr:hover td {
1143
+ background: var(--bg-page);
1144
+ }
1145
+
1146
+ :global(.eu-cell) {
1147
+ font-weight: 700;
1148
+ color: #3b82f6;
1149
+ }
1150
+
1151
+ :global(.eu-cell.active) {
1152
+ background: rgba(59, 130, 246, 0.1);
1153
+ }
1154
+
1155
+ .info-banner {
1156
+ display: flex;
1157
+ align-items: flex-start;
1158
+ gap: 1rem;
1159
+ padding: 1.25rem;
1160
+ background: rgba(59, 130, 246, 0.05);
1161
+ border: 1px solid rgba(59, 130, 246, 0.2);
1162
+ border-radius: 1.25rem;
1163
+ color: #1e40af;
1164
+ font-size: 0.875rem;
1165
+ line-height: 1.5;
1166
+ }
1167
+
1168
+ .theme-dark .info-banner {
1169
+ background: rgba(59, 130, 246, 0.08);
1170
+ color: #bfdbfe;
1171
+ }
1172
+
1173
+ .info-banner svg {
1174
+ width: 1.25rem;
1175
+ height: 1.25rem;
1176
+ flex-shrink: 0;
1177
+ margin-top: 0.125rem;
1178
+ }
1179
+
1180
+ .info-banner p {
1181
+ margin: 0;
1182
+ }
1183
+
1184
+ .info-banner strong {
1185
+ display: block;
1186
+ margin-bottom: 0.25rem;
1187
+ }
1188
+
1189
+ @media (max-width: 640px) {
1190
+ .converter-card {
1191
+ padding: 1.5rem;
1192
+ gap: 2rem;
1193
+ border-radius: 1.5rem;
1194
+ }
1195
+
1196
+ .btn-group {
1197
+ grid-template-columns: repeat(2, 1fr);
1198
+ }
1199
+
1200
+ .control-row {
1201
+ grid-template-columns: 1fr;
1202
+ }
1203
+
1204
+ .results-grid {
1205
+ grid-template-columns: 1fr;
1206
+ }
1207
+
1208
+ .measurements-layout {
1209
+ grid-template-columns: 1fr;
1210
+ }
1211
+
1212
+ .fit-grid {
1213
+ grid-template-columns: repeat(3, 1fr);
1214
+ }
1215
+ }
1216
+
1217
+ @media (max-width: 400px) {
1218
+ .btn-group {
1219
+ grid-template-columns: 1fr;
1220
+ }
1221
+
1222
+ .fit-grid {
1223
+ grid-template-columns: repeat(2, 1fr);
1224
+ }
1225
+ }
1226
+ </style>