@jjlmoya/utils-diy 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 (135) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +110 -0
  3. package/src/category/i18n/es.ts +110 -0
  4. package/src/category/i18n/fr.ts +101 -0
  5. package/src/category/index.ts +25 -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 +57 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -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/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/balusterCalculator/bibliography.astro +14 -0
  25. package/src/tool/balusterCalculator/component.astro +605 -0
  26. package/src/tool/balusterCalculator/i18n/en.ts +264 -0
  27. package/src/tool/balusterCalculator/i18n/es.ts +264 -0
  28. package/src/tool/balusterCalculator/i18n/fr.ts +264 -0
  29. package/src/tool/balusterCalculator/index.ts +33 -0
  30. package/src/tool/balusterCalculator/seo.astro +15 -0
  31. package/src/tool/balusterCalculator/ui.ts +15 -0
  32. package/src/tool/clayCalculator/bibliography.astro +14 -0
  33. package/src/tool/clayCalculator/component.astro +731 -0
  34. package/src/tool/clayCalculator/i18n/en.ts +183 -0
  35. package/src/tool/clayCalculator/i18n/es.ts +183 -0
  36. package/src/tool/clayCalculator/i18n/fr.ts +183 -0
  37. package/src/tool/clayCalculator/index.ts +33 -0
  38. package/src/tool/clayCalculator/seo.astro +15 -0
  39. package/src/tool/clayCalculator/ui.ts +19 -0
  40. package/src/tool/concreteCalculator/bibliography.astro +13 -0
  41. package/src/tool/concreteCalculator/component.astro +1089 -0
  42. package/src/tool/concreteCalculator/i18n/en.ts +201 -0
  43. package/src/tool/concreteCalculator/i18n/es.ts +201 -0
  44. package/src/tool/concreteCalculator/i18n/fr.ts +201 -0
  45. package/src/tool/concreteCalculator/index.ts +28 -0
  46. package/src/tool/concreteCalculator/seo.astro +14 -0
  47. package/src/tool/concreteCalculator/ui.ts +35 -0
  48. package/src/tool/cutOptimizer/bibliography.astro +13 -0
  49. package/src/tool/cutOptimizer/component.astro +825 -0
  50. package/src/tool/cutOptimizer/i18n/en.ts +178 -0
  51. package/src/tool/cutOptimizer/i18n/es.ts +178 -0
  52. package/src/tool/cutOptimizer/i18n/fr.ts +178 -0
  53. package/src/tool/cutOptimizer/index.ts +28 -0
  54. package/src/tool/cutOptimizer/optimizer.ts +199 -0
  55. package/src/tool/cutOptimizer/seo.astro +14 -0
  56. package/src/tool/cutOptimizer/ui.ts +25 -0
  57. package/src/tool/drillCalculator/bibliography.astro +13 -0
  58. package/src/tool/drillCalculator/component.astro +1210 -0
  59. package/src/tool/drillCalculator/engine.ts +63 -0
  60. package/src/tool/drillCalculator/i18n/en.ts +137 -0
  61. package/src/tool/drillCalculator/i18n/es.ts +137 -0
  62. package/src/tool/drillCalculator/i18n/fr.ts +137 -0
  63. package/src/tool/drillCalculator/index.ts +28 -0
  64. package/src/tool/drillCalculator/seo.astro +14 -0
  65. package/src/tool/drillCalculator/ui.ts +41 -0
  66. package/src/tool/drillSharpener/bibliography.astro +13 -0
  67. package/src/tool/drillSharpener/component.astro +712 -0
  68. package/src/tool/drillSharpener/engine.ts +42 -0
  69. package/src/tool/drillSharpener/i18n/en.ts +121 -0
  70. package/src/tool/drillSharpener/i18n/es.ts +121 -0
  71. package/src/tool/drillSharpener/i18n/fr.ts +121 -0
  72. package/src/tool/drillSharpener/index.ts +31 -0
  73. package/src/tool/drillSharpener/seo.astro +14 -0
  74. package/src/tool/drillSharpener/ui.ts +53 -0
  75. package/src/tool/epoxyCalculator/bibliography.astro +14 -0
  76. package/src/tool/epoxyCalculator/component.astro +888 -0
  77. package/src/tool/epoxyCalculator/i18n/en.ts +195 -0
  78. package/src/tool/epoxyCalculator/i18n/es.ts +195 -0
  79. package/src/tool/epoxyCalculator/i18n/fr.ts +195 -0
  80. package/src/tool/epoxyCalculator/index.ts +33 -0
  81. package/src/tool/epoxyCalculator/seo.astro +15 -0
  82. package/src/tool/epoxyCalculator/ui.ts +22 -0
  83. package/src/tool/furnitureFit/bibliography.astro +13 -0
  84. package/src/tool/furnitureFit/component.astro +552 -0
  85. package/src/tool/furnitureFit/engine.ts +51 -0
  86. package/src/tool/furnitureFit/i18n/en.ts +152 -0
  87. package/src/tool/furnitureFit/i18n/es.ts +152 -0
  88. package/src/tool/furnitureFit/i18n/fr.ts +152 -0
  89. package/src/tool/furnitureFit/index.ts +28 -0
  90. package/src/tool/furnitureFit/seo.astro +14 -0
  91. package/src/tool/furnitureFit/ui.ts +21 -0
  92. package/src/tool/mortarCalculator/bibliography.astro +13 -0
  93. package/src/tool/mortarCalculator/component.astro +1139 -0
  94. package/src/tool/mortarCalculator/i18n/en.ts +219 -0
  95. package/src/tool/mortarCalculator/i18n/es.ts +205 -0
  96. package/src/tool/mortarCalculator/i18n/fr.ts +220 -0
  97. package/src/tool/mortarCalculator/index.ts +24 -0
  98. package/src/tool/mortarCalculator/seo.astro +14 -0
  99. package/src/tool/mortarCalculator/ui.ts +37 -0
  100. package/src/tool/passepartoutCalculator/bibliography.astro +13 -0
  101. package/src/tool/passepartoutCalculator/component.astro +741 -0
  102. package/src/tool/passepartoutCalculator/i18n/en.ts +178 -0
  103. package/src/tool/passepartoutCalculator/i18n/es.ts +178 -0
  104. package/src/tool/passepartoutCalculator/i18n/fr.ts +178 -0
  105. package/src/tool/passepartoutCalculator/index.ts +28 -0
  106. package/src/tool/passepartoutCalculator/seo.astro +14 -0
  107. package/src/tool/passepartoutCalculator/ui.ts +17 -0
  108. package/src/tool/stairCalculator/bibliography.astro +52 -0
  109. package/src/tool/stairCalculator/component.astro +766 -0
  110. package/src/tool/stairCalculator/engine.ts +128 -0
  111. package/src/tool/stairCalculator/i18n/en.ts +149 -0
  112. package/src/tool/stairCalculator/i18n/es.ts +149 -0
  113. package/src/tool/stairCalculator/i18n/fr.ts +149 -0
  114. package/src/tool/stairCalculator/index.ts +31 -0
  115. package/src/tool/stairCalculator/seo.astro +211 -0
  116. package/src/tool/stairCalculator/ui.ts +109 -0
  117. package/src/tool/thermalExpansionCalculator/bibliography.astro +13 -0
  118. package/src/tool/thermalExpansionCalculator/component.astro +771 -0
  119. package/src/tool/thermalExpansionCalculator/engine.ts +23 -0
  120. package/src/tool/thermalExpansionCalculator/i18n/en.ts +174 -0
  121. package/src/tool/thermalExpansionCalculator/i18n/es.ts +174 -0
  122. package/src/tool/thermalExpansionCalculator/i18n/fr.ts +174 -0
  123. package/src/tool/thermalExpansionCalculator/index.ts +28 -0
  124. package/src/tool/thermalExpansionCalculator/seo.astro +14 -0
  125. package/src/tool/thermalExpansionCalculator/ui.ts +41 -0
  126. package/src/tool/voltageDropCalculator/bibliography.astro +13 -0
  127. package/src/tool/voltageDropCalculator/component.astro +1022 -0
  128. package/src/tool/voltageDropCalculator/i18n/en.ts +155 -0
  129. package/src/tool/voltageDropCalculator/i18n/es.ts +155 -0
  130. package/src/tool/voltageDropCalculator/i18n/fr.ts +148 -0
  131. package/src/tool/voltageDropCalculator/index.ts +28 -0
  132. package/src/tool/voltageDropCalculator/seo.astro +14 -0
  133. package/src/tool/voltageDropCalculator/ui.ts +22 -0
  134. package/src/tools.ts +16 -0
  135. package/src/types.ts +72 -0
