@weasel-js/labkit 0.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 (217) hide show
  1. package/README.md +72 -0
  2. package/dist/_dts/fitViewToBounds-rWrBI4a6.d.ts +870 -0
  3. package/dist/_dts/index-C6Yze7sQ.d.ts +93 -0
  4. package/dist/_dts/types-Si4Fw-1F.d.ts +48 -0
  5. package/dist/_dts/useExperimentState-eSvpwV_P.d.ts +102 -0
  6. package/dist/canvas/index.d.ts +34 -0
  7. package/dist/canvas/index.js +4 -0
  8. package/dist/canvas/index.js.map +1 -0
  9. package/dist/chunk-2QNYYL3V.js +86 -0
  10. package/dist/chunk-2QNYYL3V.js.map +1 -0
  11. package/dist/chunk-54IQ2DX7.js +222 -0
  12. package/dist/chunk-54IQ2DX7.js.map +1 -0
  13. package/dist/chunk-6FO7G5BB.js +6095 -0
  14. package/dist/chunk-6FO7G5BB.js.map +1 -0
  15. package/dist/chunk-7BKDG73Z.js +54 -0
  16. package/dist/chunk-7BKDG73Z.js.map +1 -0
  17. package/dist/chunk-B5ZUYRKN.js +23330 -0
  18. package/dist/chunk-B5ZUYRKN.js.map +1 -0
  19. package/dist/chunk-HXZHVU4G.js +126 -0
  20. package/dist/chunk-HXZHVU4G.js.map +1 -0
  21. package/dist/chunk-PWC7AQZM.js +8 -0
  22. package/dist/chunk-PWC7AQZM.js.map +1 -0
  23. package/dist/chunk-SD3HDXR7.js +161 -0
  24. package/dist/chunk-SD3HDXR7.js.map +1 -0
  25. package/dist/chunk-SSMHPMDV.js +350 -0
  26. package/dist/chunk-SSMHPMDV.js.map +1 -0
  27. package/dist/chunk-VLAHRJOC.js +201 -0
  28. package/dist/chunk-VLAHRJOC.js.map +1 -0
  29. package/dist/controls/index.d.ts +13 -0
  30. package/dist/controls/index.js +3 -0
  31. package/dist/controls/index.js.map +1 -0
  32. package/dist/dragdrop/index.d.ts +3 -0
  33. package/dist/dragdrop/index.js +3 -0
  34. package/dist/dragdrop/index.js.map +1 -0
  35. package/dist/fonts/oswald-latin-variable.woff2 +0 -0
  36. package/dist/index.d.ts +410 -0
  37. package/dist/index.js +1425 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/layers/index.d.ts +17 -0
  40. package/dist/layers/index.js +3 -0
  41. package/dist/layers/index.js.map +1 -0
  42. package/dist/passthrough/weasel-canvas.d.ts +258 -0
  43. package/dist/passthrough/weasel-canvas.js +3 -0
  44. package/dist/passthrough/weasel-canvas.js.map +1 -0
  45. package/dist/passthrough/weasel-ui.d.ts +2826 -0
  46. package/dist/passthrough/weasel-ui.js +4 -0
  47. package/dist/passthrough/weasel-ui.js.map +1 -0
  48. package/dist/primitives/index.d.ts +57 -0
  49. package/dist/primitives/index.js +4 -0
  50. package/dist/primitives/index.js.map +1 -0
  51. package/dist/state/index.d.ts +33 -0
  52. package/dist/state/index.js +34 -0
  53. package/dist/state/index.js.map +1 -0
  54. package/dist/styles.css +1224 -0
  55. package/dist/theme-interstellar.css +25 -0
  56. package/dist/theme-light.css +13 -0
  57. package/dist/ui/layers/index.d.ts +42 -0
  58. package/dist/ui/layers/index.js +5 -0
  59. package/dist/ui/layers/index.js.map +1 -0
  60. package/dist/undo/index.d.ts +30 -0
  61. package/dist/undo/index.js +3 -0
  62. package/dist/undo/index.js.map +1 -0
  63. package/package.json +99 -0
  64. package/src/canvas/AGENTS.md +72 -0
  65. package/src/canvas/CanvasStack.less +22 -0
  66. package/src/canvas/CanvasStack.stories.tsx +63 -0
  67. package/src/canvas/CanvasStack.test.tsx +82 -0
  68. package/src/canvas/CanvasStack.tsx +108 -0
  69. package/src/canvas/CanvasStackContext.ts +8 -0
  70. package/src/canvas/canvasCoords.test.ts +30 -0
  71. package/src/canvas/canvasCoords.ts +15 -0
  72. package/src/canvas/index.ts +6 -0
  73. package/src/canvas/useLayerScheduler.ts +72 -0
  74. package/src/canvas/usePanZoom.ts +98 -0
  75. package/src/controls/ControlPanel.less +61 -0
  76. package/src/controls/ControlPanel.stories.tsx +77 -0
  77. package/src/controls/ControlPanel.test.tsx +149 -0
  78. package/src/controls/ControlPanel.tsx +249 -0
  79. package/src/controls/index.ts +12 -0
  80. package/src/controls/types.ts +61 -0
  81. package/src/dragdrop/DragDropRuntime.tsx +135 -0
  82. package/src/dragdrop/DragGhost.less +17 -0
  83. package/src/dragdrop/DragGhost.tsx +24 -0
  84. package/src/dragdrop/Palette.less +34 -0
  85. package/src/dragdrop/Palette.tsx +27 -0
  86. package/src/dragdrop/dragDrop.test.tsx +145 -0
  87. package/src/dragdrop/index.ts +1 -0
  88. package/src/fonts/oswald-latin-variable.woff2 +0 -0
  89. package/src/index.ts +70 -0
  90. package/src/instrument/SineWave.smoke.test.tsx +94 -0
  91. package/src/instrument/capabilityDetector.test.ts +64 -0
  92. package/src/instrument/capabilityDetector.ts +17 -0
  93. package/src/instrument/defineInstrument.test.ts +27 -0
  94. package/src/instrument/defineInstrument.ts +5 -0
  95. package/src/instrument/index.ts +19 -0
  96. package/src/instrument/types.ts +65 -0
  97. package/src/instrument/validateConfigSchema.test.ts +125 -0
  98. package/src/instrument/validateConfigSchema.ts +65 -0
  99. package/src/lab/Lab.less +3 -0
  100. package/src/lab/Lab.stories.tsx +51 -0
  101. package/src/lab/Lab.test.tsx +126 -0
  102. package/src/lab/Lab.tsx +196 -0
  103. package/src/lab/LabContext.test.ts +11 -0
  104. package/src/lab/LabContext.ts +28 -0
  105. package/src/lab/LabShell.less +45 -0
  106. package/src/lab/LabShell.stories.tsx +39 -0
  107. package/src/lab/LabShell.test.tsx +59 -0
  108. package/src/lab/LabShell.tsx +33 -0
  109. package/src/lab/WorkspaceGrid.less +8 -0
  110. package/src/lab/WorkspaceGrid.stories.tsx +61 -0
  111. package/src/lab/WorkspaceGrid.test.tsx +40 -0
  112. package/src/lab/WorkspaceGrid.tsx +20 -0
  113. package/src/lab/gridDims.test.ts +35 -0
  114. package/src/lab/gridDims.ts +11 -0
  115. package/src/lab/index.ts +10 -0
  116. package/src/layers/AGENTS.md +65 -0
  117. package/src/layers/LayerList.less +53 -0
  118. package/src/layers/LayerList.test.tsx +49 -0
  119. package/src/layers/LayerList.tsx +105 -0
  120. package/src/layers/index.ts +3 -0
  121. package/src/passthrough/weasel-canvas.test.ts +30 -0
  122. package/src/passthrough/weasel-canvas.ts +23 -0
  123. package/src/passthrough/weasel-ui.test.ts +79 -0
  124. package/src/passthrough/weasel-ui.ts +150 -0
  125. package/src/primitives/FpsMeter.less +14 -0
  126. package/src/primitives/FpsMeter.stories.tsx +14 -0
  127. package/src/primitives/FpsMeter.test.tsx +38 -0
  128. package/src/primitives/FpsMeter.tsx +36 -0
  129. package/src/primitives/ScaleIndicator.less +25 -0
  130. package/src/primitives/ScaleIndicator.stories.tsx +16 -0
  131. package/src/primitives/ScaleIndicator.test.tsx +46 -0
  132. package/src/primitives/ScaleIndicator.tsx +48 -0
  133. package/src/primitives/Sidebar.less +57 -0
  134. package/src/primitives/Sidebar.stories.tsx +38 -0
  135. package/src/primitives/Sidebar.test.tsx +74 -0
  136. package/src/primitives/Sidebar.tsx +32 -0
  137. package/src/primitives/StatusBar.less +21 -0
  138. package/src/primitives/StatusBar.stories.tsx +24 -0
  139. package/src/primitives/StatusBar.test.tsx +25 -0
  140. package/src/primitives/StatusBar.tsx +18 -0
  141. package/src/primitives/Toolbar.less +43 -0
  142. package/src/primitives/Toolbar.stories.tsx +36 -0
  143. package/src/primitives/Toolbar.test.tsx +65 -0
  144. package/src/primitives/Toolbar.tsx +44 -0
  145. package/src/primitives/fpsAverage.test.ts +21 -0
  146. package/src/primitives/fpsAverage.ts +5 -0
  147. package/src/primitives/index.ts +9 -0
  148. package/src/state/SingletonExperiment.test.tsx +101 -0
  149. package/src/state/SingletonExperiment.tsx +52 -0
  150. package/src/state/adapters.test.ts +94 -0
  151. package/src/state/adapters.ts +102 -0
  152. package/src/state/context.tsx +42 -0
  153. package/src/state/helpers.test.ts +89 -0
  154. package/src/state/helpers.ts +61 -0
  155. package/src/state/index.ts +40 -0
  156. package/src/state/store.test.ts +243 -0
  157. package/src/state/store.ts +225 -0
  158. package/src/state/types.ts +53 -0
  159. package/src/state/useExperimentState.test.tsx +102 -0
  160. package/src/state/useExperimentState.ts +28 -0
  161. package/src/styles.less +20 -0
  162. package/src/test-setup.ts +17 -0
  163. package/src/theme/Interstellar.stories.less +92 -0
  164. package/src/theme/Interstellar.stories.tsx +243 -0
  165. package/src/theme/base.less +122 -0
  166. package/src/theme/interstellar.less +34 -0
  167. package/src/theme/light.less +14 -0
  168. package/src/theme/tokens.less +85 -0
  169. package/src/ui/format.ts +19 -0
  170. package/src/ui/layers/LayerStack.less +143 -0
  171. package/src/ui/layers/LayerStack.stories.tsx +45 -0
  172. package/src/ui/layers/LayerStack.test.tsx +170 -0
  173. package/src/ui/layers/LayerStack.tsx +215 -0
  174. package/src/ui/layers/index.ts +1 -0
  175. package/src/ui/properties/CheckboxRow.stories.tsx +27 -0
  176. package/src/ui/properties/ColorRow.stories.tsx +106 -0
  177. package/src/ui/properties/CurveField.less +48 -0
  178. package/src/ui/properties/CurveField.stories.tsx +20 -0
  179. package/src/ui/properties/CurveField.test.tsx +127 -0
  180. package/src/ui/properties/CurveField.tsx +181 -0
  181. package/src/ui/properties/EffectCard.tsx +312 -0
  182. package/src/ui/properties/Gallery.stories.tsx +178 -0
  183. package/src/ui/properties/NumberRow.stories.tsx +65 -0
  184. package/src/ui/properties/PropertyGroup.less +50 -0
  185. package/src/ui/properties/PropertyGroup.stories.tsx +34 -0
  186. package/src/ui/properties/PropertyGroup.test.tsx +33 -0
  187. package/src/ui/properties/PropertyGroup.tsx +44 -0
  188. package/src/ui/properties/PropertyList.stories.tsx +83 -0
  189. package/src/ui/properties/PropertyPanel.less +592 -0
  190. package/src/ui/properties/PropertyPanel.stories.tsx +191 -0
  191. package/src/ui/properties/PropertyPanel.test.tsx +193 -0
  192. package/src/ui/properties/PropertyPanel.tsx +441 -0
  193. package/src/ui/properties/PropertyRow.stories.tsx +53 -0
  194. package/src/ui/properties/SelectRow.stories.tsx +58 -0
  195. package/src/ui/properties/SliderRow.stories.tsx +74 -0
  196. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +396 -0
  197. package/src/ui/properties/TextRow.stories.tsx +62 -0
  198. package/src/ui/properties/ToggleRow.stories.tsx +57 -0
  199. package/src/ui/properties/index.ts +39 -0
  200. package/src/ui/properties/storyLayouts.tsx +36 -0
  201. package/src/undo/eventBus.test.ts +49 -0
  202. package/src/undo/eventBus.ts +32 -0
  203. package/src/undo/index.ts +5 -0
  204. package/src/undo/undoStack.test.ts +70 -0
  205. package/src/undo/undoStack.ts +38 -0
  206. package/src/workspace/DefaultSidebar.tsx +26 -0
  207. package/src/workspace/DefaultStatusBar.tsx +15 -0
  208. package/src/workspace/DefaultToolbar.tsx +88 -0
  209. package/src/workspace/Workspace.less +58 -0
  210. package/src/workspace/Workspace.stories.tsx +78 -0
  211. package/src/workspace/Workspace.test.tsx +186 -0
  212. package/src/workspace/Workspace.tsx +255 -0
  213. package/src/workspace/WorkspaceChrome.tsx +150 -0
  214. package/src/workspace/index.ts +24 -0
  215. package/src/workspace/slotTypes.ts +44 -0
  216. package/src/workspace/workspaceOps.test.ts +142 -0
  217. package/src/workspace/workspaceOps.ts +78 -0
