@jjlmoya/utils-science 1.41.0 → 1.42.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 (36) hide show
  1. package/package.json +1 -1
  2. package/scripts/crystal-lattice-catalog-translations.mjs +459 -0
  3. package/scripts/sync-crystal-lattice-i18n.mjs +50 -0
  4. package/src/tool/crystal-lattice-structure-finder/component.astro +120 -39
  5. package/src/tool/crystal-lattice-structure-finder/crystal-lattice-structure-finder.css +204 -56
  6. package/src/tool/crystal-lattice-structure-finder/data.ts +107 -133
  7. package/src/tool/crystal-lattice-structure-finder/i18n/de.ts +39 -0
  8. package/src/tool/crystal-lattice-structure-finder/i18n/en.ts +45 -6
  9. package/src/tool/crystal-lattice-structure-finder/i18n/es.ts +39 -0
  10. package/src/tool/crystal-lattice-structure-finder/i18n/fr.ts +39 -0
  11. package/src/tool/crystal-lattice-structure-finder/i18n/id.ts +39 -0
  12. package/src/tool/crystal-lattice-structure-finder/i18n/it.ts +39 -0
  13. package/src/tool/crystal-lattice-structure-finder/i18n/ja.ts +39 -0
  14. package/src/tool/crystal-lattice-structure-finder/i18n/ko.ts +39 -0
  15. package/src/tool/crystal-lattice-structure-finder/i18n/nl.ts +39 -0
  16. package/src/tool/crystal-lattice-structure-finder/i18n/pl.ts +39 -0
  17. package/src/tool/crystal-lattice-structure-finder/i18n/pt.ts +39 -0
  18. package/src/tool/crystal-lattice-structure-finder/i18n/ru.ts +39 -0
  19. package/src/tool/crystal-lattice-structure-finder/i18n/sv.ts +39 -0
  20. package/src/tool/crystal-lattice-structure-finder/i18n/tr.ts +39 -0
  21. package/src/tool/crystal-lattice-structure-finder/i18n/zh.ts +39 -0
  22. package/src/tool/crystal-lattice-structure-finder/logic.ts +7 -3
  23. package/src/tool/crystal-lattice-structure-finder/structures/README.md +26 -0
  24. package/src/tool/crystal-lattice-structure-finder/structures/body-centered-cubic.ts +19 -0
  25. package/src/tool/crystal-lattice-structure-finder/structures/cesium-chloride.ts +19 -0
  26. package/src/tool/crystal-lattice-structure-finder/structures/diamond-cubic.ts +23 -0
  27. package/src/tool/crystal-lattice-structure-finder/structures/face-centered-cubic.ts +16 -0
  28. package/src/tool/crystal-lattice-structure-finder/structures/hexagonal-close-packed.ts +30 -0
  29. package/src/tool/crystal-lattice-structure-finder/structures/index.ts +25 -0
  30. package/src/tool/crystal-lattice-structure-finder/structures/perovskite.ts +20 -0
  31. package/src/tool/crystal-lattice-structure-finder/structures/rock-salt.ts +23 -0
  32. package/src/tool/crystal-lattice-structure-finder/structures/rutile.ts +25 -0
  33. package/src/tool/crystal-lattice-structure-finder/structures/shared.ts +86 -0
  34. package/src/tool/crystal-lattice-structure-finder/structures/simple-cubic.ts +16 -0
  35. package/src/tool/crystal-lattice-structure-finder/structures/wurtzite.ts +22 -0
  36. package/src/tool/crystal-lattice-structure-finder/structures/zinc-blende.ts +23 -0
@@ -1,5 +1,5 @@
1
1
  ---
2
- import type { LatticeId } from './logic';
2
+ import { LATTICE_STRUCTURES, MATERIAL_PRESETS } from './logic';
3
3
  import './crystal-lattice-structure-finder.css';
4
4
 