@@ -0,0 +1,1139 @@
1
+ ---
2
+ import type { MortarCalculatorUI } from './ui';
3
+
4
+ interface Props {
5
+ ui?: Record<string, unknown>;
6
+ }
7
+ const { ui } = Astro.props;
8
+ const t = (ui ?? {}) as MortarCalculatorUI;
9
+ ---
10
+
11
+ <div
12
+ class="mc-container"
13
+ data-mc-root
14
+ data-mc-desc-trullissatio={t.phaseDescTrullissatio}
15
+ data-mc-desc-arenato={t.phaseDescArenato}
16
+ data-mc-desc-marmorato={t.phaseDescMarmorato}
17
+ data-mc-singular={t.layersSingular}
18
+ data-mc-plural={t.layersPlural}
19
+ data-mc-mat-cal={t.materialCal}
20
+ data-mc-mat-arena={t.materialArena}
21
+ data-mc-mat-marmol={t.materialMarmol}
22
+ >
23
+ <div class="mc-canvas">
24
+ <div class="mc-panel mc-panel-left">
25
+ <h2 class="mc-panel-title">{t.leftTitle}</h2>
26
+ <svg class="mc-column-svg" viewBox="0 0 200 480" xmlns="http://www.w3.org/2000/svg">
27
+ <defs>
28
+ <pattern id="mc-brick" x="0" y="0" width="40" height="20" patternUnits="userSpaceOnUse">
29
+ <rect width="40" height="20" fill="#8b7355"></rect>
30
+ <rect x="0" y="0" width="19" height="9" fill="#a0826d" stroke="#6b5d4f" stroke-width="0.5"></rect>
31
+ <rect x="20" y="0" width="19" height="9" fill="#a0826d" stroke="#6b5d4f" stroke-width="0.5"></rect>
32
+ <rect x="10" y="10" width="19" height="9" fill="#a0826d" stroke="#6b5d4f" stroke-width="0.5"></rect>
33
+ </pattern>
34
+ </defs>
35
+ <rect x="10" y="50" width="180" height="400" fill="url(#mc-brick)" stroke="#333" stroke-width="2"></rect>
36
+ <g class="mc-column-section mc-active" data-mc-phase="marmorato">
37
+ <rect x="10" y="50" width="180" height="25" fill="#f8f8f8" stroke="#333" stroke-width="2"></rect>
38
+ <line x1="15" y1="55" x2="185" y2="55" stroke="#fff" stroke-width="0.5" opacity="0.6"></line>
39
+ <line x1="15" y1="65" x2="185" y2="65" stroke="#fff" stroke-width="0.3" opacity="0.4"></line>
40
+ <text x="100" y="63" class="mc-column-label" font-size="10">Marmorato</text>
41
+ <text x="100" y="72" class="mc-column-sublabel" font-size="7">1:1 (4mm)</text>
42
+ </g>
43
+ <g class="mc-column-section" data-mc-phase="arenato">
44
+ <rect x="10" y="75" width="180" height="100" fill="#e6d5c0" stroke="#333" stroke-width="2"></rect>
45
+ <line x1="15" y1="95" x2="185" y2="95" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
46
+ <line x1="15" y1="115" x2="185" y2="115" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
47
+ <line x1="15" y1="135" x2="185" y2="135" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
48
+ <line x1="15" y1="155" x2="185" y2="155" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
49
+ <text x="100" y="120" class="mc-column-label" font-size="11">Arenato</text>
50
+ <text x="100" y="132" class="mc-column-sublabel" font-size="8">1:2 (16mm)</text>
51
+ </g>
52
+ <g class="mc-column-section mc-active" data-mc-phase="trullissatio">
53
+ <rect x="10" y="175" width="180" height="200" fill="#d4c5b0" stroke="#333" stroke-width="2"></rect>
54
+ <line x1="15" y1="200" x2="185" y2="200" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
55
+ <line x1="15" y1="230" x2="185" y2="230" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
56
+ <line x1="15" y1="260" x2="185" y2="260" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
57
+ <line x1="15" y1="290" x2="185" y2="290" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
58
+ <line x1="15" y1="320" x2="185" y2="320" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
59
+ <line x1="15" y1="350" x2="185" y2="350" stroke="#333" stroke-width="0.5" opacity="0.3"></line>
60
+ <text x="100" y="270" class="mc-column-label" font-size="12">Trullissatio</text>
61
+ <text x="100" y="285" class="mc-column-sublabel" font-size="9">1:3 (30mm)</text>
62
+ </g>
63
+ <rect x="5" y="375" width="190" height="75" fill="#78716c" stroke="#333" stroke-width="2"></rect>
64
+ </svg>
65
+ <div class="mc-phase-info">
66
+ <h3 data-mc-phase-name>Trullissatio</h3>
67
+ <p data-mc-phase-desc>{t.phaseDescTrullissatio}</p>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="mc-panel mc-panel-center">
72
+ <h2 class="mc-panel-title">{t.centerTitle}</h2>
73
+ <div class="mc-material-selector">
74
+ <button class="mc-material-btn mc-active" data-mc-mat="cal">
75
+ <svg viewBox="0 0 24 24" class="mc-btn-icon" aria-hidden="true"><path fill="currentColor" d="M12 3C9.8 3 8 4.8 8 7C8 8.4 8.7 9.7 9.8 10.5L7 22H17L14.2 10.5C15.3 9.7 16 8.4 16 7C16 4.8 14.2 3 12 3Z"/></svg>
76
+ {t.btnCal}
77
+ </button>
78
+ <button class="mc-material-btn" data-mc-mat="arena">
79
+ <svg viewBox="0 0 24 24" class="mc-btn-icon" aria-hidden="true"><path fill="currentColor" d="M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 4C14.8 4 17.2 5.5 18.6 7.8L7.8 18.6C5.5 17.2 4 14.8 4 12C4 7.6 7.6 4 12 4M12 20C9.2 20 6.8 18.5 5.4 16.2L16.2 5.4C18.5 6.8 20 9.2 20 12C20 16.4 16.4 20 12 20Z"/></svg>
80
+ {t.btnArena}
81
+ </button>
82
+ </div>
83
+ <div class="mc-cal-toggle" data-mc-cal-toggle>
84
+ <label class="mc-toggle-label">
85
+ <input type="checkbox" data-mc-cal-switch />
86
+ <span class="mc-toggle-slider"></span>
87
+ <span class="mc-toggle-text">
88
+ <span class="mc-type-pasta">{t.labelCalPasta}</span>
89
+ <span class="mc-type-polvo">{t.labelCalPolvo}</span>
90
+ </span>
91
+ </label>
92
+ </div>
93
+ <svg id="mc-material-bodegon" viewBox="0 0 300 200" class="mc-bodegon-svg">
94
+ <g id="mc-sack-pile"></g>
95
+ </svg>
96
+ <div class="mc-slider-container">
97
+ <input type="range" data-mc-slider min="5" max="500" value="150" step="5" class="mc-slider" />
98
+ <div class="mc-ruler-marks">
99
+ <span>5</span><span>100</span><span>200</span><span>300</span><span>400</span><span>500</span>
100
+ </div>
101
+ </div>
102
+ <div class="mc-quantity-display">
103
+ <span class="mc-quantity-label">{t.labelQuantity}</span>
104
+ <span data-mc-quantity class="mc-quantity-value">150</span>
105
+ <span class="mc-quantity-unit">kg</span>
106
+ </div>
107
+ <div class="mc-complementary">
108
+ <div class="mc-complementary-header">
109
+ <svg viewBox="0 0 24 24" class="mc-arrow-icon" aria-hidden="true"><path fill="currentColor" d="M4 11V13H16L10.5 18.5L11.92 19.92L19.84 12L11.92 4.08L10.5 5.5L16 11H4Z"/></svg>
110
+ <span>{t.labelNeedsAlso}</span>
111
+ </div>
112
+ <div class="mc-complementary-content">
113
+ <div class="mc-complementary-item">
114
+ <span data-mc-comp-label class="mc-comp-label">{t.materialArena}</span>
115
+ <span data-mc-comp-val class="mc-comp-value">450</span>
116
+ <span class="mc-comp-unit">kg</span>
117
+ </div>
118
+ <div class="mc-ratio-row">
119
+ <span class="mc-ratio-text">{t.labelProportion}</span>
120
+ <span data-mc-ratio class="mc-ratio-value">1:3</span>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ <div class="mc-panel mc-panel-right">
127
+ <h2 class="mc-panel-title">{t.rightTitle}</h2>
128
+ <svg id="mc-living-wall" viewBox="0 0 300 400" class="mc-wall-svg">
129
+ <defs>
130
+ <pattern id="mc-wall-brick" x="0" y="0" width="60" height="30" patternUnits="userSpaceOnUse">
131
+ <rect x="0" y="0" width="60" height="30" fill="none" stroke="#78716c" stroke-width="1"></rect>
132
+ <line x1="30" y1="0" x2="30" y2="30" stroke="#78716c" stroke-width="1"></line>
133
+ </pattern>
134
+ <pattern id="mc-texture-rough" x="0" y="0" width="10" height="10" patternUnits="userSpaceOnUse">
135
+ <circle cx="2" cy="2" r="1" fill="#000" opacity="0.1"></circle>
136
+ <circle cx="7" cy="7" r="1" fill="#000" opacity="0.1"></circle>
137
+ </pattern>
138
+ <pattern id="mc-texture-medium" x="0" y="0" width="6" height="6" patternUnits="userSpaceOnUse">
139
+ <circle cx="2" cy="2" r="0.5" fill="#000" opacity="0.08"></circle>
140
+ <circle cx="5" cy="5" r="0.5" fill="#000" opacity="0.08"></circle>
141
+ </pattern>
142
+ <pattern id="mc-texture-smooth" x="0" y="0" width="4" height="4" patternUnits="userSpaceOnUse">
143
+ <circle cx="2" cy="2" r="0.3" fill="#000" opacity="0.05"></circle>
144
+ </pattern>
145
+ </defs>
146
+ <rect x="0" y="0" width="300" height="400" fill="url(#mc-wall-brick)" opacity="0.3"></rect>
147
+ <rect id="mc-mortar-coverage" x="150" y="200" width="0" height="0" fill="#d4c5b0" opacity="0.9"></rect>
148
+ <rect id="mc-mortar-texture" x="150" y="200" width="0" height="0" fill="url(#mc-texture-rough)"></rect>
149
+ </svg>
150
+ <div class="mc-coverage-display">
151
+ <div class="mc-coverage-label">{t.labelCoverage}</div>
152
+ <span data-mc-coverage class="mc-coverage-value">0</span>
153
+ <span class="mc-coverage-unit">m²</span>
154
+ </div>
155
+ <div class="mc-technical-details">
156
+ <div class="mc-detail-row">
157
+ <span class="mc-detail-label">{t.labelThickness}</span>
158
+ <span data-mc-thickness class="mc-detail-value">30 mm</span>
159
+ </div>
160
+ <div class="mc-detail-row">
161
+ <span class="mc-detail-label">{t.labelLayers}</span>
162
+ <span data-mc-layers class="mc-detail-value">2</span>
163
+ </div>
164
+ <div class="mc-detail-row">
165
+ <span class="mc-detail-label">{t.labelWasteFactor}</span>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ <div class="mc-granulometry-section">
172
+ <h2 class="mc-section-title">{t.granulometryTitle}</h2>
173
+ <div class="mc-granulometry-cards">
174
+ <div class="mc-grain-card mc-active" data-mc-grain="trullissatio">
175
+ <div class="mc-grain-header">
176
+ <h3 class="mc-grain-title">{t.grainTitleCoarse}</h3>
177
+ <span class="mc-grain-subtitle">{t.grainSubtitleCoarse}</span>
178
+ </div>
179
+ <div class="mc-grain-visual">
180
+ <svg viewBox="0 0 200 120" class="mc-grain-svg">
181
+ <circle cx="30" cy="40" r="8" fill="#d4a574" opacity="0.9"></circle>
182
+ <circle cx="60" cy="35" r="7" fill="#c9985e" opacity="0.9"></circle>
183
+ <circle cx="90" cy="45" r="9" fill="#d4a574" opacity="0.9"></circle>
184
+ <circle cx="120" cy="38" r="8" fill="#b8895a" opacity="0.9"></circle>
185
+ <circle cx="150" cy="42" r="7" fill="#d4a574" opacity="0.9"></circle>
186
+ <circle cx="170" cy="40" r="8" fill="#c9985e" opacity="0.9"></circle>
187
+ <circle cx="45" cy="65" r="8" fill="#b8895a" opacity="0.9"></circle>
188
+ <circle cx="75" cy="70" r="9" fill="#d4a574" opacity="0.9"></circle>
189
+ <circle cx="105" cy="68" r="7" fill="#c9985e" opacity="0.9"></circle>
190
+ <circle cx="135" cy="72" r="8" fill="#d4a574" opacity="0.9"></circle>
191
+ <circle cx="165" cy="65" r="8" fill="#b8895a" opacity="0.9"></circle>
192
+ <circle cx="30" cy="95" r="9" fill="#d4a574" opacity="0.9"></circle>
193
+ <circle cx="60" cy="100" r="7" fill="#c9985e" opacity="0.9"></circle>
194
+ <circle cx="90" cy="98" r="8" fill="#b8895a" opacity="0.9"></circle>
195
+ <circle cx="120" cy="95" r="9" fill="#d4a574" opacity="0.9"></circle>
196
+ <circle cx="150" cy="100" r="7" fill="#c9985e" opacity="0.9"></circle>
197
+ <circle cx="170" cy="95" r="8" fill="#d4a574" opacity="0.9"></circle>
198
+ </svg>
199
+ </div>
200
+ <div class="mc-grain-specs">
201
+ <div class="mc-spec-item">
202
+ <span class="mc-spec-label">{t.specSize}</span>
203
+ <span class="mc-spec-value">2-5 mm</span>
204
+ </div>
205
+ <div class="mc-spec-item">
206
+ <span class="mc-spec-label">{t.specUse}</span>
207
+ <span class="mc-spec-value">{t.grainUsageCoarse}</span>
208
+ </div>
209
+ <div class="mc-spec-item">
210
+ <span class="mc-spec-label">{t.specDensity}</span>
211
+ <span class="mc-spec-value">1.6 kg/L</span>
212
+ </div>
213
+ </div>
214
+ </div>
215
+
216
+ <div class="mc-grain-card" data-mc-grain="arenato">
217
+ <div class="mc-grain-header">
218
+ <h3 class="mc-grain-title">{t.grainTitleMedium}</h3>
219
+ <span class="mc-grain-subtitle">{t.grainSubtitleMedium}</span>
220
+ </div>
221
+ <div class="mc-grain-visual">
222
+ <svg viewBox="0 0 200 120" class="mc-grain-svg">
223
+ <circle cx="25" cy="30" r="4" fill="#e6d5c0" opacity="0.9"></circle>
224
+ <circle cx="55" cy="28" r="3.5" fill="#d9c4a8" opacity="0.9"></circle>
225
+ <circle cx="85" cy="32" r="4" fill="#e6d5c0" opacity="0.9"></circle>
226
+ <circle cx="115" cy="30" r="3.5" fill="#cdb89a" opacity="0.9"></circle>
227
+ <circle cx="145" cy="33" r="4" fill="#e6d5c0" opacity="0.9"></circle>
228
+ <circle cx="175" cy="30" r="3.5" fill="#d9c4a8" opacity="0.9"></circle>
229
+ <circle cx="40" cy="58" r="4" fill="#cdb89a" opacity="0.9"></circle>
230
+ <circle cx="70" cy="56" r="3.5" fill="#e6d5c0" opacity="0.9"></circle>
231
+ <circle cx="100" cy="59" r="4" fill="#d9c4a8" opacity="0.9"></circle>
232
+ <circle cx="130" cy="57" r="3.5" fill="#e6d5c0" opacity="0.9"></circle>
233
+ <circle cx="160" cy="58" r="4" fill="#cdb89a" opacity="0.9"></circle>
234
+ <circle cx="25" cy="85" r="3.5" fill="#d9c4a8" opacity="0.9"></circle>
235
+ <circle cx="55" cy="83" r="4" fill="#e6d5c0" opacity="0.9"></circle>
236
+ <circle cx="85" cy="86" r="3.5" fill="#cdb89a" opacity="0.9"></circle>
237
+ <circle cx="115" cy="84" r="4" fill="#e6d5c0" opacity="0.9"></circle>
238
+ <circle cx="145" cy="85" r="3.5" fill="#d9c4a8" opacity="0.9"></circle>
239
+ <circle cx="175" cy="83" r="4" fill="#e6d5c0" opacity="0.9"></circle>
240
+ <circle cx="40" cy="108" r="3.5" fill="#d9c4a8" opacity="0.9"></circle>
241
+ <circle cx="70" cy="110" r="4" fill="#e6d5c0" opacity="0.9"></circle>
242
+ <circle cx="100" cy="108" r="3.5" fill="#cdb89a" opacity="0.9"></circle>
243
+ <circle cx="130" cy="110" r="4" fill="#e6d5c0" opacity="0.9"></circle>
244
+ <circle cx="160" cy="109" r="3.5" fill="#cdb89a" opacity="0.9"></circle>
245
+ </svg>
246
+ </div>
247
+ <div class="mc-grain-specs">
248
+ <div class="mc-spec-item">
249
+ <span class="mc-spec-label">{t.specSize}</span>
250
+ <span class="mc-spec-value">0.5-2 mm</span>
251
+ </div>
252
+ <div class="mc-spec-item">
253
+ <span class="mc-spec-label">{t.specUse}</span>
254
+ <span class="mc-spec-value">{t.grainUsageMedium}</span>
255
+ </div>
256
+ <div class="mc-spec-item">
257
+ <span class="mc-spec-label">{t.specDensity}</span>
258
+ <span class="mc-spec-value">1.5 kg/L</span>
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ <div class="mc-grain-card" data-mc-grain="marmorato">
264
+ <div class="mc-grain-header">
265
+ <h3 class="mc-grain-title">{t.grainTitleFine}</h3>
266
+ <span class="mc-grain-subtitle">{t.grainSubtitleFine}</span>
267
+ </div>
268
+ <div class="mc-grain-visual">
269
+ <svg viewBox="0 0 200 120" class="mc-grain-svg">
270
+ <defs>
271
+ <pattern id="mc-fine-powder" x="0" y="0" width="10" height="10" patternUnits="userSpaceOnUse">
272
+ <circle cx="2" cy="2" r="0.8" fill="#f8f8f8" opacity="0.9"></circle>
273
+ <circle cx="6" cy="3" r="0.7" fill="#f0f0f0" opacity="0.9"></circle>
274
+ <circle cx="4" cy="6" r="0.8" fill="#f8f8f8" opacity="0.9"></circle>
275
+ <circle cx="8" cy="7" r="0.7" fill="#e8e8e8" opacity="0.9"></circle>
276
+ </pattern>
277
+ </defs>
278
+ <rect x="10" y="10" width="180" height="100" fill="url(#mc-fine-powder)"></rect>
279
+ <circle cx="40" cy="35" r="1.5" fill="#f8f8f8" opacity="0.9"></circle>
280
+ <circle cx="80" cy="45" r="1.5" fill="#f0f0f0" opacity="0.9"></circle>
281
+ <circle cx="120" cy="40" r="1.5" fill="#f8f8f8" opacity="0.9"></circle>
282
+ <circle cx="160" cy="50" r="1.5" fill="#e8e8e8" opacity="0.9"></circle>
283
+ <circle cx="50" cy="75" r="1.5" fill="#f0f0f0" opacity="0.9"></circle>
284
+ <circle cx="90" cy="80" r="1.5" fill="#f8f8f8" opacity="0.9"></circle>
285
+ <circle cx="130" cy="85" r="1.5" fill="#f0f0f0" opacity="0.9"></circle>
286
+ <circle cx="170" cy="75" r="1.5" fill="#e8e8e8" opacity="0.9"></circle>
287
+ </svg>
288
+ </div>
289
+ <div class="mc-grain-specs">
290
+ <div class="mc-spec-item">
291
+ <span class="mc-spec-label">{t.specSize}</span>
292
+ <span class="mc-spec-value">&lt; 0.5 mm</span>
293
+ </div>
294
+ <div class="mc-spec-item">
295
+ <span class="mc-spec-label">{t.specUse}</span>
296
+ <span class="mc-spec-value">{t.grainUsageFine}</span>
297
+ </div>
298
+ <div class="mc-spec-item">
299
+ <span class="mc-spec-label">{t.specDensity}</span>
300
+ <span class="mc-spec-value">1.2 kg/L</span>
301
+ </div>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ </div>
307
+
308
+ <style>
309
+ .mc-container {
310
+ position: relative;
311
+ }
312
+
313
+ .mc-canvas {
314
+ max-width: 1400px;
315
+ margin: 0 auto;
316
+ display: grid;
317
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
318
+ gap: 1.5rem;
319
+ }
320
+
321
+ @media (min-width: 1024px) {
322
+ .mc-canvas {
323
+ grid-template-columns: 1fr 1.2fr 1fr;
324
+ }
325
+ }
326
+
327
+ .mc-panel {
328
+ background: rgba(255, 255, 255, 0.8);
329
+ backdrop-filter: blur(10px);
330
+ border: 2px solid #e7e5e4;
331
+ border-radius: 1.5rem;
332
+ padding: 2rem;
333
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.05);
334
+ transition: transform 0.3s, box-shadow 0.3s;
335
+ }
336
+
337
+ :global(.theme-dark) .mc-panel {
338
+ background: rgba(26, 26, 26, 0.8);
339
+ border-color: rgba(255, 255, 255, 0.1);
340
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
341
+ }
342
+
343
+ .mc-panel:hover {
344
+ transform: translateY(-4px);
345
+ box-shadow: 0 15px 50px rgba(0, 0, 0, 0.1);
346
+ }
347
+
348
+ .mc-panel-title {
349
+ font-size: 1.5rem;
350
+ font-weight: 700;
351
+ color: #292524;
352
+ margin-bottom: 1.5rem;
353
+ text-align: center;
354
+ }
355
+
356
+ :global(.theme-dark) .mc-panel-title {
357
+ color: #f5f1e8;
358
+ }
359
+
360
+ .mc-column-svg {
361
+ width: 100%;
362
+ max-width: 250px;
363
+ margin: 0 auto;
364
+ display: block;
365
+ }
366
+
367
+ .mc-column-section {
368
+ cursor: pointer;
369
+ transition: all 0.3s ease;
370
+ }
371
+
372
+ .mc-column-section:hover {
373
+ filter: brightness(1.1);
374
+ }
375
+
376
+ .mc-column-section.mc-active {
377
+ filter: drop-shadow(0 0 10px rgba(193, 105, 79, 0.6));
378
+ }
379
+
380
+ .mc-column-label {
381
+ font-size: 14px;
382
+ font-weight: 700;
383
+ text-anchor: middle;
384
+ fill: #292524;
385
+ pointer-events: none;
386
+ }
387
+
388
+ :global(.theme-dark) .mc-column-label {
389
+ fill: #f5f1e8;
390
+ }
391
+
392
+ .mc-column-sublabel {
393
+ font-size: 10px;
394
+ text-anchor: middle;
395
+ fill: #78716c;
396
+ pointer-events: none;
397
+ }
398
+
399
+ .mc-phase-info {
400
+ margin-top: 2rem;
401
+ padding: 1.5rem;
402
+ background: rgba(245, 241, 232, 0.5);
403
+ border-radius: 1rem;
404
+ border: 1px solid #e7e5e4;
405
+ }
406
+
407
+ :global(.theme-dark) .mc-phase-info {
408
+ background: rgba(40, 35, 30, 0.5);
409
+ border-color: rgba(255, 255, 255, 0.05);
410
+ }
411
+
412
+ .mc-phase-info h3 {
413
+ font-size: 1.25rem;
414
+ color: #c1694f;
415
+ margin-bottom: 0.5rem;
416
+ font-weight: 700;
417
+ }
418
+
419
+ .mc-phase-info p {
420
+ font-size: 0.875rem;
421
+ color: #57534e;
422
+ line-height: 1.6;
423
+ }
424
+
425
+ :global(.theme-dark) .mc-phase-info p {
426
+ color: #a8a29e;
427
+ }
428
+
429
+ .mc-material-selector {
430
+ display: flex;
431
+ gap: 1rem;
432
+ margin-bottom: 1.5rem;
433
+ }
434
+
435
+ .mc-material-btn {
436
+ flex: 1;
437
+ display: flex;
438
+ align-items: center;
439
+ justify-content: center;
440
+ gap: 0.5rem;
441
+ padding: 1rem;
442
+ background: rgba(245, 241, 232, 0.5);
443
+ border: 2px solid #e7e5e4;
444
+ border-radius: 0.75rem;
445
+ font-weight: 600;
446
+ color: #57534e;
447
+ cursor: pointer;
448
+ transition: all 0.3s;
449
+ }
450
+
451
+ :global(.theme-dark) .mc-material-btn {
452
+ background: rgba(40, 35, 30, 0.5);
453
+ border-color: rgba(255, 255, 255, 0.1);
454
+ color: #a8a29e;
455
+ }
456
+
457
+ .mc-material-btn.mc-active {
458
+ background: #c1694f;
459
+ border-color: #c1694f;
460
+ color: #fff;
461
+ }
462
+
463
+ .mc-btn-icon {
464
+ width: 1.25rem;
465
+ height: 1.25rem;
466
+ }
467
+
468
+ .mc-cal-toggle {
469
+ margin-bottom: 1.5rem;
470
+ padding: 0.75rem 1rem;
471
+ background: rgba(245, 241, 232, 0.5);
472
+ border-radius: 0.75rem;
473
+ border: 1px solid #e7e5e4;
474
+ }
475
+
476
+ :global(.theme-dark) .mc-cal-toggle {
477
+ background: rgba(40, 35, 30, 0.5);
478
+ border-color: rgba(255, 255, 255, 0.1);
479
+ }
480
+
481
+ .mc-toggle-label {
482
+ display: flex;
483
+ align-items: center;
484
+ gap: 0.75rem;
485
+ cursor: pointer;
486
+ }
487
+
488
+ .mc-toggle-label input[type="checkbox"] {
489
+ width: 2.5rem;
490
+ height: 1.25rem;
491
+ appearance: none;
492
+ background: #e7e5e4;
493
+ border-radius: 9999px;
494
+ position: relative;
495
+ cursor: pointer;
496
+ transition: background 0.3s;
497
+ flex-shrink: 0;
498
+ }
499
+
500
+ .mc-toggle-label input[type="checkbox"]:checked {
501
+ background: #c1694f;
502
+ }
503
+
504
+ .mc-toggle-label input[type="checkbox"]::after {
505
+ content: '';
506
+ position: absolute;
507
+ width: 1rem;
508
+ height: 1rem;
509
+ background: #fff;
510
+ border-radius: 50%;
511
+ top: 0.125rem;
512
+ left: 0.125rem;
513
+ transition: left 0.3s;
514
+ }
515
+
516
+ .mc-toggle-label input[type="checkbox"]:checked::after {
517
+ left: 1.375rem;
518
+ }
519
+
520
+ .mc-toggle-text {
521
+ font-size: 0.875rem;
522
+ color: #57534e;
523
+ display: flex;
524
+ flex-direction: column;
525
+ gap: 0.125rem;
526
+ }
527
+
528
+ :global(.theme-dark) .mc-toggle-text {
529
+ color: #a8a29e;
530
+ }
531
+
532
+ .mc-type-pasta {
533
+ font-weight: 600;
534
+ color: #292524;
535
+ }
536
+
537
+ :global(.theme-dark) .mc-type-pasta {
538
+ color: #f5f1e8;
539
+ }
540
+
541
+ .mc-type-polvo {
542
+ font-size: 0.8rem;
543
+ opacity: 0.7;
544
+ }
545
+
546
+ .mc-bodegon-svg {
547
+ width: 100%;
548
+ height: 200px;
549
+ border: 1px solid #e7e5e4;
550
+ border-radius: 0.75rem;
551
+ background: rgba(245, 241, 232, 0.3);
552
+ margin-bottom: 1rem;
553
+ }
554
+
555
+ :global(.theme-dark) .mc-bodegon-svg {
556
+ border-color: rgba(255, 255, 255, 0.1);
557
+ background: rgba(10, 10, 10, 0.3);
558
+ }
559
+
560
+ .mc-slider-container {
561
+ margin-bottom: 0.75rem;
562
+ }
563
+
564
+ .mc-slider {
565
+ width: 100%;
566
+ accent-color: #c1694f;
567
+ }
568
+
569
+ .mc-ruler-marks {
570
+ display: flex;
571
+ justify-content: space-between;
572
+ font-size: 0.75rem;
573
+ color: #78716c;
574
+ margin-top: 0.25rem;
575
+ }
576
+
577
+ .mc-quantity-display {
578
+ display: flex;
579
+ align-items: center;
580
+ gap: 0.5rem;
581
+ margin-bottom: 1.5rem;
582
+ font-size: 0.875rem;
583
+ color: #57534e;
584
+ }
585
+
586
+ :global(.theme-dark) .mc-quantity-display {
587
+ color: #a8a29e;
588
+ }
589
+
590
+ .mc-quantity-value {
591
+ font-size: 1.5rem;
592
+ font-weight: 700;
593
+ color: #c1694f;
594
+ }
595
+
596
+ .mc-quantity-unit {
597
+ font-weight: 600;
598
+ color: #78716c;
599
+ }
600
+
601
+ .mc-complementary {
602
+ padding: 1rem;
603
+ background: rgba(245, 241, 232, 0.5);
604
+ border-radius: 0.75rem;
605
+ border: 1px solid #e7e5e4;
606
+ }
607
+
608
+ :global(.theme-dark) .mc-complementary {
609
+ background: rgba(40, 35, 30, 0.5);
610
+ border-color: rgba(255, 255, 255, 0.1);
611
+ }
612
+
613
+ .mc-complementary-header {
614
+ display: flex;
615
+ align-items: center;
616
+ gap: 0.5rem;
617
+ font-size: 0.875rem;
618
+ font-weight: 600;
619
+ color: #78716c;
620
+ margin-bottom: 0.75rem;
621
+ }
622
+
623
+ :global(.theme-dark) .mc-complementary-header {
624
+ color: #a8a29e;
625
+ }
626
+
627
+ .mc-arrow-icon {
628
+ width: 1rem;
629
+ height: 1rem;
630
+ color: #c1694f;
631
+ }
632
+
633
+ .mc-complementary-content {
634
+ display: flex;
635
+ flex-direction: column;
636
+ gap: 0.5rem;
637
+ }
638
+
639
+ .mc-complementary-item {
640
+ display: flex;
641
+ align-items: center;
642
+ gap: 0.5rem;
643
+ }
644
+
645
+ .mc-comp-label {
646
+ font-weight: 600;
647
+ color: #292524;
648
+ min-width: 4rem;
649
+ }
650
+
651
+ :global(.theme-dark) .mc-comp-label {
652
+ color: #f5f1e8;
653
+ }
654
+
655
+ .mc-comp-value {
656
+ font-size: 1.25rem;
657
+ font-weight: 700;
658
+ color: #c1694f;
659
+ }
660
+
661
+ .mc-comp-unit {
662
+ color: #78716c;
663
+ }
664
+
665
+ .mc-ratio-row {
666
+ font-size: 0.875rem;
667
+ color: #78716c;
668
+ }
669
+
670
+ :global(.theme-dark) .mc-ratio-row {
671
+ color: #a8a29e;
672
+ }
673
+
674
+ .mc-ratio-value {
675
+ font-weight: 700;
676
+ color: #292524;
677
+ }
678
+
679
+ :global(.theme-dark) .mc-ratio-value {
680
+ color: #f5f1e8;
681
+ }
682
+
683
+ .mc-wall-svg {
684
+ width: 100%;
685
+ height: 300px;
686
+ border-radius: 1rem;
687
+ border: 1px solid #e7e5e4;
688
+ background: #fafaf9;
689
+ margin-bottom: 1.5rem;
690
+ }
691
+
692
+ :global(.theme-dark) .mc-wall-svg {
693
+ border-color: rgba(255, 255, 255, 0.1);
694
+ background: #0a0a0a;
695
+ }
696
+
697
+ #mc-mortar-coverage,
698
+ #mc-mortar-texture {
699
+ transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
700
+ }
701
+
702
+ .mc-coverage-display {
703
+ display: flex;
704
+ align-items: center;
705
+ gap: 0.5rem;
706
+ margin-bottom: 1.5rem;
707
+ }
708
+
709
+ .mc-coverage-label {
710
+ font-size: 0.875rem;
711
+ color: #78716c;
712
+ font-weight: 600;
713
+ }
714
+
715
+ :global(.theme-dark) .mc-coverage-label {
716
+ color: #a8a29e;
717
+ }
718
+
719
+ .mc-coverage-value {
720
+ font-size: 2rem;
721
+ font-weight: 900;
722
+ color: #c1694f;
723
+ line-height: 1;
724
+ }
725
+
726
+ .mc-coverage-unit {
727
+ font-size: 1rem;
728
+ font-weight: 600;
729
+ color: #78716c;
730
+ }
731
+
732
+ .mc-technical-details {
733
+ display: flex;
734
+ flex-direction: column;
735
+ gap: 0.5rem;
736
+ }
737
+
738
+ .mc-detail-row {
739
+ display: flex;
740
+ justify-content: space-between;
741
+ align-items: center;
742
+ padding: 0.5rem 0;
743
+ border-bottom: 1px solid #e7e5e4;
744
+ font-size: 0.875rem;
745
+ }
746
+
747
+ :global(.theme-dark) .mc-detail-row {
748
+ border-bottom-color: rgba(255, 255, 255, 0.05);
749
+ }
750
+
751
+ .mc-detail-row:last-child {
752
+ border-bottom: none;
753
+ }
754
+
755
+ .mc-detail-label {
756
+ color: #78716c;
757
+ font-weight: 500;
758
+ }
759
+
760
+ :global(.theme-dark) .mc-detail-label {
761
+ color: #a8a29e;
762
+ }
763
+
764
+ .mc-detail-value {
765
+ font-weight: 700;
766
+ color: #292524;
767
+ }
768
+
769
+ :global(.theme-dark) .mc-detail-value {
770
+ color: #f5f1e8;
771
+ }
772
+
773
+ .mc-granulometry-section {
774
+ max-width: 1400px;
775
+ margin: 4rem auto 2rem;
776
+ }
777
+
778
+ .mc-section-title {
779
+ font-size: 2rem;
780
+ font-weight: 700;
781
+ text-align: center;
782
+ color: #292524;
783
+ margin-bottom: 2.5rem;
784
+ position: relative;
785
+ }
786
+
787
+ :global(.theme-dark) .mc-section-title {
788
+ color: #f5f1e8;
789
+ }
790
+
791
+ .mc-section-title::after {
792
+ content: '';
793
+ display: block;
794
+ width: 100px;
795
+ height: 3px;
796
+ background: linear-gradient(90deg, transparent, #c1694f, transparent);
797
+ margin: 0.75rem auto 0;
798
+ }
799
+
800
+ .mc-granulometry-cards {
801
+ display: grid;
802
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
803
+ gap: 2rem;
804
+ margin-bottom: 3rem;
805
+ }
806
+
807
+ .mc-grain-card {
808
+ background: #fafaf9;
809
+ border: 2px solid #e7e5e4;
810
+ border-radius: 1rem;
811
+ padding: 2rem;
812
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
813
+ opacity: 0.6;
814
+ transform: scale(0.98);
815
+ }
816
+
817
+ :global(.theme-dark) .mc-grain-card {
818
+ background: #1c1917;
819
+ border-color: rgba(255, 255, 255, 0.1);
820
+ }
821
+
822
+ .mc-grain-card:hover {
823
+ transform: scale(1);
824
+ opacity: 0.8;
825
+ }
826
+
827
+ .mc-grain-card.mc-active {
828
+ opacity: 1;
829
+ transform: scale(1.02);
830
+ border-color: #c1694f;
831
+ box-shadow: 0 8px 32px rgba(193, 105, 79, 0.2);
832
+ background: #fff;
833
+ }
834
+
835
+ :global(.theme-dark) .mc-grain-card.mc-active {
836
+ background: #292524;
837
+ border-color: #c1694f;
838
+ box-shadow: 0 8px 32px rgba(193, 105, 79, 0.3);
839
+ }
840
+
841
+ .mc-grain-header {
842
+ text-align: center;
843
+ margin-bottom: 1.5rem;
844
+ }
845
+
846
+ .mc-grain-title {
847
+ font-size: 1.25rem;
848
+ font-weight: 700;
849
+ color: #292524;
850
+ margin: 0 0 0.5rem;
851
+ }
852
+
853
+ :global(.theme-dark) .mc-grain-title {
854
+ color: #f5f1e8;
855
+ }
856
+
857
+ .mc-grain-subtitle {
858
+ font-size: 0.875rem;
859
+ color: #78716c;
860
+ font-weight: 500;
861
+ }
862
+
863
+ :global(.theme-dark) .mc-grain-subtitle {
864
+ color: #a8a29e;
865
+ }
866
+
867
+ .mc-grain-visual {
868
+ background: #f5f5f4;
869
+ border-radius: 0.75rem;
870
+ padding: 1rem;
871
+ margin-bottom: 1.5rem;
872
+ border: 1px solid #e7e5e4;
873
+ }
874
+
875
+ :global(.theme-dark) .mc-grain-visual {
876
+ background: #0a0a0a;
877
+ border-color: rgba(255, 255, 255, 0.05);
878
+ }
879
+
880
+ .mc-grain-svg {
881
+ width: 100%;
882
+ height: auto;
883
+ display: block;
884
+ }
885
+
886
+ .mc-grain-specs {
887
+ display: flex;
888
+ flex-direction: column;
889
+ gap: 0.75rem;
890
+ }
891
+
892
+ .mc-spec-item {
893
+ display: flex;
894
+ justify-content: space-between;
895
+ align-items: center;
896
+ padding: 0.5rem 0;
897
+ border-bottom: 1px solid #e7e5e4;
898
+ }
899
+
900
+ :global(.theme-dark) .mc-spec-item {
901
+ border-bottom-color: rgba(255, 255, 255, 0.05);
902
+ }
903
+
904
+ .mc-spec-item:last-child {
905
+ border-bottom: none;
906
+ }
907
+
908
+ .mc-spec-label {
909
+ font-size: 0.875rem;
910
+ color: #78716c;
911
+ font-weight: 500;
912
+ }
913
+
914
+ :global(.theme-dark) .mc-spec-label {
915
+ color: #a8a29e;
916
+ }
917
+
918
+ .mc-spec-value {
919
+ font-size: 0.875rem;
920
+ color: #292524;
921
+ font-weight: 600;
922
+ }
923
+
924
+ :global(.theme-dark) .mc-spec-value {
925
+ color: #f5f1e8;
926
+ }
927
+ </style>
928
+
929
+ <script>
930
+ type McPhase = 'trullissatio' | 'arenato' | 'marmorato';
931
+ type McCalType = 'pasta' | 'polvo';
932
+ type McMaterial = 'cal' | 'arena';
933
+
934
+ const MC_PHASES = {
935
+ trullissatio: { ratio: { cal: 1, agg: 3 }, aggName: 'arena', thickness: 30, layers: 2, density: 1.6, color: '#d4c5b0', texture: 'texture-rough' },
936
+ arenato: { ratio: { cal: 1, agg: 2 }, aggName: 'arena', thickness: 16, layers: 2, density: 1.5, color: '#e6d5c0', texture: 'texture-medium' },
937
+ marmorato: { ratio: { cal: 1, agg: 1 }, aggName: 'marmol', thickness: 4, layers: 1, density: 1.2, color: '#f8f8f8', texture: 'texture-smooth' },
938
+ } as const;
939
+
940
+ const MC_CAL_DENSITY: Record<McCalType, number> = { pasta: 1.3, polvo: 0.65 };
941
+ const MC_WASTE = 1.2;
942
+
943
+ let mcPhase: McPhase = 'trullissatio';
944
+ let mcMaterial: McMaterial = 'cal';
945
+ let mcCalType: McCalType = 'pasta';
946
+ let mcKg = 150;
947
+
948
+ function mcCoverage(): number {
949
+ const phase = MC_PHASES[mcPhase];
950
+ const t = phase.thickness / 1000;
951
+ if (mcMaterial === 'cal') {
952
+ const calVol = mcKg / MC_CAL_DENSITY[mcCalType];
953
+ const calFrac = phase.ratio.cal / (phase.ratio.cal + phase.ratio.agg);
954
+ return calVol / (t * calFrac * 1000 * MC_WASTE);
955
+ }
956
+ return mcKg / phase.density / (t * 1000 * MC_WASTE);
957
+ }
958
+
959
+ function mcComplementary(): { amount: number; name: string; ratio: string } {
960
+ const phase = MC_PHASES[mcPhase];
961
+ const ratioStr = `${phase.ratio.cal}:${phase.ratio.agg}`;
962
+ if (mcMaterial === 'cal') {
963
+ const calVol = mcKg / MC_CAL_DENSITY[mcCalType];
964
+ const aggVol = calVol * phase.ratio.agg;
965
+ return { amount: aggVol * phase.density, name: phase.aggName, ratio: ratioStr };
966
+ }
967
+ const aggVol = mcKg / phase.density;
968
+ const calVol = aggVol * (phase.ratio.cal / phase.ratio.agg);
969
+ return { amount: calVol * MC_CAL_DENSITY[mcCalType], name: 'cal', ratio: ratioStr };
970
+ }
971
+
972
+ function mcUpdatePhase(root: Element): void {
973
+ root.querySelectorAll('[data-mc-phase]').forEach((el) => {
974
+ el.classList.toggle('mc-active', el.getAttribute('data-mc-phase') === mcPhase);
975
+ });
976
+ const nameEl = root.querySelector('[data-mc-phase-name]');
977
+ const descEl = root.querySelector('[data-mc-phase-desc]');
978
+ const cap = mcPhase.charAt(0).toUpperCase() + mcPhase.slice(1);
979
+ if (nameEl) nameEl.textContent = cap;
980
+ if (descEl) descEl.textContent = root.getAttribute(`data-mc-desc-${mcPhase}`) ?? '';
981
+ }
982
+
983
+ function mcUpdateMaterial(root: Element): void {
984
+ root.querySelectorAll('[data-mc-mat]').forEach((el) => {
985
+ el.classList.toggle('mc-active', el.getAttribute('data-mc-mat') === mcMaterial);
986
+ });
987
+ const calToggle = root.querySelector<HTMLElement>('[data-mc-cal-toggle]');
988
+ if (calToggle) calToggle.style.display = mcMaterial === 'cal' ? 'block' : 'none';
989
+ }
990
+
991
+ function mcUpdateCoverage(root: Element): number {
992
+ const cov = mcCoverage();
993
+ const covEl = root.querySelector('[data-mc-coverage]');
994
+ if (covEl) covEl.textContent = cov.toFixed(1);
995
+ return cov;
996
+ }
997
+
998
+ function mcUpdateComplement(root: Element): void {
999
+ const comp = mcComplementary();
1000
+ const labelEl = root.querySelector('[data-mc-comp-label]');
1001
+ const valEl = root.querySelector('[data-mc-comp-val]');
1002
+ const ratioEl = root.querySelector('[data-mc-ratio]');
1003
+ if (labelEl) labelEl.textContent = root.getAttribute(`data-mc-mat-${comp.name}`) ?? comp.name;
1004
+ if (valEl) valEl.textContent = Math.round(comp.amount).toString();
1005
+ if (ratioEl) ratioEl.textContent = comp.ratio;
1006
+ }
1007
+
1008
+ function mcUpdateDetails(root: Element): void {
1009
+ const phase = MC_PHASES[mcPhase];
1010
+ const qEl = root.querySelector('[data-mc-quantity]');
1011
+ const thEl = root.querySelector('[data-mc-thickness]');
1012
+ const lyEl = root.querySelector('[data-mc-layers]');
1013
+ if (qEl) qEl.textContent = mcKg.toString();
1014
+ if (thEl) thEl.textContent = `${phase.thickness} mm`;
1015
+ if (lyEl) {
1016
+ const s = root.getAttribute('data-mc-singular') ?? '';
1017
+ const p = root.getAttribute('data-mc-plural') ?? '';
1018
+ lyEl.textContent = `${phase.layers} ${phase.layers === 1 ? s : p}`;
1019
+ }
1020
+ }
1021
+
1022
+ function mcUpdateWall(root: Element, cov: number): void {
1023
+ const phase = MC_PHASES[mcPhase];
1024
+ const covEl = root.querySelector('#mc-mortar-coverage');
1025
+ const texEl = root.querySelector('#mc-mortar-texture');
1026
+ if (!covEl || !texEl) return;
1027
+ const ratio = Math.min(cov / 50, 1);
1028
+ const w = 300 * Math.sqrt(ratio);
1029
+ const h = 400 * Math.sqrt(ratio);
1030
+ const x = (300 - w) / 2;
1031
+ const y = (400 - h) / 2;
1032
+ for (const el of [covEl, texEl]) {
1033
+ el.setAttribute('x', x.toString());
1034
+ el.setAttribute('y', y.toString());
1035
+ el.setAttribute('width', w.toString());
1036
+ el.setAttribute('height', h.toString());
1037
+ }
1038
+ covEl.setAttribute('fill', phase.color);
1039
+ texEl.setAttribute('fill', `url(#mc-${phase.texture})`);
1040
+ }
1041
+
1042
+ function mcSackPos(i: number): { x: number; y: number } {
1043
+ return { x: 30 + (i % 4) * 50, y: 150 - Math.floor(i / 4) * 40 };
1044
+ }
1045
+
1046
+ function mcFullSack(x: number, y: number, color: string): string {
1047
+ return `<rect x="${x}" y="${y}" width="40" height="50" fill="${color}" stroke="#333" stroke-width="1.5" rx="3"/>` +
1048
+ `<line x1="${x + 5}" y1="${y + 15}" x2="${x + 35}" y2="${y + 15}" stroke="#333" stroke-width="1" opacity="0.3"/>` +
1049
+ `<line x1="${x + 5}" y1="${y + 25}" x2="${x + 35}" y2="${y + 25}" stroke="#333" stroke-width="1" opacity="0.3"/>`;
1050
+ }
1051
+
1052
+ function mcHalfSack(x: number, y: number, color: string, light: string): string {
1053
+ const pts = `${x},${y} ${x + 40},${y} ${x + 40},${y + 50} ${x},${y + 25}`;
1054
+ return `<defs><clipPath id="mc-half-clip"><polygon points="${pts}"/></clipPath></defs>` +
1055
+ `<rect x="${x}" y="${y}" width="40" height="50" fill="${light}" stroke="#333" stroke-width="1.5" stroke-dasharray="3,3" rx="3"/>` +
1056
+ `<rect x="${x}" y="${y}" width="40" height="50" fill="${color}" clip-path="url(#mc-half-clip)" stroke="none"/>` +
1057
+ `<line x1="${x}" y1="${y + 25}" x2="${x + 40}" y2="${y + 50}" stroke="#333" stroke-width="1.5"/>`;
1058
+ }
1059
+
1060
+ function mcUpdateSacks(root: Element): void {
1061
+ const pileEl = root.querySelector('#mc-sack-pile');
1062
+ if (!pileEl) return;
1063
+ const total = mcKg / 25;
1064
+ const full = Math.floor(total);
1065
+ const color = mcMaterial === 'cal' ? '#f5f5f5' : '#e6c288';
1066
+ const light = mcMaterial === 'cal' ? '#fafafa' : '#f0d5a8';
1067
+ let svg = '';
1068
+ for (let i = 0; i < full; i++) {
1069
+ const { x, y } = mcSackPos(i);
1070
+ svg += mcFullSack(x, y, color);
1071
+ }
1072
+ if (total - full >= 0.25) {
1073
+ const { x, y } = mcSackPos(full);
1074
+ svg += mcHalfSack(x, y, color, light);
1075
+ }
1076
+ pileEl.innerHTML = svg;
1077
+ }
1078
+
1079
+ function mcUpdateGrain(root: Element): void {
1080
+ root.querySelectorAll('[data-mc-grain]').forEach((el) => {
1081
+ el.classList.toggle('mc-active', el.getAttribute('data-mc-grain') === mcPhase);
1082
+ });
1083
+ }
1084
+
1085
+ function mcUpdate(root: Element): void {
1086
+ mcUpdatePhase(root);
1087
+ mcUpdateMaterial(root);
1088
+ const cov = mcUpdateCoverage(root);
1089
+ mcUpdateComplement(root);
1090
+ mcUpdateDetails(root);
1091
+ mcUpdateWall(root, cov);
1092
+ mcUpdateSacks(root);
1093
+ mcUpdateGrain(root);
1094
+ }
1095
+
1096
+ function mcAttachColumn(root: Element): void {
1097
+ root.querySelectorAll('[data-mc-phase]').forEach((el) => {
1098
+ el.addEventListener('click', () => {
1099
+ mcPhase = (el.getAttribute('data-mc-phase') ?? 'trullissatio') as McPhase;
1100
+ mcUpdate(root);
1101
+ });
1102
+ });
1103
+ }
1104
+
1105
+ function mcAttachMaterial(root: Element): void {
1106
+ root.querySelectorAll('[data-mc-mat]').forEach((el) => {
1107
+ el.addEventListener('click', () => {
1108
+ mcMaterial = (el.getAttribute('data-mc-mat') ?? 'cal') as McMaterial;
1109
+ mcUpdate(root);
1110
+ });
1111
+ });
1112
+ }
1113
+
1114
+ function mcAttachControls(root: Element): void {
1115
+ const toggle = root.querySelector<HTMLInputElement>('[data-mc-cal-switch]');
1116
+ const slider = root.querySelector<HTMLInputElement>('[data-mc-slider]');
1117
+ if (toggle) toggle.addEventListener('change', () => {
1118
+ mcCalType = toggle.checked ? 'polvo' : 'pasta';
1119
+ mcUpdate(root);
1120
+ });
1121
+ if (slider) slider.addEventListener('input', () => {
1122
+ mcKg = parseInt(slider.value);
1123
+ mcUpdate(root);
1124
+ });
1125
+ }
1126
+
1127
+ function mcInit(root: Element): void {
1128
+ mcPhase = 'trullissatio';
1129
+ mcMaterial = 'cal';
1130
+ mcCalType = 'pasta';
1131
+ mcKg = 150;
1132
+ mcAttachColumn(root);
1133
+ mcAttachMaterial(root);
1134
+ mcAttachControls(root);
1135
+ mcUpdate(root);
1136
+ }
1137
+
1138
+ document.querySelectorAll('[data-mc-root]').forEach(mcInit);
1139
+ </script>