@hyperframes/studio 0.7.71 → 0.7.73

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 (79) hide show
  1. package/dist/assets/{hyperframes-player-C1X90psg.js → hyperframes-player-DGlzhf_s.js} +1 -1
  2. package/dist/assets/index-B37rWXo4.css +1 -0
  3. package/dist/assets/{index-DzxDHPR1.js → index-CbVTOJ-E.js} +1 -1
  4. package/dist/assets/{index-nY4lLBqM.js → index-CcA5WVJv.js} +1 -1
  5. package/dist/assets/index-DXcLI2wu.js +428 -0
  6. package/dist/index.d.ts +8 -0
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +7762 -4261
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -7
  11. package/src/App.tsx +4 -4
  12. package/src/components/StudioLeftSidebar.tsx +2 -3
  13. package/src/components/StudioRightPanel.tsx +10 -4
  14. package/src/components/editor/AnimationCard.test.tsx +100 -0
  15. package/src/components/editor/AnimationCard.tsx +5 -2
  16. package/src/components/editor/EaseCurveSection.test.tsx +348 -0
  17. package/src/components/editor/EaseCurveSection.tsx +444 -237
  18. package/src/components/editor/EaseParamFields.test.tsx +188 -0
  19. package/src/components/editor/EaseParamFields.tsx +246 -0
  20. package/src/components/editor/PropertyPanel.test.tsx +9 -7
  21. package/src/components/editor/PropertyPanelFlat.tsx +75 -88
  22. package/src/components/editor/colorGradingFrameAnalysis.test.ts +101 -0
  23. package/src/components/editor/colorGradingFrameAnalysis.ts +203 -0
  24. package/src/components/editor/colorGradingScopePatch.test.ts +3 -3
  25. package/src/components/editor/easeCurveSvg.tsx +65 -0
  26. package/src/components/editor/easePresetLibrary.test.ts +114 -0
  27. package/src/components/editor/easePresetLibrary.ts +49 -0
  28. package/src/components/editor/gsapAnimationConstants.ts +15 -13
  29. package/src/components/editor/propertyPanelColorCurveGraph.tsx +549 -0
  30. package/src/components/editor/propertyPanelColorCurves.test.tsx +158 -0
  31. package/src/components/editor/propertyPanelColorCurves.tsx +185 -0
  32. package/src/components/editor/propertyPanelColorGradingControls.tsx +115 -77
  33. package/src/components/editor/propertyPanelColorScopes.tsx +258 -0
  34. package/src/components/editor/propertyPanelColorSecondary.test.tsx +181 -0
  35. package/src/components/editor/propertyPanelColorSecondary.tsx +448 -0
  36. package/src/components/editor/propertyPanelColorWheels.test.tsx +119 -0
  37. package/src/components/editor/propertyPanelColorWheels.tsx +334 -0
  38. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +109 -0
  39. package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +239 -41
  40. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +260 -300
  41. package/src/components/editor/propertyPanelFlatEffectControl.tsx +96 -0
  42. package/src/components/editor/propertyPanelFlatEffectSpecs.ts +305 -0
  43. package/src/components/editor/propertyPanelFlatEffectsSection.test.tsx +331 -0
  44. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +503 -0
  45. package/src/components/editor/propertyPanelFlatOverlaysSection.test.tsx +113 -0
  46. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +108 -0
  47. package/src/components/editor/propertyPanelFlatPrimitives.tsx +2 -0
  48. package/src/components/editor/propertyPanelGradingNumberField.test.tsx +79 -0
  49. package/src/components/editor/propertyPanelGradingNumberField.tsx +116 -0
  50. package/src/components/editor/propertyPanelPresetPreview.ts +36 -0
  51. package/src/components/editor/propertyPanelTypes.ts +13 -0
  52. package/src/components/editor/useColorGradingController.test.ts +331 -10
  53. package/src/components/editor/useColorGradingController.ts +57 -90
  54. package/src/components/editor/useColorGradingPreviews.ts +350 -0
  55. package/src/components/editor/useColorGradingScopes.test.tsx +143 -0
  56. package/src/components/editor/useColorGradingScopes.ts +62 -0
  57. package/src/components/editor/useInspectorGestureTransaction.ts +30 -1
  58. package/src/components/nle/NLEContext.tsx +1 -1
  59. package/src/contexts/PanelLayoutContext.tsx +3 -6
  60. package/src/hooks/useBlockCatalog.ts +31 -13
  61. package/src/hooks/useBlockHandlers.ts +22 -0
  62. package/src/hooks/useGsapScriptCommits.test.tsx +15 -0
  63. package/src/hooks/useGsapScriptCommits.ts +14 -1
  64. package/src/hooks/usePanelLayout.test.ts +68 -1
  65. package/src/hooks/usePanelLayout.ts +72 -34
  66. package/src/icons/SystemIcons.tsx +4 -0
  67. package/src/player/components/PlayerControls.tsx +68 -229
  68. package/src/player/components/ShortcutsPanel.tsx +2 -4
  69. package/src/player/components/SpeedMenu.tsx +1 -2
  70. package/src/telemetry/events.test.ts +9 -0
  71. package/src/telemetry/events.ts +5 -0
  72. package/src/utils/blockInstaller.test.ts +67 -0
  73. package/src/utils/blockInstaller.ts +162 -109
  74. package/src/utils/studioUiPreferences.test.ts +5 -0
  75. package/src/utils/studioUiPreferences.ts +8 -0
  76. package/dist/assets/index-CSCh2Vrp.js +0 -428
  77. package/dist/assets/index-Ceyz8Qt2.css +0 -1
  78. package/src/player/components/PlayerControls.test.ts +0 -20
  79. package/src/player/components/useSeekBarDrag.ts +0 -169
