@jjlmoya/utils-diy 1.15.0 → 1.17.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 (112) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/fr.ts +5 -5
  3. package/src/entries.ts +3 -1
  4. package/src/index.ts +2 -0
  5. package/src/layouts/PreviewLayout.astro +1 -0
  6. package/src/tests/diacritics_density.test.ts +118 -0
  7. package/src/tests/inverted_punctuation.test.ts +84 -0
  8. package/src/tests/locale_completeness.test.ts +2 -2
  9. package/src/tests/no_en_dash.test.ts +70 -0
  10. package/src/tests/script_density.test.ts +94 -0
  11. package/src/tests/tool_validation.test.ts +2 -2
  12. package/src/tool/balusterCalculator/i18n/de.ts +7 -7
  13. package/src/tool/balusterCalculator/i18n/en.ts +4 -4
  14. package/src/tool/balusterCalculator/i18n/es.ts +2 -2
  15. package/src/tool/balusterCalculator/i18n/fr.ts +3 -3
  16. package/src/tool/balusterCalculator/i18n/it.ts +16 -16
  17. package/src/tool/balusterCalculator/i18n/ru.ts +14 -14
  18. package/src/tool/balusterCalculator/i18n/tr.ts +5 -5
  19. package/src/tool/clayCalculator/i18n/fr.ts +3 -3
  20. package/src/tool/clayCalculator/i18n/ru.ts +3 -3
  21. package/src/tool/concreteCalculator/i18n/en.ts +4 -4
  22. package/src/tool/concreteCalculator/i18n/fr.ts +2 -2
  23. package/src/tool/concreteCalculator/i18n/ko.ts +2 -2
  24. package/src/tool/concreteCalculator/i18n/ru.ts +3 -3
  25. package/src/tool/cutOptimizer/i18n/en.ts +4 -4
  26. package/src/tool/cutOptimizer/i18n/fr.ts +2 -2
  27. package/src/tool/cutOptimizer/i18n/ru.ts +2 -2
  28. package/src/tool/drillCalculator/bibliography.ts +2 -2
  29. package/src/tool/drillCalculator/i18n/ru.ts +5 -5
  30. package/src/tool/drillCalculator/i18n/zh.ts +2 -2
  31. package/src/tool/drillSharpener/i18n/ru.ts +3 -3
  32. package/src/tool/epoxyCalculator/epoxy-resin-calculator.css +81 -1
  33. package/src/tool/epoxyCalculator/i18n/de.ts +2 -2
  34. package/src/tool/epoxyCalculator/i18n/en.ts +2 -2
  35. package/src/tool/epoxyCalculator/i18n/es.ts +2 -2
  36. package/src/tool/epoxyCalculator/i18n/fr.ts +3 -3
  37. package/src/tool/epoxyCalculator/i18n/id.ts +2 -2
  38. package/src/tool/epoxyCalculator/i18n/it.ts +2 -2
  39. package/src/tool/epoxyCalculator/i18n/ja.ts +2 -2
  40. package/src/tool/epoxyCalculator/i18n/ko.ts +2 -2
  41. package/src/tool/epoxyCalculator/i18n/nl.ts +2 -2
  42. package/src/tool/epoxyCalculator/i18n/pl.ts +2 -2
  43. package/src/tool/epoxyCalculator/i18n/pt.ts +2 -2
  44. package/src/tool/epoxyCalculator/i18n/ru.ts +5 -5
  45. package/src/tool/epoxyCalculator/i18n/sv.ts +2 -2
  46. package/src/tool/epoxyCalculator/i18n/tr.ts +2 -2
  47. package/src/tool/epoxyCalculator/i18n/zh.ts +4 -4
  48. package/src/tool/furnitureFit/i18n/de.ts +2 -2
  49. package/src/tool/furnitureFit/i18n/en.ts +4 -4
  50. package/src/tool/furnitureFit/i18n/es.ts +2 -2
  51. package/src/tool/furnitureFit/i18n/fr.ts +3 -3
  52. package/src/tool/furnitureFit/i18n/id.ts +2 -2
  53. package/src/tool/furnitureFit/i18n/it.ts +2 -2
  54. package/src/tool/furnitureFit/i18n/ja.ts +2 -2
  55. package/src/tool/furnitureFit/i18n/ko.ts +2 -2
  56. package/src/tool/furnitureFit/i18n/nl.ts +2 -2
  57. package/src/tool/furnitureFit/i18n/pl.ts +2 -2
  58. package/src/tool/furnitureFit/i18n/pt.ts +2 -2
  59. package/src/tool/furnitureFit/i18n/ru.ts +6 -6
  60. package/src/tool/furnitureFit/i18n/sv.ts +2 -2
  61. package/src/tool/furnitureFit/i18n/tr.ts +2 -2
  62. package/src/tool/furnitureFit/i18n/zh.ts +2 -2
  63. package/src/tool/mortarCalculator/i18n/en.ts +3 -3
  64. package/src/tool/mortarCalculator/i18n/ru.ts +6 -6
  65. package/src/tool/mortarCalculator/i18n/zh.ts +3 -3
  66. package/src/tool/passepartoutCalculator/i18n/en.ts +4 -4
  67. package/src/tool/passepartoutCalculator/i18n/ru.ts +7 -7
  68. package/src/tool/passepartoutCalculator/i18n/zh.ts +6 -6
  69. package/src/tool/passepartoutCalculator/passepartout-calculator.css +99 -3
  70. package/src/tool/pythagoreanRightAngleCalculator/i18n/fr.ts +3 -3
  71. package/src/tool/pythagoreanRightAngleCalculator/i18n/pl.ts +2 -2
  72. package/src/tool/pythagoreanRightAngleCalculator/i18n/ru.ts +5 -5
  73. package/src/tool/stairCalculator/i18n/ru.ts +6 -6
  74. package/src/tool/stairCalculator/stair-calculator.css +76 -5
  75. package/src/tool/thermalExpansionCalculator/bibliography.ts +4 -4
  76. package/src/tool/thermalExpansionCalculator/i18n/en.ts +2 -2
  77. package/src/tool/thermalExpansionCalculator/i18n/fr.ts +3 -3
  78. package/src/tool/thermalExpansionCalculator/i18n/ru.ts +4 -4
  79. package/src/tool/twoStrokeMixtureCalculator/bibliography.astro +6 -0
  80. package/src/tool/twoStrokeMixtureCalculator/bibliography.ts +12 -0
  81. package/src/tool/twoStrokeMixtureCalculator/component.astro +266 -0
  82. package/src/tool/twoStrokeMixtureCalculator/entry.ts +24 -0
  83. package/src/tool/twoStrokeMixtureCalculator/i18n/de.ts +194 -0
  84. package/src/tool/twoStrokeMixtureCalculator/i18n/en.ts +194 -0
  85. package/src/tool/twoStrokeMixtureCalculator/i18n/es.ts +194 -0
  86. package/src/tool/twoStrokeMixtureCalculator/i18n/fr.ts +194 -0
  87. package/src/tool/twoStrokeMixtureCalculator/i18n/id.ts +194 -0
  88. package/src/tool/twoStrokeMixtureCalculator/i18n/it.ts +194 -0
  89. package/src/tool/twoStrokeMixtureCalculator/i18n/ja.ts +194 -0
  90. package/src/tool/twoStrokeMixtureCalculator/i18n/ko.ts +194 -0
  91. package/src/tool/twoStrokeMixtureCalculator/i18n/nl.ts +194 -0
  92. package/src/tool/twoStrokeMixtureCalculator/i18n/pl.ts +194 -0
  93. package/src/tool/twoStrokeMixtureCalculator/i18n/pt.ts +194 -0
  94. package/src/tool/twoStrokeMixtureCalculator/i18n/ru.ts +194 -0
  95. package/src/tool/twoStrokeMixtureCalculator/i18n/sv.ts +194 -0
  96. package/src/tool/twoStrokeMixtureCalculator/i18n/tr.ts +194 -0
  97. package/src/tool/twoStrokeMixtureCalculator/i18n/zh.ts +194 -0
  98. package/src/tool/twoStrokeMixtureCalculator/index.ts +11 -0
  99. package/src/tool/twoStrokeMixtureCalculator/logic.ts +92 -0
  100. package/src/tool/twoStrokeMixtureCalculator/seo.astro +15 -0
  101. package/src/tool/twoStrokeMixtureCalculator/two-stroke-fuel-mixture-calculator.css +405 -0
  102. package/src/tool/twoStrokeMixtureCalculator/ui.ts +81 -0
  103. package/src/tool/voltageDropCalculator/i18n/en.ts +2 -2
  104. package/src/tool/voltageDropCalculator/i18n/es.ts +4 -4
  105. package/src/tool/voltageDropCalculator/i18n/ru.ts +4 -4
  106. package/src/tool/workshopFractionConverter/bibliography.ts +2 -2
  107. package/src/tool/workshopFractionConverter/i18n/de.ts +2 -2
  108. package/src/tool/workshopFractionConverter/i18n/es.ts +2 -2
  109. package/src/tool/workshopFractionConverter/i18n/fr.ts +2 -2
  110. package/src/tool/workshopFractionConverter/i18n/ru.ts +9 -9
  111. package/src/tool/workshopFractionConverter/i18n/zh.ts +6 -6
  112. package/src/tools.ts +2 -1
