react-native-image-stitcher 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 (151) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +21 -0
  4. package/README.md +189 -0
  5. package/RNImageStitcher.podspec +76 -0
  6. package/android/build.gradle +224 -0
  7. package/android/src/main/AndroidManifest.xml +3 -0
  8. package/android/src/main/cpp/CMakeLists.txt +124 -0
  9. package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
  10. package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
  11. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
  12. package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
  13. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
  14. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
  15. package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
  16. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
  17. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
  18. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
  19. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
  20. package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
  21. package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
  22. package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
  23. package/cpp/ar_frame_pose.h +63 -0
  24. package/cpp/keyframe_gate.cpp +927 -0
  25. package/cpp/keyframe_gate.hpp +240 -0
  26. package/cpp/stitcher.cpp +2207 -0
  27. package/cpp/stitcher.hpp +275 -0
  28. package/dist/ar/useARSession.d.ts +102 -0
  29. package/dist/ar/useARSession.js +133 -0
  30. package/dist/camera/ARCameraView.d.ts +93 -0
  31. package/dist/camera/ARCameraView.js +170 -0
  32. package/dist/camera/Camera.d.ts +134 -0
  33. package/dist/camera/Camera.js +688 -0
  34. package/dist/camera/CameraShutter.d.ts +80 -0
  35. package/dist/camera/CameraShutter.js +237 -0
  36. package/dist/camera/CameraView.d.ts +65 -0
  37. package/dist/camera/CameraView.js +117 -0
  38. package/dist/camera/CaptureControlsBar.d.ts +87 -0
  39. package/dist/camera/CaptureControlsBar.js +82 -0
  40. package/dist/camera/CaptureHeader.d.ts +62 -0
  41. package/dist/camera/CaptureHeader.js +81 -0
  42. package/dist/camera/CapturePreview.d.ts +70 -0
  43. package/dist/camera/CapturePreview.js +188 -0
  44. package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
  45. package/dist/camera/CaptureStatusOverlay.js +326 -0
  46. package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
  47. package/dist/camera/CaptureThumbnailStrip.js +177 -0
  48. package/dist/camera/IncrementalPanGuide.d.ts +83 -0
  49. package/dist/camera/IncrementalPanGuide.js +267 -0
  50. package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
  51. package/dist/camera/PanoramaBandOverlay.js +399 -0
  52. package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
  53. package/dist/camera/PanoramaConfirmModal.js +128 -0
  54. package/dist/camera/PanoramaGuidance.d.ts +79 -0
  55. package/dist/camera/PanoramaGuidance.js +246 -0
  56. package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
  57. package/dist/camera/PanoramaSettingsModal.js +611 -0
  58. package/dist/camera/ViewportCropOverlay.d.ts +46 -0
  59. package/dist/camera/ViewportCropOverlay.js +67 -0
  60. package/dist/camera/useCapture.d.ts +111 -0
  61. package/dist/camera/useCapture.js +160 -0
  62. package/dist/camera/useDeviceOrientation.d.ts +48 -0
  63. package/dist/camera/useDeviceOrientation.js +131 -0
  64. package/dist/camera/useVideoCapture.d.ts +79 -0
  65. package/dist/camera/useVideoCapture.js +151 -0
  66. package/dist/index.d.ts +26 -0
  67. package/dist/index.js +39 -0
  68. package/dist/quality/normaliseOrientation.d.ts +36 -0
  69. package/dist/quality/normaliseOrientation.js +62 -0
  70. package/dist/quality/runQualityCheck.d.ts +41 -0
  71. package/dist/quality/runQualityCheck.js +98 -0
  72. package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
  73. package/dist/sensors/useIMUTranslationGate.js +235 -0
  74. package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
  75. package/dist/stitching/IncrementalStitcherView.js +157 -0
  76. package/dist/stitching/incremental.d.ts +930 -0
  77. package/dist/stitching/incremental.js +133 -0
  78. package/dist/stitching/stitchFrames.d.ts +55 -0
  79. package/dist/stitching/stitchFrames.js +56 -0
  80. package/dist/stitching/stitchVideo.d.ts +119 -0
  81. package/dist/stitching/stitchVideo.js +57 -0
  82. package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
  83. package/dist/stitching/useIncrementalJSDriver.js +199 -0
  84. package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
  85. package/dist/stitching/useIncrementalStitcher.js +172 -0
  86. package/dist/types.d.ts +58 -0
  87. package/dist/types.js +15 -0
  88. package/ios/Package.swift +72 -0
  89. package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
  90. package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
  91. package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
  92. package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
  93. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
  94. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
  95. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
  96. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
  97. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
  98. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
  99. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
  100. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
  101. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
  102. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
  103. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
  104. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
  105. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
  106. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
  107. package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
  108. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
  109. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
  110. package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
  111. package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
  112. package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
  113. package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
  114. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
  115. package/package.json +73 -0
  116. package/react-native.config.js +34 -0
  117. package/scripts/opencv-version.txt +1 -0
  118. package/scripts/postinstall-fetch-binaries.js +286 -0
  119. package/src/ar/useARSession.ts +210 -0
  120. package/src/camera/.gitkeep +0 -0
  121. package/src/camera/ARCameraView.tsx +256 -0
  122. package/src/camera/Camera.tsx +1053 -0
  123. package/src/camera/CameraShutter.tsx +292 -0
  124. package/src/camera/CameraView.tsx +157 -0
  125. package/src/camera/CaptureControlsBar.tsx +204 -0
  126. package/src/camera/CaptureHeader.tsx +184 -0
  127. package/src/camera/CapturePreview.tsx +318 -0
  128. package/src/camera/CaptureStatusOverlay.tsx +391 -0
  129. package/src/camera/CaptureThumbnailStrip.tsx +277 -0
  130. package/src/camera/IncrementalPanGuide.tsx +328 -0
  131. package/src/camera/PanoramaBandOverlay.tsx +498 -0
  132. package/src/camera/PanoramaConfirmModal.tsx +206 -0
  133. package/src/camera/PanoramaGuidance.tsx +327 -0
  134. package/src/camera/PanoramaSettingsModal.tsx +1357 -0
  135. package/src/camera/ViewportCropOverlay.tsx +81 -0
  136. package/src/camera/useCapture.ts +279 -0
  137. package/src/camera/useDeviceOrientation.ts +140 -0
  138. package/src/camera/useVideoCapture.ts +236 -0
  139. package/src/index.ts +53 -0
  140. package/src/quality/.gitkeep +0 -0
  141. package/src/quality/normaliseOrientation.ts +79 -0
  142. package/src/quality/runQualityCheck.ts +131 -0
  143. package/src/sensors/useIMUTranslationGate.ts +347 -0
  144. package/src/stitching/.gitkeep +0 -0
  145. package/src/stitching/IncrementalStitcherView.tsx +198 -0
  146. package/src/stitching/incremental.ts +1021 -0
  147. package/src/stitching/stitchFrames.ts +88 -0
  148. package/src/stitching/stitchVideo.ts +153 -0
  149. package/src/stitching/useIncrementalJSDriver.ts +273 -0
  150. package/src/stitching/useIncrementalStitcher.ts +252 -0
  151. package/src/types.ts +78 -0
