zopassport 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 (110) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +407 -0
  3. package/app/.env.example +15 -0
  4. package/app/README.md +28 -0
  5. package/app/package.json +24 -0
  6. package/app/reanimated-mock.js +102 -0
  7. package/app/reanimated-mock.jsx +97 -0
  8. package/app/src/App.tsx +331 -0
  9. package/app/src/components/FounderBadge.tsx +26 -0
  10. package/app/src/components/OTPInput.tsx +149 -0
  11. package/app/src/components/PhoneInput.tsx +109 -0
  12. package/app/src/components/ZoAuth.tsx +320 -0
  13. package/app/src/components/ZoAvatar.tsx +87 -0
  14. package/app/src/components/ZoLanding.tsx +231 -0
  15. package/app/src/components/ZoOnboarding.tsx +524 -0
  16. package/app/src/components/ZoPassportCard.tsx +183 -0
  17. package/app/src/components/ZoProgressRing.tsx +57 -0
  18. package/app/src/components/index.ts +16 -0
  19. package/app/src/components/wallet/MovingShine.tsx +43 -0
  20. package/app/src/components/wallet/TransactionItem.tsx +84 -0
  21. package/app/src/components/wallet/TransactionList.tsx +65 -0
  22. package/app/src/components/wallet/WalletCard.tsx +152 -0
  23. package/app/src/components/wallet/WalletScreen.tsx +190 -0
  24. package/app/src/components/wallet/ZoToken.tsx +69 -0
  25. package/app/src/components/wallet/index.ts +8 -0
  26. package/app/src/components/wallet/styles/index.ts +4 -0
  27. package/app/src/components/wallet/styles/walletStyles.ts +210 -0
  28. package/app/src/sdk/ZoPassportSDK.ts +277 -0
  29. package/app/src/sdk/lib/api/auth.ts +223 -0
  30. package/app/src/sdk/lib/api/avatar.ts +155 -0
  31. package/app/src/sdk/lib/api/client.ts +135 -0
  32. package/app/src/sdk/lib/api/index.ts +8 -0
  33. package/app/src/sdk/lib/api/profile.ts +80 -0
  34. package/app/src/sdk/lib/api/wallet.ts +59 -0
  35. package/app/src/sdk/lib/types/auth.ts +78 -0
  36. package/app/src/sdk/lib/types/avatar.ts +22 -0
  37. package/app/src/sdk/lib/types/index.ts +8 -0
  38. package/app/src/sdk/lib/types/profile.ts +18 -0
  39. package/app/src/sdk/lib/types/wallet.ts +103 -0
  40. package/app/src/sdk/lib/types.ts +205 -0
  41. package/app/src/sdk/lib/utils/index.ts +6 -0
  42. package/app/src/sdk/lib/utils/phone.ts +71 -0
  43. package/app/src/sdk/lib/utils/storage.ts +116 -0
  44. package/app/src/sdk/lib/utils/wallet.ts +73 -0
  45. package/app/src/sdk/types.ts +205 -0
  46. package/app/src/styles.css +154 -0
  47. package/app/svg-mock.js +125 -0
  48. package/app/svg-mock.jsx +120 -0
  49. package/app/vite.config.ts +70 -0
  50. package/assets/ASSETS_MANIFEST.md +124 -0
  51. package/assets/bae.png +0 -0
  52. package/assets/bro.png +0 -0
  53. package/assets/cultural-stickers/Business.png +0 -0
  54. package/assets/cultural-stickers/Default (2).jpg +0 -0
  55. package/assets/cultural-stickers/Design.png +0 -0
  56. package/assets/cultural-stickers/FollowYourHeart.png +0 -0
  57. package/assets/cultural-stickers/Food.png +0 -0
  58. package/assets/cultural-stickers/Game.png +0 -0
  59. package/assets/cultural-stickers/Health&Fitness.png +0 -0
  60. package/assets/cultural-stickers/Home&Lifestyle.png +0 -0
  61. package/assets/cultural-stickers/Law.png +0 -0
  62. package/assets/cultural-stickers/Literature&Stories.png +0 -0
  63. package/assets/cultural-stickers/Music&Entertainment.png +0 -0
  64. package/assets/cultural-stickers/Nature&Wildlife.png +0 -0
  65. package/assets/cultural-stickers/Photography.png +0 -0
  66. package/assets/cultural-stickers/Science&Technology.png +0 -0
  67. package/assets/cultural-stickers/Spiritual.png +0 -0
  68. package/assets/cultural-stickers/Sport.png +0 -0
  69. package/assets/cultural-stickers/Stories&Journal.png +0 -0
  70. package/assets/cultural-stickers/Television&Cinema.png +0 -0
  71. package/assets/cultural-stickers/Travel&Adventure.png +0 -0
  72. package/assets/cultural-stickers/z.jpg (1).jpg +0 -0
  73. package/assets/figma-assets/landing-zo-logo.png +0 -0
  74. package/assets/images/rank1.jpeg +0 -0
  75. package/assets/index.ts +76 -0
  76. package/assets/lotties/loader.json +1216 -0
  77. package/assets/lotties/spinner.json +1 -0
  78. package/assets/videos/loading-screen-background.mp4 +0 -0
  79. package/assets/videos/opening-disks.mp4 +0 -0
  80. package/assets/wallet/constants.ts +38 -0
  81. package/assets/zo-coin.gif +0 -0
  82. package/assets/zo-fallback.png +0 -0
  83. package/dist/assets/index.d.mts +136 -0
  84. package/dist/assets/index.d.ts +136 -0
  85. package/dist/assets/index.js +133 -0
  86. package/dist/assets/index.js.map +1 -0
  87. package/dist/assets/index.mjs +100 -0
  88. package/dist/assets/index.mjs.map +1 -0
  89. package/dist/index.d.mts +789 -0
  90. package/dist/index.d.ts +789 -0
  91. package/dist/index.js +1118 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/index.mjs +1060 -0
  94. package/dist/index.mjs.map +1 -0
  95. package/dist/react-native.d.mts +537 -0
  96. package/dist/react-native.d.ts +537 -0
  97. package/dist/react-native.js +1617 -0
  98. package/dist/react-native.js.map +1 -0
  99. package/dist/react-native.mjs +1588 -0
  100. package/dist/react-native.mjs.map +1 -0
  101. package/dist/react.d.mts +824 -0
  102. package/dist/react.d.ts +824 -0
  103. package/dist/react.js +3856 -0
  104. package/dist/react.js.map +1 -0
  105. package/dist/react.mjs +3801 -0
  106. package/dist/react.mjs.map +1 -0
  107. package/package.json +112 -0
  108. package/scripts/init.js +196 -0
  109. package/scripts/postinstall.js +174 -0
  110. package/scripts/verify-build.js +121 -0
