@things-factory/figure-ui 10.1.46 → 10.1.48

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 (148) hide show
  1. package/client/modeller/angle-dial.ts +70 -0
  2. package/client/modeller/figure-settings.ts +328 -0
  3. package/client/modeller/figure-v3-capability.ts +137 -0
  4. package/client/modeller/figure-v3-clips.ts +253 -0
  5. package/client/modeller/figure-v3-controls.ts +212 -0
  6. package/client/modeller/figure-v3-links.ts +61 -81
  7. package/client/modeller/figure-v3-material.ts +108 -35
  8. package/client/modeller/figure-v3-parts.ts +96 -3
  9. package/client/modeller/figure-v3-preview.ts +326 -0
  10. package/client/modeller/figure-v3-properties.ts +29 -57
  11. package/client/modeller/figure-v3-verdict.ts +598 -60
  12. package/client/modeller/figure-view.ts +152 -0
  13. package/client/modeller/side-panel-styles.ts +326 -0
  14. package/client/modeller/v3-canvas.ts +254 -262
  15. package/client/modeller/v3-clips.ts +89 -0
  16. package/client/modeller/v3-controls.ts +54 -0
  17. package/client/modeller/v3-editor.ts +566 -223
  18. package/client/modeller/v3-fields.ts +84 -0
  19. package/client/modeller/v3-help.ts +41 -0
  20. package/client/modeller/v3-joint-fan.ts +292 -0
  21. package/client/modeller/v3-keys.ts +34 -0
  22. package/client/modeller/v3-links.ts +101 -9
  23. package/client/modeller/v3-live.ts +79 -0
  24. package/client/modeller/v3-maturity.ts +415 -0
  25. package/client/modeller/v3-palette.ts +45 -0
  26. package/client/modeller/v3-preview-model.ts +54 -0
  27. package/client/modeller/v3-stage-model.ts +159 -0
  28. package/client/modeller/v3-thumbnail.ts +8 -4
  29. package/client/pages/figure-list-page.ts +4 -4
  30. package/client/pages/figure-v3-modeller-page.ts +66 -134
  31. package/dist-client/modeller/angle-dial.d.ts +32 -0
  32. package/dist-client/modeller/angle-dial.js +41 -0
  33. package/dist-client/modeller/angle-dial.js.map +1 -0
  34. package/dist-client/modeller/figure-settings.d.ts +39 -0
  35. package/dist-client/modeller/figure-settings.js +317 -0
  36. package/dist-client/modeller/figure-settings.js.map +1 -0
  37. package/dist-client/modeller/figure-v3-capability.d.ts +22 -0
  38. package/dist-client/modeller/figure-v3-capability.js +141 -0
  39. package/dist-client/modeller/figure-v3-capability.js.map +1 -0
  40. package/dist-client/modeller/figure-v3-clips.d.ts +31 -0
  41. package/dist-client/modeller/figure-v3-clips.js +268 -0
  42. package/dist-client/modeller/figure-v3-clips.js.map +1 -0
  43. package/dist-client/modeller/figure-v3-controls.d.ts +36 -0
  44. package/dist-client/modeller/figure-v3-controls.js +226 -0
  45. package/dist-client/modeller/figure-v3-controls.js.map +1 -0
  46. package/dist-client/modeller/figure-v3-links.d.ts +4 -1
  47. package/dist-client/modeller/figure-v3-links.js +64 -82
  48. package/dist-client/modeller/figure-v3-links.js.map +1 -1
  49. package/dist-client/modeller/figure-v3-material.d.ts +4 -1
  50. package/dist-client/modeller/figure-v3-material.js +109 -35
  51. package/dist-client/modeller/figure-v3-material.js.map +1 -1
  52. package/dist-client/modeller/figure-v3-parts.d.ts +10 -0
  53. package/dist-client/modeller/figure-v3-parts.js +88 -3
  54. package/dist-client/modeller/figure-v3-parts.js.map +1 -1
  55. package/dist-client/modeller/figure-v3-preview.d.ts +34 -0
  56. package/dist-client/modeller/figure-v3-preview.js +322 -0
  57. package/dist-client/modeller/figure-v3-preview.js.map +1 -0
  58. package/dist-client/modeller/figure-v3-properties.d.ts +1 -1
  59. package/dist-client/modeller/figure-v3-properties.js +29 -57
  60. package/dist-client/modeller/figure-v3-properties.js.map +1 -1
  61. package/dist-client/modeller/figure-v3-verdict.d.ts +31 -17
  62. package/dist-client/modeller/figure-v3-verdict.js +579 -59
  63. package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
  64. package/dist-client/modeller/figure-view.d.ts +118 -0
  65. package/dist-client/modeller/figure-view.js +78 -0
  66. package/dist-client/modeller/figure-view.js.map +1 -0
  67. package/dist-client/modeller/side-panel-styles.d.ts +23 -0
  68. package/dist-client/modeller/side-panel-styles.js +326 -0
  69. package/dist-client/modeller/side-panel-styles.js.map +1 -0
  70. package/dist-client/modeller/v3-canvas.d.ts +44 -37
  71. package/dist-client/modeller/v3-canvas.js +230 -260
  72. package/dist-client/modeller/v3-canvas.js.map +1 -1
  73. package/dist-client/modeller/v3-clips.d.ts +39 -0
  74. package/dist-client/modeller/v3-clips.js +58 -0
  75. package/dist-client/modeller/v3-clips.js.map +1 -0
  76. package/dist-client/modeller/v3-controls.d.ts +28 -0
  77. package/dist-client/modeller/v3-controls.js +28 -0
  78. package/dist-client/modeller/v3-controls.js.map +1 -0
  79. package/dist-client/modeller/v3-editor.d.ts +73 -1
  80. package/dist-client/modeller/v3-editor.js +593 -218
  81. package/dist-client/modeller/v3-editor.js.map +1 -1
  82. package/dist-client/modeller/v3-fields.d.ts +39 -0
  83. package/dist-client/modeller/v3-fields.js +69 -0
  84. package/dist-client/modeller/v3-fields.js.map +1 -0
  85. package/dist-client/modeller/v3-help.d.ts +18 -0
  86. package/dist-client/modeller/v3-help.js +37 -0
  87. package/dist-client/modeller/v3-help.js.map +1 -0
  88. package/dist-client/modeller/v3-joint-fan.d.ts +9 -0
  89. package/dist-client/modeller/v3-joint-fan.js +264 -0
  90. package/dist-client/modeller/v3-joint-fan.js.map +1 -0
  91. package/dist-client/modeller/v3-keys.d.ts +14 -0
  92. package/dist-client/modeller/v3-keys.js +23 -0
  93. package/dist-client/modeller/v3-keys.js.map +1 -0
  94. package/dist-client/modeller/v3-links.d.ts +35 -1
  95. package/dist-client/modeller/v3-links.js +101 -8
  96. package/dist-client/modeller/v3-links.js.map +1 -1
  97. package/dist-client/modeller/v3-live.d.ts +31 -0
  98. package/dist-client/modeller/v3-live.js +58 -0
  99. package/dist-client/modeller/v3-live.js.map +1 -0
  100. package/dist-client/modeller/v3-maturity.d.ts +120 -0
  101. package/dist-client/modeller/v3-maturity.js +317 -0
  102. package/dist-client/modeller/v3-maturity.js.map +1 -0
  103. package/dist-client/modeller/v3-palette.d.ts +7 -0
  104. package/dist-client/modeller/v3-palette.js +42 -0
  105. package/dist-client/modeller/v3-palette.js.map +1 -0
  106. package/dist-client/modeller/v3-preview-model.d.ts +32 -0
  107. package/dist-client/modeller/v3-preview-model.js +48 -0
  108. package/dist-client/modeller/v3-preview-model.js.map +1 -0
  109. package/dist-client/modeller/v3-stage-model.d.ts +31 -0
  110. package/dist-client/modeller/v3-stage-model.js +121 -0
  111. package/dist-client/modeller/v3-stage-model.js.map +1 -0
  112. package/dist-client/modeller/v3-thumbnail.js +6 -3
  113. package/dist-client/modeller/v3-thumbnail.js.map +1 -1
  114. package/dist-client/pages/figure-list-page.js +4 -4
  115. package/dist-client/pages/figure-list-page.js.map +1 -1
  116. package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -6
  117. package/dist-client/pages/figure-v3-modeller-page.js +64 -136
  118. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  119. package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
  120. package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -1
  121. package/dist-client/tsconfig.tsbuildinfo +1 -1
  122. package/package.json +6 -6
  123. package/test/v3-clips.test.ts +70 -0
  124. package/test/v3-controls.test.ts +68 -0
  125. package/test/v3-fields.test.ts +71 -0
  126. package/test/v3-gizmo.test.ts +3 -5
  127. package/test/v3-joint-fan.test.ts +210 -0
  128. package/test/v3-keys.test.ts +37 -0
  129. package/test/v3-layout.test.ts +78 -0
  130. package/test/v3-links.test.ts +89 -6
  131. package/test/v3-live.test.ts +99 -0
  132. package/test/v3-material.test.ts +6 -0
  133. package/test/v3-maturity.test.ts +268 -0
  134. package/test/v3-palette-icons.test.ts +22 -0
  135. package/test/v3-parts-wiring.test.ts +3 -1
  136. package/test/v3-preview.test.ts +42 -0
  137. package/test/v3-properties.test.ts +4 -2
  138. package/test/v3-scene-canvas.test.ts +85 -0
  139. package/test/v3-stage-model.test.ts +25 -0
  140. package/test/v3-theme.test.ts +16 -25
  141. package/test/v3-thumbnail.test.ts +29 -0
  142. package/test/v3-verdict.test.ts +12 -6
  143. package/test/v3-view.test.ts +20 -0
  144. package/translations/en.json +90 -6
  145. package/translations/ja.json +92 -8
  146. package/translations/ko.json +90 -6
  147. package/translations/ms.json +92 -8
  148. package/translations/zh.json +92 -8