@@ -0,0 +1,334 @@
1
+ import { useRef, type KeyboardEvent as ReactKeyboardEvent } from "react";
2
+ import {
3
+ getHfColorGradingCapabilities,
4
+ type HfColorGradingWheelKey,
5
+ type NormalizedHfColorGradingWheels,
6
+ } from "@hyperframes/core/color-grading";
7
+ import { RotateCcw } from "../../icons/SystemIcons";
8
+ import { clampNumber } from "../../utils/studioHelpers";
9
+ import { GradingNumberField } from "./propertyPanelGradingNumberField";
10
+ import { useInspectorGestureDraft } from "./useInspectorGestureTransaction";
11
+
12
+ const WHEELS: ReadonlyArray<{ key: HfColorGradingWheelKey; label: string }> = [
13
+ { key: "shadows", label: "Shadows" },
14
+ { key: "midtones", label: "Midtones" },
15
+ { key: "highlights", label: "Highlights" },
16
+ ];
17
+ type NormalizedTonalWheel = NormalizedHfColorGradingWheels[HfColorGradingWheelKey];
18
+
19
+ const WHEEL_CONTROLS = getHfColorGradingCapabilities().wheels.controls;
20
+ const PERCENT_SCALE = 100;
21
+ const HUE_MAX = WHEEL_CONTROLS.hue.maxExclusive - 0.01;
22
+ const RESET_WHEEL: NormalizedTonalWheel = {
23
+ hue: WHEEL_CONTROLS.hue.identity,
24
+ amount: WHEEL_CONTROLS.amount.identity,
25
+ level: WHEEL_CONTROLS.level.identity,
26
+ };
27
+
28
+ function wrapHue(value: number): number {
29
+ const { min, maxExclusive } = WHEEL_CONTROLS.hue;
30
+ const span = maxExclusive - min;
31
+ return ((((value - min) % span) + span) % span) + min;
32
+ }
33
+
34
+ function formatNumberInput(value: number, decimals: number): string {
35
+ return String(Number(value.toFixed(decimals)));
36
+ }
37
+
38
+ const formatIntegerInput = (value: number) => formatNumberInput(value, 0);
39
+
40
+ function wheelFromPointer(
41
+ wheel: NormalizedTonalWheel,
42
+ clientX: number,
43
+ clientY: number,
44
+ rect: DOMRect,
45
+ ): NormalizedTonalWheel {
46
+ const radius = Math.max(1, Math.min(rect.width, rect.height) / 2);
47
+ const x = (clientX - (rect.left + rect.width / 2)) / radius;
48
+ const y = (rect.top + rect.height / 2 - clientY) / radius;
49
+ return {
50
+ ...wheel,
51
+ hue: wrapHue((Math.atan2(y, x) * 180) / Math.PI),
52
+ amount: clampNumber(Math.hypot(x, y), WHEEL_CONTROLS.amount.min, WHEEL_CONTROLS.amount.max),
53
+ };
54
+ }
55
+
56
+ function updateWheel(
57
+ value: NormalizedHfColorGradingWheels,
58
+ key: HfColorGradingWheelKey,
59
+ wheel: NormalizedTonalWheel,
60
+ ): NormalizedHfColorGradingWheels {
61
+ return { ...value, [key]: wheel };
62
+ }
63
+
64
+ function wheelFromKey(
65
+ wheel: NormalizedTonalWheel,
66
+ key: string,
67
+ large: boolean,
68
+ ): NormalizedTonalWheel | null {
69
+ const hueStep = large ? 10 : 1;
70
+ const amountStep = large ? 0.1 : 0.01;
71
+ switch (key) {
72
+ case "ArrowLeft":
73
+ return { ...wheel, hue: wrapHue(wheel.hue - hueStep) };
74
+ case "ArrowRight":
75
+ return { ...wheel, hue: wrapHue(wheel.hue + hueStep) };
76
+ case "ArrowDown":
77
+ return {
78
+ ...wheel,
79
+ amount: clampNumber(
80
+ wheel.amount - amountStep,
81
+ WHEEL_CONTROLS.amount.min,
82
+ WHEEL_CONTROLS.amount.max,
83
+ ),
84
+ };
85
+ case "ArrowUp":
86
+ return {
87
+ ...wheel,
88
+ amount: clampNumber(
89
+ wheel.amount + amountStep,
90
+ WHEEL_CONTROLS.amount.min,
91
+ WHEEL_CONTROLS.amount.max,
92
+ ),
93
+ };
94
+ case "Home":
95
+ return { ...wheel, amount: WHEEL_CONTROLS.amount.min };
96
+ case "End":
97
+ return { ...wheel, amount: WHEEL_CONTROLS.amount.max };
98
+ default:
99
+ return null;
100
+ }
101
+ }
102
+
103
+ function TonalWheel({
104
+ label,
105
+ wheel,
106
+ disabled,
107
+ onBegin,
108
+ onPreview,
109
+ onSettle,
110
+ onCancel,
111
+ onReset,
112
+ }: {
113
+ label: string;
114
+ wheel: NormalizedTonalWheel;
115
+ disabled?: boolean;
116
+ onBegin: () => void;
117
+ onPreview: (wheel: NormalizedTonalWheel) => void;
118
+ onSettle: () => void;
119
+ onCancel: () => void;
120
+ onReset: () => void;
121
+ }) {
122
+ const pointerIdRef = useRef<number | null>(null);
123
+ const hueRadians = (wheel.hue * Math.PI) / 180;
124
+ const thumbLeft = 50 + Math.cos(hueRadians) * wheel.amount * 46;
125
+ const thumbTop = 50 - Math.sin(hueRadians) * wheel.amount * 46;
126
+
127
+ const previewPointer = (target: HTMLDivElement, clientX: number, clientY: number) => {
128
+ onPreview(wheelFromPointer(wheel, clientX, clientY, target.getBoundingClientRect()));
129
+ };
130
+ const handleKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {
131
+ if (disabled) return;
132
+ if (event.key === "Escape") {
133
+ event.preventDefault();
134
+ onCancel();
135
+ return;
136
+ }
137
+ const next = wheelFromKey(wheel, event.key, event.shiftKey);
138
+ if (!next) return;
139
+ event.preventDefault();
140
+ onBegin();
141
+ onPreview(next);
142
+ };
143
+
144
+ return (
145
+ <div data-color-wheel={label.toLowerCase()} className="min-w-[88px] flex-1 space-y-1.5">
146
+ <div className="flex items-center justify-between gap-1">
147
+ <span className="truncate text-[10px] font-medium text-panel-text-2">{label}</span>
148
+ <button
149
+ type="button"
150
+ aria-label={`Reset ${label}`}
151
+ title={`Reset ${label}`}
152
+ disabled={disabled}
153
+ onClick={onReset}
154
+ className="text-panel-text-4 hover:text-panel-text-1 disabled:opacity-40"
155
+ >
156
+ <RotateCcw size={10} />
157
+ </button>
158
+ </div>
159
+ <div
160
+ role="slider"
161
+ tabIndex={disabled ? -1 : 0}
162
+ aria-label={`${label} color`}
163
+ aria-valuemin={WHEEL_CONTROLS.amount.min * PERCENT_SCALE}
164
+ aria-valuemax={WHEEL_CONTROLS.amount.max * PERCENT_SCALE}
165
+ aria-valuenow={Math.round(wheel.amount * PERCENT_SCALE)}
166
+ aria-valuetext={`${Math.round(wheel.hue)} degrees, ${Math.round(
167
+ wheel.amount * PERCENT_SCALE,
168
+ )} percent`}
169
+ aria-disabled={disabled}
170
+ data-color-wheel-surface="true"
171
+ onDoubleClick={onReset}
172
+ onPointerDown={(event) => {
173
+ if (disabled) return;
174
+ event.preventDefault();
175
+ onBegin();
176
+ pointerIdRef.current = event.pointerId;
177
+ event.currentTarget.setPointerCapture(event.pointerId);
178
+ previewPointer(event.currentTarget, event.clientX, event.clientY);
179
+ }}
180
+ onPointerMove={(event) => {
181
+ if (disabled || pointerIdRef.current !== event.pointerId) return;
182
+ previewPointer(event.currentTarget, event.clientX, event.clientY);
183
+ }}
184
+ onPointerUp={(event) => {
185
+ if (pointerIdRef.current !== event.pointerId) return;
186
+ previewPointer(event.currentTarget, event.clientX, event.clientY);
187
+ pointerIdRef.current = null;
188
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
189
+ event.currentTarget.releasePointerCapture(event.pointerId);
190
+ }
191
+ onSettle();
192
+ }}
193
+ onPointerCancel={(event) => {
194
+ if (pointerIdRef.current !== event.pointerId) return;
195
+ pointerIdRef.current = null;
196
+ onCancel();
197
+ }}
198
+ onKeyDown={handleKeyDown}
199
+ onKeyUp={(event) => {
200
+ if (
201
+ ["ArrowLeft", "ArrowRight", "ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)
202
+ ) {
203
+ onSettle();
204
+ }
205
+ }}
206
+ onBlur={onSettle}
207
+ className="relative mx-auto aspect-square w-full max-w-[112px] touch-none rounded-full border border-panel-border-input outline-none focus:ring-1 focus:ring-panel-accent disabled:opacity-40"
208
+ style={{
209
+ background:
210
+ "radial-gradient(circle, rgb(128 128 128) 0%, transparent 72%), conic-gradient(from 90deg, #f33, #f3f, #33f, #3ff, #3f3, #ff3, #f33)",
211
+ }}
212
+ >
213
+ <span
214
+ data-color-wheel-thumb="true"
215
+ className="pointer-events-none absolute h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white bg-transparent shadow-[0_0_0_1px_rgba(0,0,0,0.8)]"
216
+ style={{ left: `${thumbLeft}%`, top: `${thumbTop}%` }}
217
+ />
218
+ </div>
219
+ <input
220
+ type="range"
221
+ aria-label={`${label} level`}
222
+ min={WHEEL_CONTROLS.level.min}
223
+ max={WHEEL_CONTROLS.level.max}
224
+ step={0.01}
225
+ value={wheel.level}
226
+ disabled={disabled}
227
+ onPointerDown={onBegin}
228
+ onChange={(event) => onPreview({ ...wheel, level: Number(event.target.value) })}
229
+ onPointerUp={onSettle}
230
+ onPointerCancel={onCancel}
231
+ onKeyDown={(event) => {
232
+ if (event.key === "Escape") {
233
+ event.preventDefault();
234
+ onCancel();
235
+ } else {
236
+ onBegin();
237
+ }
238
+ }}
239
+ onKeyUp={onSettle}
240
+ onBlur={onSettle}
241
+ className="h-3 w-full accent-panel-accent"
242
+ />
243
+ <div className="grid grid-cols-3 gap-1">
244
+ <GradingNumberField
245
+ label="Hue"
246
+ value={wheel.hue}
247
+ min={WHEEL_CONTROLS.hue.min}
248
+ max={HUE_MAX}
249
+ disabled={disabled}
250
+ formatValue={formatIntegerInput}
251
+ labelTextClassName="block text-[8px] uppercase text-panel-text-5"
252
+ onBegin={onBegin}
253
+ onPreview={(hue) => onPreview({ ...wheel, hue: wrapHue(hue) })}
254
+ onSettle={onSettle}
255
+ onCancel={onCancel}
256
+ />
257
+ <GradingNumberField
258
+ label="Amount"
259
+ value={wheel.amount * PERCENT_SCALE}
260
+ min={WHEEL_CONTROLS.amount.min * PERCENT_SCALE}
261
+ max={WHEEL_CONTROLS.amount.max * PERCENT_SCALE}
262
+ disabled={disabled}
263
+ formatValue={formatIntegerInput}
264
+ labelTextClassName="block text-[8px] uppercase text-panel-text-5"
265
+ onBegin={onBegin}
266
+ onPreview={(amount) => onPreview({ ...wheel, amount: amount / PERCENT_SCALE })}
267
+ onSettle={onSettle}
268
+ onCancel={onCancel}
269
+ />
270
+ <GradingNumberField
271
+ label="Level"
272
+ value={wheel.level * PERCENT_SCALE}
273
+ min={WHEEL_CONTROLS.level.min * PERCENT_SCALE}
274
+ max={WHEEL_CONTROLS.level.max * PERCENT_SCALE}
275
+ disabled={disabled}
276
+ formatValue={formatIntegerInput}
277
+ labelTextClassName="block text-[8px] uppercase text-panel-text-5"
278
+ onBegin={onBegin}
279
+ onPreview={(level) => onPreview({ ...wheel, level: level / PERCENT_SCALE })}
280
+ onSettle={onSettle}
281
+ onCancel={onCancel}
282
+ />
283
+ </div>
284
+ </div>
285
+ );
286
+ }
287
+
288
+ export function ColorWheels({
289
+ value,
290
+ disabled,
291
+ onPreview,
292
+ onCommit,
293
+ }: {
294
+ value: NormalizedHfColorGradingWheels;
295
+ disabled?: boolean;
296
+ onPreview: (value: NormalizedHfColorGradingWheels) => void;
297
+ onCommit: (value: NormalizedHfColorGradingWheels) => void;
298
+ }) {
299
+ const { draft, setDraft, transaction } = useInspectorGestureDraft({
300
+ sourceValue: value,
301
+ onPreview,
302
+ onCommit,
303
+ });
304
+
305
+ const previewWheel = (key: HfColorGradingWheelKey, wheel: NormalizedTonalWheel) =>
306
+ transaction.preview(updateWheel(draft, key, wheel));
307
+ const resetWheel = (key: HfColorGradingWheelKey) => {
308
+ transaction.cancel();
309
+ const next = updateWheel(draft, key, RESET_WHEEL);
310
+ setDraft(next);
311
+ onCommit(next);
312
+ };
313
+
314
+ return (
315
+ <div
316
+ data-color-wheels="true"
317
+ className="grid grid-cols-[repeat(auto-fit,minmax(88px,1fr))] gap-2"
318
+ >
319
+ {WHEELS.map(({ key, label }) => (
320
+ <TonalWheel
321
+ key={key}
322
+ label={label}
323
+ wheel={draft[key]}
324
+ disabled={disabled}
325
+ onBegin={transaction.begin}
326
+ onPreview={(wheel) => previewWheel(key, wheel)}
327
+ onSettle={transaction.settle}
328
+ onCancel={transaction.cancel}
329
+ onReset={() => resetWheel(key)}
330
+ />
331
+ ))}
332
+ </div>
333
+ );
334
+ }
@@ -0,0 +1,109 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { isHfColorGradingActive } from "@hyperframes/core/color-grading";
3
+ import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
4
+ import { Compare, RotateCcw } from "../../icons/SystemIcons";
5
+ import type { ColorGradingControllerState } from "./useColorGradingController";
6
+
7
+ const STATUS_DOT_CLASS: Record<ColorGradingControllerState["runtimeStatus"]["state"], string> = {
8
+ active: "bg-emerald-400",
9
+ pending: "bg-amber-300",
10
+ unavailable: "bg-red-400",
11
+ missing: "bg-panel-text-5",
12
+ inactive: "bg-panel-text-5",
13
+ };
14
+
15
+ export function FlatColorGradingAccessory({
16
+ state,
17
+ }: {
18
+ state: Pick<
19
+ ColorGradingControllerState,
20
+ "grading" | "compareEnabled" | "runtimeStatus" | "commitCompare" | "resetGrading"
21
+ >;
22
+ }) {
23
+ const track = useTrackDesignInput();
24
+ const { grading, compareEnabled, runtimeStatus, commitCompare, resetGrading } = state;
25
+ const gradingActive = isHfColorGradingActive(grading);
26
+ const releaseRef = useRef<(() => void) | null>(null);
27
+ useEffect(
28
+ () => () => {
29
+ releaseRef.current?.();
30
+ releaseRef.current = null;
31
+ },
32
+ [],
33
+ );
34
+
35
+ return (
36
+ <span className="flex items-center gap-2.5">
37
+ <button
38
+ type="button"
39
+ aria-pressed={compareEnabled}
40
+ aria-label="Hold to show original"
41
+ disabled={!gradingActive}
42
+ onPointerDown={(e) => {
43
+ if (!gradingActive) return;
44
+ e.preventDefault();
45
+ e.stopPropagation();
46
+ track("button", "Compare original");
47
+ commitCompare(true);
48
+ const release = () => {
49
+ commitCompare(false);
50
+ window.removeEventListener("pointerup", release);
51
+ window.removeEventListener("pointercancel", release);
52
+ window.removeEventListener("blur", release);
53
+ releaseRef.current = null;
54
+ };
55
+ releaseRef.current = release;
56
+ window.addEventListener("pointerup", release);
57
+ window.addEventListener("pointercancel", release);
58
+ window.addEventListener("blur", release);
59
+ }}
60
+ onBlur={() => {
61
+ if (compareEnabled) commitCompare(false);
62
+ }}
63
+ onKeyDown={(e) => {
64
+ if (!gradingActive || (e.key !== " " && e.key !== "Enter")) return;
65
+ e.preventDefault();
66
+ if (!compareEnabled) {
67
+ track("button", "Compare original");
68
+ commitCompare(true);
69
+ }
70
+ }}
71
+ onKeyUp={(e) => {
72
+ if (!gradingActive || (e.key !== " " && e.key !== "Enter")) return;
73
+ e.preventDefault();
74
+ commitCompare(false);
75
+ }}
76
+ title="Hold to show original"
77
+ className="flex-shrink-0 text-panel-text-3 hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-40"
78
+ >
79
+ <Compare size={12} />
80
+ </button>
81
+ <span className="flex min-w-0 items-center gap-1" title={runtimeStatus.message}>
82
+ <span
83
+ data-flat-grade-status-dot="true"
84
+ title={runtimeStatus.message}
85
+ className={`h-[5px] w-[5px] flex-shrink-0 rounded-full ${STATUS_DOT_CLASS[runtimeStatus.state]}`}
86
+ />
87
+ <span
88
+ data-flat-grade-status-message="true"
89
+ className="max-w-[84px] truncate text-[9px] text-panel-text-4"
90
+ >
91
+ {runtimeStatus.message}
92
+ </span>
93
+ </span>
94
+ <button
95
+ type="button"
96
+ data-flat-grade-reset="true"
97
+ title="Reset color grading"
98
+ onClick={(e) => {
99
+ e.stopPropagation();
100
+ track("button", "Reset color grading");
101
+ resetGrading();
102
+ }}
103
+ className="flex-shrink-0 text-panel-text-3 hover:text-panel-text-1"
104
+ >
105
+ <RotateCcw size={12} />
106
+ </button>
107
+ </span>
108
+ );
109
+ }