@@ -0,0 +1,328 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * IncrementalPanGuide — V12.11 Step 2 (item 2 of the four-step
4
+ * preview-UX overhaul).
5
+ *
6
+ * Apple-pano-style "keep the arrow on the line" pan guide for the
7
+ * incremental capture flow.
8
+ *
9
+ * ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
10
+ * ▲
11
+ * ● ← marker drifts perpendicular
12
+ * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← guide line
13
+ * (along pan axis)
14
+ * ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
15
+ *
16
+ * The user is told (via the band overlay's caption) to pan along
17
+ * one axis. This guide gives them live feedback on how WELL they
18
+ * are obeying that instruction:
19
+ *
20
+ * • A solid GUIDE LINE runs along the intended pan axis (the
21
+ * "ideal" pan path) across the centre of the screen.
22
+ * • A circular MARKER sits on the line. As the user tilts the
23
+ * device perpendicular to the pan axis, the marker drifts
24
+ * OFF the line by an amount proportional to the integrated
25
+ * perpendicular rotation since capture started.
26
+ * • The marker's COLOUR signals how far off they've drifted —
27
+ * green (on track), amber (slight drift), red (significant
28
+ * drift). Same colour scale as PanoramaGuidance for
29
+ * consistency.
30
+ *
31
+ * Why integrate perpendicular rotation rather than absolute device
32
+ * angle? We don't care about the user's starting orientation (they
33
+ * may begin a horizontal pan with the phone tilted slightly down
34
+ * — that's fine). We care about CHANGE during the pan. So we
35
+ * reset the integral to 0 at `active=true` and accumulate the
36
+ * perpendicular gyro rate from there. Drift over a typical 5–10 s
37
+ * capture is well within tolerance (a few degrees max).
38
+ *
39
+ * Why not consume ARKit pose? Two reasons:
40
+ * 1. The vision-camera (non-AR) capture path doesn't have ARKit
41
+ * pose at all — gyro is the only common signal.
42
+ * 2. We want this guide to work the SAME way regardless of
43
+ * whether AR mode is on or off — gyro keeps the UX
44
+ * consistent.
45
+ *
46
+ * Performance: gyro at 30 Hz, integration is two multiplies per
47
+ * sample, marker position update via setState. Negligible.
48
+ */
49
+
50
+ import React, { useEffect, useRef, useState } from 'react';
51
+ import {
52
+ StyleSheet,
53
+ View,
54
+ type StyleProp,
55
+ type ViewStyle,
56
+ } from 'react-native';
57
+ import {
58
+ gyroscope,
59
+ setUpdateIntervalForType,
60
+ SensorTypes,
61
+ } from 'react-native-sensors';
62
+ import type { Subscription } from 'rxjs';
63
+
64
+ import { useDeviceOrientation, type DeviceOrientation } from './useDeviceOrientation';
65
+
66
+
67
+ export interface IncrementalPanGuideProps {
68
+ /**
69
+ * Subscribe to the gyroscope only while this is true. Typically
70
+ * driven by the host's `statusPhase === 'recording' &&
71
+ * useIncrementalPath`. When this flips false the integral
72
+ * resets so the next capture starts from a known zero.
73
+ */
74
+ active: boolean;
75
+ /**
76
+ * Pixels per radian of perpendicular drift. Higher = more
77
+ * sensitive (small drifts move the marker more visibly).
78
+ * Default 600 px/rad gives ~10 px of marker travel for ~1° of
79
+ * tilt — visible without feeling twitchy.
80
+ */
81
+ pixelsPerRad?: number;
82
+ /**
83
+ * Maximum marker travel from the line, in pixels. Beyond this
84
+ * the marker pins to the edge of its track. Default 80 px.
85
+ */
86
+ maxTravelPx?: number;
87
+ /**
88
+ * Drift thresholds for the colour scale, in radians. Tuned so
89
+ * "green" covers ±1° (no human-perceptible misalignment),
90
+ * "amber" up to ±3°, "red" beyond. Same defaults as
91
+ * PanoramaGuidance's pan-speed pill for visual consistency.
92
+ */
93
+ warnRad?: number;
94
+ badRad?: number;
95
+ style?: StyleProp<ViewStyle>;
96
+ }
97
+
98
+
99
+ type DriftBucket = 'good' | 'warn' | 'bad';
100
+
101
+ const COLOR_GOOD = '#34C759';
102
+ const COLOR_WARN = '#FFCC00';
103
+ const COLOR_BAD = '#FF3B30';
104
+
105
+
106
+ function bucketFor(absRad: number, warn: number, bad: number): DriftBucket {
107
+ if (absRad <= warn) return 'good';
108
+ if (absRad <= bad) return 'warn';
109
+ return 'bad';
110
+ }
111
+
112
+
113
+ function colorFor(bucket: DriftBucket): string {
114
+ switch (bucket) {
115
+ case 'good': return COLOR_GOOD;
116
+ case 'warn': return COLOR_WARN;
117
+ case 'bad': return COLOR_BAD;
118
+ }
119
+ }
120
+
121
+
122
+ /**
123
+ * Pan axis vs orientation — same convention as
124
+ * PanoramaBandOverlay (and PanoramaGuidance):
125
+ * • portrait → pan horizontal → guide line is HORIZONTAL across
126
+ * the screen, marker drifts vertically.
127
+ * • landscape → pan vertical → guide line is VERTICAL down the
128
+ * screen, marker drifts horizontally.
129
+ */
130
+ function panAxis(orientation: DeviceOrientation): 'horizontal' | 'vertical' {
131
+ return orientation === 'landscape-left' || orientation === 'landscape-right'
132
+ ? 'vertical'
133
+ : 'horizontal';
134
+ }
135
+
136
+
137
+ export function IncrementalPanGuide({
138
+ active,
139
+ pixelsPerRad = 600,
140
+ maxTravelPx = 80,
141
+ warnRad = 0.05, // ≈ 2.9°
142
+ badRad = 0.10, // ≈ 5.7°
143
+ style,
144
+ }: IncrementalPanGuideProps): React.JSX.Element | null {
145
+ const orientation = useDeviceOrientation();
146
+ const axis = panAxis(orientation);
147
+
148
+ // Integrated PERPENDICULAR rotation since `active` flipped true.
149
+ // Held in a ref so per-sample updates don't re-render. We re-
150
+ // render only when the displayed marker offset (rounded to int
151
+ // px) or colour bucket changes.
152
+ const perpIntegralRef = useRef(0);
153
+ const lastSampleTsRef = useRef<number | null>(null);
154
+
155
+ // Displayed marker offset in px (signed; sign tells us which
156
+ // side of the line the user has drifted to). Capped to
157
+ // ±maxTravelPx. Rounded to int to keep React reconciliation
158
+ // cheap (one re-render per integer pixel of travel).
159
+ const [markerOffsetPx, setMarkerOffsetPx] = useState(0);
160
+ const [bucket, setBucket] = useState<DriftBucket>('good');
161
+ const lastBucketRef = useRef<DriftBucket>('good');
162
+ const lastOffsetRef = useRef(0);
163
+
164
+ useEffect(() => {
165
+ // Inactive: reset integral and on-screen state so the next
166
+ // capture starts at a clean zero.
167
+ if (!active) {
168
+ perpIntegralRef.current = 0;
169
+ lastSampleTsRef.current = null;
170
+ lastBucketRef.current = 'good';
171
+ lastOffsetRef.current = 0;
172
+ setMarkerOffsetPx(0);
173
+ setBucket('good');
174
+ return;
175
+ }
176
+
177
+ setUpdateIntervalForType(SensorTypes.gyroscope, 33);
178
+
179
+ let subscription: Subscription | null = gyroscope.subscribe({
180
+ next: ({ x, y }) => {
181
+ // The PERPENDICULAR axis is the orthogonal one to the pan:
182
+ // portrait pans horizontally → pan axis = gyro Y →
183
+ // perp axis = gyro X (pitch — head up/down).
184
+ // landscape pans vertically → pan axis = gyro X →
185
+ // perp axis = gyro Y (yaw — head left/right).
186
+ const perpRate = axis === 'horizontal' ? x : y;
187
+
188
+ // Δt in seconds since the previous sample. Use Date.now()
189
+ // rather than the upstream's `timestamp` field because
190
+ // react-native-sensors emits seconds on iOS and ms on
191
+ // Android — Date.now() is unambiguous and reliable enough
192
+ // at 30 Hz integration.
193
+ const nowMs = Date.now();
194
+ const last = lastSampleTsRef.current ?? nowMs;
195
+ const dt = Math.min(0.1, Math.max(0, (nowMs - last) / 1000));
196
+ lastSampleTsRef.current = nowMs;
197
+
198
+ perpIntegralRef.current += perpRate * dt;
199
+
200
+ // Convert integral (radians) → px offset, clamp.
201
+ const rawPx = perpIntegralRef.current * pixelsPerRad;
202
+ const clampedPx = Math.max(
203
+ -maxTravelPx,
204
+ Math.min(maxTravelPx, rawPx),
205
+ );
206
+ const roundedPx = Math.round(clampedPx);
207
+
208
+ // setState only when the rounded px changed — keeps the
209
+ // re-render rate to ~max(60, gyro rate) fps and usually
210
+ // far less since drift is gradual.
211
+ if (roundedPx !== lastOffsetRef.current) {
212
+ lastOffsetRef.current = roundedPx;
213
+ setMarkerOffsetPx(roundedPx);
214
+ }
215
+
216
+ const nextBucket = bucketFor(
217
+ Math.abs(perpIntegralRef.current),
218
+ warnRad,
219
+ badRad,
220
+ );
221
+ if (nextBucket !== lastBucketRef.current) {
222
+ lastBucketRef.current = nextBucket;
223
+ setBucket(nextBucket);
224
+ }
225
+ },
226
+ error: (err) => {
227
+ // eslint-disable-next-line no-console
228
+ console.warn('[IncrementalPanGuide] gyroscope error', err);
229
+ },
230
+ });
231
+
232
+ return () => {
233
+ subscription?.unsubscribe();
234
+ subscription = null;
235
+ };
236
+ }, [active, axis, pixelsPerRad, maxTravelPx, warnRad, badRad]);
237
+
238
+ if (!active) return null;
239
+
240
+ const markerColor = colorFor(bucket);
241
+
242
+ if (axis === 'horizontal') {
243
+ // Portrait: guide line spans horizontally across the centre,
244
+ // marker drifts vertically. Marker is a small circle outlined
245
+ // in the bucket colour.
246
+ return (
247
+ <View
248
+ pointerEvents="none"
249
+ style={[styles.rootCentered, style]}
250
+ >
251
+ <View style={styles.lineHorizontal} />
252
+ <View
253
+ style={[
254
+ styles.marker,
255
+ { borderColor: markerColor },
256
+ // Vertical translation from the line. Negative px =
257
+ // device tilted UP from start → marker UP.
258
+ { transform: [{ translateY: markerOffsetPx }] },
259
+ ]}
260
+ />
261
+ </View>
262
+ );
263
+ }
264
+
265
+ // Landscape: guide line is VERTICAL down the centre, marker
266
+ // drifts horizontally.
267
+ return (
268
+ <View
269
+ pointerEvents="none"
270
+ style={[styles.rootCentered, style]}
271
+ >
272
+ <View style={styles.lineVertical} />
273
+ <View
274
+ style={[
275
+ styles.marker,
276
+ { borderColor: markerColor },
277
+ { transform: [{ translateX: markerOffsetPx }] },
278
+ ]}
279
+ />
280
+ </View>
281
+ );
282
+ }
283
+
284
+
285
+ // Layout: position the entire guide as an absolute, full-screen
286
+ // overlay so the line spans the camera viewport edge-to-edge.
287
+ // The marker is rendered absolutely at the centre and translated
288
+ // by markerOffsetPx (signed) along the perpendicular axis.
289
+ const styles = StyleSheet.create({
290
+ rootCentered: {
291
+ position: 'absolute',
292
+ top: 0,
293
+ left: 0,
294
+ right: 0,
295
+ bottom: 0,
296
+ alignItems: 'center',
297
+ justifyContent: 'center',
298
+ },
299
+ // Horizontal line across the screen, centred vertically. Thin
300
+ // (1.5 px), white-translucent, dashed via repeating segments
301
+ // would be nicer but a flat translucent rectangle is sufficient
302
+ // for V12.11 Step 2 — leaves room for a polish pass later.
303
+ lineHorizontal: {
304
+ position: 'absolute',
305
+ left: 0,
306
+ right: 0,
307
+ height: 1.5,
308
+ backgroundColor: 'rgba(255, 255, 255, 0.55)',
309
+ },
310
+ // Vertical line down the screen, centred horizontally.
311
+ lineVertical: {
312
+ position: 'absolute',
313
+ top: 0,
314
+ bottom: 0,
315
+ width: 1.5,
316
+ backgroundColor: 'rgba(255, 255, 255, 0.55)',
317
+ },
318
+ // Marker — a 22 px ringed circle. Filled with a translucent
319
+ // dark so the bucket-coloured ring reads cleanly against any
320
+ // camera-feed background.
321
+ marker: {
322
+ width: 22,
323
+ height: 22,
324
+ borderRadius: 11,
325
+ borderWidth: 3,
326
+ backgroundColor: 'rgba(0, 0, 0, 0.35)',
327
+ },
328
+ });