@@ -0,0 +1,592 @@
1
+ // Composable property panel — visual cribbed from the speech-balloons lab.
2
+ // PropertyPanel is a glass card with optional <h2> + a 2-column grid body
3
+ // where consecutive .lk-property-row--color rows pair side-by-side and
4
+ // everything else spans the full width.
5
+
6
+ .lk-property-panel {
7
+ background: var(--lk-bg-elevated);
8
+ border: 1px solid var(--lk-border);
9
+ border-radius: var(--lk-radius-lg, 14px);
10
+ padding: 14px 16px;
11
+ backdrop-filter: blur(var(--lk-glass-blur, 0));
12
+ -webkit-backdrop-filter: blur(var(--lk-glass-blur, 0));
13
+ }
14
+
15
+ .lk-property-panel__title {
16
+ font-family: var(--lk-font-display);
17
+ font-weight: var(--lk-font-weight-light, 300);
18
+ font-size: 1.05rem;
19
+ line-height: 1.2;
20
+ letter-spacing: 0.01em;
21
+ color: var(--lk-text);
22
+ margin: 0 0 12px;
23
+ }
24
+
25
+ .lk-property-list {
26
+ display: grid;
27
+ grid-template-columns: 1fr 1fr;
28
+ column-gap: 18px;
29
+ row-gap: 6px;
30
+ align-items: stretch;
31
+ }
32
+
33
+ // Default packing: only color rows pair, everything else spans full width.
34
+ // Scoped to NOT apply in pairs mode (otherwise this 0,2,0 selector beats the
35
+ // pairs override below, which is only 0,1,0).
36
+ .lk-property-list:not(.lk-property-list--pairs) > :not(.lk-property-row--color) {
37
+ grid-column: 1 / -1;
38
+ }
39
+
40
+ // `pack="pairs"` flips that: every row auto-places into the 2-col grid two
41
+ // per row, and explicit full-width children opt in via the span helper class.
42
+ .lk-property-list--pairs > * {
43
+ min-width: 0;
44
+ }
45
+ .lk-property-list--pairs > .lk-property-list__span,
46
+ .lk-property-list--pairs > .lk-subpanel,
47
+ .lk-property-list--pairs > h3 {
48
+ grid-column: 1 / -1;
49
+ }
50
+
51
+ // ── Row ────────────────────────────────────────────────────────
52
+ // Default shape: label on top (with optional right-aligned readout),
53
+ // control below.
54
+
55
+ .lk-property-row {
56
+ display: flex;
57
+ flex-direction: column;
58
+ // No gap by default — controls sit flush under their label, matching
59
+ // speech-balloons `.field { gap: 0 }`. Slider rows pull tighter still
60
+ // via a negative margin on the label below.
61
+ gap: 0;
62
+ min-width: 0;
63
+ }
64
+
65
+ // Slider rows: pull the track up under the label so the row reads as one
66
+ // tight block rather than a label + a separate horizontal strip. Also
67
+ // bottom-align the track within the row so paired rows in a 2-col grid
68
+ // keep their tracks lined up even when one label wraps to two lines.
69
+ // Ported from speech-balloons styles.css:344-355.
70
+ .lk-property-row:has(> input[type='range']) {
71
+ justify-content: flex-end;
72
+ }
73
+ .lk-property-row:has(> input[type='range']) > .lk-property-row__label {
74
+ // Baseline-align (not center) so the readout input doesn't inflate the
75
+ // label-group height past the text baseline. Matches SB styles.css:351.
76
+ align-items: baseline;
77
+ justify-content: space-between;
78
+ gap: 4px;
79
+ margin-bottom: -4px;
80
+ }
81
+ .lk-property-row > input[type='range'] {
82
+ margin-top: auto;
83
+ }
84
+
85
+ .lk-property-row__label {
86
+ font-family: var(--lk-font-display);
87
+ font-weight: var(--lk-font-weight-light, 300);
88
+ font-size: 0.78rem;
89
+ line-height: 1.2;
90
+ color: var(--lk-text-muted);
91
+ text-transform: uppercase;
92
+ letter-spacing: 0.05em;
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 6px;
96
+ }
97
+
98
+ .lk-property-row__readout {
99
+ font-style: normal;
100
+ font-weight: var(--lk-font-weight-medium, 500);
101
+ color: var(--lk-accent);
102
+ margin-left: auto;
103
+ }
104
+
105
+ // Single always-rendered <input> styled to read as a text label until focused.
106
+ // Padding/margin/width copied verbatim from speech-balloons (styles.css:384-409)
107
+ // so visual density matches the source.
108
+ .lk-property-row__readout-input {
109
+ font: inherit;
110
+ font-weight: var(--lk-font-weight-medium, 500);
111
+ // Force line-height 1 (the label parent inherits 1.2, which would make
112
+ // the input box ~3px taller than the SB reference). Matches SB
113
+ // styles.css:385 `font: 500 0.78rem/1`.
114
+ line-height: 1;
115
+ color: var(--lk-accent);
116
+ background: transparent;
117
+ border: 1px solid transparent;
118
+ border-radius: 3px;
119
+ padding: 1px 0;
120
+ margin: 0;
121
+ width: calc(5em - 24px);
122
+ text-align: right;
123
+ cursor: text;
124
+ outline: none;
125
+ transition: background 80ms ease, border-color 80ms ease;
126
+ -moz-appearance: textfield;
127
+ }
128
+ .lk-property-row__readout-input:hover {
129
+ background: rgba(255, 255, 255, 0.06);
130
+ }
131
+ .lk-property-row__readout-input:focus {
132
+ background: rgba(0, 0, 0, 0.4);
133
+ border-color: var(--lk-accent);
134
+ }
135
+ .lk-property-row__readout-input::-webkit-inner-spin-button,
136
+ .lk-property-row__readout-input::-webkit-outer-spin-button {
137
+ -webkit-appearance: none;
138
+ margin: 0;
139
+ }
140
+
141
+ // Inline-block (not flex) so the browser's native vertical-align for
142
+ // <sub>/<sup> still works inside — flex collapses baseline semantics
143
+ // and sinks superscripts onto the baseline.
144
+ .lk-property-row__readout-group {
145
+ display: inline-block;
146
+ white-space: nowrap;
147
+ }
148
+ // Word-style units (e.g. "px") read as a small dim suffix next to the digits.
149
+ .lk-property-row__readout-unit {
150
+ color: var(--lk-text-muted);
151
+ font-size: 0.78em;
152
+ line-height: 1;
153
+ pointer-events: none;
154
+ }
155
+ // Symbol units (e.g. `<sup>°</sup>`) lean on the browser's native super
156
+ // positioning. Just dim them to match — do NOT set line-height: 0, that
157
+ // would re-collapse the glyph onto the baseline.
158
+ .lk-property-row__readout-group sup {
159
+ color: var(--lk-text-muted);
160
+ font-size: 0.7em;
161
+ pointer-events: none;
162
+ }
163
+
164
+ .lk-property-row input[type='range'] {
165
+ // Hairline track + small translucent thumb, tinted with the active --lk-accent.
166
+ -webkit-appearance: none;
167
+ appearance: none;
168
+ background: transparent;
169
+ width: 100%;
170
+ height: 12px;
171
+ margin: 0;
172
+ padding: 0;
173
+ cursor: pointer;
174
+ }
175
+ .lk-property-row input[type='range']::-webkit-slider-runnable-track {
176
+ height: 4px;
177
+ border-radius: 2px;
178
+ background: color-mix(in srgb, var(--lk-accent) 18%, transparent);
179
+ }
180
+ .lk-property-row input[type='range']::-moz-range-track {
181
+ height: 4px;
182
+ border-radius: 2px;
183
+ background: color-mix(in srgb, var(--lk-accent) 18%, transparent);
184
+ }
185
+ .lk-property-row input[type='range']::-webkit-slider-thumb {
186
+ -webkit-appearance: none;
187
+ appearance: none;
188
+ width: 8px;
189
+ height: 8px;
190
+ border-radius: 50%;
191
+ background: color-mix(in srgb, var(--lk-accent) 70%, transparent);
192
+ border: none;
193
+ margin-top: -2px;
194
+ }
195
+ .lk-property-row input[type='range']::-moz-range-thumb {
196
+ width: 8px;
197
+ height: 8px;
198
+ border-radius: 50%;
199
+ background: color-mix(in srgb, var(--lk-accent) 70%, transparent);
200
+ border: none;
201
+ }
202
+
203
+ // ── Inline layout: label sits to the left of the control. ──────
204
+ .lk-property-row--inline {
205
+ flex-direction: row;
206
+ align-items: center;
207
+ gap: 8px;
208
+ }
209
+
210
+ .lk-property-row--inline .lk-property-row__label {
211
+ flex: 0 0 auto;
212
+ // Label keeps its uppercase styling but won't expand to fill.
213
+ }
214
+
215
+ .lk-property-row--inline > input,
216
+ .lk-property-row--inline > select,
217
+ .lk-property-row--inline > .lk-property-row__toggle {
218
+ flex: 1 1 auto;
219
+ min-width: 0;
220
+ }
221
+
222
+ .lk-property-row--inline input[type='range'] {
223
+ width: auto;
224
+ }
225
+
226
+ .lk-property-row input[type='text']:not(.lk-property-row__readout-input),
227
+ .lk-property-row input[type='number'] {
228
+ background: rgba(0, 0, 0, 0.35);
229
+ color: var(--lk-text);
230
+ border: 1px solid var(--lk-border);
231
+ border-radius: var(--lk-radius-sm);
232
+ padding: 6px 8px;
233
+ font: 400 0.85rem/1 var(--lk-font);
234
+ }
235
+
236
+ .lk-property-row input[type='text']:not(.lk-property-row__readout-input):focus,
237
+ .lk-property-row input[type='number']:focus {
238
+ outline: 1px solid var(--lk-accent);
239
+ }
240
+
241
+ // Selects in property rows render borderless — chevron only, transparent
242
+ // background, no chrome. Matches the effect-kind-select pattern from SB
243
+ // styles.css:699-723. Keep the native arrow via appearance: auto so users
244
+ // still see something clickable.
245
+ .lk-property-row select {
246
+ background: transparent;
247
+ color: var(--lk-text);
248
+ border: 0;
249
+ border-radius: 0;
250
+ padding: 2px 0;
251
+ font: 400 0.85rem/1 var(--lk-font);
252
+ cursor: pointer;
253
+ }
254
+ .lk-property-row select:focus {
255
+ outline: 1px solid var(--lk-accent);
256
+ outline-offset: 2px;
257
+ }
258
+
259
+ // ── Color variant: label and swatch side-by-side, two per row in the grid.
260
+ // Switches to a 2-row grid when an alpha slider is rendered beneath.
261
+ .lk-property-row--color {
262
+ display: grid;
263
+ grid-template-columns: 1fr auto;
264
+ grid-template-rows: auto;
265
+ align-content: end;
266
+ column-gap: 8px;
267
+ row-gap: 0;
268
+ min-width: 0;
269
+ }
270
+
271
+ .lk-property-row--color .lk-property-row__label {
272
+ grid-column: 1;
273
+ grid-row: 1;
274
+ align-self: center;
275
+ flex: initial;
276
+ min-width: 0;
277
+ }
278
+
279
+ // Color swatch matches speech-balloons styles.css:451-461 exactly:
280
+ // 36×24, hairline border, no padding. Padding: 0 is important — the native
281
+ // <input type="color"> in some browsers (esp. Chrome) inflates beyond its
282
+ // declared box when given padding, which expands the grid row and pushes
283
+ // the paired slider track out of bottom alignment.
284
+ .lk-property-row--color input[type='color'] {
285
+ grid-column: 2;
286
+ grid-row: 1;
287
+ align-self: center;
288
+ box-sizing: border-box;
289
+ width: 36px;
290
+ height: 18px;
291
+ background: transparent;
292
+ border: 0;
293
+ border-radius: var(--lk-radius-sm);
294
+ padding: 0;
295
+ cursor: pointer;
296
+ flex-shrink: 0;
297
+ }
298
+
299
+ // Alpha slider: full-width second row beneath the color swatch.
300
+ .lk-property-row--color input[type='range'].lk-property-row__alpha {
301
+ grid-column: 1 / -1;
302
+ grid-row: 2;
303
+ height: 12px;
304
+ width: 100%;
305
+ margin: 0;
306
+ }
307
+ .lk-property-row--color input[type='range'].lk-property-row__alpha::-webkit-slider-runnable-track {
308
+ background: color-mix(in srgb, var(--lk-accent) 10%, transparent);
309
+ }
310
+ .lk-property-row--color input[type='range'].lk-property-row__alpha::-moz-range-track {
311
+ background: color-mix(in srgb, var(--lk-accent) 10%, transparent);
312
+ }
313
+ // Disabled state: dim the track, hide the thumb, swap the cursor — used when
314
+ // the consumer of the color drops alpha so the affordance reads as inert.
315
+ .lk-property-row--color.lk-property-row--alpha-disabled input[type='range'].lk-property-row__alpha {
316
+ cursor: not-allowed;
317
+ opacity: 0.55;
318
+ }
319
+ .lk-property-row--color.lk-property-row--alpha-disabled
320
+ input[type='range'].lk-property-row__alpha::-webkit-slider-runnable-track {
321
+ background: rgba(255, 255, 255, 0.05);
322
+ }
323
+ .lk-property-row--color.lk-property-row--alpha-disabled
324
+ input[type='range'].lk-property-row__alpha::-moz-range-track {
325
+ background: rgba(255, 255, 255, 0.05);
326
+ }
327
+ .lk-property-row--color.lk-property-row--alpha-disabled
328
+ input[type='range'].lk-property-row__alpha::-webkit-slider-thumb {
329
+ background: transparent;
330
+ }
331
+ .lk-property-row--color.lk-property-row--alpha-disabled
332
+ input[type='range'].lk-property-row__alpha::-moz-range-thumb {
333
+ background: transparent;
334
+ }
335
+
336
+ // ── Checkbox variant: control-first inline layout, no uppercase label.
337
+ .lk-property-row--checkbox {
338
+ flex-direction: row;
339
+ align-items: center;
340
+ gap: 8px;
341
+ cursor: pointer;
342
+ }
343
+
344
+ .lk-property-row--checkbox .lk-property-row__label {
345
+ text-transform: none;
346
+ letter-spacing: normal;
347
+ font-size: 0.85rem;
348
+ color: var(--lk-text);
349
+ // Place the label after the checkbox visually.
350
+ order: 1;
351
+ }
352
+
353
+ .lk-property-row--checkbox input[type='checkbox'] {
354
+ accent-color: var(--lk-accent);
355
+ width: 14px;
356
+ height: 14px;
357
+ cursor: pointer;
358
+ }
359
+
360
+ // ── Segmented toggle group ─────────────────────────────────────
361
+ .lk-property-row__toggle {
362
+ display: flex;
363
+ width: 100%;
364
+ border: 1px solid var(--lk-border);
365
+ border-radius: var(--lk-radius-sm);
366
+ overflow: hidden;
367
+ }
368
+
369
+ .lk-property-row__toggle-button {
370
+ flex: 1;
371
+ height: 26px;
372
+ padding: 0 8px;
373
+ background: rgba(0, 0, 0, 0.25);
374
+ color: var(--lk-text-muted);
375
+ border: 0;
376
+ border-radius: 0;
377
+ font: 400 0.78rem/1 var(--lk-font);
378
+ cursor: pointer;
379
+ backdrop-filter: none;
380
+ -webkit-backdrop-filter: none;
381
+ }
382
+
383
+ .lk-property-row__toggle-button + .lk-property-row__toggle-button {
384
+ border-left: 1px solid var(--lk-border);
385
+ }
386
+
387
+ .lk-property-row__toggle-button:hover {
388
+ color: var(--lk-text);
389
+ border-color: var(--lk-border);
390
+ }
391
+
392
+ .lk-property-row__toggle-button--selected {
393
+ background: var(--lk-accent);
394
+ color: #0a0a14;
395
+ font-weight: var(--lk-font-weight-medium, 500);
396
+ }
397
+
398
+ .lk-property-row__toggle-button--selected:hover {
399
+ color: #0a0a14;
400
+ }
401
+
402
+ // ── Subpanel ────────────────────────────────────────────────────────
403
+ // Headered group of controls inside a list. The header is a typographic
404
+ // label flanked by a horizontal rule — no card chrome, just the divider.
405
+ .lk-subpanel {
406
+ display: grid;
407
+ grid-template-columns: 1fr 1fr;
408
+ column-gap: 10px;
409
+ row-gap: 3px;
410
+ align-items: stretch;
411
+ background: transparent;
412
+ border: 0;
413
+ padding: 0;
414
+ margin: 0;
415
+ }
416
+ .lk-subpanel > * { min-width: 0; }
417
+ .lk-subpanel > .lk-subpanel__title,
418
+ .lk-subpanel > .lk-property-list__span {
419
+ grid-column: 1 / -1;
420
+ }
421
+ .lk-subpanel__title {
422
+ font-family: var(--lk-font-display);
423
+ font-weight: 400;
424
+ font-size: 0.72rem;
425
+ line-height: 1;
426
+ color: var(--lk-text);
427
+ text-transform: uppercase;
428
+ letter-spacing: 0.08em;
429
+ margin: 0;
430
+ padding: 6px 0 4px;
431
+ display: flex;
432
+ align-items: center;
433
+ gap: 8px;
434
+ }
435
+ .lk-subpanel__title > span { flex: 0 0 auto; }
436
+ .lk-subpanel__title > hr {
437
+ flex: 1 1 auto;
438
+ border: 0;
439
+ border-top: 1px solid rgba(255, 255, 255, 0.12);
440
+ margin: 0;
441
+ }
442
+
443
+ // ── EffectCard ──────────────────────────────────────────────────────
444
+ // Draggable, collapsible card. Optional --lk-panel-accent re-binds the
445
+ // local --lk-accent so every descendant control auto-tints. Ported from
446
+ // speech-balloons styles.css:597-758 with class renames.
447
+ .lk-effect-card {
448
+ background: rgba(0, 0, 0, 0.25);
449
+ border: 1px solid var(--lk-border);
450
+ border-radius: var(--lk-radius-sm);
451
+ display: flex;
452
+ flex-direction: column;
453
+ overflow: hidden;
454
+ transition: background 120ms ease, border-color 120ms ease;
455
+ }
456
+ .lk-effect-card.is-dragging { opacity: 0.45; }
457
+ .lk-effect-card.is-expanded { background: rgba(0, 0, 0, 0.3); }
458
+
459
+ .lk-effect-card.has-accent {
460
+ // Re-bind --lk-accent inside the card so every descendant style that paints
461
+ // with var(--lk-accent) (readouts, slider thumb, etc.) picks up the
462
+ // per-card tint automatically.
463
+ --lk-accent: var(--lk-panel-accent);
464
+ background: color-mix(in srgb, var(--lk-panel-accent) 4%, rgba(0, 0, 0, 0.28));
465
+ border-color: color-mix(in srgb, var(--lk-panel-accent) 28%, var(--lk-border));
466
+ border-left-width: 4px;
467
+ border-left-color: var(--lk-panel-accent);
468
+ }
469
+ .lk-effect-card.has-accent.is-expanded {
470
+ background: color-mix(in srgb, var(--lk-panel-accent) 5%, rgba(0, 0, 0, 0.35));
471
+ }
472
+ .lk-effect-card.has-accent .lk-effect-card__head {
473
+ background: color-mix(in srgb, var(--lk-panel-accent) 38%, rgba(0, 0, 0, 0.4));
474
+ }
475
+ .lk-effect-card.has-accent .lk-effect-card__head:hover {
476
+ background: color-mix(in srgb, var(--lk-panel-accent) 48%, rgba(0, 0, 0, 0.35));
477
+ }
478
+
479
+ .lk-effect-card__head {
480
+ display: flex;
481
+ align-items: baseline;
482
+ gap: 8px;
483
+ padding: 2px 10px;
484
+ cursor: pointer;
485
+ user-select: none;
486
+ }
487
+ .lk-effect-card__head:hover { background: rgba(255, 255, 255, 0.03); }
488
+
489
+ .lk-effect-card__handle {
490
+ display: inline-flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ color: var(--lk-text-muted);
494
+ cursor: grab;
495
+ padding: 0 2px;
496
+ }
497
+ .lk-effect-card__handle:active { cursor: grabbing; }
498
+
499
+ .lk-effect-card__index-badge {
500
+ // Rendered as a <button> (keyboard-operable drag/toggle handle); strip the
501
+ // native button chrome so it matches the surrounding badge styling.
502
+ appearance: none;
503
+ border: 0;
504
+ background: none;
505
+ display: inline-flex;
506
+ align-items: baseline;
507
+ justify-content: center;
508
+ min-width: 14px;
509
+ padding: 0 2px;
510
+ color: var(--lk-panel-accent, var(--lk-text-muted));
511
+ font-size: 14px;
512
+ font-weight: 700;
513
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
514
+ line-height: 1;
515
+ }
516
+
517
+ .lk-effect-card__title {
518
+ font-family: var(--lk-font-display);
519
+ font-weight: 500;
520
+ font-size: 0.9rem;
521
+ line-height: 1;
522
+ color: var(--lk-text);
523
+ text-transform: uppercase;
524
+ letter-spacing: 0.04em;
525
+ }
526
+
527
+ .lk-effect-card__primary {
528
+ font: 400 0.75rem/1 var(--lk-font);
529
+ color: var(--lk-text);
530
+ background: rgba(0, 0, 0, 0.3);
531
+ border: 1px solid var(--lk-border);
532
+ border-radius: var(--lk-radius-sm);
533
+ padding: 2px 6px;
534
+ margin-left: auto;
535
+ }
536
+ // Push the remove button to the right edge — whether it follows a primary
537
+ // chip or sits alone after the title.
538
+ .lk-effect-card__head .lk-effect-card__remove,
539
+ .lk-effect-card__head .lk-effect-card__primary + .lk-effect-card__remove {
540
+ margin-left: auto;
541
+ }
542
+ .lk-effect-card__head .lk-effect-card__primary ~ .lk-effect-card__remove {
543
+ margin-left: 4px;
544
+ }
545
+
546
+ .lk-effect-card__remove {
547
+ background: transparent;
548
+ color: var(--lk-text-muted);
549
+ border: 1px solid transparent;
550
+ border-radius: 4px;
551
+ width: 22px;
552
+ height: 22px;
553
+ font-size: 0.85rem;
554
+ cursor: pointer;
555
+ display: inline-flex;
556
+ align-items: center;
557
+ justify-content: center;
558
+ }
559
+ .lk-effect-card__remove:hover {
560
+ color: #ffb3a8;
561
+ border-color: rgba(240, 68, 56, 0.4);
562
+ background: rgba(240, 68, 56, 0.08);
563
+ }
564
+
565
+ .lk-effect-card__body {
566
+ padding: 4px 12px 12px;
567
+ border-top: 1px solid var(--lk-border);
568
+ // Body content is typically a PropertyList — the consumer chooses pack mode.
569
+ }
570
+
571
+ // ── EffectCardList ──────────────────────────────────────────────────
572
+ .lk-effect-card-list {
573
+ display: flex;
574
+ flex-direction: column;
575
+ gap: 6px;
576
+ }
577
+ .lk-effect-card-list__wrap { position: relative; }
578
+ .lk-effect-card-list__drop-hint {
579
+ height: 0;
580
+ border-top: 2px solid var(--lk-accent);
581
+ border-radius: 2px;
582
+ margin: 2px 0;
583
+ box-shadow: 0 0 6px color-mix(in srgb, var(--lk-accent) 60%, transparent);
584
+ }
585
+ .lk-effect-card-list__empty {
586
+ font: var(--lk-font-weight-light, 300) 0.78rem/1.4 var(--lk-font-display);
587
+ color: var(--lk-text-muted);
588
+ text-align: center;
589
+ padding: 16px 12px;
590
+ border: 1px dashed var(--lk-border);
591
+ border-radius: var(--lk-radius-sm);
592
+ }