5
5
  interface Props {
@@ -8,20 +8,32 @@ interface Props {
8
8
 
9
9
  const { ui } = Astro.props;
10
10
 
11
- const latticeOptions: Array<{ id: LatticeId; uiKey: string; shortKey: string }> = [
12
- { id: 'simple-cubic', uiKey: 'latticeSimpleCubic', shortKey: 'shortSc' },
13
- { id: 'face-centered-cubic', uiKey: 'latticeFcc', shortKey: 'shortFcc' },
14
- { id: 'hexagonal-close-packed', uiKey: 'latticeHcp', shortKey: 'shortHcp' },
15
- ];
16
-
17
- const materialOptions: Array<{ id: string; uiKey: string; noteKey: string }> = [
18
- { id: 'copper', uiKey: 'materialCopper', noteKey: 'materialCopperNote' },
19
- { id: 'aluminum', uiKey: 'materialAluminum', noteKey: 'materialAluminumNote' },
20
- { id: 'polonium', uiKey: 'materialPolonium', noteKey: 'materialPoloniumNote' },
21
- { id: 'magnesium', uiKey: 'materialMagnesium', noteKey: 'materialMagnesiumNote' },
22
- { id: 'titanium', uiKey: 'materialTitanium', noteKey: 'materialTitaniumNote' },
23
- { id: 'halite', uiKey: 'materialHalite', noteKey: 'materialHaliteNote' },
24
- ];
11
+ const latticeOptions = LATTICE_STRUCTURES;
12
+ const materialOptions = MATERIAL_PRESETS;
13
+
14
+ function pascalId(id: string): string {
15
+ return id.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('');
16
+ }
17
+
18
+ function uiText(key: string, fallback: string): string {
19
+ return ui[key] ?? fallback;
20
+ }
21
+
22
+ function materialName(material: { id: string; name: string }): string {
23
+ return uiText(`material${pascalId(material.id)}`, material.name);
24
+ }
25
+
26
+ function materialNote(material: { id: string; note: string }): string {
27
+ return uiText(`material${pascalId(material.id)}Note`, material.note);
28
+ }
29
+
30
+ function latticeName(lattice: { id: string; name: string }): string {
31
+ return uiText(`lattice${pascalId(lattice.id)}`, lattice.name);
32
+ }
33
+
34
+ function latticeShortName(lattice: { id: string; shortName: string }): string {
35
+ return uiText(`short${pascalId(lattice.id)}`, lattice.shortName);
36
+ }
25
37
  ---
26
38
 
27
39
  <div class="lattice-lab" id="lattice-lab">
@@ -66,28 +78,50 @@ const materialOptions: Array<{ id: string; uiKey: string; noteKey: string }> = [
66
78
  <p>{ui.density}</p>
67
79
  </div>
68
80
 
69
- <label class="lattice-field lattice-field-select" for="lattice-material">
81
+ <div class="lattice-field lattice-field-select">
70
82
  <span>{ui.material}</span>
71
- <select id="lattice-material">
72
- {materialOptions.map((opt) => (
73
- <option value={opt.id} data-note={ui[opt.noteKey]}>{ui[opt.uiKey]}</option>
83
+ <select class="lattice-native-select" id="lattice-material" tabindex="-1" aria-hidden="true">
84
+ {materialOptions.map((material) => (
85
+ <option value={material.id} data-note={materialNote(material)}>{materialName(material)}</option>
74
86
  ))}
75
87
  </select>
76
- </label>
88
+ <button class="lattice-select-button" id="lattice-material-button" type="button" aria-haspopup="listbox" aria-expanded="false">
89
+ <span>Copper</span>
90
+ <i aria-hidden="true"></i>
91
+ </button>
92
+ <div class="lattice-select-menu" id="lattice-material-menu" role="listbox" aria-label={ui.material}>
93
+ {materialOptions.map((material) => (
94
+ <button type="button" role="option" data-select-target="lattice-material" data-value={material.id}>
95
+ {materialName(material)}
96
+ </button>
97
+ ))}
98
+ </div>
99
+ </div>
77
100
 
78
101
  <div class="lattice-note">
79
102
  <span id="lattice-formula">Cu</span>
80
103
  <p id="lattice-material-note">{ui.materialNote}</p>
81
104
  </div>
82
105
 
83
- <label class="lattice-field lattice-field-select" for="lattice-structure">
106
+ <div class="lattice-field lattice-field-select">
84
107
  <span>{ui.lattice}</span>
85
- <select id="lattice-structure">
86
- {latticeOptions.map((opt) => (
87
- <option value={opt.id} data-short={ui[opt.shortKey]}>{ui[opt.uiKey]}</option>
108
+ <select class="lattice-native-select" id="lattice-structure" tabindex="-1" aria-hidden="true">
109
+ {latticeOptions.map((lattice) => (
110
+ <option value={lattice.id} data-short={latticeShortName(lattice)}>{latticeName(lattice)}</option>
88
111
  ))}
89
112
  </select>
90
- </label>
113
+ <button class="lattice-select-button" id="lattice-structure-button" type="button" aria-haspopup="listbox" aria-expanded="false">
114
+ <span>Face-centered cubic</span>
115
+ <i aria-hidden="true"></i>
116
+ </button>
117
+ <div class="lattice-select-menu" id="lattice-structure-menu" role="listbox" aria-label={ui.lattice}>
118
+ {latticeOptions.map((lattice) => (
119
+ <button type="button" role="option" data-select-target="lattice-structure" data-value={lattice.id}>
120
+ {latticeName(lattice)}
121
+ </button>
122
+ ))}
123
+ </div>
124
+ </div>
91
125
 
92
126
  <div class="lattice-slider-grid">
93
127
  <label class="lattice-field" for="lattice-rotation">
@@ -105,20 +139,20 @@ const materialOptions: Array<{ id: string; uiKey: string; noteKey: string }> = [
105
139
 
106
140
  <div class="lattice-input-grid">
107
141
  <label class="lattice-number" for="lattice-a">
108
- <span>{ui.latticeA} (Å)</span>
142
+ <span>{ui.latticeA}</span>
109
143
  <input id="lattice-a" type="text" inputmode="decimal" pattern="[0-9.]*" value="3.615" />
144
+ <em>Å</em>
110
145
  </label>
111
146
  <label class="lattice-number" for="lattice-c-ratio">
112
147
  <span>{ui.cRatio}</span>
113
148
  <input id="lattice-c-ratio" type="text" inputmode="decimal" pattern="[0-9.]*" value="1" />
114
149
  </label>
115
150
  <label class="lattice-number" for="lattice-mass">
116
- <span>{ui.molarMass} (g/mol)</span>
151
+ <span>{ui.molarMass}</span>
117
152
  <input id="lattice-mass" type="text" inputmode="decimal" pattern="[0-9.]*" value="63.546" />
153
+ <em>g/mol</em>
118
154
  </label>
119
155
  </div>
120
-
121
- <button class="lattice-reset" id="lattice-reset" type="button">{ui.resetView}</button>
122
156
  </section>
123
157
 
124
158
  <section class="lattice-results" aria-label="Crystal lattice results">
@@ -176,7 +210,8 @@ const materialOptions: Array<{ id: string; uiKey: string; noteKey: string }> = [
176
210
  const massInput = document.getElementById('lattice-mass') as HTMLInputElement | null;
177
211
  const gridLayer = document.getElementById('lattice-grid');
178
212
  const atomLayer = document.getElementById('lattice-atoms');
179
- const resetButton = document.getElementById('lattice-reset');
213
+ const customSelectButtons = document.querySelectorAll<HTMLButtonElement>('.lattice-select-button');
214
+ const customSelectOptions = document.querySelectorAll<HTMLButtonElement>('[data-select-target]');
180
215
 
181
216
  const labels = {
182
217
  formula: document.getElementById('lattice-formula'),
@@ -204,6 +239,26 @@ const materialOptions: Array<{ id: string; uiKey: string; noteKey: string }> = [
204
239
  return getMaterial(materialSelect?.value ?? MATERIAL_PRESETS[0].id);
205
240
  }
206
241
 
242
+ function closeSelectMenus() {
243
+ customSelectButtons.forEach((button) => {
244
+ button.setAttribute('aria-expanded', 'false');
245
+ button.closest('.lattice-field-select')?.classList.remove('lattice-select-open');
246
+ });
247
+ }
248
+
249
+ function syncCustomSelect(select: HTMLSelectElement | null) {
250
+ if (!select) return;
251
+
252
+ const button = document.getElementById(`${select.id}-button`);
253
+ const label = button?.querySelector('span');
254
+ const selected = select.options[select.selectedIndex];
255
+ if (label && selected) label.textContent = selected.textContent ?? '';
256
+
257
+ document.querySelectorAll<HTMLButtonElement>(`[data-select-target="${select.id}"]`).forEach((option) => {
258
+ option.setAttribute('aria-selected', `${option.dataset.value === select.value}`);
259
+ });
260
+ }
261
+
207
262
  function decimalValue(input: HTMLInputElement | null, fallback: number): number {
208
263
  if (!input) return fallback;
209
264
 
@@ -268,7 +323,7 @@ const materialOptions: Array<{ id: string; uiKey: string; noteKey: string }> = [
268
323
  const lattice = getLattice(structureSelect.value as LatticeId);
269
324
  const rotation = Number(rotationInput.value);
270
325
  const zoom = Number(zoomInput.value);
271
- const isHex = lattice.id === 'hexagonal-close-packed';
326
+ const isHex = lattice.cellGeometry === 'hexagonal';
272
327
  const projected = lattice.vertices.map((vertex) => projectPoint(vertex, rotation, zoom / 100, isHex));
273
328
 
274
329
  clearLayer(gridLayer);
@@ -328,13 +383,13 @@ const materialOptions: Array<{ id: string; uiKey: string; noteKey: string }> = [
328
383
  labels.volume!.textContent = `${formatNumber(result.cellVolumeCm3 * 1e24, 3)} ų`;
329
384
  labels.cellMass!.textContent = `${result.cellMassG.toExponential(3)} g`;
330
385
  labels.radius!.textContent = `${formatNumber(radius, 3)} Å`;
331
- resetButton?.classList.toggle('lattice-reset-active', rotationInput!.value !== '38' || zoomInput!.value !== '100');
332
386
  }
333
387
 
334
388
  function loadMaterial(materialId: string) {
335
389
  const material = getMaterial(materialId);
336
390
  const lattice = getLattice(material.latticeId);
337
391
 
392
+ syncCustomSelect(materialSelect);
338
393
  populateInputs(material, lattice);
339
394
  labels.formula!.textContent = material.formula;
340
395
  const selectedMaterial = materialSelect?.options[materialSelect.selectedIndex ?? -1];
@@ -347,20 +402,46 @@ const materialOptions: Array<{ id: string; uiKey: string; noteKey: string }> = [
347
402
  if (aInput) aInput.value = `${material.latticeA}`;
348
403
  if (cRatioInput) cRatioInput.value = `${material.cToA ?? lattice.cToA ?? 1}`;
349
404
  if (massInput) massInput.value = `${material.atomicMass}`;
405
+ syncCustomSelect(structureSelect);
350
406
  }
351
407
 
408
+ customSelectButtons.forEach((button) => {
409
+ button.addEventListener('click', () => {
410
+ const isOpen = button.getAttribute('aria-expanded') === 'true';
411
+ closeSelectMenus();
412
+ button.setAttribute('aria-expanded', `${!isOpen}`);
413
+ button.closest('.lattice-field-select')?.classList.toggle('lattice-select-open', !isOpen);
414
+ });
415
+ });
416
+
417
+ customSelectOptions.forEach((option) => {
418
+ option.addEventListener('click', () => {
419
+ const select = document.getElementById(option.dataset.selectTarget ?? '') as HTMLSelectElement | null;
420
+ if (!select || !option.dataset.value) return;
421
+
422
+ select.value = option.dataset.value;
423
+ syncCustomSelect(select);
424
+ select.dispatchEvent(new Event('change', { bubbles: true }));
425
+ closeSelectMenus();
426
+ });
427
+ });
428
+
429
+ document.addEventListener('click', (event) => {
430
+ if (event.target instanceof Element && event.target.closest('.lattice-field-select')) return;
431
+ closeSelectMenus();
432
+ });
433
+
352
434
  materialSelect?.addEventListener('change', () => loadMaterial(materialSelect.value));
353
- structureSelect?.addEventListener('change', updateResults);
435
+ structureSelect?.addEventListener('change', () => {
436
+ syncCustomSelect(structureSelect);
437
+ updateResults();
438
+ });
354
439
  rotationInput?.addEventListener('input', updateResults);
355
440
  zoomInput?.addEventListener('input', updateResults);
356
441
  aInput?.addEventListener('input', updateResults);
357
442
  cRatioInput?.addEventListener('input', updateResults);
358
443
  massInput?.addEventListener('input', updateResults);
359
- resetButton?.addEventListener('click', () => {
360
- if (rotationInput) rotationInput.value = '38';
361
- if (zoomInput) zoomInput.value = '100';
362
- updateResults();
363
- });
364
-
444
+ syncCustomSelect(materialSelect);
445
+ syncCustomSelect(structureSelect);
365
446
  loadMaterial(materialSelect?.value ?? MATERIAL_PRESETS[0].id);
366
447
  </script>
@@ -17,7 +17,8 @@
17
17
  --lattice-button-text: #fff;
18
18
  --lattice-structure-label: #187c78;
19
19
  --lattice-main-value: #9f4f23;
20
- --lattice-reset-bg: rgba(19, 40, 44, 0.04);
20
+ --lattice-divider: rgba(19, 40, 44, 0.12);
21
+ --lattice-menu-bg: rgba(247, 252, 249, 0.88);
21
22
 
22
23
  display: grid;
23
24
  grid-template-columns: minmax(0, 1.15fr) minmax(280px, 0.85fr);
@@ -51,7 +52,8 @@ html.theme-dark .lattice-lab {
51
52
  --lattice-button-text: #071314;
52
53
  --lattice-structure-label: #91d8c8;
53
54
  --lattice-main-value: #f6d37a;
54
- --lattice-reset-bg: rgba(255, 255, 255, 0.04);
55
+ --lattice-divider: rgba(255, 255, 255, 0.08);
56
+ --lattice-menu-bg: rgba(10, 22, 27, 0.86);
55
57
  }
56
58
 
57
59
  .lattice-viewer {
@@ -123,7 +125,8 @@ html.theme-dark .lattice-lab {
123
125
 
124
126
  .lattice-summary strong {
125
127
  color: var(--lattice-main-value);
126
- font-size: clamp(1.35rem, 5vw, 2rem);
128
+ font-size: 1.85rem;
129
+ font-variant-numeric: tabular-nums;
127
130
  line-height: 1;
128
131
  }
129
132
 
@@ -140,6 +143,8 @@ html.theme-dark .lattice-lab {
140
143
  display: flex;
141
144
  flex-wrap: wrap;
142
145
  gap: 0.55rem;
146
+ align-self: start;
147
+ padding-inline: 0.15rem;
143
148
  }
144
149
 
145
150
  .lattice-legend span {
@@ -190,17 +195,37 @@ html.theme-dark .lattice-lab {
190
195
  min-width: 0;
191
196
  }
192
197
 
198
+ .lattice-field-select {
199
+ position: relative;
200
+ }
201
+
193
202
  .lattice-number {
194
203
  position: relative;
204
+ gap: 0.2rem;
205
+ padding: 0.15rem 1rem 0.2rem;
206
+ }
207
+
208
+ .lattice-number + .lattice-number {
209
+ border-left: 1px solid var(--lattice-divider);
195
210
  }
196
211
 
197
212
  .lattice-number > span {
213
+ position: static;
214
+ font-size: 0.64rem;
215
+ pointer-events: none;
216
+ }
217
+
218
+ .lattice-number em {
198
219
  position: absolute;
199
- top: 0.55rem;
200
- left: 0.68rem;
201
- z-index: 1;
202
- font-size: 0.62rem;
220
+ right: 1rem;
221
+ bottom: 0.5rem;
222
+ color: var(--lattice-muted);
223
+ font-size: 0.9rem;
224
+ font-style: normal;
225
+ line-height: 1;
226
+ opacity: 0.3;
203
227
  pointer-events: none;
228
+ white-space: nowrap;
204
229
  }
205
230
 
206
231
  .lattice-field > span,
@@ -214,50 +239,121 @@ html.theme-dark .lattice-lab {
214
239
  text-transform: uppercase;
215
240
  }
216
241
 
217
- .lattice-field select,
242
+ .lattice-native-select {
243
+ position: absolute;
244
+ width: 1px;
245
+ height: 1px;
246
+ overflow: hidden;
247
+ opacity: 0;
248
+ pointer-events: none;
249
+ }
250
+
251
+ .lattice-select-button,
218
252
  .lattice-number input {
219
253
  width: 100%;
220
254
  min-height: 2.65rem;
221
- border: 1px solid var(--lattice-border);
222
- border-radius: 8px;
223
- background: var(--lattice-panel-strong);
255
+ border: 0;
256
+ border-radius: 0;
257
+ background: transparent;
224
258
  color: var(--lattice-text);
225
259
  font-size: inherit;
226
260
  font-weight: inherit;
227
261
  }
228
262
 
229
- .lattice-field select {
230
- appearance: none;
231
- padding: 0 2.45rem 0 0.7rem;
232
- background-image:
233
- linear-gradient(45deg, transparent 50%, currentcolor 50%),
234
- linear-gradient(135deg, currentcolor 50%, transparent 50%);
235
- background-position:
236
- calc(100% - 1.15rem) 50%,
237
- calc(100% - 0.82rem) 50%;
238
- background-repeat: no-repeat;
239
- background-size:
240
- 0.38rem 0.38rem,
241
- 0.38rem 0.38rem;
263
+ .lattice-select-button {
264
+ display: flex;
265
+ align-items: center;
266
+ justify-content: space-between;
267
+ min-height: 2.9rem;
268
+ padding: 0 0.15rem 0.35rem;
269
+ border-bottom: 1px solid var(--lattice-divider);
270
+ text-align: left;
271
+ cursor: pointer;
272
+ }
273
+
274
+ .lattice-select-button span {
275
+ overflow: hidden;
276
+ text-overflow: ellipsis;
277
+ white-space: nowrap;
278
+ }
279
+
280
+ .lattice-select-button i {
281
+ width: 0.48rem;
282
+ height: 0.48rem;
283
+ margin-left: 0.8rem;
284
+ border-right: 1.5px solid currentcolor;
285
+ border-bottom: 1.5px solid currentcolor;
286
+ opacity: 0.55;
287
+ transform: rotate(45deg) translateY(-0.12rem);
288
+ transition: transform 160ms ease;
289
+ }
290
+
291
+ .lattice-select-open .lattice-select-button i {
292
+ transform: rotate(225deg) translate(-0.08rem, -0.08rem);
293
+ }
294
+
295
+ .lattice-select-menu {
296
+ position: absolute;
297
+ z-index: 5;
298
+ top: calc(100% + 0.45rem);
299
+ right: 0;
300
+ left: 0;
301
+ display: none;
302
+ overflow: hidden;
303
+ border: 1px solid var(--lattice-border);
304
+ border-radius: 8px;
305
+ background: var(--lattice-menu-bg);
306
+ box-shadow: 0 22px 50px rgba(0, 0, 0, 0.18);
307
+ backdrop-filter: blur(12px);
308
+ }
309
+
310
+ .lattice-select-open .lattice-select-menu {
311
+ display: grid;
312
+ }
313
+
314
+ .lattice-select-menu button {
315
+ min-height: 2.45rem;
316
+ padding: 0 0.8rem;
317
+ border: 0;
318
+ border-bottom: 1px solid var(--lattice-divider);
319
+ background: transparent;
320
+ color: var(--lattice-text);
321
+ text-align: left;
322
+ cursor: pointer;
323
+ }
324
+
325
+ .lattice-select-menu button:last-child {
326
+ border-bottom: 0;
327
+ }
328
+
329
+ .lattice-select-menu button:hover,
330
+ .lattice-select-menu button[aria-selected="true"] {
331
+ background: color-mix(in srgb, var(--lattice-button-bg) 12%, transparent);
242
332
  }
243
333
 
244
334
  .lattice-number input {
245
- min-height: 4rem;
246
- padding: 1.45rem 0.65rem 0.45rem;
247
- font-size: 1.12rem;
335
+ min-height: 2.35rem;
336
+ padding: 0 2.55rem 0 0;
337
+ font-size: 1.15rem;
248
338
  font-variant-numeric: tabular-nums;
249
339
  }
250
340
 
251
- .lattice-field option {
252
- color: #102025;
341
+ .lattice-number input:focus,
342
+ .lattice-select-button:focus {
343
+ outline: 0;
344
+ }
345
+
346
+ .lattice-number:focus-within,
347
+ .lattice-field-select:focus-within {
348
+ color: var(--lattice-text);
253
349
  }
254
350
 
255
351
  .lattice-note {
256
352
  display: grid;
257
353
  grid-template-columns: auto 1fr;
258
- gap: 0.7rem;
354
+ gap: 0.85rem;
259
355
  align-items: center;
260
- padding: 0.75rem;
356
+ padding: 0.85rem 1rem;
261
357
  border: 1px solid var(--lattice-border);
262
358
  border-radius: 8px;
263
359
  background: var(--lattice-panel);
@@ -276,6 +372,7 @@ html.theme-dark .lattice-lab {
276
372
 
277
373
  .lattice-note p {
278
374
  margin: 0;
375
+ padding-right: 0.3rem;
279
376
  color: var(--lattice-text);
280
377
  font-size: 0.95rem;
281
378
  line-height: 1.35;
@@ -284,76 +381,111 @@ html.theme-dark .lattice-lab {
284
381
  .lattice-slider-grid,
285
382
  .lattice-input-grid {
286
383
  display: grid;
287
- gap: 0.7rem;
384
+ gap: 0.9rem;
385
+ }
386
+
387
+ .lattice-slider-grid .lattice-field {
388
+ grid-template-columns: minmax(0, 1fr) auto;
389
+ align-items: center;
390
+ gap: 0.35rem 0.75rem;
391
+ }
392
+
393
+ .lattice-slider-grid .lattice-field input {
394
+ grid-column: 1 / -1;
288
395
  }
289
396
 
290
397
  .lattice-input-grid {
291
398
  grid-template-columns: repeat(3, minmax(0, 1fr));
399
+ padding-block: 0.25rem;
292
400
  }
293
401
 
294
402
  .lattice-field output {
403
+ align-self: start;
295
404
  justify-self: end;
296
- margin-top: -1.55rem;
297
405
  color: var(--lattice-text);
406
+ font-size: 0.86rem;
298
407
  font-weight: 800;
408
+ font-variant-numeric: tabular-nums;
409
+ text-align: right;
299
410
  }
300
411
 
301
412
  .lattice-field input[type="range"] {
413
+ appearance: none;
302
414
  width: 100%;
303
- accent-color: #54d6b0;
415
+ height: 1rem;
416
+ background: transparent;
304
417
  }
305
418
 
306
- .lattice-reset {
307
- min-height: 2.8rem;
308
- border: 1px solid var(--lattice-border);
309
- border-radius: 8px;
310
- background: var(--lattice-reset-bg);
311
- color: var(--lattice-muted);
312
- font-size: inherit;
313
- font-weight: inherit;
314
- cursor: pointer;
315
- transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
419
+ .lattice-field input[type="range"]::-webkit-slider-runnable-track {
420
+ height: 2px;
421
+ border-radius: 999px;
422
+ background: var(--lattice-divider);
316
423
  }
317
424
 
318
- .lattice-reset-active,
319
- .lattice-reset:hover {
320
- border-color: color-mix(in srgb, var(--lattice-button-bg) 45%, var(--lattice-border));
321
- background: color-mix(in srgb, var(--lattice-button-bg) 12%, transparent);
322
- color: var(--lattice-text);
425
+ .lattice-field input[type="range"]::-webkit-slider-thumb {
426
+ appearance: none;
427
+ width: 0.86rem;
428
+ height: 0.86rem;
429
+ margin-top: -0.39rem;
430
+ border: 0;
431
+ border-radius: 50%;
432
+ background: var(--lattice-button-bg);
433
+ }
434
+
435
+ .lattice-field input[type="range"]::-moz-range-track {
436
+ height: 2px;
437
+ border-radius: 999px;
438
+ background: var(--lattice-divider);
439
+ }
440
+
441
+ .lattice-field input[type="range"]::-moz-range-thumb {
442
+ width: 0.86rem;
443
+ height: 0.86rem;
444
+ border: 0;
445
+ border-radius: 50%;
446
+ background: var(--lattice-button-bg);
323
447
  }
324
448
 
325
449
  .lattice-results {
326
450
  grid-column: 1 / -1;
327
451
  display: grid;
328
452
  grid-template-columns: repeat(2, minmax(0, 1fr));
329
- gap: clamp(1.5rem, 5vw, 4rem);
453
+ gap: 2.5rem;
330
454
  padding: 0.35rem 0.25rem 0;
331
455
  }
332
456
 
333
457
  .lattice-result-group {
334
458
  display: grid;
335
- gap: 0.7rem;
459
+ grid-template-columns: 1fr;
460
+ gap: 0.52rem;
336
461
  min-width: 0;
337
462
  }
338
463
 
339
464
  .lattice-result-group h2 {
340
- margin: 0 0 0.25rem;
465
+ margin: 0 0 0.35rem;
341
466
  }
342
467
 
343
468
  .lattice-metric-row {
344
469
  display: grid;
345
- grid-template-columns: minmax(0, 1fr) auto;
346
- gap: 1rem;
470
+ grid-template-columns: minmax(7.75rem, 8.75rem) minmax(0, 1fr);
471
+ gap: 0.9rem;
347
472
  align-items: baseline;
473
+ min-width: 0;
348
474
  }
349
475
 
350
476
  .lattice-metric-row strong {
351
477
  overflow-wrap: anywhere;
352
478
  color: var(--lattice-text);
353
- font-size: 1.05rem;
479
+ font-size: 1.25rem;
354
480
  font-variant-numeric: tabular-nums;
355
481
  line-height: 1.15;
356
- text-align: right;
482
+ text-align: left;
483
+ }
484
+
485
+ .lattice-metric-row span {
486
+ max-width: 8.75rem;
487
+ font-size: 0.68rem;
488
+ line-height: 1.2;
357
489
  }
358
490
 
359
491
  @media (max-width: 900px) {
@@ -364,6 +496,10 @@ html.theme-dark .lattice-lab {
364
496
  .lattice-results {
365
497
  grid-template-columns: 1fr;
366
498
  }
499
+
500
+ .lattice-result-group {
501
+ grid-template-columns: 1fr;
502
+ }
367
503
  }
368
504
 
369
505
  @media (max-width: 560px) {
@@ -381,6 +517,18 @@ html.theme-dark .lattice-lab {
381
517
  grid-template-columns: 1fr;
382
518
  }
383
519
 
520
+ .lattice-number + .lattice-number {
521
+ border-left: 0;
522
+ }
523
+
524
+ .lattice-number {
525
+ padding-inline: 0;
526
+ }
527
+
528
+ .lattice-metric-row {
529
+ grid-template-columns: minmax(6.8rem, 7.5rem) minmax(0, 1fr);
530
+ }
531
+
384
532
  .lattice-note {
385
533
  grid-template-columns: 1fr;
386
534
  }