@@ -1,4 +1,4 @@
1
- import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
2
  import type { ToolLocaleContent } from '../../../types';
3
3
  import type { StairCalculatorUI } from '../ui';
4
4
  import { bibliography } from '../bibliography';
@@ -14,7 +14,7 @@ const faqData = [
14
14
  },
15
15
  {
16
16
  question: 'Какова максимальная высота ступени?',
17
- answer: 'В жилых домах рекомендуется не превышать 18–19 см. Для общественных мест максимум по нормам обычно составляет 17,5 см. Более высокие ступени сильнее утомляют и могут быть опасны.',
17
+ answer: 'В жилых домах рекомендуется не превышать 18-19 см. Для общественных мест максимум по нормам обычно составляет 17,5 см. Более высокие ступени сильнее утомляют и могут быть опасны.',
18
18
  },
19
19
  {
20
20
  question: 'Как рассчитать, если мало места?',
@@ -33,7 +33,7 @@ const faqData = [
33
33
  const howToData = [
34
34
  { name: 'Измерить перепад высот', text: 'Измерьте общий перепад (H) от чистового пола нижнего уровня до чистового пола верхнего уровня. Это базовый параметр для всех расчетов.' },
35
35
  { name: 'Ввести доступную длину', text: 'Опционально: если у вас есть ограничения по месту, введите доступную горизонтальную длину. Используйте 0 для свободного расчета.' },
36
- { name: 'Настроить количество ступеней', text: 'Калькулятор предложит начальное количество, но вы можете его изменить. Следите за индикатором Блонделя: он должен быть зеленым (62–64 см).' },
36
+ { name: 'Настроить количество ступеней', text: 'Калькулятор предложит начальное количество, но вы можете его изменить. Следите за индикатором Блонделя: он должен быть зеленым (62-64 см).' },
37
37
  { name: 'Определить параметры объекта', text: 'Укажите толщину плиты, отделку, марку бетона и ширину лестницы. Эти данные влияют на итоговый расчет материалов.' },
38
38
  ];
39
39
 
@@ -82,17 +82,17 @@ export const content: ToolLocaleContent<StairCalculatorUI> = {
82
82
  schemas: [faqSchema, howToSchema, appSchema],
83
83
  seo: [
84
84
  { type: 'title', text: 'Геометрическое проектирование лестниц: закон Блонделя', level: 2 },
85
- { type: 'paragraph', html: 'Комфорт лестницы зависит от длины человеческого шага. При подъеме или спуске тело ожидает предсказуемого ритма. Закон Блонделя гласит, что 2 высоты подступенка + 1 ширина проступи = 62–64 см — это стандарт, гарантирующий максимальный комфорт и безопасность.' },
85
+ { type: 'paragraph', html: 'Комфорт лестницы зависит от длины человеческого шага. При подъеме или спуске тело ожидает предсказуемого ритма. Закон Блонделя гласит, что 2 высоты подступенка + 1 ширина проступи = 62-64 см - это стандарт, гарантирующий максимальный комфорт и безопасность.' },
86
86
  { type: 'title', text: 'Учет конструкций и материалов', level: 3 },
87
87
  { type: 'paragraph', html: 'Для профессионального исполнения жизненно важно учитывать бетонную плиту основания. Игнорирование этого объема приведет к нехватке материала при заказе. Калькулятор автоматически включает этот расчет.' },
88
88
  { type: 'title', text: 'Разметка и накопленная ошибка', level: 3 },
89
- { type: 'paragraph', html: 'В строительстве главный враг — миллиметровая ошибка. Если вы измеряете каждую ступень отдельно, сумма маленьких погрешностей может привести к тому, что последняя ступень окажется на 5 см выше или ниже первой.' },
89
+ { type: 'paragraph', html: 'В строительстве главный враг - миллиметровая ошибка. Если вы измеряете каждую ступень отдельно, сумма маленьких погрешностей может привести к тому, что последняя ступень окажется на 5 см выше или ниже первой.' },
90
90
  ],
91
91
  ui: {
92
92
  labelGeometry: 'Геометрия и габариты',
93
93
  labelTotalHeight: 'Общая высота (H)',
94
94
  labelAvailableLength: 'Доступная длина (L)',
95
- labelAvailableLengthHint: '0 — без ограничений.',
95
+ labelAvailableLengthHint: '0 - без ограничений.',
96
96
  labelNumSteps: 'Кол-во ступеней',
97
97
  labelOptimize: 'Оптимизация (18см)',
98
98
  labelWork: 'Строительство и отделка',
@@ -72,6 +72,7 @@
72
72
  }
73
73
 
74
74
  .sc-stat-box {
75
+ min-width: 0;
75
76
  background: rgba(255, 255, 255, 0.5);
76
77
  padding: 0.75rem 0.5rem;
77
78
  border-radius: 1.25rem;
@@ -93,9 +94,12 @@
93
94
  }
94
95
 
95
96
  .sc-stat-value {
97
+ display: inline-block;
98
+ max-width: 100%;
96
99
  font-size: 1.5rem;
97
100
  font-weight: 800;
98
101
  color: var(--sc-neutral);
102
+ overflow-wrap: anywhere;
99
103
  }
100
104
 
101
105
  .sc-stat-unit {
@@ -317,7 +321,7 @@
317
321
 
318
322
  .sc-table-wrapper {
319
323
  max-height: 220px;
320
- overflow-y: auto;
324
+ overflow: auto;
321
325
  }
322
326
 
323
327
  .sc-table-wrapper table {
@@ -370,6 +374,7 @@
370
374
 
371
375
  .sc-material-row {
372
376
  display: flex;
377
+ gap: 0.75rem;
373
378
  justify-content: space-between;
374
379
  align-items: baseline;
375
380
  padding-bottom: 0.4rem;
@@ -384,6 +389,7 @@
384
389
  .sc-material-row strong {
385
390
  font-size: 1.15rem;
386
391
  color: var(--sc-primary);
392
+ overflow-wrap: anywhere;
387
393
  }
388
394
 
389
395
  .sc-actions-row {
@@ -459,20 +465,85 @@
459
465
  }
460
466
 
461
467
  @media (max-width: 480px) {
468
+ .sc-root {
469
+ padding: 0 0.5rem;
470
+ }
471
+
472
+ .sc-card {
473
+ border-radius: 1rem;
474
+ }
475
+
476
+ .sc-sidebar,
477
+ .sc-main {
478
+ padding: 1rem;
479
+ }
480
+
481
+ .sc-input-grid {
482
+ grid-template-columns: 1fr;
483
+ }
484
+
485
+ .sc-step-btn {
486
+ width: 38px;
487
+ height: 38px;
488
+ font-size: 1.35rem;
489
+ }
490
+
491
+ .sc-stepper input {
492
+ height: 38px;
493
+ font-size: 1rem;
494
+ }
495
+
496
+ .sc-select {
497
+ font-size: 0.9rem;
498
+ padding: 0.7rem 2.5rem 0.7rem 0.85rem;
499
+ }
500
+
462
501
  .sc-results-header {
463
- gap: 0.4rem;
502
+ grid-template-columns: 1fr;
503
+ gap: 0.5rem;
464
504
  }
465
505
 
466
506
  .sc-stat-box {
467
- padding: 0.4rem;
468
- border-radius: 1rem;
507
+ display: grid;
508
+ grid-template-columns: 1fr auto auto;
509
+ gap: 0.35rem;
510
+ align-items: baseline;
511
+ padding: 0.65rem 0.75rem;
512
+ text-align: left;
513
+ border-radius: 0.875rem;
469
514
  }
470
515
 
471
516
  .sc-stat-value {
472
517
  font-size: 1.1rem;
473
518
  }
474
519
 
520
+ .sc-stat-unit {
521
+ font-size: 0.7rem;
522
+ }
523
+
524
+ .sc-visualizer-container {
525
+ min-height: 210px;
526
+ padding: 0.5rem;
527
+ border-radius: 1rem;
528
+ }
529
+
530
+ #sc-stair-svg {
531
+ min-width: 360px;
532
+ }
533
+
534
+ .sc-table-card,
535
+ .sc-materials-card {
536
+ padding: 1rem;
537
+ border-radius: 1rem;
538
+ }
539
+
540
+ .sc-material-row {
541
+ align-items: flex-start;
542
+ flex-direction: column;
543
+ gap: 0.25rem;
544
+ }
545
+
475
546
  .sc-material-row strong {
476
547
  font-size: 1rem;
477
548
  }
478
- }
549
+ }
@@ -1,16 +1,16 @@
1
- import type { BibliographyEntry } from '../../types';
1
+ import type { BibliographyEntry } from '../../types';
2
2
 
3
3
  export const bibliography: BibliographyEntry[] = [
4
4
  {
5
- name: 'Coefficient of Thermal Expansion — Engineering Toolbox',
5
+ name: 'Coefficient of Thermal Expansion - Engineering Toolbox',
6
6
  url: 'https://www.engineeringtoolbox.com/linear-expansion-coefficients-d_95.html',
7
7
  },
8
8
  {
9
- name: 'Thermal expansion and temperature — Britannica',
9
+ name: 'Thermal expansion and temperature - Britannica',
10
10
  url: 'https://www.britannica.com/science/thermal-expansion',
11
11
  },
12
12
  {
13
- name: 'Linear Thermal Expansion — Physics Hypertextbook',
13
+ name: 'Linear Thermal Expansion - Physics Hypertextbook',
14
14
  url: 'https://physics.info/expansion/',
15
15
  },
16
16
  ];
@@ -1,4 +1,4 @@
1
- import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
2
  import type { ToolLocaleContent } from '../../../types';
3
3
  import type { ThermalExpansionCalculatorUI } from '../ui';
4
4
  import { bibliography } from '../bibliography';
@@ -97,7 +97,7 @@ export const content: ToolLocaleContent<ThermalExpansionCalculatorUI> = {
97
97
  { type: 'title', text: 'What is Thermal Expansion and How Does It Affect Structures', level: 2 },
98
98
  {
99
99
  type: 'paragraph',
100
- html: '<strong>Thermal expansion</strong> is the physical process by which a material increases its length in response to a rise in temperature. At the microscopic level, particles vibrate with more energy and require a greater average spacing between them. In construction, this phenomenon is not a curiosity — it is a force that can crack concrete, deform steel beams and buckle railway tracks.',
100
+ html: '<strong>Thermal expansion</strong> is the physical process by which a material increases its length in response to a rise in temperature. At the microscopic level, particles vibrate with more energy and require a greater average spacing between them. In construction, this phenomenon is not a curiosity - it is a force that can crack concrete, deform steel beams and buckle railway tracks.',
101
101
  },
102
102
  { type: 'title', text: 'The Linear Expansion Formula', level: 3 },
103
103
  {
@@ -1,4 +1,4 @@
1
- import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
2
  import type { ToolLocaleContent } from '../../../types';
3
3
  import type { ThermalExpansionCalculatorUI } from '../ui';
4
4
  import { bibliography } from '../bibliography';
@@ -97,14 +97,14 @@ export const content: ToolLocaleContent<ThermalExpansionCalculatorUI> = {
97
97
  { type: 'title', text: 'Qu\'est-ce que la Dilatation Thermique et comment affecte-t-elle les Structures', level: 2 },
98
98
  {
99
99
  type: 'paragraph',
100
- html: 'La <strong>dilatation thermique</strong> est le processus physique par lequel un matériau augmente sa longueur en réponse à une élévation de température. À l\'échelle microscopique, les particules vibrent avec plus d\'énergie et nécessitent un espacement moyen plus grand entre elles. En construction, ce phénomène n\'est pas une curiosité : c\'est une force qui peut fissurer le béton, déformer des poutres en acier et faire se tordre les rails d\'un chemin de fer.',
100
+ html: 'La <strong>dilatation thermique</strong> est le processus physique par lequel un matériau augmente sa longueur en réponse à une élévation de température. À l\'échelle microscopique, les particules vibrent avec plus d\'énergie et nécessitent un espacement moyen plus grand entre elles. En construction, ce phénomène n\'est pas une curiosité: c\'est une force qui peut fissurer le béton, déformer des poutres en acier et faire se tordre les rails d\'un chemin de fer.',
101
101
  },
102
102
  { type: 'title', text: 'La Formule de la Dilatation Linéaire', level: 3 },
103
103
  {
104
104
  type: 'card',
105
105
  icon: 'mdi:math-log',
106
106
  title: 'Formule de Dilatation Linéaire',
107
- html: '<p>L\'expansion d\'un élément structurel se calcule avec : <code>ΔL = L₀ · α · ΔT</code>. Où <strong>ΔL</strong> est l\'allongement total, <strong>L₀</strong> est la longueur originale, <strong>α</strong> est le coefficient de dilatation du matériau (en °C⁻¹) et <strong>ΔT</strong> est la différence de température. Cette formule est à la base du dimensionnement des joints de dilatation dans les ponts, les toitures et les façades.</p>',
107
+ html: '<p>L\'expansion d\'un élément structurel se calcule avec: <code>ΔL = L₀ · α · ΔT</code>. Où <strong>ΔL</strong> est l\'allongement total, <strong>L₀</strong> est la longueur originale, <strong>α</strong> est le coefficient de dilatation du matériau (en °C⁻¹) et <strong>ΔT</strong> est la différence de température. Cette formule est à la base du dimensionnement des joints de dilatation dans les ponts, les toitures et les façades.</p>',
108
108
  },
109
109
  { type: 'title', text: 'Coefficients de Dilatation par Matériau', level: 3 },
110
110
  {
@@ -1,4 +1,4 @@
1
- import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
2
  import type { ToolLocaleContent } from '../../../types';
3
3
  import type { ThermalExpansionCalculatorUI } from '../ui';
4
4
  import { bibliography } from '../bibliography';
@@ -18,7 +18,7 @@ const faqData = [
18
18
  },
19
19
  {
20
20
  question: 'Расширяется ли дерево одинаково во всех направлениях?',
21
- answer: 'Нет. Дерево — анизотропный материал. Оно очень мало расширяется вдоль волокон, но значительно в поперечном направлении (в ширину и толщину).',
21
+ answer: 'Нет. Дерево - анизотропный материал. Оно очень мало расширяется вдоль волокон, но значительно в поперечном направлении (в ширину и толщину).',
22
22
  },
23
23
  {
24
24
  question: 'Какую температуру использовать в качестве начальной?',
@@ -78,9 +78,9 @@ export const content: ToolLocaleContent<ThermalExpansionCalculatorUI> = {
78
78
  schemas: [faqSchema, howToSchema, appSchema],
79
79
  seo: [
80
80
  { type: 'title', text: 'Что такое тепловое расширение и как оно влияет на конструкции', level: 2 },
81
- { type: 'paragraph', html: '<strong>Тепловое расширение</strong> — это физический процесс, при котором материал увеличивает свою длину в ответ на повышение температуры. В строительстве это явление — сила, способная разрушить бетон, деформировать стальные балки и искривить железнодорожные рельсы.' },
81
+ { type: 'paragraph', html: '<strong>Тепловое расширение</strong> - это физический процесс, при котором материал увеличивает свою длину в ответ на повышение температуры. В строительстве это явление - сила, способная разрушить бетон, деформировать стальные балки и искривить железнодорожные рельсы.' },
82
82
  { type: 'title', text: 'Формула линейного расширения', level: 3 },
83
- { type: 'card', icon: 'mdi:math-log', title: 'Формула линейного расширения', html: '<p>Расширение структурного элемента рассчитывается по формуле: <code>ΔL = L₀ · α · ΔT</code>. Где <strong>ΔL</strong> — общий рост, <strong>L₀</strong> — исходная длина, <strong>α</strong> — коэффициент расширения материала (в °C⁻¹), а <strong>ΔT</strong> — разница температур.</p>' },
83
+ { type: 'card', icon: 'mdi:math-log', title: 'Формула линейного расширения', html: '<p>Расширение структурного элемента рассчитывается по формуле: <code>ΔL = L₀ · α · ΔT</code>. Где <strong>ΔL</strong> - общий рост, <strong>L₀</strong> - исходная длина, <strong>α</strong> - коэффициент расширения материала (в °C⁻¹), а <strong>ΔT</strong> - разница температур.</p>' },
84
84
  ],
85
85
  ui: {
86
86
  configTitle: 'Настройка',
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { bibliography } from './bibliography';
4
+ ---
5
+
6
+ <SharedBibliography links={bibliography} />
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Two-Stroke Engine Fuel Mixture Ratios',
6
+ url: 'https://en.wikipedia.org/wiki/Two-stroke_engine',
7
+ },
8
+ {
9
+ name: 'Gasoline and Oil Mix Ratios for Small Engines',
10
+ url: 'https://homelivinghandbook.com/how-much-oil-should-you-mix-with-gas-for-small-engine-tools',
11
+ },
12
+ ];
@@ -0,0 +1,266 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { TwoStrokeMixtureCalculatorUI } from './ui';
4
+
5
+ interface Props { ui?: TwoStrokeMixtureCalculatorUI; }
6
+ const { ui } = Astro.props;
7
+ const t = (ui ?? {}) as TwoStrokeMixtureCalculatorUI;
8
+ ---
9
+
10
+ <div class="tsmc-root" data-tsmc-root data-ui={JSON.stringify(t)}>
11
+ <div class="tsmc-mission-selector">
12
+ <button class="tsmc-mission-btn active" data-mission="fuel">
13
+ <Icon name="mdi:gas-cylinder" />
14
+ <span>{t.copyTextFuel}</span>
15
+ </button>
16
+ <button class="tsmc-mission-btn" data-mission="oil">
17
+ <Icon name="mdi:beaker-outline" />
18
+ <span>{t.copyTextOil}</span>
19
+ </button>
20
+ </div>
21
+
22
+ <div class="tsmc-container">
23
+ <div class="tsmc-input-section">
24
+ <label id="tsmc-volume-label" class="tsmc-section-label">{t.labelVolume}</label>
25
+
26
+ <div class="tsmc-slider-container">
27
+ <input
28
+ type="range"
29
+ id="tsmc-slider"
30
+ min="1"
31
+ max="50"
32
+ step="1"
33
+ value="5"
34
+ class="tsmc-slider"
35
+ >
36
+ <div class="tsmc-slider-value-display">
37
+ <span id="tsmc-slider-value">5</span><span id="tsmc-slider-unit" class="tsmc-unit">L</span>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="tsmc-quick-buttons">
42
+ <button class="tsmc-quick-btn fuel-btn" data-volume="2">2L</button>
43
+ <button class="tsmc-quick-btn fuel-btn active" data-volume="5">5L</button>
44
+ <button class="tsmc-quick-btn fuel-btn" data-volume="10">10L</button>
45
+ <button class="tsmc-quick-btn fuel-btn" data-volume="20">20L</button>
46
+
47
+ <button class="tsmc-quick-btn oil-btn hidden" data-volume="125">125ml</button>
48
+ <button class="tsmc-quick-btn oil-btn hidden" data-volume="250">250ml</button>
49
+ <button class="tsmc-quick-btn oil-btn hidden" data-volume="500">500ml</button>
50
+ <button class="tsmc-quick-btn oil-btn hidden" data-volume="1000">1L</button>
51
+ </div>
52
+ </div>
53
+
54
+ <div class="tsmc-ratio-section">
55
+ <label class="tsmc-section-label">{t.labelPresets}</label>
56
+ <div class="tsmc-ratio-cards">
57
+ <button class="tsmc-ratio-card" data-ratio="25">
58
+ <span class="tsmc-ratio-value">1:25</span>
59
+ <span class="tsmc-ratio-desc">{t.ratio25Desc}</span>
60
+ </button>
61
+ <button class="tsmc-ratio-card" data-ratio="33">
62
+ <span class="tsmc-ratio-value">1:33</span>
63
+ <span class="tsmc-ratio-desc">{t.ratio33Desc}</span>
64
+ </button>
65
+ <button class="tsmc-ratio-card active" data-ratio="40">
66
+ <span class="tsmc-ratio-value">1:40</span>
67
+ <span class="tsmc-ratio-desc">{t.ratio40Desc}</span>
68
+ </button>
69
+ <button class="tsmc-ratio-card" data-ratio="50">
70
+ <span class="tsmc-ratio-value">1:50</span>
71
+ <span class="tsmc-ratio-desc">{t.ratio50Desc}</span>
72
+ </button>
73
+ </div>
74
+ </div>
75
+
76
+ <div class="tsmc-mega-result">
77
+ <div class="tsmc-result-label">{t.labelOilRequired}</div>
78
+ <div class="tsmc-result-display">
79
+ <span id="tsmc-result-value" class="tsmc-result-big">--</span>
80
+ <span id="tsmc-result-unit" class="tsmc-result-unit">ml</span>
81
+ </div>
82
+
83
+ <svg id="tsmc-container-visual" class="tsmc-container-visual" viewBox="0 0 200 240">
84
+ <rect x="65" y="30" width="70" height="12" fill="#64748b" rx="3"/>
85
+ <rect x="70" y="22" width="60" height="8" fill="#475569" rx="2"/>
86
+ <path d="M 50 42 Q 45 50 45 70 L 45 160 Q 45 175 60 180 L 140 180 Q 155 175 155 160 L 155 70 Q 155 50 150 42 Z"
87
+ fill="none" stroke="#64748b" stroke-width="3" stroke-linejoin="round"/>
88
+ <rect id="tsmc-fuel-fill" x="50" y="160" width="100" height="0" fill="#fbbf24"/>
89
+ <rect id="tsmc-oil-fill" x="50" y="160" width="100" height="0" fill="#dc2626"/>
90
+ <text id="tsmc-ratio-display" x="100" y="105" text-anchor="middle" dominant-baseline="middle"
91
+ font-size="20" font-weight="bold" fill="#1e293b" />
92
+ </svg>
93
+
94
+ <button id="tsmc-share" class="tsmc-share-btn" title="Share">
95
+ <Icon name="mdi:share-variant" />
96
+ </button>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <style>
102
+ @import './two-stroke-fuel-mixture-calculator.css';
103
+ </style>
104
+
105
+ <script>
106
+ import { calculateMixture } from './logic';
107
+
108
+ const root = document.querySelector('.tsmc-root') as HTMLElement;
109
+ const ui = root ? JSON.parse(root.dataset.ui || '{}') : {};
110
+
111
+ const slider = document.getElementById('tsmc-slider') as HTMLInputElement;
112
+ const missionBtns = document.querySelectorAll('.tsmc-mission-btn');
113
+ const sliderUnit = document.getElementById('tsmc-slider-unit')!;
114
+ const sliderValue = document.getElementById('tsmc-slider-value')!;
115
+ const quickBtns = document.querySelectorAll('.tsmc-quick-btn');
116
+ const fuelBtns = document.querySelectorAll('.fuel-btn');
117
+ const oilBtns = document.querySelectorAll('.oil-btn');
118
+ const ratioCards = document.querySelectorAll('.tsmc-ratio-card');
119
+ const resultLabel = document.querySelector('.tsmc-result-label')!;
120
+ const resultValue = document.getElementById('tsmc-result-value')!;
121
+ const resultUnit = document.getElementById('tsmc-result-unit')!;
122
+ const fuelFill = document.getElementById('tsmc-fuel-fill')!;
123
+ const oilFill = document.getElementById('tsmc-oil-fill')!;
124
+ const ratioDisplay = document.getElementById('tsmc-ratio-display')!;
125
+ const shareBtn = document.getElementById('tsmc-share')!;
126
+ const volumeLabel = document.getElementById('tsmc-volume-label')!;
127
+ const container = document.querySelector('.tsmc-container')!;
128
+
129
+ const params = new URLSearchParams(window.location.search);
130
+ let currentMission: 'fuel' | 'oil' = (params.get('mode') as 'fuel' | 'oil') || 'fuel';
131
+ let currentVolume = parseFloat(params.get('volume') || '5');
132
+ let currentRatio = parseInt(params.get('ratio') || '40');
133
+
134
+ function setFuelMode(skipSliderUpdate: boolean) {
135
+ container.classList.remove('tsmc-oil-mode');
136
+ slider.min = '1';
137
+ slider.max = '50';
138
+ slider.step = '1';
139
+ if (!skipSliderUpdate) slider.value = '5';
140
+ sliderUnit.textContent = 'L';
141
+ volumeLabel.textContent = ui.labelVolume || 'Volume';
142
+ resultLabel.textContent = ui.labelOilRequired || 'Oil Required';
143
+ fuelBtns.forEach(btn => btn.classList.remove('hidden'));
144
+ oilBtns.forEach(btn => btn.classList.add('hidden'));
145
+ }
146
+
147
+ function setOilMode(skipSliderUpdate: boolean) {
148
+ container.classList.add('tsmc-oil-mode');
149
+ slider.min = '20';
150
+ slider.max = '1000';
151
+ slider.step = '10';
152
+ if (!skipSliderUpdate) slider.value = '125';
153
+ sliderUnit.textContent = 'ml';
154
+ volumeLabel.textContent = ui.labelOilRequired || 'Oil';
155
+ resultLabel.textContent = ui.labelFuelVolume || 'Fuel Required';
156
+ fuelBtns.forEach(btn => btn.classList.add('hidden'));
157
+ oilBtns.forEach(btn => btn.classList.remove('hidden'));
158
+ }
159
+
160
+ function setMission(mission: 'fuel' | 'oil', skipSliderUpdate = false) {
161
+ currentMission = mission;
162
+ missionBtns.forEach(btn => {
163
+ btn.classList.toggle('active', (btn as HTMLElement).getAttribute('data-mission') === mission);
164
+ });
165
+ if (mission === 'fuel') {
166
+ setFuelMode(skipSliderUpdate);
167
+ } else {
168
+ setOilMode(skipSliderUpdate);
169
+ }
170
+ sliderValue.textContent = currentVolume.toString();
171
+ }
172
+
173
+ function setVolume(volume: number) {
174
+ currentVolume = volume;
175
+ slider.value = String(volume);
176
+ sliderValue.textContent = volume.toFixed(1);
177
+ quickBtns.forEach(btn => {
178
+ btn.classList.toggle('active', parseFloat((btn as HTMLElement).getAttribute('data-volume') || '0') === volume);
179
+ });
180
+ calculate();
181
+ }
182
+
183
+ function setRatio(ratio: number) {
184
+ currentRatio = ratio;
185
+ ratioCards.forEach(card => {
186
+ card.classList.toggle('active', parseInt((card as HTMLElement).getAttribute('data-ratio') || '40') === ratio);
187
+ });
188
+ calculate();
189
+ }
190
+
191
+ function calculate() {
192
+ const calc = calculateMixture(currentVolume, currentRatio, ui);
193
+
194
+ if (calc.status === 'calculated') {
195
+ const oilMl = calc.oilRequired * (calc.displayUnit === 'ml' ? 1 : 1000);
196
+
197
+ if (oilMl < 1000) {
198
+ resultValue.textContent = oilMl.toFixed(0);
199
+ resultUnit.textContent = 'ml';
200
+ } else if (oilMl % 1000 === 0) {
201
+ resultValue.textContent = (oilMl / 1000).toFixed(0);
202
+ resultUnit.textContent = 'L';
203
+ } else {
204
+ resultValue.textContent = oilMl.toFixed(0);
205
+ resultUnit.textContent = 'ml';
206
+ }
207
+
208
+ ratioDisplay.textContent = `1:${currentRatio}`;
209
+
210
+ const containerHeight = 120;
211
+ const oilRatioHeight = (1 / (1 + currentRatio)) * containerHeight;
212
+
213
+ fuelFill.setAttribute('y', (160 - containerHeight).toString());
214
+ fuelFill.setAttribute('height', containerHeight.toString());
215
+
216
+ oilFill.setAttribute('y', (160 - oilRatioHeight).toString());
217
+ oilFill.setAttribute('height', oilRatioHeight.toString());
218
+ } else {
219
+ resultValue.textContent = '--';
220
+ resultUnit.textContent = 'ml';
221
+ ratioDisplay.textContent = '';
222
+ fuelFill.setAttribute('height', '0');
223
+ oilFill.setAttribute('height', '0');
224
+ }
225
+ }
226
+
227
+ missionBtns.forEach(btn => {
228
+ btn.addEventListener('click', () => {
229
+ setMission((btn as HTMLElement).getAttribute('data-mission') as 'fuel' | 'oil');
230
+ });
231
+ });
232
+
233
+ slider.addEventListener('input', (e) => {
234
+ setVolume(parseFloat((e.target as HTMLInputElement).value));
235
+ });
236
+
237
+ quickBtns.forEach(btn => {
238
+ btn.addEventListener('click', () => {
239
+ setVolume(parseFloat((btn as HTMLElement).getAttribute('data-volume') || '5'));
240
+ });
241
+ });
242
+
243
+ ratioCards.forEach(card => {
244
+ card.addEventListener('click', () => {
245
+ setRatio(parseInt((card as HTMLElement).getAttribute('data-ratio') || '40'));
246
+ });
247
+ });
248
+
249
+ shareBtn.addEventListener('click', () => {
250
+ const url = new URL(window.location.href);
251
+ url.searchParams.set('mode', currentMission);
252
+ url.searchParams.set('volume', String(currentVolume));
253
+ url.searchParams.set('ratio', String(currentRatio));
254
+ navigator.clipboard.writeText(url.toString()).then(() => {
255
+ const originalHTML = shareBtn.innerHTML;
256
+ shareBtn.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/></svg>';
257
+ setTimeout(() => (shareBtn.innerHTML = originalHTML), 1500);
258
+ });
259
+ });
260
+
261
+ setMission(currentMission, true);
262
+ setRatio(currentRatio);
263
+ slider.value = String(currentVolume);
264
+ sliderValue.textContent = currentVolume.toString();
265
+ calculate();
266
+ </script>
@@ -0,0 +1,24 @@
1
+ import type { DiyToolEntry } from '../../types';
2
+ import type { TwoStrokeMixtureCalculatorUI } from './ui';
3
+
4
+ export const twoStrokeMixtureCalculator: DiyToolEntry<TwoStrokeMixtureCalculatorUI> = {
5
+ id: 'two-stroke-fuel-mixture-calculator',
6
+ icons: { bg: 'mdi:gas-cylinder', fg: 'mdi:beaker-outline' },
7
+ i18n: {
8
+ de: () => import('./i18n/de').then((m) => m.content),
9
+ en: () => import('./i18n/en').then((m) => m.content),
10
+ es: () => import('./i18n/es').then((m) => m.content),
11
+ fr: () => import('./i18n/fr').then((m) => m.content),
12
+ id: () => import('./i18n/id').then((m) => m.content),
13
+ it: () => import('./i18n/it').then((m) => m.content),
14
+ ja: () => import('./i18n/ja').then((m) => m.content),
15
+ ko: () => import('./i18n/ko').then((m) => m.content),
16
+ nl: () => import('./i18n/nl').then((m) => m.content),
17
+ pl: () => import('./i18n/pl').then((m) => m.content),
18
+ pt: () => import('./i18n/pt').then((m) => m.content),
19
+ ru: () => import('./i18n/ru').then((m) => m.content),
20
+ sv: () => import('./i18n/sv').then((m) => m.content),
21
+ tr: () => import('./i18n/tr').then((m) => m.content),
22
+ zh: () => import('./i18n/zh').then((m) => m.content),
23
+ },
24
+ };