reelme 0.3.0 → 0.4.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.
- package/README.md +10 -3
- package/assets/audio/generate-tracks.mjs +6 -1
- package/package.json +8 -2
- package/src/cache.mjs +154 -14
- package/src/cli.mjs +20 -11
- package/src/render.mjs +91 -19
- package/template/package.json +6 -6
- package/template/pnpm-lock.yaml +177 -122
- package/template/pnpm-workspace.yaml +2 -0
- package/template/src/Root.tsx +27 -15
- package/template/src/brief.json +6 -6
- package/template/src/brief.ts +13 -3
- package/template/src/cinematic/Camera.tsx +1 -1
- package/template/src/cinematic/look.ts +25 -8
- package/template/src/cinematic/transitions.tsx +27 -9
- package/template/src/components/primitives/Bar.tsx +6 -2
- package/template/src/components/primitives/Caption.tsx +2 -2
- package/template/src/components/primitives/Icon.tsx +7 -0
- package/template/src/components/primitives/KeyPill.tsx +1 -1
- package/template/src/components/primitives/Label.tsx +4 -1
- package/template/src/components/primitives/Loop.tsx +65 -0
- package/template/src/components/primitives/RevealText.tsx +27 -5
- package/template/src/components/primitives/Stage.tsx +62 -0
- package/template/src/components/scenes/BrowserFrame.tsx +1 -1
- package/template/src/components/scenes/CTA.tsx +45 -82
- package/template/src/components/scenes/Clip.tsx +9 -3
- package/template/src/components/scenes/CodeReveal.tsx +6 -5
- package/template/src/components/scenes/DataFlow.tsx +1 -1
- package/template/src/components/scenes/FeatureList.tsx +74 -77
- package/template/src/components/scenes/FileTree.tsx +1 -1
- package/template/src/components/scenes/HotkeyScene.tsx +4 -3
- package/template/src/components/scenes/MobileScreen.tsx +222 -107
- package/template/src/components/scenes/OSWindowScene.tsx +4 -4
- package/template/src/components/scenes/Problem.tsx +1 -1
- package/template/src/components/scenes/SplitComparison.tsx +1 -1
- package/template/src/components/scenes/StatCallout.tsx +123 -68
- package/template/src/components/scenes/TerminalScene.tsx +12 -11
- package/template/src/duration.ts +12 -2
- package/template/src/index.ts +7 -5
- package/template/src/timing.ts +49 -0
|
@@ -3,6 +3,7 @@ import { AbsoluteFill, useCurrentFrame, useVideoConfig, spring, interpolate, Img
|
|
|
3
3
|
import { MobileScene as MobileBrief } from "../../brief";
|
|
4
4
|
import { Theme } from "../../theme";
|
|
5
5
|
import { Caption } from "../primitives/Caption";
|
|
6
|
+
import { Icon } from "../primitives/Icon";
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
scene: MobileBrief;
|
|
@@ -17,153 +18,267 @@ const RADIUS = 46;
|
|
|
17
18
|
|
|
18
19
|
export const MobileScreen: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) => {
|
|
19
20
|
const frame = useCurrentFrame();
|
|
20
|
-
const { fps } = useVideoConfig();
|
|
21
|
+
const { fps, width, height } = useVideoConfig();
|
|
22
|
+
const portrait = height > width;
|
|
23
|
+
const hasCopy = Boolean(scene.headline || (scene.points && scene.points.length > 0));
|
|
21
24
|
|
|
25
|
+
// Phone entrance.
|
|
22
26
|
const progress = spring({ frame, fps, config: theme.motion });
|
|
23
27
|
const opacity = interpolate(progress, [0, 1], [0, 1]);
|
|
24
28
|
const translateY = interpolate(progress, [0, 1], [60, 0]);
|
|
25
|
-
const
|
|
29
|
+
const enterScale = interpolate(progress, [0, 1], [0.9, 1]);
|
|
30
|
+
|
|
31
|
+
// The phone is a fixed 384×808 device; scale it to the frame. With copy beside
|
|
32
|
+
// it (landscape) or above it (vertical) it sits a touch smaller to share the
|
|
33
|
+
// frame; on its own it grows to command the height.
|
|
34
|
+
const targetH = hasCopy
|
|
35
|
+
? (portrait ? height * 0.56 : height * 0.84)
|
|
36
|
+
: (portrait ? height * 0.74 : height * 0.84);
|
|
37
|
+
const fit = targetH / PHONE_H;
|
|
38
|
+
|
|
39
|
+
// The wrapper takes the *scaled* footprint so the device occupies a real
|
|
40
|
+
// layout slot — a bare transform:scale would resize the phone visually while
|
|
41
|
+
// its box stayed 808px tall, overlapping copy stacked above it on vertical cuts.
|
|
42
|
+
const phone = (
|
|
43
|
+
<div
|
|
44
|
+
style={{
|
|
45
|
+
width: PHONE_W * fit,
|
|
46
|
+
height: PHONE_H * fit,
|
|
47
|
+
flexShrink: 0,
|
|
48
|
+
opacity,
|
|
49
|
+
translate: `0 ${translateY}px`,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<div style={{ scale: String(enterScale * fit), transformOrigin: "top left" }}>
|
|
53
|
+
<PhoneFrame scene={scene} theme={theme} frame={frame} fps={fps} />
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Single centered device when no copy is supplied (composed symmetric margins).
|
|
59
|
+
if (!hasCopy) {
|
|
60
|
+
return (
|
|
61
|
+
<AbsoluteFill style={{ background: "transparent", display: "flex", alignItems: "center", justifyContent: "center" }}>
|
|
62
|
+
{phone}
|
|
63
|
+
{scene.caption && <Caption text={scene.caption} theme={theme} startFrame={50} bottomInset={bottomInset} />}
|
|
64
|
+
</AbsoluteFill>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const copy = <CopyColumn scene={scene} theme={theme} frame={frame} fps={fps} portrait={portrait} width={width} />;
|
|
26
69
|
|
|
27
70
|
return (
|
|
28
71
|
<AbsoluteFill
|
|
29
72
|
style={{
|
|
30
73
|
background: "transparent",
|
|
31
74
|
display: "flex",
|
|
75
|
+
flexDirection: portrait ? "column" : "row",
|
|
32
76
|
alignItems: "center",
|
|
33
|
-
justifyContent: "center",
|
|
77
|
+
justifyContent: portrait ? "center" : "space-between",
|
|
78
|
+
gap: portrait ? 12 : 80,
|
|
79
|
+
padding: portrait ? "0 80px" : "0 130px",
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{copy}
|
|
83
|
+
{phone}
|
|
84
|
+
{scene.caption && <Caption text={scene.caption} theme={theme} startFrame={50} bottomInset={bottomInset} />}
|
|
85
|
+
</AbsoluteFill>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Headline + supporting bullets that balance the device. */
|
|
90
|
+
const CopyColumn: React.FC<{
|
|
91
|
+
scene: MobileBrief;
|
|
92
|
+
theme: Theme;
|
|
93
|
+
frame: number;
|
|
94
|
+
fps: number;
|
|
95
|
+
portrait: boolean;
|
|
96
|
+
width: number;
|
|
97
|
+
}> = ({ scene, theme, frame, fps, portrait, width }) => {
|
|
98
|
+
const headlineSize = portrait ? Math.round(width * 0.06) : Math.round(width * 0.032);
|
|
99
|
+
const pointSize = portrait ? Math.round(width * 0.032) : Math.round(width * 0.019);
|
|
100
|
+
|
|
101
|
+
const headP = spring({ frame: frame - 6, fps, config: theme.motion });
|
|
102
|
+
const headOpacity = interpolate(headP, [0, 1], [0, 1]);
|
|
103
|
+
const headShift = interpolate(headP, [0, 1], [portrait ? 18 : -28, 0]);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
style={{
|
|
108
|
+
display: "flex",
|
|
109
|
+
flexDirection: "column",
|
|
110
|
+
gap: portrait ? 22 : 30,
|
|
111
|
+
maxWidth: portrait ? width * 0.82 : width * 0.44,
|
|
112
|
+
alignItems: portrait ? "center" : "flex-start",
|
|
113
|
+
textAlign: portrait ? "center" : "left",
|
|
34
114
|
}}
|
|
35
115
|
>
|
|
116
|
+
{scene.headline && (
|
|
117
|
+
<div
|
|
118
|
+
style={{
|
|
119
|
+
opacity: headOpacity,
|
|
120
|
+
translate: portrait ? `0 ${headShift}px` : `${headShift}px 0`,
|
|
121
|
+
fontFamily: theme.fontSans,
|
|
122
|
+
fontSize: headlineSize,
|
|
123
|
+
fontWeight: 800,
|
|
124
|
+
lineHeight: 1.08,
|
|
125
|
+
letterSpacing: "-0.03em",
|
|
126
|
+
color: theme.text,
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
{scene.headline}
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
{scene.points && scene.points.length > 0 && (
|
|
134
|
+
<div style={{ display: "flex", flexDirection: "column", gap: portrait ? 16 : 20 }}>
|
|
135
|
+
{scene.points.map((point, i) => {
|
|
136
|
+
const p = spring({ frame: frame - 18 - i * 7, fps, config: theme.motion });
|
|
137
|
+
return (
|
|
138
|
+
<div
|
|
139
|
+
key={i}
|
|
140
|
+
style={{
|
|
141
|
+
display: "flex",
|
|
142
|
+
alignItems: "center",
|
|
143
|
+
gap: 16,
|
|
144
|
+
opacity: interpolate(p, [0, 1], [0, 1]),
|
|
145
|
+
translate: `${interpolate(p, [0, 1], [portrait ? 0 : -16, 0])}px 0`,
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<Icon name="check" size={Math.round(pointSize * 0.95)} color={theme.accent} />
|
|
149
|
+
<span style={{ fontFamily: theme.fontSans, fontSize: pointSize, fontWeight: 500, color: theme.text, lineHeight: 1.3 }}>
|
|
150
|
+
{point}
|
|
151
|
+
</span>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
})}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/** The fixed-size device. Scaled to the frame by its parent. */
|
|
162
|
+
const PhoneFrame: React.FC<{ scene: MobileBrief; theme: Theme; frame: number; fps: number }> = ({ scene, theme, frame, fps }) => {
|
|
163
|
+
return (
|
|
164
|
+
<div
|
|
165
|
+
style={{
|
|
166
|
+
width: PHONE_W,
|
|
167
|
+
height: PHONE_H,
|
|
168
|
+
borderRadius: RADIUS,
|
|
169
|
+
border: `3px solid ${theme.border}`,
|
|
170
|
+
background: theme.surface,
|
|
171
|
+
boxShadow: `0 50px 120px rgba(0,0,0,0.6), inset 0 0 0 1px rgba(255,255,255,0.05)`,
|
|
172
|
+
position: "relative",
|
|
173
|
+
overflow: "hidden",
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{/* Notch */}
|
|
36
177
|
<div
|
|
37
178
|
style={{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
179
|
+
position: "absolute",
|
|
180
|
+
top: 0,
|
|
181
|
+
left: "50%",
|
|
182
|
+
transform: "translateX(-50%)",
|
|
183
|
+
width: 120,
|
|
184
|
+
height: NOTCH_H,
|
|
42
185
|
background: theme.surface,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
opacity,
|
|
47
|
-
transform: `translateY(${translateY}px) scale(${scale})`,
|
|
186
|
+
borderBottomLeftRadius: 18,
|
|
187
|
+
borderBottomRightRadius: 18,
|
|
188
|
+
zIndex: 10,
|
|
48
189
|
}}
|
|
49
|
-
|
|
50
|
-
{/* Notch */}
|
|
51
|
-
<div
|
|
52
|
-
style={{
|
|
53
|
-
position: "absolute",
|
|
54
|
-
top: 0,
|
|
55
|
-
left: "50%",
|
|
56
|
-
transform: "translateX(-50%)",
|
|
57
|
-
width: 120,
|
|
58
|
-
height: NOTCH_H,
|
|
59
|
-
background: theme.surface,
|
|
60
|
-
borderBottomLeftRadius: 18,
|
|
61
|
-
borderBottomRightRadius: 18,
|
|
62
|
-
zIndex: 10,
|
|
63
|
-
}}
|
|
64
|
-
/>
|
|
190
|
+
/>
|
|
65
191
|
|
|
66
|
-
|
|
192
|
+
{/* Screen */}
|
|
193
|
+
<div style={{ position: "absolute", inset: 0, background: theme.bg, display: "flex", flexDirection: "column" }}>
|
|
194
|
+
{/* Status bar */}
|
|
67
195
|
<div
|
|
68
196
|
style={{
|
|
69
|
-
|
|
70
|
-
inset: 0,
|
|
71
|
-
background: theme.bg,
|
|
197
|
+
height: NOTCH_H + 2,
|
|
72
198
|
display: "flex",
|
|
73
|
-
|
|
199
|
+
alignItems: "flex-end",
|
|
200
|
+
justifyContent: "space-between",
|
|
201
|
+
paddingLeft: 22,
|
|
202
|
+
paddingRight: 22,
|
|
203
|
+
paddingBottom: 4,
|
|
204
|
+
fontFamily: theme.fontSans,
|
|
205
|
+
fontSize: 12,
|
|
206
|
+
fontWeight: 600,
|
|
207
|
+
color: theme.text,
|
|
208
|
+
flexShrink: 0,
|
|
74
209
|
}}
|
|
75
210
|
>
|
|
76
|
-
|
|
211
|
+
<span>9:41</span>
|
|
212
|
+
<span style={{ letterSpacing: 1 }}>●●●</span>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* App header */}
|
|
216
|
+
{scene.title && (
|
|
77
217
|
<div
|
|
78
218
|
style={{
|
|
79
|
-
height:
|
|
219
|
+
height: 52,
|
|
80
220
|
display: "flex",
|
|
81
|
-
alignItems: "
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
paddingBottom: 4,
|
|
221
|
+
alignItems: "center",
|
|
222
|
+
gap: 10,
|
|
223
|
+
padding: "0 18px",
|
|
224
|
+
borderBottom: `1px solid ${theme.border}`,
|
|
86
225
|
fontFamily: theme.fontSans,
|
|
87
|
-
fontSize:
|
|
88
|
-
fontWeight:
|
|
226
|
+
fontSize: 17,
|
|
227
|
+
fontWeight: 700,
|
|
89
228
|
color: theme.text,
|
|
90
229
|
flexShrink: 0,
|
|
91
230
|
}}
|
|
92
231
|
>
|
|
93
|
-
<
|
|
94
|
-
|
|
232
|
+
<div style={{ width: 12, height: 12, borderRadius: "50%", background: theme.accent }} />
|
|
233
|
+
{scene.title}
|
|
95
234
|
</div>
|
|
235
|
+
)}
|
|
96
236
|
|
|
97
|
-
|
|
98
|
-
|
|
237
|
+
{/* Content */}
|
|
238
|
+
<div style={{ flex: 1, overflow: "hidden", position: "relative" }}>
|
|
239
|
+
{scene.screenshot ? (
|
|
240
|
+
<KenBurns frame={frame}>
|
|
241
|
+
<Img
|
|
242
|
+
src={staticFile(scene.screenshot)}
|
|
243
|
+
style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
|
|
244
|
+
/>
|
|
245
|
+
</KenBurns>
|
|
246
|
+
) : (
|
|
247
|
+
<MockFeed theme={theme} frame={frame} fps={fps} />
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
{/* Bottom nav */}
|
|
252
|
+
<div
|
|
253
|
+
style={{
|
|
254
|
+
height: 56,
|
|
255
|
+
borderTop: `1px solid ${theme.border}`,
|
|
256
|
+
display: "flex",
|
|
257
|
+
alignItems: "center",
|
|
258
|
+
justifyContent: "space-around",
|
|
259
|
+
flexShrink: 0,
|
|
260
|
+
background: theme.surface,
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
{["⌂", "⊞", "♡", "◉"].map((icon, i) => (
|
|
99
264
|
<div
|
|
265
|
+
key={i}
|
|
100
266
|
style={{
|
|
101
|
-
|
|
267
|
+
width: 38,
|
|
268
|
+
height: 38,
|
|
102
269
|
display: "flex",
|
|
103
270
|
alignItems: "center",
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
fontFamily: theme.fontSans,
|
|
108
|
-
fontSize: 17,
|
|
109
|
-
fontWeight: 700,
|
|
110
|
-
color: theme.text,
|
|
111
|
-
flexShrink: 0,
|
|
271
|
+
justifyContent: "center",
|
|
272
|
+
fontSize: 19,
|
|
273
|
+
color: i === 0 ? theme.accent : theme.textMuted,
|
|
112
274
|
}}
|
|
113
275
|
>
|
|
114
|
-
|
|
115
|
-
{scene.title}
|
|
276
|
+
{icon}
|
|
116
277
|
</div>
|
|
117
|
-
)}
|
|
118
|
-
|
|
119
|
-
{/* Content */}
|
|
120
|
-
<div style={{ flex: 1, overflow: "hidden", position: "relative" }}>
|
|
121
|
-
{scene.screenshot ? (
|
|
122
|
-
<KenBurns frame={frame}>
|
|
123
|
-
<Img
|
|
124
|
-
src={staticFile(scene.screenshot)}
|
|
125
|
-
style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
|
|
126
|
-
/>
|
|
127
|
-
</KenBurns>
|
|
128
|
-
) : (
|
|
129
|
-
<MockFeed theme={theme} frame={frame} fps={fps} />
|
|
130
|
-
)}
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
|
-
{/* Bottom nav */}
|
|
134
|
-
<div
|
|
135
|
-
style={{
|
|
136
|
-
height: 56,
|
|
137
|
-
borderTop: `1px solid ${theme.border}`,
|
|
138
|
-
display: "flex",
|
|
139
|
-
alignItems: "center",
|
|
140
|
-
justifyContent: "space-around",
|
|
141
|
-
flexShrink: 0,
|
|
142
|
-
background: theme.surface,
|
|
143
|
-
}}
|
|
144
|
-
>
|
|
145
|
-
{["⌂", "⊞", "♡", "◉"].map((icon, i) => (
|
|
146
|
-
<div
|
|
147
|
-
key={i}
|
|
148
|
-
style={{
|
|
149
|
-
width: 38,
|
|
150
|
-
height: 38,
|
|
151
|
-
display: "flex",
|
|
152
|
-
alignItems: "center",
|
|
153
|
-
justifyContent: "center",
|
|
154
|
-
fontSize: 19,
|
|
155
|
-
color: i === 0 ? theme.accent : theme.textMuted,
|
|
156
|
-
}}
|
|
157
|
-
>
|
|
158
|
-
{icon}
|
|
159
|
-
</div>
|
|
160
|
-
))}
|
|
161
|
-
</div>
|
|
278
|
+
))}
|
|
162
279
|
</div>
|
|
163
280
|
</div>
|
|
164
|
-
|
|
165
|
-
{scene.caption && <Caption text={scene.caption} theme={theme} startFrame={50} bottomInset={bottomInset} />}
|
|
166
|
-
</AbsoluteFill>
|
|
281
|
+
</div>
|
|
167
282
|
);
|
|
168
283
|
};
|
|
169
284
|
|
|
@@ -185,7 +300,7 @@ const MockFeed: React.FC<{ theme: Theme; frame: number; fps: number }> = ({ them
|
|
|
185
300
|
const Item: React.FC<{ index: number; children: React.ReactNode }> = ({ index, children }) => {
|
|
186
301
|
const p = spring({ frame: frame - 8 - index * 7, fps, config: theme.motion });
|
|
187
302
|
return (
|
|
188
|
-
<div style={{ opacity: interpolate(p, [0, 1], [0, 1]),
|
|
303
|
+
<div style={{ opacity: interpolate(p, [0, 1], [0, 1]), translate: `0 ${interpolate(p, [0, 1], [16, 0])}px` }}>
|
|
189
304
|
{children}
|
|
190
305
|
</div>
|
|
191
306
|
);
|
|
@@ -198,7 +313,7 @@ const MockFeed: React.FC<{ theme: Theme; frame: number; fps: number }> = ({ them
|
|
|
198
313
|
};
|
|
199
314
|
|
|
200
315
|
return (
|
|
201
|
-
<div style={{ padding: 16, display: "flex", flexDirection: "column", gap: 12,
|
|
316
|
+
<div style={{ padding: 16, display: "flex", flexDirection: "column", gap: 12, translate: `0 ${scrollY}px` }}>
|
|
202
317
|
<Item index={0}>
|
|
203
318
|
<div style={{ ...cardBase, height: 36, display: "flex", alignItems: "center", padding: "0 14px", fontFamily: theme.fontSans, fontSize: 13, color: theme.textMuted }}>
|
|
204
319
|
Search
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { AbsoluteFill, useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";
|
|
3
3
|
import { OSWindowScene as OSWindowBrief } from "../../brief";
|
|
4
4
|
import { Theme } from "../../theme";
|
|
5
|
-
import { Icon } from "../primitives/Icon";
|
|
5
|
+
import { Icon, hasIcon } from "../primitives/Icon";
|
|
6
6
|
import { Caption } from "../primitives/Caption";
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
@@ -48,7 +48,7 @@ export const OSWindow: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) =>
|
|
|
48
48
|
background: theme.surface,
|
|
49
49
|
border: `1px solid ${theme.border}`,
|
|
50
50
|
boxShadow: "0 40px 100px rgba(0,0,0,0.45), 0 0 0 0.5px rgba(255,255,255,0.05)",
|
|
51
|
-
|
|
51
|
+
scale: String(windowScale),
|
|
52
52
|
opacity: windowOpacity,
|
|
53
53
|
}}
|
|
54
54
|
>
|
|
@@ -125,10 +125,10 @@ export const OSWindow: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) =>
|
|
|
125
125
|
padding: "9px 14px",
|
|
126
126
|
background: item.highlighted ? theme.accentMuted : "transparent",
|
|
127
127
|
opacity,
|
|
128
|
-
|
|
128
|
+
translate: `${tx}px 0`,
|
|
129
129
|
}}
|
|
130
130
|
>
|
|
131
|
-
{item.icon && (
|
|
131
|
+
{item.icon && hasIcon(item.icon) && (
|
|
132
132
|
<div
|
|
133
133
|
style={{
|
|
134
134
|
width: 30,
|
|
@@ -52,7 +52,7 @@ export const Problem: React.FC<Props> = ({ scene, theme, project, platform, bott
|
|
|
52
52
|
<div
|
|
53
53
|
style={{
|
|
54
54
|
opacity: badgeOpacity,
|
|
55
|
-
|
|
55
|
+
translate: `0 ${badgeY}px`,
|
|
56
56
|
background: theme.accentMuted,
|
|
57
57
|
border: `1.5px solid ${theme.accent}`,
|
|
58
58
|
borderRadius: 999,
|