@@ -0,0 +1,320 @@
1
+ // src/components/ZoAuth.tsx
2
+ // Complete phone OTP authentication flow component
3
+
4
+ import React, { useState, useEffect } from 'react';
5
+ import { PhoneInput } from './PhoneInput';
6
+ import { OTPInput } from './OTPInput';
7
+
8
+ import type { ZoUser } from '../lib/types';
9
+
10
+ export interface ZoAuthProps {
11
+ /** Callback when auth is successful */
12
+ onSuccess: (userId: string, user: any) => void;
13
+ /** Callback to close modal */
14
+ onClose?: () => void;
15
+ /** Send OTP function (from SDK) */
16
+ sendOTP: (countryCode: string, phoneNumber: string) => Promise<{ success: boolean; message: string }>;
17
+ /** Verify OTP function (from SDK) */
18
+ verifyOTP: (countryCode: string, phoneNumber: string, otp: string) => Promise<{ success: boolean; user?: ZoUser; error?: string }>;
19
+ /** Default country code (default: '91') */
20
+ defaultCountryCode?: string;
21
+ /** Show close button */
22
+ showCloseButton?: boolean;
23
+ /** Additional CSS class */
24
+ className?: string;
25
+ }
26
+
27
+ type Step = 'phone' | 'otp';
28
+
29
+ export const ZoAuth: React.FC<ZoAuthProps> = ({
30
+ onSuccess,
31
+ onClose,
32
+ sendOTP,
33
+ verifyOTP,
34
+ defaultCountryCode = '91',
35
+ showCloseButton = true,
36
+ className = '',
37
+ }) => {
38
+ const [step, setStep] = useState<Step>('phone');
39
+ const [countryCode, setCountryCode] = useState(defaultCountryCode);
40
+ const [phoneNumber, setPhoneNumber] = useState('');
41
+ const [otp, setOtp] = useState<string[]>(['', '', '', '', '', '']);
42
+ const [isLoading, setIsLoading] = useState(false);
43
+ const [error, setError] = useState<string | null>(null);
44
+ const [resendCooldown, setResendCooldown] = useState(0);
45
+
46
+ // Resend cooldown timer
47
+ useEffect(() => {
48
+ if (resendCooldown > 0) {
49
+ const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1000);
50
+ return () => clearTimeout(timer);
51
+ }
52
+ }, [resendCooldown]);
53
+
54
+ const handleSendOTP = async () => {
55
+ if (!phoneNumber || phoneNumber.length < 10) {
56
+ setError('Please enter a valid phone number');
57
+ return;
58
+ }
59
+
60
+ setIsLoading(true);
61
+ setError(null);
62
+
63
+ try {
64
+ const result = await sendOTP(countryCode, phoneNumber);
65
+
66
+ if (result.success) {
67
+ setStep('otp');
68
+ setResendCooldown(60);
69
+ } else {
70
+ setError(result.message || 'Failed to send OTP');
71
+ }
72
+ } catch (err: any) {
73
+ setError(err.message || 'Failed to send verification code');
74
+ } finally {
75
+ setIsLoading(false);
76
+ }
77
+ };
78
+
79
+ const handleVerifyOTP = async (otpString: string) => {
80
+ setIsLoading(true);
81
+ setError(null);
82
+
83
+ try {
84
+ const result = await verifyOTP(countryCode, phoneNumber, otpString);
85
+
86
+ if (result.success && result.user) {
87
+ onSuccess(result.user.id || '', result.user);
88
+ } else {
89
+ setError(result.error || 'Invalid verification code');
90
+ setOtp(['', '', '', '', '', '']);
91
+ }
92
+ } catch (err: any) {
93
+ setError(err.message || 'Invalid verification code');
94
+ setOtp(['', '', '', '', '', '']);
95
+ } finally {
96
+ setIsLoading(false);
97
+ }
98
+ };
99
+
100
+ const handleResendOTP = async () => {
101
+ if (resendCooldown > 0) return;
102
+ await handleSendOTP();
103
+ };
104
+
105
+ const containerStyle: React.CSSProperties = {
106
+ position: 'relative',
107
+ width: '100%',
108
+ maxWidth: '400px',
109
+ backgroundColor: 'rgba(0, 0, 0, 0.95)',
110
+ border: '1px solid rgba(255, 255, 255, 0.2)',
111
+ borderRadius: '24px',
112
+ padding: '24px 32px',
113
+ fontFamily: 'Rubik, system-ui, sans-serif',
114
+ };
115
+
116
+ return (
117
+ <div className={`zo-auth ${className}`} style={containerStyle}>
118
+ {/* Close Button */}
119
+ {showCloseButton && onClose && (
120
+ <button
121
+ onClick={onClose}
122
+ style={{
123
+ position: 'absolute',
124
+ top: '16px',
125
+ right: '16px',
126
+ width: '32px',
127
+ height: '32px',
128
+ display: 'flex',
129
+ alignItems: 'center',
130
+ justifyContent: 'center',
131
+ backgroundColor: 'transparent',
132
+ border: 'none',
133
+ cursor: 'pointer',
134
+ color: 'rgba(255, 255, 255, 0.6)',
135
+ }}
136
+ >
137
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
138
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
139
+ </svg>
140
+ </button>
141
+ )}
142
+
143
+ {/* Step 1: Phone Input */}
144
+ {step === 'phone' && (
145
+ <div>
146
+ {/* Icon */}
147
+ <div
148
+ style={{
149
+ width: '64px',
150
+ height: '64px',
151
+ margin: '0 auto 16px',
152
+ display: 'flex',
153
+ alignItems: 'center',
154
+ justifyContent: 'center',
155
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
156
+ borderRadius: '50%',
157
+ }}
158
+ >
159
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
160
+ <path strokeLinecap="round" strokeLinejoin="round" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
161
+ </svg>
162
+ </div>
163
+
164
+ {/* Title */}
165
+ <h2
166
+ style={{
167
+ textAlign: 'center',
168
+ color: 'white',
169
+ fontSize: '24px',
170
+ fontWeight: 800,
171
+ marginBottom: '8px',
172
+ fontFamily: 'Syne, system-ui, sans-serif',
173
+ }}
174
+ >
175
+ Enter Your Phone Number
176
+ </h2>
177
+ <p
178
+ style={{
179
+ textAlign: 'center',
180
+ color: 'rgba(255, 255, 255, 0.6)',
181
+ fontSize: '14px',
182
+ marginBottom: '24px',
183
+ }}
184
+ >
185
+ We'll send you a verification code
186
+ </p>
187
+
188
+ {/* Phone Input */}
189
+ <PhoneInput
190
+ value={phoneNumber}
191
+ countryCode={countryCode}
192
+ onChange={setPhoneNumber}
193
+ onCountryChange={setCountryCode}
194
+ disabled={isLoading}
195
+ error={error || undefined}
196
+ />
197
+
198
+ {/* Send Button */}
199
+ <button
200
+ onClick={handleSendOTP}
201
+ disabled={isLoading || phoneNumber.length < 10}
202
+ style={{
203
+ width: '100%',
204
+ marginTop: '16px',
205
+ padding: '16px',
206
+ backgroundColor: 'black',
207
+ border: '2px solid rgba(255, 255, 255, 0.2)',
208
+ borderRadius: '12px',
209
+ color: 'white',
210
+ fontWeight: 500,
211
+ fontSize: '16px',
212
+ cursor: isLoading || phoneNumber.length < 10 ? 'not-allowed' : 'pointer',
213
+ opacity: isLoading || phoneNumber.length < 10 ? 0.5 : 1,
214
+ }}
215
+ >
216
+ {isLoading ? 'Sending Code...' : 'Send Code'}
217
+ </button>
218
+ </div>
219
+ )}
220
+
221
+ {/* Step 2: OTP Input */}
222
+ {step === 'otp' && (
223
+ <div>
224
+ {/* Icon */}
225
+ <div
226
+ style={{
227
+ width: '64px',
228
+ height: '64px',
229
+ margin: '0 auto 16px',
230
+ display: 'flex',
231
+ alignItems: 'center',
232
+ justifyContent: 'center',
233
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
234
+ borderRadius: '50%',
235
+ }}
236
+ >
237
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
238
+ <path strokeLinecap="round" strokeLinejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
239
+ </svg>
240
+ </div>
241
+
242
+ {/* Title */}
243
+ <h2
244
+ style={{
245
+ textAlign: 'center',
246
+ color: 'white',
247
+ fontSize: '24px',
248
+ fontWeight: 800,
249
+ marginBottom: '8px',
250
+ fontFamily: 'Syne, system-ui, sans-serif',
251
+ }}
252
+ >
253
+ Enter Verification Code
254
+ </h2>
255
+ <p
256
+ style={{
257
+ textAlign: 'center',
258
+ color: 'rgba(255, 255, 255, 0.6)',
259
+ fontSize: '14px',
260
+ marginBottom: '24px',
261
+ }}
262
+ >
263
+ We sent a code to +{countryCode} {phoneNumber}
264
+ </p>
265
+
266
+ {/* OTP Input */}
267
+ <OTPInput
268
+ value={otp}
269
+ onChange={setOtp}
270
+ onComplete={handleVerifyOTP}
271
+ disabled={isLoading}
272
+ error={error || undefined}
273
+ />
274
+
275
+ {/* Verify Button */}
276
+ <button
277
+ onClick={() => handleVerifyOTP(otp.join(''))}
278
+ disabled={isLoading || otp.some(d => !d)}
279
+ style={{
280
+ width: '100%',
281
+ marginTop: '16px',
282
+ padding: '16px',
283
+ backgroundColor: 'black',
284
+ border: '2px solid rgba(255, 255, 255, 0.2)',
285
+ borderRadius: '12px',
286
+ color: 'white',
287
+ fontWeight: 500,
288
+ fontSize: '16px',
289
+ cursor: isLoading || otp.some(d => !d) ? 'not-allowed' : 'pointer',
290
+ opacity: isLoading || otp.some(d => !d) ? 0.5 : 1,
291
+ }}
292
+ >
293
+ {isLoading ? 'Verifying...' : 'Verify'}
294
+ </button>
295
+
296
+ {/* Resend Link */}
297
+ <div style={{ textAlign: 'center', marginTop: '16px' }}>
298
+ <button
299
+ onClick={handleResendOTP}
300
+ disabled={resendCooldown > 0}
301
+ style={{
302
+ backgroundColor: 'transparent',
303
+ border: 'none',
304
+ color: 'rgba(255, 255, 255, 0.6)',
305
+ fontSize: '14px',
306
+ cursor: resendCooldown > 0 ? 'not-allowed' : 'pointer',
307
+ opacity: resendCooldown > 0 ? 0.5 : 1,
308
+ }}
309
+ >
310
+ {resendCooldown > 0
311
+ ? `Resend code in ${resendCooldown}s`
312
+ : "Didn't receive? Resend code"}
313
+ </button>
314
+ </div>
315
+ </div>
316
+ )}
317
+ </div>
318
+ );
319
+ };
320
+
@@ -0,0 +1,87 @@
1
+ // src/components/ZoAvatar.tsx
2
+ // Avatar display component with fallback
3
+
4
+ import React, { useState } from 'react';
5
+
6
+ export interface ZoAvatarProps {
7
+ /** Avatar image URL */
8
+ src?: string;
9
+ /** User name (for alt text and fallback) */
10
+ name?: string;
11
+ /** Size in pixels (default: 120) */
12
+ size?: number;
13
+ /** Fallback image URL */
14
+ fallbackUrl?: string;
15
+ /** Additional CSS class */
16
+ className?: string;
17
+ }
18
+
19
+ // Default fallback avatar
20
+ const DEFAULT_FALLBACK = '/images/rank1.jpeg';
21
+
22
+ export const ZoAvatar: React.FC<ZoAvatarProps> = ({
23
+ src,
24
+ name = 'User',
25
+ size = 120,
26
+ fallbackUrl = DEFAULT_FALLBACK,
27
+ className = '',
28
+ }) => {
29
+ const [imgSrc, setImgSrc] = useState(src || fallbackUrl);
30
+ const [hasError, setHasError] = useState(false);
31
+
32
+ const handleError = () => {
33
+ if (!hasError) {
34
+ setHasError(true);
35
+ setImgSrc(fallbackUrl);
36
+ }
37
+ };
38
+
39
+ // Generate initials for text fallback
40
+ const initials = name
41
+ .split(' ')
42
+ .map(n => n[0])
43
+ .join('')
44
+ .toUpperCase()
45
+ .slice(0, 2);
46
+
47
+ return (
48
+ <div
49
+ className={`zo-avatar ${className}`}
50
+ style={{
51
+ width: size,
52
+ height: size,
53
+ borderRadius: '50%',
54
+ overflow: 'hidden',
55
+ backgroundColor: '#1a1a1a',
56
+ display: 'flex',
57
+ alignItems: 'center',
58
+ justifyContent: 'center',
59
+ }}
60
+ >
61
+ {imgSrc ? (
62
+ <img
63
+ src={imgSrc}
64
+ alt={name}
65
+ onError={handleError}
66
+ style={{
67
+ width: '100%',
68
+ height: '100%',
69
+ objectFit: 'cover',
70
+ }}
71
+ />
72
+ ) : (
73
+ <span
74
+ style={{
75
+ fontSize: size * 0.4,
76
+ fontWeight: 700,
77
+ color: '#ffffff',
78
+ fontFamily: 'Rubik, system-ui, sans-serif',
79
+ }}
80
+ >
81
+ {initials}
82
+ </span>
83
+ )}
84
+ </div>
85
+ );
86
+ };
87
+
@@ -0,0 +1,231 @@
1
+ // src/components/ZoLanding.tsx
2
+ // Landing page component with video background
3
+
4
+ import React, { useState } from 'react';
5
+ import { ZoAuth } from './ZoAuth';
6
+
7
+ export interface ZoLandingProps {
8
+ /** Callback when user successfully authenticates */
9
+ onAuthSuccess: (userId: string, user: any) => void;
10
+ /** Send OTP function (from SDK) */
11
+ sendOTP: (countryCode: string, phoneNumber: string) => Promise<{ success: boolean; message: string }>;
12
+ /** Verify OTP function (from SDK) */
13
+ verifyOTP: (countryCode: string, phoneNumber: string, otp: string) => Promise<{ success: boolean; error?: string }>;
14
+ /** Video background URL */
15
+ videoUrl?: string;
16
+ /** Zo logo URL */
17
+ logoUrl?: string;
18
+ /** Title text */
19
+ title?: string;
20
+ /** Subtitle lines */
21
+ subtitles?: string[];
22
+ /** Button text */
23
+ buttonText?: string;
24
+ /** Additional CSS class */
25
+ className?: string;
26
+ }
27
+
28
+ export const ZoLanding: React.FC<ZoLandingProps> = ({
29
+ onAuthSuccess,
30
+ sendOTP,
31
+ verifyOTP,
32
+ videoUrl = '/videos/loading-screen-background.mp4',
33
+ logoUrl = '/figma-assets/landing-zo-logo.png',
34
+ title = 'ZOHMMM!',
35
+ subtitles = [
36
+ 'Welcome to Zo World',
37
+ 'A parallel reality where you live your best life, by following your heart.',
38
+ 'Are you ready to tune in, Anon?',
39
+ ],
40
+ buttonText = 'Tune into Zo World',
41
+ className = '',
42
+ }) => {
43
+ const [showAuth, setShowAuth] = useState(false);
44
+
45
+ const containerStyle: React.CSSProperties = {
46
+ position: 'fixed',
47
+ inset: 0,
48
+ display: 'flex',
49
+ flexDirection: 'column',
50
+ backgroundColor: 'black',
51
+ width: '100vw',
52
+ height: '100vh',
53
+ overflow: 'hidden',
54
+ fontFamily: 'Rubik, system-ui, sans-serif',
55
+ };
56
+
57
+ return (
58
+ <div className={`zo-landing ${className}`} style={containerStyle}>
59
+ {/* Video Background */}
60
+ <video
61
+ autoPlay
62
+ loop
63
+ muted
64
+ playsInline
65
+ style={{
66
+ position: 'fixed',
67
+ inset: 0,
68
+ width: '100%',
69
+ height: '100%',
70
+ objectFit: 'cover',
71
+ zIndex: 1,
72
+ pointerEvents: 'none',
73
+ }}
74
+ >
75
+ <source src={videoUrl} type="video/mp4" />
76
+ </video>
77
+
78
+ {/* Gradient Overlay */}
79
+ <div
80
+ style={{
81
+ position: 'fixed',
82
+ inset: 0,
83
+ background: 'linear-gradient(to bottom, transparent 50%, black)',
84
+ zIndex: 2,
85
+ pointerEvents: 'none',
86
+ }}
87
+ />
88
+
89
+ {/* Zo Logo */}
90
+ <img
91
+ src={logoUrl}
92
+ alt="Zo"
93
+ style={{
94
+ position: 'absolute',
95
+ left: '24px',
96
+ top: '40px',
97
+ width: '40px',
98
+ height: '40px',
99
+ objectFit: 'cover',
100
+ zIndex: 10,
101
+ }}
102
+ />
103
+
104
+ {/* Main Content */}
105
+ <div
106
+ style={{
107
+ position: 'relative',
108
+ zIndex: 10,
109
+ width: '100%',
110
+ height: '100%',
111
+ display: 'flex',
112
+ flexDirection: 'column',
113
+ alignItems: 'center',
114
+ justifyContent: 'center',
115
+ padding: '24px',
116
+ }}
117
+ >
118
+ <div
119
+ style={{
120
+ display: 'flex',
121
+ flexDirection: 'column',
122
+ alignItems: 'center',
123
+ justifyContent: 'center',
124
+ width: '100%',
125
+ maxWidth: '800px',
126
+ gap: '32px',
127
+ }}
128
+ >
129
+ {/* Header */}
130
+ <div style={{ textAlign: 'center' }}>
131
+ <h1
132
+ style={{
133
+ fontFamily: 'Syne, system-ui, sans-serif',
134
+ fontSize: 'clamp(32px, 8vw, 72px)',
135
+ fontWeight: 800,
136
+ color: 'white',
137
+ lineHeight: 1.1,
138
+ letterSpacing: '0.32px',
139
+ textTransform: 'uppercase',
140
+ margin: 0,
141
+ }}
142
+ >
143
+ {title}
144
+ </h1>
145
+
146
+ <div
147
+ style={{
148
+ marginTop: '16px',
149
+ fontSize: 'clamp(16px, 3vw, 24px)',
150
+ color: 'white',
151
+ lineHeight: 1.5,
152
+ }}
153
+ >
154
+ {subtitles.map((text, i) => (
155
+ <p key={i} style={{ margin: '8px 0' }}>
156
+ {text}
157
+ </p>
158
+ ))}
159
+ </div>
160
+ </div>
161
+
162
+ {/* CTA Button */}
163
+ <button
164
+ onClick={() => setShowAuth(true)}
165
+ style={{
166
+ backgroundColor: 'black',
167
+ border: '2px solid rgba(255, 255, 255, 0.2)',
168
+ borderRadius: '12px',
169
+ padding: '16px 20px',
170
+ width: '100%',
171
+ maxWidth: '400px',
172
+ height: '64px',
173
+ cursor: 'pointer',
174
+ transition: 'all 0.3s',
175
+ }}
176
+ >
177
+ <span
178
+ style={{
179
+ fontFamily: 'Rubik, system-ui, sans-serif',
180
+ fontSize: '18px',
181
+ fontWeight: 500,
182
+ color: 'white',
183
+ }}
184
+ >
185
+ {buttonText}
186
+ </span>
187
+ </button>
188
+ </div>
189
+ </div>
190
+
191
+ {/* Auth Modal */}
192
+ {showAuth && (
193
+ <div
194
+ style={{
195
+ position: 'fixed',
196
+ inset: 0,
197
+ zIndex: 10000,
198
+ display: 'flex',
199
+ alignItems: 'center',
200
+ justifyContent: 'center',
201
+ padding: '16px',
202
+ }}
203
+ >
204
+ {/* Backdrop */}
205
+ <div
206
+ onClick={() => setShowAuth(false)}
207
+ style={{
208
+ position: 'absolute',
209
+ inset: 0,
210
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
211
+ backdropFilter: 'blur(4px)',
212
+ }}
213
+ />
214
+ {/* Auth Component */}
215
+ <div style={{ position: 'relative', zIndex: 1 }}>
216
+ <ZoAuth
217
+ onSuccess={(userId, user) => {
218
+ setShowAuth(false);
219
+ onAuthSuccess(userId, user);
220
+ }}
221
+ onClose={() => setShowAuth(false)}
222
+ sendOTP={sendOTP}
223
+ verifyOTP={verifyOTP}
224
+ />
225
+ </div>
226
+ </div>
227
+ )}
228
+ </div>
229
+ );
230
+ };
231
+