@umituz/react-native-splash 1.4.1 → 1.5.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/package.json +1 -1
- package/src/presentation/components/SplashDecorations.tsx +48 -0
- package/src/presentation/components/SplashLoading.tsx +72 -0
- package/src/presentation/components/SplashLogo.tsx +78 -0
- package/src/presentation/components/SplashScreen.tsx +35 -144
- package/src/presentation/components/SplashTypography.tsx +70 -0
- package/src/presentation/utils/splashGradient.utils.ts +56 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-splash",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Generic splash screen for React Native apps with animations, gradients, and customizable branding. SOLID, DRY, KISS principles applied.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splash Decorations Component
|
|
3
|
+
* Single Responsibility: Render decorative background elements
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
|
|
9
|
+
export const SplashDecorations: React.FC = () => {
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
<View style={styles.circle1} />
|
|
13
|
+
<View style={styles.circle2} />
|
|
14
|
+
<View style={styles.circle3} />
|
|
15
|
+
</>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
circle1: {
|
|
21
|
+
position: "absolute",
|
|
22
|
+
width: 300,
|
|
23
|
+
height: 300,
|
|
24
|
+
borderRadius: 150,
|
|
25
|
+
backgroundColor: "rgba(255, 255, 255, 0.05)",
|
|
26
|
+
top: -100,
|
|
27
|
+
right: -100,
|
|
28
|
+
},
|
|
29
|
+
circle2: {
|
|
30
|
+
position: "absolute",
|
|
31
|
+
width: 200,
|
|
32
|
+
height: 200,
|
|
33
|
+
borderRadius: 100,
|
|
34
|
+
backgroundColor: "rgba(255, 255, 255, 0.03)",
|
|
35
|
+
bottom: -50,
|
|
36
|
+
left: -50,
|
|
37
|
+
},
|
|
38
|
+
circle3: {
|
|
39
|
+
position: "absolute",
|
|
40
|
+
width: 150,
|
|
41
|
+
height: 150,
|
|
42
|
+
borderRadius: 75,
|
|
43
|
+
backgroundColor: "rgba(255, 255, 255, 0.04)",
|
|
44
|
+
top: "30%",
|
|
45
|
+
right: "10%",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splash Loading Component
|
|
3
|
+
* Single Responsibility: Render splash screen loading indicator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
8
|
+
import type { AppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
|
|
10
|
+
export interface SplashLoadingProps {
|
|
11
|
+
loadingText: string;
|
|
12
|
+
tokens: AppDesignTokens;
|
|
13
|
+
bottomInset: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SplashLoading: React.FC<SplashLoadingProps> = ({
|
|
17
|
+
loadingText,
|
|
18
|
+
tokens,
|
|
19
|
+
bottomInset,
|
|
20
|
+
}) => {
|
|
21
|
+
const styles = getStyles(tokens, bottomInset);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<View style={styles.container}>
|
|
25
|
+
<View style={styles.barContainer}>
|
|
26
|
+
<View style={styles.bar} />
|
|
27
|
+
</View>
|
|
28
|
+
<Text style={styles.text}>{loadingText}</Text>
|
|
29
|
+
</View>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getStyles = (tokens: AppDesignTokens, bottomInset: number) => {
|
|
34
|
+
return StyleSheet.create({
|
|
35
|
+
container: {
|
|
36
|
+
alignItems: "center",
|
|
37
|
+
justifyContent: "center",
|
|
38
|
+
paddingBottom: tokens.spacing.xxxl + bottomInset,
|
|
39
|
+
zIndex: 1,
|
|
40
|
+
},
|
|
41
|
+
barContainer: {
|
|
42
|
+
width: 120,
|
|
43
|
+
height: 4,
|
|
44
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
45
|
+
borderRadius: 2,
|
|
46
|
+
overflow: "hidden",
|
|
47
|
+
marginBottom: tokens.spacing.md,
|
|
48
|
+
},
|
|
49
|
+
bar: {
|
|
50
|
+
width: "60%",
|
|
51
|
+
height: "100%",
|
|
52
|
+
backgroundColor: "#FFFFFF",
|
|
53
|
+
borderRadius: 2,
|
|
54
|
+
shadowColor: "#FFFFFF",
|
|
55
|
+
shadowOffset: { width: 0, height: 0 },
|
|
56
|
+
shadowOpacity: 0.8,
|
|
57
|
+
shadowRadius: 4,
|
|
58
|
+
elevation: 4,
|
|
59
|
+
},
|
|
60
|
+
text: {
|
|
61
|
+
fontSize: 13,
|
|
62
|
+
color: "#FFFFFF",
|
|
63
|
+
opacity: 0.9,
|
|
64
|
+
fontWeight: "500" as const,
|
|
65
|
+
letterSpacing: 0.8,
|
|
66
|
+
textShadowColor: "rgba(0, 0, 0, 0.15)",
|
|
67
|
+
textShadowOffset: { width: 0, height: 1 },
|
|
68
|
+
textShadowRadius: 2,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splash Logo Component
|
|
3
|
+
* Single Responsibility: Render splash screen logo
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import { AtomicIcon } from "@umituz/react-native-design-system-atoms";
|
|
9
|
+
|
|
10
|
+
export interface SplashLogoProps {
|
|
11
|
+
logo?: string | React.ReactNode;
|
|
12
|
+
logoSize?: number;
|
|
13
|
+
glowSize?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SplashLogo: React.FC<SplashLogoProps> = ({
|
|
17
|
+
logo,
|
|
18
|
+
logoSize = 140,
|
|
19
|
+
glowSize = 160,
|
|
20
|
+
}) => {
|
|
21
|
+
const styles = getStyles(logoSize, glowSize);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<View style={styles.container}>
|
|
25
|
+
<View style={styles.glow} />
|
|
26
|
+
<View style={styles.background}>
|
|
27
|
+
{typeof logo === "string" ? (
|
|
28
|
+
<AtomicIcon name={logo || "Sparkles"} size="xxl" color="white" />
|
|
29
|
+
) : logo ? (
|
|
30
|
+
logo
|
|
31
|
+
) : (
|
|
32
|
+
<AtomicIcon name="Sparkles" size="xxl" color="white" />
|
|
33
|
+
)}
|
|
34
|
+
</View>
|
|
35
|
+
</View>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getStyles = (logoSize: number, glowSize: number) => {
|
|
40
|
+
const radius = logoSize / 2;
|
|
41
|
+
const glowRadius = glowSize / 2;
|
|
42
|
+
|
|
43
|
+
return StyleSheet.create({
|
|
44
|
+
container: {
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
justifyContent: "center",
|
|
47
|
+
position: "relative",
|
|
48
|
+
},
|
|
49
|
+
glow: {
|
|
50
|
+
position: "absolute",
|
|
51
|
+
width: glowSize,
|
|
52
|
+
height: glowSize,
|
|
53
|
+
borderRadius: glowRadius,
|
|
54
|
+
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
|
55
|
+
shadowColor: "#FFFFFF",
|
|
56
|
+
shadowOffset: { width: 0, height: 0 },
|
|
57
|
+
shadowOpacity: 0.5,
|
|
58
|
+
shadowRadius: 30,
|
|
59
|
+
elevation: 10,
|
|
60
|
+
},
|
|
61
|
+
background: {
|
|
62
|
+
width: logoSize,
|
|
63
|
+
height: logoSize,
|
|
64
|
+
borderRadius: radius,
|
|
65
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
66
|
+
alignItems: "center",
|
|
67
|
+
justifyContent: "center",
|
|
68
|
+
borderWidth: 2,
|
|
69
|
+
borderColor: "rgba(255, 255, 255, 0.3)",
|
|
70
|
+
shadowColor: "#000",
|
|
71
|
+
shadowOffset: { width: 0, height: 4 },
|
|
72
|
+
shadowOpacity: 0.3,
|
|
73
|
+
shadowRadius: 8,
|
|
74
|
+
elevation: 8,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Splash Screen Component
|
|
3
|
-
*
|
|
4
|
-
* Simple splash screen with logo, app name, and loading indicator
|
|
3
|
+
* Single Responsibility: Orchestrate splash screen UI
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
import React, { useEffect } from "react";
|
|
8
|
-
import { View, StyleSheet
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
9
8
|
import { LinearGradient } from "expo-linear-gradient";
|
|
10
9
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
11
10
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
@@ -13,13 +12,17 @@ import {
|
|
|
13
12
|
useDesignSystemTheme,
|
|
14
13
|
useAppDesignTokens,
|
|
15
14
|
} from "@umituz/react-native-design-system-theme";
|
|
16
|
-
import {
|
|
15
|
+
import { SplashLogo } from "./SplashLogo";
|
|
16
|
+
import { SplashTypography } from "./SplashTypography";
|
|
17
|
+
import { SplashLoading } from "./SplashLoading";
|
|
18
|
+
import { SplashDecorations } from "./SplashDecorations";
|
|
19
|
+
import {
|
|
20
|
+
getDefaultGradient,
|
|
21
|
+
generateGradientFromColor,
|
|
22
|
+
} from "../utils/splashGradient.utils";
|
|
17
23
|
import type { SplashOptions } from "../../domain/entities/SplashOptions";
|
|
18
24
|
|
|
19
25
|
export interface SplashScreenProps extends SplashOptions {
|
|
20
|
-
/**
|
|
21
|
-
* Whether splash is visible
|
|
22
|
-
*/
|
|
23
26
|
visible?: boolean;
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -44,26 +47,15 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
44
47
|
const { t } = useLocalization();
|
|
45
48
|
const { themeMode } = useDesignSystemTheme();
|
|
46
49
|
const tokens = useAppDesignTokens();
|
|
47
|
-
|
|
50
|
+
|
|
48
51
|
const isDark = themeMode === "dark";
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const gradientColors: [string, string] = backgroundColor
|
|
57
|
-
? [backgroundColor, adjustColorBrightness(backgroundColor, isDark ? -15 : 10)]
|
|
58
|
-
: isDark
|
|
59
|
-
? [colors.primary, adjustColorBrightness(colors.primary, -20)]
|
|
60
|
-
: [colors.primary, adjustColorBrightness(colors.primary, 15)];
|
|
61
|
-
|
|
62
|
-
const styles = getStyles(insets, finalBackgroundColor, colors, spacing, isDark);
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Call onReady after minimum display time
|
|
66
|
-
*/
|
|
52
|
+
|
|
53
|
+
const gradientColors = backgroundColor
|
|
54
|
+
? generateGradientFromColor(backgroundColor, isDark)
|
|
55
|
+
: getDefaultGradient(isDark);
|
|
56
|
+
|
|
57
|
+
const styles = getStyles(insets, tokens.spacing);
|
|
58
|
+
|
|
67
59
|
useEffect(() => {
|
|
68
60
|
if (!visible) return;
|
|
69
61
|
|
|
@@ -90,162 +82,61 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
90
82
|
end={{ x: 1, y: 1 }}
|
|
91
83
|
style={styles.gradient}
|
|
92
84
|
>
|
|
93
|
-
|
|
85
|
+
<SplashDecorations />
|
|
86
|
+
|
|
94
87
|
<View style={styles.content}>
|
|
95
88
|
{renderLogo ? (
|
|
96
89
|
renderLogo()
|
|
97
90
|
) : (
|
|
98
|
-
<
|
|
99
|
-
<View style={styles.logoBackground}>
|
|
100
|
-
{typeof logo === "string" ? (
|
|
101
|
-
<AtomicIcon
|
|
102
|
-
name={logo || "Sparkles"}
|
|
103
|
-
size="xl"
|
|
104
|
-
color={isDark ? "primary" : "textInverse"}
|
|
105
|
-
/>
|
|
106
|
-
) : logo ? (
|
|
107
|
-
logo
|
|
108
|
-
) : (
|
|
109
|
-
<AtomicIcon
|
|
110
|
-
name="Sparkles"
|
|
111
|
-
size="xl"
|
|
112
|
-
color={isDark ? "primary" : "textInverse"}
|
|
113
|
-
/>
|
|
114
|
-
)}
|
|
115
|
-
</View>
|
|
116
|
-
</View>
|
|
91
|
+
<SplashLogo logo={logo} />
|
|
117
92
|
)}
|
|
118
93
|
|
|
119
94
|
{renderContent ? (
|
|
120
95
|
renderContent()
|
|
121
96
|
) : (
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
{displayTagline
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
</View>
|
|
97
|
+
<SplashTypography
|
|
98
|
+
appName={displayAppName}
|
|
99
|
+
tagline={displayTagline}
|
|
100
|
+
tokens={tokens}
|
|
101
|
+
/>
|
|
128
102
|
)}
|
|
129
103
|
</View>
|
|
130
104
|
|
|
131
|
-
{/* Loading Indicator */}
|
|
132
105
|
{showLoading && (
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<Text style={styles.loadingText}>{displayLoadingText}</Text>
|
|
139
|
-
</View>
|
|
106
|
+
<SplashLoading
|
|
107
|
+
loadingText={displayLoadingText}
|
|
108
|
+
tokens={tokens}
|
|
109
|
+
bottomInset={insets.bottom}
|
|
110
|
+
/>
|
|
140
111
|
)}
|
|
141
112
|
|
|
142
|
-
{/* Footer */}
|
|
143
113
|
{renderFooter && renderFooter()}
|
|
144
114
|
</LinearGradient>
|
|
145
115
|
</View>
|
|
146
116
|
);
|
|
147
117
|
};
|
|
148
118
|
|
|
149
|
-
/**
|
|
150
|
-
* Adjust color brightness
|
|
151
|
-
*/
|
|
152
|
-
const adjustColorBrightness = (hex: string, percent: number): string => {
|
|
153
|
-
const num = parseInt(hex.replace("#", ""), 16);
|
|
154
|
-
const amt = Math.round(2.55 * percent);
|
|
155
|
-
const R = (num >> 16) + amt;
|
|
156
|
-
const G = ((num >> 8) & 0x00ff) + amt;
|
|
157
|
-
const B = (num & 0x0000ff) + amt;
|
|
158
|
-
return (
|
|
159
|
-
"#" +
|
|
160
|
-
(
|
|
161
|
-
0x1000000 +
|
|
162
|
-
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
|
|
163
|
-
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
|
|
164
|
-
(B < 255 ? (B < 1 ? 0 : B) : 255)
|
|
165
|
-
)
|
|
166
|
-
.toString(16)
|
|
167
|
-
.slice(1)
|
|
168
|
-
);
|
|
169
|
-
};
|
|
170
|
-
|
|
171
119
|
const getStyles = (
|
|
172
120
|
insets: { top: number; bottom: number },
|
|
173
|
-
backgroundColor: string,
|
|
174
|
-
colors: any,
|
|
175
121
|
spacing: any,
|
|
176
|
-
isDark: boolean,
|
|
177
122
|
) => {
|
|
178
|
-
const textColor = isDark ? colors.textPrimary : "#FFFFFF";
|
|
179
|
-
const logoBgOpacity = isDark ? 0.15 : 0.2;
|
|
180
|
-
const textOpacity = isDark ? 1 : 0.95;
|
|
181
|
-
|
|
182
123
|
return StyleSheet.create({
|
|
183
124
|
container: {
|
|
184
125
|
flex: 1,
|
|
185
|
-
backgroundColor: backgroundColor,
|
|
186
126
|
},
|
|
187
127
|
gradient: {
|
|
188
128
|
flex: 1,
|
|
189
129
|
paddingTop: insets.top,
|
|
190
130
|
paddingBottom: insets.bottom,
|
|
131
|
+
position: "relative",
|
|
132
|
+
overflow: "hidden",
|
|
191
133
|
},
|
|
192
134
|
content: {
|
|
193
135
|
flex: 1,
|
|
194
136
|
alignItems: "center",
|
|
195
137
|
justifyContent: "center",
|
|
196
|
-
paddingHorizontal: spacing.
|
|
197
|
-
|
|
198
|
-
logoContainer: {
|
|
199
|
-
marginBottom: spacing.xl,
|
|
200
|
-
alignItems: "center",
|
|
201
|
-
justifyContent: "center",
|
|
202
|
-
},
|
|
203
|
-
logoBackground: {
|
|
204
|
-
width: 120,
|
|
205
|
-
height: 120,
|
|
206
|
-
borderRadius: 60,
|
|
207
|
-
backgroundColor: isDark
|
|
208
|
-
? `${colors.primary}${Math.round(logoBgOpacity * 255).toString(16).padStart(2, "0")}`
|
|
209
|
-
: `rgba(255, 255, 255, ${logoBgOpacity})`,
|
|
210
|
-
alignItems: "center",
|
|
211
|
-
justifyContent: "center",
|
|
212
|
-
padding: spacing.md,
|
|
213
|
-
borderWidth: isDark ? 2 : 0,
|
|
214
|
-
borderColor: isDark ? `${colors.primary}40` : "transparent",
|
|
215
|
-
},
|
|
216
|
-
textContainer: {
|
|
217
|
-
alignItems: "center",
|
|
218
|
-
marginBottom: spacing.xxxl,
|
|
219
|
-
},
|
|
220
|
-
appName: {
|
|
221
|
-
fontSize: 32,
|
|
222
|
-
fontWeight: "700",
|
|
223
|
-
color: textColor,
|
|
224
|
-
textAlign: "center",
|
|
225
|
-
marginBottom: spacing.sm,
|
|
226
|
-
letterSpacing: -0.5,
|
|
227
|
-
},
|
|
228
|
-
tagline: {
|
|
229
|
-
fontSize: 16,
|
|
230
|
-
color: textColor,
|
|
231
|
-
textAlign: "center",
|
|
232
|
-
opacity: textOpacity * 0.85,
|
|
233
|
-
maxWidth: 300,
|
|
234
|
-
lineHeight: 22,
|
|
235
|
-
fontWeight: "400",
|
|
236
|
-
},
|
|
237
|
-
loadingContainer: {
|
|
238
|
-
alignItems: "center",
|
|
239
|
-
justifyContent: "center",
|
|
240
|
-
paddingBottom: spacing.xxxl,
|
|
241
|
-
},
|
|
242
|
-
loadingText: {
|
|
243
|
-
fontSize: 14,
|
|
244
|
-
color: textColor,
|
|
245
|
-
opacity: textOpacity * 0.7,
|
|
246
|
-
marginTop: spacing.md,
|
|
247
|
-
fontWeight: "500",
|
|
138
|
+
paddingHorizontal: spacing.xl,
|
|
139
|
+
zIndex: 1,
|
|
248
140
|
},
|
|
249
141
|
});
|
|
250
142
|
};
|
|
251
|
-
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splash Typography Component
|
|
3
|
+
* Single Responsibility: Render splash screen typography
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
8
|
+
import type { AppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
|
|
10
|
+
export interface SplashTypographyProps {
|
|
11
|
+
appName: string;
|
|
12
|
+
tagline?: string;
|
|
13
|
+
tokens: AppDesignTokens;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SplashTypography: React.FC<SplashTypographyProps> = ({
|
|
17
|
+
appName,
|
|
18
|
+
tagline,
|
|
19
|
+
tokens,
|
|
20
|
+
}) => {
|
|
21
|
+
const styles = getStyles(tokens);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<View style={styles.container}>
|
|
25
|
+
<Text style={styles.appName} numberOfLines={1}>
|
|
26
|
+
{appName}
|
|
27
|
+
</Text>
|
|
28
|
+
{tagline && (
|
|
29
|
+
<Text style={styles.tagline} numberOfLines={2}>
|
|
30
|
+
{tagline}
|
|
31
|
+
</Text>
|
|
32
|
+
)}
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getStyles = (tokens: AppDesignTokens) => {
|
|
38
|
+
return StyleSheet.create({
|
|
39
|
+
container: {
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
marginTop: tokens.spacing.xl,
|
|
42
|
+
},
|
|
43
|
+
appName: {
|
|
44
|
+
fontSize: 48,
|
|
45
|
+
fontWeight: "800" as const,
|
|
46
|
+
color: "#FFFFFF",
|
|
47
|
+
textAlign: "center" as const,
|
|
48
|
+
marginBottom: tokens.spacing.md,
|
|
49
|
+
letterSpacing: -1.2,
|
|
50
|
+
lineHeight: 56,
|
|
51
|
+
textShadowColor: "rgba(0, 0, 0, 0.25)",
|
|
52
|
+
textShadowOffset: { width: 0, height: 2 },
|
|
53
|
+
textShadowRadius: 6,
|
|
54
|
+
},
|
|
55
|
+
tagline: {
|
|
56
|
+
fontSize: 17,
|
|
57
|
+
color: "#FFFFFF",
|
|
58
|
+
textAlign: "center" as const,
|
|
59
|
+
opacity: 0.95,
|
|
60
|
+
maxWidth: 320,
|
|
61
|
+
lineHeight: 24,
|
|
62
|
+
fontWeight: "500" as const,
|
|
63
|
+
letterSpacing: 0.2,
|
|
64
|
+
textShadowColor: "rgba(0, 0, 0, 0.2)",
|
|
65
|
+
textShadowOffset: { width: 0, height: 1 },
|
|
66
|
+
textShadowRadius: 3,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splash Gradient Utilities
|
|
3
|
+
* Single Responsibility: Gradient color generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Adjust color brightness
|
|
8
|
+
*/
|
|
9
|
+
export const adjustColorBrightness = (hex: string, percent: number): string => {
|
|
10
|
+
const num = parseInt(hex.replace("#", ""), 16);
|
|
11
|
+
const amt = Math.round(2.55 * percent);
|
|
12
|
+
const R = (num >> 16) + amt;
|
|
13
|
+
const G = ((num >> 8) & 0x00ff) + amt;
|
|
14
|
+
const B = (num & 0x0000ff) + amt;
|
|
15
|
+
return (
|
|
16
|
+
"#" +
|
|
17
|
+
(
|
|
18
|
+
0x1000000 +
|
|
19
|
+
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
|
|
20
|
+
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
|
|
21
|
+
(B < 255 ? (B < 1 ? 0 : B) : 255)
|
|
22
|
+
)
|
|
23
|
+
.toString(16)
|
|
24
|
+
.slice(1)
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get default gradient colors based on theme
|
|
30
|
+
*/
|
|
31
|
+
export const getDefaultGradient = (isDark: boolean): string[] => {
|
|
32
|
+
return isDark
|
|
33
|
+
? ["#1E1B4B", "#312E81", "#4C1D95", "#6B21A8"] // Dark indigo to purple
|
|
34
|
+
: ["#6366F1", "#8B5CF6", "#A855F7", "#EC4899"]; // Indigo to pink
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate gradient colors from backgroundColor
|
|
39
|
+
*/
|
|
40
|
+
export const generateGradientFromColor = (
|
|
41
|
+
backgroundColor: string,
|
|
42
|
+
isDark: boolean,
|
|
43
|
+
): string[] => {
|
|
44
|
+
return isDark
|
|
45
|
+
? [
|
|
46
|
+
backgroundColor,
|
|
47
|
+
adjustColorBrightness(backgroundColor, -20),
|
|
48
|
+
adjustColorBrightness(backgroundColor, -30),
|
|
49
|
+
]
|
|
50
|
+
: [
|
|
51
|
+
backgroundColor,
|
|
52
|
+
adjustColorBrightness(backgroundColor, 10),
|
|
53
|
+
adjustColorBrightness(backgroundColor, 20),
|
|
54
|
+
];
|
|
55
|
+
};
|
|
56
|
+
|