@hyperframes/studio 0.7.58 → 0.7.59

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 (156) hide show
  1. package/dist/assets/{hyperframes-player-CtTDO63S.js → hyperframes-player-Bm07FLkl.js} +1 -1
  2. package/dist/assets/{index-C47jAC3Q.js → index-B995FG46.js} +1 -1
  3. package/dist/assets/index-CrkAdJkb.js +426 -0
  4. package/dist/assets/index-Dj5p8U_A.css +1 -0
  5. package/dist/assets/{index-DeQPzqwH.js → index-FvzmPhfG.js} +1 -1
  6. package/dist/index.d.ts +12 -3
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +13514 -8528
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles/tailwind-preset.d.ts +5 -0
  11. package/dist/styles/tailwind-preset.js +8 -1
  12. package/dist/styles/tailwind-preset.js.map +1 -1
  13. package/package.json +7 -7
  14. package/src/App.tsx +3 -3
  15. package/src/components/DesignPanelPromoteProvider.tsx +15 -2
  16. package/src/components/StudioRightPanel.tsx +29 -51
  17. package/src/components/editor/AnimationCard.test.tsx +134 -0
  18. package/src/components/editor/AnimationCard.tsx +19 -4
  19. package/src/components/editor/ArcPathControls.tsx +1 -0
  20. package/src/components/editor/GestureRecordControl.tsx +7 -1
  21. package/src/components/editor/GsapAnimationSection.tsx +109 -14
  22. package/src/components/editor/InspectorHeaderActions.tsx +42 -4
  23. package/src/components/editor/PropertyPanel.test.tsx +948 -0
  24. package/src/components/editor/PropertyPanel.tsx +144 -148
  25. package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
  26. package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
  27. package/src/components/editor/PropertyPanelFlat.tsx +580 -0
  28. package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
  29. package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
  30. package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
  31. package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
  32. package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
  33. package/src/components/editor/gsapLivePreview.ts +34 -0
  34. package/src/components/editor/manualEditingAvailability.test.ts +10 -0
  35. package/src/components/editor/manualEditingAvailability.ts +9 -0
  36. package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
  37. package/src/components/editor/propertyPanelColor.test.tsx +28 -0
  38. package/src/components/editor/propertyPanelColor.tsx +31 -1
  39. package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
  40. package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
  41. package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
  42. package/src/components/editor/propertyPanelFill.tsx +22 -5
  43. package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
  44. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
  45. package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
  46. package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
  47. package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
  48. package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
  49. package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
  50. package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
  51. package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
  52. package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
  53. package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
  54. package/src/components/editor/propertyPanelFlatSelectRow.tsx +101 -0
  55. package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
  56. package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
  57. package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
  58. package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
  59. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
  60. package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
  61. package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
  62. package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
  63. package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
  64. package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
  65. package/src/components/editor/propertyPanelFont.test.tsx +30 -0
  66. package/src/components/editor/propertyPanelFont.tsx +136 -95
  67. package/src/components/editor/propertyPanelHelpers.ts +73 -0
  68. package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
  69. package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
  70. package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
  71. package/src/components/editor/propertyPanelSections.test.tsx +161 -0
  72. package/src/components/editor/propertyPanelSections.tsx +154 -90
  73. package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
  74. package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
  75. package/src/components/editor/propertyPanelTypes.ts +14 -1
  76. package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
  77. package/src/components/editor/propertyPanelValueTier.ts +28 -0
  78. package/src/components/editor/useColorGradingController.test.ts +418 -0
  79. package/src/components/editor/useColorGradingController.ts +596 -0
  80. package/src/components/panels/VariablesPanel.tsx +4 -0
  81. package/src/components/sidebar/CompositionsTab.test.ts +28 -2
  82. package/src/components/sidebar/CompositionsTab.tsx +3 -1
  83. package/src/components/storyboard/FramePoster.tsx +24 -4
  84. package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
  85. package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
  86. package/src/components/storyboard/StoryboardGrid.tsx +23 -1
  87. package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
  88. package/src/components/storyboard/StoryboardView.tsx +9 -2
  89. package/src/components/storyboard/frameComments.test.ts +95 -0
  90. package/src/components/storyboard/frameComments.ts +115 -0
  91. package/src/components/storyboard/useFrameComments.ts +82 -0
  92. package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
  93. package/src/contexts/DesignPanelInputContext.tsx +48 -0
  94. package/src/contexts/DomEditContext.tsx +4 -0
  95. package/src/contexts/VariablePromoteContext.test.tsx +67 -0
  96. package/src/contexts/VariablePromoteContext.tsx +10 -5
  97. package/src/hooks/domEditCommitRunner.ts +11 -0
  98. package/src/hooks/gsapScriptCommitTypes.ts +6 -4
  99. package/src/hooks/timelineTrackVisibility.test.ts +52 -0
  100. package/src/hooks/timelineTrackVisibility.ts +27 -10
  101. package/src/hooks/useAppHotkeys.ts +9 -0
  102. package/src/hooks/useDomEditAttributeCommits.ts +123 -1
  103. package/src/hooks/useDomEditCommits.ts +29 -175
  104. package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
  105. package/src/hooks/useDomEditSession.ts +17 -5
  106. package/src/hooks/useDomEditTextCommits.ts +14 -9
  107. package/src/hooks/useDomEditWiring.ts +10 -9
  108. package/src/hooks/useElementLifecycleOps.ts +8 -3
  109. package/src/hooks/useFileManager.projectOwnership.test.tsx +98 -0
  110. package/src/hooks/useFileManager.ts +82 -60
  111. package/src/hooks/useGsapAnimationOps.ts +7 -6
  112. package/src/hooks/useGsapKeyframeOps.ts +6 -5
  113. package/src/hooks/useGsapPropertyDebounce.ts +43 -28
  114. package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
  115. package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
  116. package/src/hooks/useGsapScriptCommits.ts +150 -50
  117. package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
  118. package/src/hooks/useGsapSelectionHandlers.ts +94 -31
  119. package/src/hooks/useInspectorSplitResize.ts +51 -0
  120. package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
  121. package/src/hooks/usePersistentEditHistory.test.ts +49 -1
  122. package/src/hooks/usePersistentEditHistory.ts +71 -31
  123. package/src/hooks/useProjectCompositionVariables.ts +10 -10
  124. package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
  125. package/src/hooks/useProjectSignaturePoll.ts +68 -0
  126. package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
  127. package/src/hooks/useSdkSession.ts +155 -14
  128. package/src/hooks/useSlideshowPersist.ts +7 -1
  129. package/src/hooks/useStoryboard.ts +23 -4
  130. package/src/hooks/useTimelineEditing.test.tsx +4 -1
  131. package/src/hooks/useTimelineEditing.ts +16 -10
  132. package/src/hooks/useTimelineEditingTypes.ts +3 -0
  133. package/src/hooks/useTimelineGroupEditing.ts +16 -2
  134. package/src/hooks/useVariablesPersist.ts +9 -7
  135. package/src/icons/SystemIcons.tsx +2 -0
  136. package/src/styles/studio.css +22 -0
  137. package/src/styles/tailwind-preset.shared.js +7 -0
  138. package/src/utils/designInputTracking.test.ts +97 -0
  139. package/src/utils/designInputTracking.ts +80 -0
  140. package/src/utils/sdkCutover.gate.test.ts +7 -7
  141. package/src/utils/sdkCutover.test.ts +490 -57
  142. package/src/utils/sdkCutover.ts +181 -219
  143. package/src/utils/sdkEditTransaction.ts +292 -0
  144. package/src/utils/sdkResolverAttempts.ts +94 -0
  145. package/src/utils/sdkResolverShadow.test.ts +148 -0
  146. package/src/utils/sdkResolverShadow.ts +139 -106
  147. package/src/utils/setSlideshowManifest.test.ts +0 -9
  148. package/src/utils/setSlideshowManifest.ts +20 -27
  149. package/src/utils/studioFileHistory.test.ts +41 -0
  150. package/src/utils/studioFileHistory.ts +30 -27
  151. package/src/utils/studioFileMutationCoordinator.ts +45 -0
  152. package/src/utils/studioHelpers.test.ts +45 -0
  153. package/src/utils/studioHelpers.ts +45 -14
  154. package/src/utils/studioSaveDiagnostics.ts +17 -0
  155. package/dist/assets/index-B_UvTX3E.js +0 -423
  156. package/dist/assets/index-uahwWkgw.css +0 -1