@@ -0,0 +1,152 @@
1
+ /**
2
+ * 저작 화면의 **보기 설정**.
3
+ *
4
+ * ## 저장되지 않는다
5
+ *
6
+ * 이것은 자산이 아니라 **작업대**다. 조명을 밝게 하고 바닥을 켠 것은 그 사람이 지금
7
+ * 보기 편한 상태일 뿐, 그 자산의 성질이 아니다. 그래서 `FigureSource` 에 안 들어간다 —
8
+ * `toFigureSource` 는 판에서 `width` · `height` · `depth` 와 몇 가지만 집어 가고 나머지는
9
+ * 버린다.
10
+ *
11
+ * 다른 사람이 같은 자산을 열면 자기 설정으로 본다. 그것이 맞다.
12
+ *
13
+ * ## 씬이 아는 이름을 그대로 쓴다
14
+ *
15
+ * 여기 이름은 전부 `model-layer` 의 상태 이름이다. 옮겨 적는 표를 두지 않는다 — 표를
16
+ * 두면 씬이 새 설정을 받아도 이쪽이 모른다.
17
+ *
18
+ * 값이 바뀌면 씬이 스스로 반영한다(`three-capability` 의 `onchange`). 다만 `grid` 는
19
+ * 한동안 3D 를 세울 때만 읽혔다 — things-scene 10.1.5 에서 고쳤다.
20
+ */
21
+ export interface ViewSettings {
22
+ /**
23
+ * 어떤 도면에 놓인 것으로 보나 — 씬 전체의 좌표 모드.
24
+ *
25
+ * **도형의 `placement` 와 다른 것이다.** 도형의 것은 「이 종류는 바닥에 서나, 천장에
26
+ * 매달리나」이고, 이것은 「지금 보고 있는 도면이 어느 쪽을 기준으로 재나」다. 바닥
27
+ * 기준 도면에 천정 기반 OHT 를 놓으면 도면은 안 뒤집히고 OHT 가 천장 높이에 매달린다.
28
+ *
29
+ * 한동안 도형의 `placement` 를 이 자리에 흘려 넣었다. 모델러에 도형이 하나뿐이라
30
+ * 그럴듯해 보였지만, 그러면 도형의 성질이 씬 전체를 뒤집는다 — 도면에서는 성립할 수가
31
+ * 없고(도면에는 도형이 여럿이다), 저작 중에는 배치를 고를 때마다 저작자가 적어 둔
32
+ * 높이가 전부 다른 숫자로 보였다.
33
+ *
34
+ * 씬의 낱말 그대로다: `floor` 바닥 기준 · `space` 중심 기준(바닥 없음) · `inverted`
35
+ * 천장 기준. **미리보기에만 먹인다** — 편집 캔버스의 `zPos` 는 저작자가 직접 적는
36
+ * 숫자라 그 뜻이 보기 설정에 따라 바뀌면 안 된다.
37
+ */
38
+ placement: 'floor' | 'space' | 'inverted'
39
+ /**
40
+ * 천장 높이 (mm). 천정 기반 도형을 미리보기에서 어디에 매달 것인가.
41
+ *
42
+ * 도형은 제가 놓일 방의 높이를 모른다. 도면은 알지만 미리보기에는 도면이 없으므로,
43
+ * 저작자가 가정할 값을 여기서 정한다.
44
+ */
45
+ ceilingHeight: number
46
+ /** 좌표 바탕(축 가이드). 원점과 눈금을 보인다. */
47
+ grid: boolean
48
+ /** 축 이름표(X · Y · Z). 눈금만 두고 글자를 끌 수 있다. */
49
+ gridLabels: boolean
50
+ /** 판의 바닥면. 좌표 바탕과 겹치므로 기본은 끈다. */
51
+ floor: boolean
52
+ /** Environment — 반사(IBL). 금속 재질이 무엇을 비출지 정한다. */
53
+ environment: EnvironmentName
54
+ /** Sky Color — 배경색. */
55
+ skyColor: string
56
+ /** Key Light Intensity. */
57
+ dirLightIntensity: number
58
+ /** Key Light — Follow Camera. 끄면 그림자 방향이 고정된다. */
59
+ dirLightFollowCamera: boolean
60
+ /** Key Light — Cast Shadow. */
61
+ dirShadowEnabled: boolean
62
+ /** Hemisphere Light Intensity — 그늘진 면을 들어올린다. */
63
+ hemiIntensity: number
64
+ }
65
+
66
+ /**
67
+ * 환경 프리셋 — **`scene-manager` 의 `SkyPreset` 과 같은 여덟이다.**
68
+ *
69
+ * 한동안 다섯만 적었다. 씬은 여덟을 다 받는데 이쪽이 셋을 몰라서 고를 수 없었다.
70
+ * operato-board 의 3D 씬 속성 패널도 같은 여덟을 보인다.
71
+ */
72
+ export const ENVIRONMENTS = [
73
+ 'studio',
74
+ 'warehouse',
75
+ 'factory',
76
+ 'office',
77
+ 'home',
78
+ 'sunny',
79
+ 'cloudy',
80
+ 'rainy'
81
+ ] as const
82
+ export type EnvironmentName = (typeof ENVIRONMENTS)[number]
83
+
84
+ /**
85
+ * 씬에 그대로 넣는 보기 설정 — **좌표 기준과 천장 높이는 빼고.**
86
+ *
87
+ * 나머지는 전부 `model-layer` 의 상태 이름이라 그대로 넣으면 씬이 알아서 반응한다.
88
+ * 그런데 `placement` 는 편집 캔버스에 들어가면 안 되고(거기는 늘 상자 기준이다),
89
+ * `ceilingHeight` 는 씬이 모르는 이름이다.
90
+ *
91
+ * **rest spread 로 넘기지 않으려고 이 함수가 있다.** 캔버스가 한동안
92
+ * `const { grid, ..., ...light } = view` 로 나머지를 통째로 넘겼는데, 그러면 설정을
93
+ * 하나 더할 때마다 그것이 두 화면에 **말없이** 실린다. 새로 더한 좌표 기준이 바로
94
+ * 그 길로 캔버스에 들어갈 뻔했다.
95
+ */
96
+ export function sceneLook(view: ViewSettings): Record<string, unknown> {
97
+ const { placement, ceilingHeight, ...look } = view
98
+ return look
99
+ }
100
+
101
+ /**
102
+ * 씬의 좌표 기준 — **씬의 낱말 그대로 셋이다.**
103
+ *
104
+ * 형식의 `floor` · `center` · `ceiling` 과 짝이 맞지만 철자가 다르다. 옮겨 적는 표를 두지
105
+ * 않는 것이 이 파일의 규칙이므로 씬 쪽을 쓴다. 화면에는 둘 다 보인다 — 「천장 기준
106
+ * (inverted)」.
107
+ */
108
+ export const SCENE_REFERENCES = ['floor', 'space', 'inverted'] as const
109
+
110
+ /** 배경색 기본값. 회색이라 부품 색이 제 색으로 보인다. */
111
+ export const WORKBENCH = '#7c7c7c'
112
+
113
+ /**
114
+ * 처음 값.
115
+ *
116
+ * **지금 저작 화면이 실제로 쓰던 값 그대로다.** 설정을 만들면서 화면이 달라지면
117
+ * 무엇이 설정 때문이고 무엇이 원래 그랬는지 갈리지 않는다.
118
+ */
119
+ export const DEFAULT_VIEW: ViewSettings = {
120
+ placement: 'floor',
121
+ /* 공장 천장의 흔한 높이. 반도체 fab 의 OHT 레일이 대체로 이 언저리다. */
122
+ ceilingHeight: 4000,
123
+ grid: true,
124
+ gridLabels: true,
125
+ floor: false,
126
+ environment: 'studio',
127
+ skyColor: WORKBENCH,
128
+ dirLightIntensity: 2.0,
129
+ dirLightFollowCamera: true,
130
+ dirShadowEnabled: true,
131
+ hemiIntensity: 4.0
132
+ }
133
+
134
+ /**
135
+ * How tall the preview board is: enough room for the tallest instance where the figure actually stands.
136
+ *
137
+ * A floor or center figure stands on the floor, so the tallest instance is the whole height. Only a
138
+ * ceiling figure needs the room up to the ceiling. The board used to reach the ceiling for every figure,
139
+ * which made a 920 tall AGV preview 4400 tall.
140
+ *
141
+ * This is an envelope, not a placement rule: where each instance goes is still the scene's
142
+ * (`FigureRealObject.effectiveZPos`), and the camera fits what the scene drew.
143
+ */
144
+ export function previewBoardDepth(
145
+ figure: { placement?: 'floor' | 'ceiling' | 'center'; base: { y: number } },
146
+ ceilingHeight: number,
147
+ factor: number
148
+ ): number {
149
+ const tallest = figure.base.y * factor
150
+ const reach = figure.placement === 'ceiling' ? Math.max(ceilingHeight, tallest) : tallest
151
+ return Math.round(reach * 1.1)
152
+ }
@@ -0,0 +1,326 @@
1
+ import { css } from 'lit'
2
+
3
+ /**
4
+ * The side panel's one look -- V2's inspector, for every element that draws in the V3 modeller's side.
5
+ *
6
+ * The side is drawn by several elements (the type field, the figure's properties, a part's values, fastening, places,
7
+ * motion, material, controls, clips, the environment) and each had styled its own fields: three label styles and
8
+ * three field heights in one column (user, 2026-09-24: "why are the styles and layout all different?"). They share
9
+ * this sheet now and keep only what is theirs.
10
+ *
11
+ * The vocabulary, V2's:
12
+ * section one titled group; the side is a stack of them
13
+ * h3 a group's title -- small capitals
14
+ * div[inline-field] a short label and its one field on one line
15
+ * div[field] a label above a full-width field
16
+ * div[form] short labels beside their fields, the fields starting on one line
17
+ * div[vector-row] a title, then X · Y · Z in one row of cells with the axis inside (`div[triple]` of
18
+ * `div[cell]`) -- three values read together, as vector tools lay them out
19
+ * div[pair] two controls side by side
20
+ * label[toggle] a checkbox and its words
21
+ * p[hint] · small the quiet line under a field
22
+ * button[act] an action under the fields it acts on
23
+ * [scrub] a label dragged sideways to scrub its field
24
+ */
25
+ export const SidePanelStyles = css`
26
+ :host {
27
+ --figure-control-height: 30px;
28
+ font-size: 0.76rem;
29
+ color: var(--md-sys-color-on-surface);
30
+ }
31
+ /* The side is a stack of sections, each a titled group (V2's inspector). */
32
+ section {
33
+ display: grid;
34
+ gap: 6px;
35
+ padding: 8px 10px;
36
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
37
+ }
38
+ section > h3 {
39
+ margin: 0;
40
+ }
41
+ section div[field] + div[field],
42
+ section div[inline-field] + div[inline-field],
43
+ section div[vector-row] + div[vector-row] {
44
+ margin-top: 0;
45
+ }
46
+ h3 {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 4px;
50
+ margin: 14px 0 6px;
51
+ font-size: var(--ui-t-micro, 11px);
52
+ font-weight: 700;
53
+ letter-spacing: 0.04em;
54
+ text-transform: uppercase;
55
+ color: var(--md-sys-color-on-surface-variant);
56
+ }
57
+ h3:first-child {
58
+ margin-top: 0;
59
+ }
60
+ label,
61
+ div[form] > span {
62
+ font-size: 0.68rem;
63
+ color: var(--md-sys-color-on-surface-variant);
64
+ }
65
+ div[field] {
66
+ display: grid;
67
+ gap: 2px;
68
+ }
69
+ div[field] + div[field],
70
+ div[field] + div[form],
71
+ div[form] + div[field],
72
+ div[vector-row] + div[vector-row],
73
+ div[vector-row] + div[field],
74
+ div[field] + div[vector-row] {
75
+ margin-top: 5px;
76
+ }
77
+ div[form],
78
+ div[inline-field],
79
+ div[vector-row] {
80
+ display: grid;
81
+ grid-template-columns: 64px minmax(0, 1fr);
82
+ align-items: center;
83
+ gap: 5px 7px;
84
+ }
85
+ div[form] > span,
86
+ div[inline-field] > label,
87
+ div[vector-row] > label {
88
+ overflow-wrap: anywhere;
89
+ white-space: normal;
90
+ }
91
+ div[inline-field] + div[inline-field],
92
+ div[inline-field] + div[vector-row],
93
+ div[vector-row] + div[inline-field] {
94
+ margin-top: 5px;
95
+ }
96
+ /* A row of icon tools (align, mirror, snap), as V2's placement section had them. */
97
+ div[tools] {
98
+ display: flex;
99
+ flex-wrap: wrap;
100
+ gap: 4px;
101
+ }
102
+ div[tools] button {
103
+ display: inline-flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+ width: 30px;
107
+ height: 28px;
108
+ padding: 0;
109
+ color: var(--md-sys-color-on-surface);
110
+ background: var(--md-sys-color-surface-container-lowest);
111
+ border: 1px solid var(--md-sys-color-outline-variant);
112
+ border-radius: 6px;
113
+ cursor: pointer;
114
+ }
115
+ div[tools] button:hover:not(:disabled) {
116
+ background: var(--md-sys-color-surface-container);
117
+ }
118
+ div[tools] button:disabled {
119
+ opacity: 0.45;
120
+ cursor: default;
121
+ }
122
+ div[tools] md-icon {
123
+ font-size: 17px;
124
+ width: 17px;
125
+ height: 17px;
126
+ }
127
+ div[pair] {
128
+ display: flex;
129
+ gap: 4px;
130
+ }
131
+ div[pair] > * {
132
+ flex: 1;
133
+ min-width: 0;
134
+ }
135
+ div[triple] {
136
+ display: grid;
137
+ grid-template-columns: repeat(3, minmax(0, 1fr));
138
+ gap: 4px;
139
+ }
140
+ input,
141
+ select {
142
+ width: 100%;
143
+ min-width: 0;
144
+ min-height: var(--figure-control-height);
145
+ box-sizing: border-box;
146
+ padding: 4px 7px;
147
+ font: inherit;
148
+ font-size: 0.76rem;
149
+ color: var(--md-sys-color-on-surface);
150
+ background-color: var(--md-sys-color-surface-container-lowest);
151
+ border: 1px solid var(--md-sys-color-outline-variant);
152
+ border-radius: 6px;
153
+ }
154
+ input[type='checkbox'],
155
+ input[type='radio'] {
156
+ width: auto;
157
+ min-height: 0;
158
+ }
159
+ /* A thin track and a small thumb: a slider is a line to drag along, not a block (user, 2026-09-24). */
160
+ input[type='range'] {
161
+ -webkit-appearance: none;
162
+ appearance: none;
163
+ min-height: 16px;
164
+ height: 16px;
165
+ padding: 0;
166
+ border: none;
167
+ background: transparent;
168
+ cursor: pointer;
169
+ }
170
+ input[type='range']::-webkit-slider-runnable-track {
171
+ height: 3px;
172
+ border-radius: 2px;
173
+ background: var(--md-sys-color-outline-variant);
174
+ }
175
+ input[type='range']::-webkit-slider-thumb {
176
+ -webkit-appearance: none;
177
+ width: 12px;
178
+ height: 12px;
179
+ margin-top: -4.5px;
180
+ border: none;
181
+ border-radius: 50%;
182
+ background: var(--md-sys-color-primary);
183
+ }
184
+ input[type='range']::-moz-range-track {
185
+ height: 3px;
186
+ border-radius: 2px;
187
+ background: var(--md-sys-color-outline-variant);
188
+ }
189
+ input[type='range']::-moz-range-thumb {
190
+ width: 12px;
191
+ height: 12px;
192
+ border: none;
193
+ border-radius: 50%;
194
+ background: var(--md-sys-color-primary);
195
+ }
196
+ input[type='range']:focus {
197
+ outline: none;
198
+ }
199
+ input[type='range']:focus-visible::-webkit-slider-thumb {
200
+ outline: 2px solid var(--md-sys-color-primary);
201
+ outline-offset: 2px;
202
+ }
203
+ input[type='color'] {
204
+ padding: 2px;
205
+ width: 40px;
206
+ }
207
+ input:focus,
208
+ select:focus {
209
+ outline: 2px solid var(--md-sys-color-primary);
210
+ outline-offset: -1px;
211
+ }
212
+ input:disabled,
213
+ select:disabled {
214
+ opacity: 0.6;
215
+ }
216
+ /* A number with its axis inside the cell, on the left. */
217
+ div[cell] {
218
+ display: flex;
219
+ box-sizing: border-box;
220
+ height: var(--figure-control-height);
221
+ align-items: center;
222
+ gap: 2px;
223
+ padding-left: 6px;
224
+ background-color: var(--md-sys-color-surface-container-lowest);
225
+ border: 1px solid var(--md-sys-color-outline-variant);
226
+ border-radius: 6px;
227
+ }
228
+ div[cell]:focus-within {
229
+ outline: 2px solid var(--md-sys-color-primary);
230
+ outline-offset: -1px;
231
+ border-color: transparent;
232
+ }
233
+ /* A label that scrubs its field when dragged sideways (vector tools' way). */
234
+ [scrub] {
235
+ cursor: ew-resize;
236
+ user-select: none;
237
+ touch-action: none;
238
+ }
239
+ div[cell] span[axis] {
240
+ flex: none;
241
+ font-size: 0.64rem;
242
+ letter-spacing: 0.02em;
243
+ color: var(--md-sys-color-on-surface-variant);
244
+ }
245
+ div[cell] input {
246
+ min-height: 0;
247
+ height: calc(var(--figure-control-height) - 2px);
248
+ padding: 4px 6px 4px 2px;
249
+ font-family: var(--mono-font, monospace);
250
+ text-align: right;
251
+ background: none;
252
+ border: none;
253
+ border-radius: 0;
254
+ }
255
+ div[cell] input:focus {
256
+ outline: none;
257
+ }
258
+ label[toggle] {
259
+ display: flex;
260
+ align-items: center;
261
+ gap: 6px;
262
+ font-size: 0.76rem;
263
+ color: var(--md-sys-color-on-surface);
264
+ }
265
+ /* The 「?」 beside a title: no border, it says there is more rather than asking to be pressed (V2). */
266
+ button[help] {
267
+ display: inline-flex;
268
+ align-items: center;
269
+ padding: 0;
270
+ margin: 0;
271
+ color: var(--md-sys-color-outline);
272
+ background: none;
273
+ border: none;
274
+ cursor: pointer;
275
+ }
276
+ button[help] md-icon {
277
+ font-size: 13px;
278
+ width: 13px;
279
+ height: 13px;
280
+ }
281
+ button[help]:hover,
282
+ button[help][aria-expanded='true'] {
283
+ color: var(--md-sys-color-primary);
284
+ }
285
+ p[hint],
286
+ small {
287
+ display: block;
288
+ margin: 2px 0 4px;
289
+ font-size: 0.68rem;
290
+ line-height: 1.5;
291
+ color: var(--md-sys-color-on-surface-variant);
292
+ }
293
+ p[hint] {
294
+ padding-left: 6px;
295
+ border-left: 2px solid var(--md-sys-color-outline-variant);
296
+ }
297
+ button[act] {
298
+ display: inline-flex;
299
+ align-items: center;
300
+ justify-content: center;
301
+ gap: 4px;
302
+ width: 100%;
303
+ margin-top: 4px;
304
+ padding: 3px 8px;
305
+ min-height: 28px;
306
+ font: inherit;
307
+ font-size: 0.72rem;
308
+ color: var(--md-sys-color-on-surface);
309
+ background-color: var(--md-sys-color-surface-container);
310
+ border: 1px solid var(--md-sys-color-outline-variant);
311
+ border-radius: 6px;
312
+ cursor: pointer;
313
+ }
314
+ button[act]:hover:not(:disabled) {
315
+ background-color: var(--md-sys-color-surface-container-high);
316
+ }
317
+ button[act]:disabled {
318
+ opacity: 0.5;
319
+ cursor: default;
320
+ }
321
+ button[act] md-icon {
322
+ font-size: 15px;
323
+ width: 15px;
324
+ height: 15px;
325
+ }
326
+ `