zelune 0.0.0-stage → 1.5.1
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/CHANGELOG.md +81 -0
- package/DESIGN.md +90 -0
- package/PRODUCT.md +31 -0
- package/README.md +63 -2
- package/assets/logo.svg +8 -0
- package/bin/cli.mjs +427 -0
- package/index.html +18 -0
- package/mcp/server.ts +196 -0
- package/mcp_config.json +8 -0
- package/package.json +77 -4
- package/plugin.json +12 -0
- package/plugins/viteRnSourceInject.ts +117 -0
- package/plugins/viteVirtualScreensPlugin.ts +85 -0
- package/rules/AGENTS.md +21 -0
- package/skills/rn-canvas/SKILL.md +33 -0
- package/src/canvas/CanvasApp.tsx +1288 -0
- package/src/canvas/FlowArrowsLayer.tsx +197 -0
- package/src/canvas/InspectorPanel.tsx +979 -0
- package/src/canvas/ScreenArtboard.tsx +1416 -0
- package/src/canvas/types.ts +23 -0
- package/src/main.tsx +10 -0
- package/src/runtime/MockNavigationContext.tsx +199 -0
- package/src/runtime/ScreenErrorBoundary.tsx +86 -0
- package/src/screens/CheckoutScreen.tsx +229 -0
- package/src/screens/HomeScreen.tsx +288 -0
- package/src/screens/LoginScreen.tsx +176 -0
- package/src/screens/ProductDetailScreen.tsx +234 -0
- package/src/screens/ProfileScreen.tsx +172 -0
- package/src/screens/index.ts +1 -0
- package/src/server/apiMiddleware.ts +327 -0
- package/src/server/astEngine.ts +1286 -0
- package/src/styles.css +298 -0
- package/src/vite-env.d.ts +7 -0
- package/tsconfig.json +23 -0
- package/vite.config.ts +148 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { InspectedElement, NavigationEdge, ScreenMetadata } from '../server/astEngine';
|
|
2
|
+
|
|
3
|
+
export type CanvasMode = 'inspect' | 'interact';
|
|
4
|
+
|
|
5
|
+
export interface ArtboardPosition {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ArtboardLayoutMap = Record<string, ArtboardPosition>;
|
|
13
|
+
|
|
14
|
+
export interface SelectedElementRef {
|
|
15
|
+
screenId: string;
|
|
16
|
+
file: string;
|
|
17
|
+
astPath: string;
|
|
18
|
+
line: number;
|
|
19
|
+
col: number;
|
|
20
|
+
tagName: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type { InspectedElement, NavigationEdge, ScreenMetadata };
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { CanvasApp } from './canvas/CanvasApp';
|
|
4
|
+
import './styles.css';
|
|
5
|
+
|
|
6
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
7
|
+
<React.StrictMode>
|
|
8
|
+
<CanvasApp />
|
|
9
|
+
</React.StrictMode>
|
|
10
|
+
);
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo, useEffect } from 'react';
|
|
2
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface NavigationController {
|
|
5
|
+
navigate: (targetRoute: string, params?: Record<string, unknown>) => void;
|
|
6
|
+
goBack: () => void;
|
|
7
|
+
push: (targetRoute: string, params?: Record<string, unknown>) => void;
|
|
8
|
+
replace: (targetRoute: string, params?: Record<string, unknown>) => void;
|
|
9
|
+
back: () => void;
|
|
10
|
+
setOptions: (options?: Record<string, unknown>) => void;
|
|
11
|
+
canGoBack: () => boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface MockNavigationContextValue {
|
|
15
|
+
currentRoute: string;
|
|
16
|
+
params: Record<string, unknown>;
|
|
17
|
+
navigation: NavigationController;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeNavTarget(raw: string): string {
|
|
21
|
+
const cleaned = String(raw ?? '')
|
|
22
|
+
.replace(/^\/+/, '')
|
|
23
|
+
.replace(/\([^)]+\)\/?/g, '')
|
|
24
|
+
.replace(/Screen$/, '')
|
|
25
|
+
.replace(/\/index$/, '');
|
|
26
|
+
if (!cleaned || cleaned === 'index') return 'Home';
|
|
27
|
+
if (cleaned.includes('-') || cleaned.includes('/') || cleaned === cleaned.toLowerCase()) {
|
|
28
|
+
return cleaned
|
|
29
|
+
.split(/[-_/]/)
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
32
|
+
.join('');
|
|
33
|
+
}
|
|
34
|
+
return cleaned;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const MockNavigationContext = createContext<MockNavigationContextValue>({
|
|
38
|
+
currentRoute: 'Home',
|
|
39
|
+
params: {},
|
|
40
|
+
navigation: {
|
|
41
|
+
navigate: () => undefined,
|
|
42
|
+
goBack: () => undefined,
|
|
43
|
+
push: () => undefined,
|
|
44
|
+
replace: () => undefined,
|
|
45
|
+
back: () => undefined,
|
|
46
|
+
setOptions: () => undefined,
|
|
47
|
+
canGoBack: () => true,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export interface MockNavigationProviderProps {
|
|
52
|
+
routeName: string;
|
|
53
|
+
params?: Record<string, unknown>;
|
|
54
|
+
onNavigate?: (fromScreen: string, toScreen: string, params?: Record<string, unknown>) => void;
|
|
55
|
+
children: React.ReactNode;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const MockNavigationProvider: React.FC<MockNavigationProviderProps> = ({
|
|
59
|
+
routeName,
|
|
60
|
+
params = {},
|
|
61
|
+
onNavigate,
|
|
62
|
+
children,
|
|
63
|
+
}) => {
|
|
64
|
+
const value = useMemo<MockNavigationContextValue>(() => {
|
|
65
|
+
const trigger = (targetRoute: string, navParams?: Record<string, unknown>) => {
|
|
66
|
+
onNavigate?.(routeName, normalizeNavTarget(targetRoute), navParams);
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
currentRoute: routeName,
|
|
70
|
+
params,
|
|
71
|
+
navigation: {
|
|
72
|
+
navigate: trigger,
|
|
73
|
+
push: trigger,
|
|
74
|
+
replace: trigger,
|
|
75
|
+
goBack: () => onNavigate?.(routeName, 'Home'),
|
|
76
|
+
back: () => onNavigate?.(routeName, 'Home'),
|
|
77
|
+
setOptions: () => undefined,
|
|
78
|
+
canGoBack: () => true,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}, [routeName, params, onNavigate]);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<MockNavigationContext.Provider value={value}>
|
|
85
|
+
{children}
|
|
86
|
+
</MockNavigationContext.Provider>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// React Navigation Shims (@react-navigation/native)
|
|
91
|
+
export function useNavigation(): NavigationController {
|
|
92
|
+
return useContext(MockNavigationContext).navigation;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function useRoute(): { key: string; name: string; params: Record<string, unknown> } {
|
|
96
|
+
const ctx = useContext(MockNavigationContext);
|
|
97
|
+
return {
|
|
98
|
+
key: ctx.currentRoute,
|
|
99
|
+
name: ctx.currentRoute,
|
|
100
|
+
params: ctx.params,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function useFocusEffect(effect: () => void | (() => void)): void {
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
return effect();
|
|
107
|
+
}, [effect]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function useIsFocused(): boolean {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const NavigationContainer: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
|
|
115
|
+
<>{children}</>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Expo Router Shims (expo-router)
|
|
119
|
+
export function useRouter(): NavigationController {
|
|
120
|
+
return useContext(MockNavigationContext).navigation;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const router: NavigationController = {
|
|
124
|
+
navigate: () => undefined,
|
|
125
|
+
push: () => undefined,
|
|
126
|
+
replace: () => undefined,
|
|
127
|
+
goBack: () => undefined,
|
|
128
|
+
back: () => undefined,
|
|
129
|
+
setOptions: () => undefined,
|
|
130
|
+
canGoBack: () => true,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export function useLocalSearchParams<T extends Record<string, unknown> = Record<string, string>>(): T {
|
|
134
|
+
const ctx = useContext(MockNavigationContext);
|
|
135
|
+
return ctx.params as T;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function useGlobalSearchParams<T extends Record<string, unknown> = Record<string, string>>(): T {
|
|
139
|
+
const ctx = useContext(MockNavigationContext);
|
|
140
|
+
return ctx.params as T;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function usePathname(): string {
|
|
144
|
+
const ctx = useContext(MockNavigationContext);
|
|
145
|
+
return `/${ctx.currentRoute.toLowerCase()}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function useSegments(): string[] {
|
|
149
|
+
const ctx = useContext(MockNavigationContext);
|
|
150
|
+
return [ctx.currentRoute.toLowerCase()];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const Link: React.FC<{
|
|
154
|
+
href: string | { pathname: string; params?: Record<string, unknown> };
|
|
155
|
+
asChild?: boolean;
|
|
156
|
+
children?: React.ReactNode;
|
|
157
|
+
style?: unknown;
|
|
158
|
+
}> = ({ href, children, style }) => {
|
|
159
|
+
const nav = useContext(MockNavigationContext).navigation;
|
|
160
|
+
const target = typeof href === 'string' ? href : href.pathname;
|
|
161
|
+
return (
|
|
162
|
+
<TouchableOpacity
|
|
163
|
+
activeOpacity={0.78}
|
|
164
|
+
style={style as never}
|
|
165
|
+
onPress={() => nav.push(target)}
|
|
166
|
+
>
|
|
167
|
+
{children}
|
|
168
|
+
</TouchableOpacity>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const NoopLayoutScreen: React.FC<{ options?: unknown }> & {
|
|
173
|
+
Screen: React.FC<{ name?: string; options?: unknown }>;
|
|
174
|
+
} = ({ children }: { children?: React.ReactNode }) => <>{children}</>;
|
|
175
|
+
NoopLayoutScreen.Screen = () => null;
|
|
176
|
+
|
|
177
|
+
export const Stack = NoopLayoutScreen;
|
|
178
|
+
export const Tabs = NoopLayoutScreen;
|
|
179
|
+
export const Slot: React.FC<{ children?: React.ReactNode }> = ({ children }) => <>{children}</>;
|
|
180
|
+
|
|
181
|
+
// React Native Safe Area Context Shims (react-native-safe-area-context)
|
|
182
|
+
export const SafeAreaProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
|
|
183
|
+
<View style={{ flex: 1 }}>{children}</View>
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
export const SafeAreaView: React.FC<{
|
|
187
|
+
children?: React.ReactNode;
|
|
188
|
+
style?: unknown;
|
|
189
|
+
edges?: string[];
|
|
190
|
+
}> = ({ children, style }) => (
|
|
191
|
+
<View style={[{ flex: 1 }, style as never]}>{children}</View>
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
export function useSafeAreaInsets(): { top: number; right: number; bottom: number; left: number } {
|
|
195
|
+
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Expo Status Bar Shim (expo-status-bar)
|
|
199
|
+
export const StatusBar: React.FC<{ style?: string; hidden?: boolean; backgroundColor?: string }> = () => null;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ScreenErrorBoundaryProps {
|
|
4
|
+
screenName: string;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ScreenErrorBoundaryState {
|
|
9
|
+
hasError: boolean;
|
|
10
|
+
errorMessage: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ScreenErrorBoundary extends React.Component<
|
|
14
|
+
ScreenErrorBoundaryProps,
|
|
15
|
+
ScreenErrorBoundaryState
|
|
16
|
+
> {
|
|
17
|
+
constructor(props: ScreenErrorBoundaryProps) {
|
|
18
|
+
super(props);
|
|
19
|
+
this.state = { hasError: false, errorMessage: null };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static getDerivedStateFromError(error: unknown): ScreenErrorBoundaryState {
|
|
23
|
+
return {
|
|
24
|
+
hasError: true,
|
|
25
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
render() {
|
|
30
|
+
if (this.state.hasError) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
style={{
|
|
34
|
+
height: '100%',
|
|
35
|
+
width: '100%',
|
|
36
|
+
backgroundColor: '#18181B',
|
|
37
|
+
color: '#FCA5A5',
|
|
38
|
+
padding: 24,
|
|
39
|
+
display: 'flex',
|
|
40
|
+
flexDirection: 'column',
|
|
41
|
+
justifyContent: 'center',
|
|
42
|
+
gap: 12,
|
|
43
|
+
fontFamily: 'JetBrains Mono, monospace',
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<div style={{ fontSize: 13, fontWeight: 700, color: '#F87171' }}>
|
|
47
|
+
Runtime Error in {this.props.screenName}
|
|
48
|
+
</div>
|
|
49
|
+
<div
|
|
50
|
+
style={{
|
|
51
|
+
fontSize: 12,
|
|
52
|
+
lineHeight: 1.5,
|
|
53
|
+
backgroundColor: '#09090B',
|
|
54
|
+
padding: 12,
|
|
55
|
+
borderRadius: 8,
|
|
56
|
+
border: '1px solid #7F1D1D',
|
|
57
|
+
whiteSpace: 'pre-wrap',
|
|
58
|
+
wordBreak: 'break-word',
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
{this.state.errorMessage}
|
|
62
|
+
</div>
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
onClick={() => this.setState({ hasError: false, errorMessage: null })}
|
|
66
|
+
style={{
|
|
67
|
+
alignSelf: 'flex-start',
|
|
68
|
+
padding: '6px 12px',
|
|
69
|
+
backgroundColor: '#EF4444',
|
|
70
|
+
color: '#FFFFFF',
|
|
71
|
+
border: 'none',
|
|
72
|
+
borderRadius: 6,
|
|
73
|
+
fontSize: 12,
|
|
74
|
+
fontWeight: 600,
|
|
75
|
+
cursor: 'pointer',
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
Retry Render
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return this.props.children;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import { useNavigation } from '../runtime/MockNavigationContext';
|
|
9
|
+
|
|
10
|
+
export default function CheckoutScreen() {
|
|
11
|
+
const navigation = useNavigation();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<View style={styles.container}>
|
|
15
|
+
<View style={styles.headerRow}>
|
|
16
|
+
<TouchableOpacity
|
|
17
|
+
activeOpacity={0.78}
|
|
18
|
+
style={styles.backButton}
|
|
19
|
+
onPress={() => navigation.navigate('ProductDetail')}
|
|
20
|
+
>
|
|
21
|
+
<Text style={styles.backButtonText}>← Spec Sheet</Text>
|
|
22
|
+
</TouchableOpacity>
|
|
23
|
+
<Text style={styles.headerTitle}>Dispatch & Settlement</Text>
|
|
24
|
+
</View>
|
|
25
|
+
|
|
26
|
+
<View style={styles.orderCard}>
|
|
27
|
+
<Text style={styles.sectionHeading}>MANIFEST // ORDER #CH-8841</Text>
|
|
28
|
+
<View style={styles.itemRow}>
|
|
29
|
+
<View>
|
|
30
|
+
<Text style={styles.itemName}>Chronos Studio Pro</Text>
|
|
31
|
+
<Text style={styles.itemVariant}>Obsidian Anodized · Unit 01</Text>
|
|
32
|
+
</View>
|
|
33
|
+
<Text style={styles.itemPrice}>$349.00</Text>
|
|
34
|
+
</View>
|
|
35
|
+
|
|
36
|
+
<View style={styles.divider} />
|
|
37
|
+
|
|
38
|
+
<View style={styles.summaryRow}>
|
|
39
|
+
<Text style={styles.summaryLabel}>Priority Courier Dispatch</Text>
|
|
40
|
+
<Text style={styles.summaryFree}>INCLUDED</Text>
|
|
41
|
+
</View>
|
|
42
|
+
<View style={styles.summaryRow}>
|
|
43
|
+
<Text style={styles.summaryLabel}>Import & Acoustic Levy</Text>
|
|
44
|
+
<Text style={styles.summaryValue}>$28.80</Text>
|
|
45
|
+
</View>
|
|
46
|
+
<View style={styles.totalRow}>
|
|
47
|
+
<Text style={styles.totalLabel}>Total Settlement</Text>
|
|
48
|
+
<Text style={styles.totalAmount}>$377.80</Text>
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
51
|
+
|
|
52
|
+
<View style={styles.paymentCard}>
|
|
53
|
+
<Text style={styles.sectionHeading}>CRYPTOGRAPHIC AUTHORIZATION</Text>
|
|
54
|
+
<View style={styles.cardMethodRow}>
|
|
55
|
+
<View style={styles.cardBrandBadge}>
|
|
56
|
+
<Text style={styles.cardBrandText}>G-PAY</Text>
|
|
57
|
+
</View>
|
|
58
|
+
<View>
|
|
59
|
+
<Text style={styles.cardNumber}>Hardware Token ·••• 4829</Text>
|
|
60
|
+
<Text style={styles.cardSub}>Biometric enclave signature ready</Text>
|
|
61
|
+
</View>
|
|
62
|
+
</View>
|
|
63
|
+
</View>
|
|
64
|
+
|
|
65
|
+
<TouchableOpacity
|
|
66
|
+
activeOpacity={0.78}
|
|
67
|
+
style={styles.confirmButton}
|
|
68
|
+
onPress={() => navigation.navigate('Home')}
|
|
69
|
+
>
|
|
70
|
+
<Text style={styles.confirmButtonText}>Authorize $377.80 Settlement</Text>
|
|
71
|
+
</TouchableOpacity>
|
|
72
|
+
</View>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
container: {
|
|
78
|
+
flex: 1,
|
|
79
|
+
backgroundColor: '#0A0B0E',
|
|
80
|
+
padding: 20,
|
|
81
|
+
justifyContent: 'space-between',
|
|
82
|
+
},
|
|
83
|
+
headerRow: {
|
|
84
|
+
flexDirection: 'row',
|
|
85
|
+
justifyContent: 'space-between',
|
|
86
|
+
alignItems: 'center',
|
|
87
|
+
marginTop: 6,
|
|
88
|
+
},
|
|
89
|
+
backButton: {
|
|
90
|
+
backgroundColor: '#13161E',
|
|
91
|
+
paddingHorizontal: 12,
|
|
92
|
+
paddingVertical: 9,
|
|
93
|
+
borderRadius: 8,
|
|
94
|
+
borderWidth: 1,
|
|
95
|
+
borderColor: '#222836',
|
|
96
|
+
},
|
|
97
|
+
backButtonText: {
|
|
98
|
+
color: '#E2E8F0',
|
|
99
|
+
fontSize: 12,
|
|
100
|
+
fontWeight: '600',
|
|
101
|
+
},
|
|
102
|
+
headerTitle: {
|
|
103
|
+
color: '#F8FAFC',
|
|
104
|
+
fontSize: 16,
|
|
105
|
+
fontWeight: '700',
|
|
106
|
+
letterSpacing: -0.2,
|
|
107
|
+
},
|
|
108
|
+
orderCard: {
|
|
109
|
+
backgroundColor: '#11141C',
|
|
110
|
+
borderRadius: 16,
|
|
111
|
+
padding: 18,
|
|
112
|
+
gap: 12,
|
|
113
|
+
borderWidth: 1,
|
|
114
|
+
borderColor: '#1E2433',
|
|
115
|
+
},
|
|
116
|
+
sectionHeading: {
|
|
117
|
+
color: '#38BDF8',
|
|
118
|
+
fontSize: 10,
|
|
119
|
+
fontWeight: '700',
|
|
120
|
+
letterSpacing: 1,
|
|
121
|
+
},
|
|
122
|
+
itemRow: {
|
|
123
|
+
flexDirection: 'row',
|
|
124
|
+
justifyContent: 'space-between',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
},
|
|
127
|
+
itemName: {
|
|
128
|
+
color: '#F8FAFC',
|
|
129
|
+
fontSize: 15,
|
|
130
|
+
fontWeight: '600',
|
|
131
|
+
},
|
|
132
|
+
itemVariant: {
|
|
133
|
+
color: '#64748B',
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
marginTop: 2,
|
|
136
|
+
},
|
|
137
|
+
itemPrice: {
|
|
138
|
+
color: '#F8FAFC',
|
|
139
|
+
fontSize: 15,
|
|
140
|
+
fontWeight: '700',
|
|
141
|
+
},
|
|
142
|
+
divider: {
|
|
143
|
+
height: 1,
|
|
144
|
+
backgroundColor: '#1E2433',
|
|
145
|
+
marginVertical: 4,
|
|
146
|
+
},
|
|
147
|
+
summaryRow: {
|
|
148
|
+
flexDirection: 'row',
|
|
149
|
+
justifyContent: 'space-between',
|
|
150
|
+
},
|
|
151
|
+
summaryLabel: {
|
|
152
|
+
color: '#94A3B8',
|
|
153
|
+
fontSize: 13,
|
|
154
|
+
},
|
|
155
|
+
summaryFree: {
|
|
156
|
+
color: '#10B981',
|
|
157
|
+
fontSize: 11,
|
|
158
|
+
fontWeight: '700',
|
|
159
|
+
letterSpacing: 0.6,
|
|
160
|
+
},
|
|
161
|
+
summaryValue: {
|
|
162
|
+
color: '#E2E8F0',
|
|
163
|
+
fontSize: 13,
|
|
164
|
+
fontWeight: '600',
|
|
165
|
+
},
|
|
166
|
+
totalRow: {
|
|
167
|
+
flexDirection: 'row',
|
|
168
|
+
justifyContent: 'space-between',
|
|
169
|
+
alignItems: 'center',
|
|
170
|
+
marginTop: 4,
|
|
171
|
+
},
|
|
172
|
+
totalLabel: {
|
|
173
|
+
color: '#F8FAFC',
|
|
174
|
+
fontSize: 14,
|
|
175
|
+
fontWeight: '700',
|
|
176
|
+
},
|
|
177
|
+
totalAmount: {
|
|
178
|
+
color: '#38BDF8',
|
|
179
|
+
fontSize: 20,
|
|
180
|
+
fontWeight: '700',
|
|
181
|
+
},
|
|
182
|
+
paymentCard: {
|
|
183
|
+
backgroundColor: '#11141C',
|
|
184
|
+
borderRadius: 14,
|
|
185
|
+
padding: 16,
|
|
186
|
+
gap: 10,
|
|
187
|
+
borderWidth: 1,
|
|
188
|
+
borderColor: '#1E2433',
|
|
189
|
+
},
|
|
190
|
+
cardMethodRow: {
|
|
191
|
+
flexDirection: 'row',
|
|
192
|
+
alignItems: 'center',
|
|
193
|
+
gap: 12,
|
|
194
|
+
},
|
|
195
|
+
cardBrandBadge: {
|
|
196
|
+
backgroundColor: '#161A26',
|
|
197
|
+
borderWidth: 1,
|
|
198
|
+
borderColor: '#262D3D',
|
|
199
|
+
paddingHorizontal: 10,
|
|
200
|
+
paddingVertical: 8,
|
|
201
|
+
borderRadius: 7,
|
|
202
|
+
},
|
|
203
|
+
cardBrandText: {
|
|
204
|
+
color: '#38BDF8',
|
|
205
|
+
fontSize: 10,
|
|
206
|
+
fontWeight: '700',
|
|
207
|
+
letterSpacing: 0.8,
|
|
208
|
+
},
|
|
209
|
+
cardNumber: {
|
|
210
|
+
color: '#F8FAFC',
|
|
211
|
+
fontSize: 14,
|
|
212
|
+
fontWeight: '600',
|
|
213
|
+
},
|
|
214
|
+
cardSub: {
|
|
215
|
+
color: '#64748B',
|
|
216
|
+
fontSize: 12,
|
|
217
|
+
},
|
|
218
|
+
confirmButton: {
|
|
219
|
+
backgroundColor: '#F8FAFC',
|
|
220
|
+
borderRadius: 10,
|
|
221
|
+
paddingVertical: 15,
|
|
222
|
+
alignItems: 'center',
|
|
223
|
+
},
|
|
224
|
+
confirmButtonText: {
|
|
225
|
+
color: '#08090C',
|
|
226
|
+
fontSize: 14,
|
|
227
|
+
fontWeight: '700',
|
|
228
|
+
},
|
|
229
|
+
});
|