@@ -0,0 +1,315 @@
1
+ import { useState } from "react";
2
+ import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
3
+ import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
4
+ import type { DomEditSelection } from "./domEditing";
5
+ import { formatTimingValue, RESPONSIVE_GRID } from "./propertyPanelHelpers";
6
+ import { parseTimingValue } from "./propertyPanelTimingSection";
7
+ import { CommitField } from "./propertyPanelPrimitives";
8
+ import { AnimationCard } from "./AnimationCard";
9
+ import { ADD_METHODS, ADD_METHOD_LABELS, METHOD_TOOLTIPS } from "./gsapAnimationConstants";
10
+ import {
11
+ trackAnimationMetaUpdate,
12
+ type GsapAnimationEditCallbacks,
13
+ } from "./gsapAnimationCallbacks";
14
+ import { deriveElementTiming } from "./propertyPanelFlatTimingDerivation";
15
+
16
+ export function FlatTimingRow({
17
+ element,
18
+ animations = [],
19
+ onSetAttribute,
20
+ onSetAttributes,
21
+ }: {
22
+ element: DomEditSelection;
23
+ animations?: GsapAnimation[];
24
+ onSetAttribute: (attr: string, value: string) => void | Promise<void>;
25
+ /** Commits start+duration together in ONE atomic persist call, bound to
26
+ * THIS render's `element` explicitly — not whatever is "currently"
27
+ * selected by the time the call resolves. Falls back to two sequential
28
+ * `onSetAttribute` calls (with the same non-atomicity/misdirection risk
29
+ * documented below) when the caller doesn't wire it up. */
30
+ onSetAttributes?: (selection: DomEditSelection, attrs: Record<string, string>) => Promise<void>;
31
+ }) {
32
+ const track = useTrackDesignInput();
33
+ const { start, duration, inferred: derived } = deriveElementTiming(element, animations);
34
+ const end = start + duration;
35
+
36
+ // While the range is inferred from animations, editing ONE field must pin the
37
+ // WHOLE displayed range: writing only data-duration flips inference off and
38
+ // drops start to data-start-or-0 (the clip silently shifts), and writing only
39
+ // data-start is ignored while duration is still inferred (the edit looks
40
+ // dead). Pin both attributes in ONE atomic commit bound to THIS element —
41
+ // two sequential `onSetAttribute` calls would each resolve `domEditSelection`
42
+ // fresh from current hook state, so a selection change between the two
43
+ // awaits could misdirect the second write at the newly-selected element, and
44
+ // a failure of just the second call would leave the pair half-applied.
45
+ const pinRange = async (nextStart: number, nextDuration: number) => {
46
+ const attrs = { start: nextStart.toFixed(2), duration: nextDuration.toFixed(2) };
47
+ if (onSetAttributes) {
48
+ await onSetAttributes(element, attrs);
49
+ return;
50
+ }
51
+ await onSetAttribute("start", attrs.start);
52
+ await onSetAttribute("duration", attrs.duration);
53
+ };
54
+
55
+ const commitStart = (nextValue: string) => {
56
+ const parsed = parseTimingValue(nextValue);
57
+ if (parsed == null) return;
58
+ if (derived) {
59
+ void pinRange(parsed, duration);
60
+ return;
61
+ }
62
+ void onSetAttribute("start", parsed.toFixed(2));
63
+ };
64
+
65
+ const commitDuration = (nextValue: string) => {
66
+ const parsed = parseTimingValue(nextValue);
67
+ if (parsed == null || parsed <= 0) return;
68
+ if (derived) {
69
+ void pinRange(start, parsed);
70
+ return;
71
+ }
72
+ void onSetAttribute("duration", parsed.toFixed(2));
73
+ };
74
+
75
+ const commitEnd = (nextValue: string) => {
76
+ const parsed = parseTimingValue(nextValue);
77
+ if (parsed == null || parsed <= start) return;
78
+ if (derived) {
79
+ void pinRange(start, parsed - start);
80
+ return;
81
+ }
82
+ void onSetAttribute("duration", (parsed - start).toFixed(2));
83
+ };
84
+
85
+ const cell = (label: string, value: string, onCommit: (next: string) => void) => (
86
+ <div className="grid gap-px">
87
+ <span className="text-[9px] text-panel-text-4">{label}</span>
88
+ <span className="border-b border-transparent font-mono text-[11px] text-panel-text-0 hover:border-panel-border-input">
89
+ <CommitField
90
+ value={value}
91
+ onCommit={(next) => {
92
+ track("metric", label);
93
+ onCommit(next);
94
+ }}
95
+ />
96
+ </span>
97
+ </div>
98
+ );
99
+
100
+ return (
101
+ <div className={RESPONSIVE_GRID}>
102
+ {cell("Start", formatTimingValue(start), commitStart)}
103
+ {cell("End", formatTimingValue(end), commitEnd)}
104
+ {cell("Duration", formatTimingValue(duration), commitDuration)}
105
+ {derived && (
106
+ <p className="col-span-3 mt-1 text-[10px] leading-snug text-panel-text-3">
107
+ Inferred from this element's animation — edit to pin an explicit clip range.
108
+ </p>
109
+ )}
110
+ </div>
111
+ );
112
+ }
113
+
114
+ export function FlatMotionSection({
115
+ element,
116
+ animations,
117
+ showTiming,
118
+ showEffects,
119
+ multipleTimelines,
120
+ unsupportedTimelinePattern,
121
+ onSetAttribute,
122
+ onSetAttributes,
123
+ onAddAnimation,
124
+ ...callbacks
125
+ }: {
126
+ element: DomEditSelection;
127
+ animations: GsapAnimation[];
128
+ showTiming: boolean;
129
+ showEffects: boolean;
130
+ multipleTimelines?: boolean;
131
+ unsupportedTimelinePattern?: boolean;
132
+ onSetAttribute: (attr: string, value: string) => void | Promise<void>;
133
+ onSetAttributes?: (selection: DomEditSelection, attrs: Record<string, string>) => Promise<void>;
134
+ onAddAnimation: (method: "to" | "from" | "set" | "fromTo") => void;
135
+ } & GsapAnimationEditCallbacks) {
136
+ const track = useTrackDesignInput();
137
+ const [addMenuOpen, setAddMenuOpen] = useState(false);
138
+ const trackProperty = (property: string) => {
139
+ const control =
140
+ property === "visibility"
141
+ ? "toggle"
142
+ : property === "filter" || property === "clipPath"
143
+ ? "text"
144
+ : "metric";
145
+ track(control, property);
146
+ };
147
+
148
+ return (
149
+ <div className="space-y-3">
150
+ {showTiming && (
151
+ <FlatTimingRow
152
+ element={element}
153
+ animations={animations}
154
+ onSetAttribute={onSetAttribute}
155
+ onSetAttributes={onSetAttributes}
156
+ />
157
+ )}
158
+ {showEffects && (
159
+ <>
160
+ {multipleTimelines && (
161
+ <p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-amber-400">
162
+ This file has multiple GSAP timelines. Animation editing is disabled to prevent data
163
+ loss — consolidate into a single timeline to enable editing.
164
+ </p>
165
+ )}
166
+ {unsupportedTimelinePattern && (
167
+ <p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-amber-400">
168
+ This timeline uses a computed key the editor can&apos;t resolve statically.
169
+ </p>
170
+ )}
171
+ {!multipleTimelines && !unsupportedTimelinePattern && (
172
+ <div className="space-y-2">
173
+ {animations.map((anim, index) => (
174
+ <AnimationCard
175
+ key={anim.id}
176
+ animation={anim}
177
+ defaultExpanded={index === 0}
178
+ flat
179
+ onUpdateProperty={(animationId, property, value) => {
180
+ trackProperty(property);
181
+ callbacks.onUpdateProperty(animationId, property, value);
182
+ }}
183
+ onUpdateMeta={(animationId, updates) => {
184
+ trackAnimationMetaUpdate(track, updates);
185
+ callbacks.onUpdateMeta(animationId, updates);
186
+ }}
187
+ onDeleteAnimation={(animationId) => {
188
+ track("button", "Remove animation");
189
+ callbacks.onDeleteAnimation(animationId);
190
+ }}
191
+ onAddProperty={(animationId, property) => {
192
+ track("select", "Add effect property");
193
+ callbacks.onAddProperty(animationId, property);
194
+ }}
195
+ onRemoveProperty={(animationId, property) => {
196
+ track("button", `Remove ${property}`);
197
+ callbacks.onRemoveProperty(animationId, property);
198
+ }}
199
+ onUpdateFromProperty={
200
+ callbacks.onUpdateFromProperty
201
+ ? (animationId, property, value) => {
202
+ trackProperty(property);
203
+ callbacks.onUpdateFromProperty?.(animationId, property, value);
204
+ }
205
+ : undefined
206
+ }
207
+ onAddFromProperty={
208
+ callbacks.onAddFromProperty
209
+ ? (animationId, property) => {
210
+ track("select", "Add from property");
211
+ callbacks.onAddFromProperty?.(animationId, property);
212
+ }
213
+ : undefined
214
+ }
215
+ onRemoveFromProperty={
216
+ callbacks.onRemoveFromProperty
217
+ ? (animationId, property) => {
218
+ track("button", `Remove from ${property}`);
219
+ callbacks.onRemoveFromProperty?.(animationId, property);
220
+ }
221
+ : undefined
222
+ }
223
+ onLivePreview={callbacks.onLivePreview}
224
+ onLivePreviewEnd={callbacks.onLivePreviewEnd}
225
+ onSetArcPath={
226
+ callbacks.onSetArcPath
227
+ ? (animationId, config) => {
228
+ track(
229
+ "toggle",
230
+ config.autoRotate !== undefined ? "Auto rotate" : "Arc motion",
231
+ );
232
+ callbacks.onSetArcPath?.(animationId, config);
233
+ }
234
+ : undefined
235
+ }
236
+ onUpdateArcSegment={
237
+ callbacks.onUpdateArcSegment
238
+ ? (animationId, segmentIndex, update) => {
239
+ if (update.curviness === undefined) {
240
+ track("button", `Reset arc segment ${segmentIndex + 1}`);
241
+ }
242
+ callbacks.onUpdateArcSegment?.(animationId, segmentIndex, update);
243
+ }
244
+ : undefined
245
+ }
246
+ onUpdateKeyframeEase={
247
+ callbacks.onUpdateKeyframeEase
248
+ ? (animationId, percentage, ease) => {
249
+ track("select", "Keyframe ease");
250
+ callbacks.onUpdateKeyframeEase?.(animationId, percentage, ease);
251
+ }
252
+ : undefined
253
+ }
254
+ onSetAllKeyframeEases={
255
+ callbacks.onSetAllKeyframeEases
256
+ ? (animationId, ease) => {
257
+ track("select", "All keyframe eases");
258
+ callbacks.onSetAllKeyframeEases?.(animationId, ease);
259
+ }
260
+ : undefined
261
+ }
262
+ onUnroll={
263
+ callbacks.onUnroll
264
+ ? (animationId) => {
265
+ track("button", "Unroll animation");
266
+ callbacks.onUnroll?.(animationId);
267
+ }
268
+ : undefined
269
+ }
270
+ />
271
+ ))}
272
+ <div className="relative pt-1">
273
+ {addMenuOpen ? (
274
+ <div className="flex gap-1.5">
275
+ {ADD_METHODS.map((method) => (
276
+ <button
277
+ key={method}
278
+ type="button"
279
+ title={METHOD_TOOLTIPS[method]}
280
+ onClick={() => {
281
+ track("button", `Add ${method} animation`);
282
+ onAddAnimation(method);
283
+ setAddMenuOpen(false);
284
+ }}
285
+ className="rounded-lg border border-panel-border-input bg-panel-input px-2.5 py-1.5 text-[11px] font-medium text-panel-text-2 transition-colors hover:border-panel-text-4 hover:text-panel-text-0"
286
+ >
287
+ {ADD_METHOD_LABELS[method] ?? method}
288
+ </button>
289
+ ))}
290
+ <button
291
+ type="button"
292
+ onClick={() => setAddMenuOpen(false)}
293
+ className="px-1.5 text-[11px] text-panel-text-3 hover:text-panel-text-1"
294
+ >
295
+ Cancel
296
+ </button>
297
+ </div>
298
+ ) : (
299
+ <button
300
+ type="button"
301
+ onClick={() => setAddMenuOpen(true)}
302
+ className="text-[11px] font-medium text-panel-text-3 transition-colors hover:text-panel-text-1"
303
+ title="Add a new animation effect to this element"
304
+ >
305
+ + Add effect
306
+ </button>
307
+ )}
308
+ </div>
309
+ </div>
310
+ )}
311
+ </>
312
+ )}
313
+ </div>
314
+ );
315
+ }