diff --git a/apps/mobile-shell/App.tsx b/apps/mobile-shell/App.tsx
index c46f0e6..c4dc9c0 100644
--- a/apps/mobile-shell/App.tsx
+++ b/apps/mobile-shell/App.tsx
@@ -1,10 +1,12 @@
import { POST_MESSAGE_EVENT } from '@comma/bridge';
+import * as SplashScreen from 'expo-splash-screen';
import { StatusBar } from 'expo-status-bar';
import { Platform, SafeAreaView, StyleSheet } from 'react-native';
import { postMessage, WebView } from './src/bridge';
const defaultWebUrl = Platform.OS === 'android' ? 'http://10.0.2.2:5173' : 'http://localhost:5173';
const webUrl = process.env.EXPO_PUBLIC_WEB_URL ?? defaultWebUrl;
+SplashScreen.preventAutoHideAsync();
export default function App() {
return (
@@ -19,6 +21,7 @@ export default function App() {
postMessage(POST_MESSAGE_EVENT.APP_READY, {
platform: Platform.OS
});
+ SplashScreen.hideAsync();
}}
/>
diff --git a/apps/mobile-shell/app.json b/apps/mobile-shell/app.json
index 611f605..86ca33d 100644
--- a/apps/mobile-shell/app.json
+++ b/apps/mobile-shell/app.json
@@ -7,11 +7,21 @@
"scheme": "comma",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
+ "plugins": [
+ [
+ "expo-splash-screen",
+ {
+ "image": "./assets/splash.png",
+ "resizeMode": "cover"
+ }
+ ]
+ ],
"ios": {
"supportsTablet": true
},
"android": {
- "edgeToEdgeEnabled": true
+ "edgeToEdgeEnabled": true,
+ "package": "com.anonymous.comma"
}
}
}
diff --git a/apps/mobile-shell/assets/splash.png b/apps/mobile-shell/assets/splash.png
new file mode 100644
index 0000000..319e371
Binary files /dev/null and b/apps/mobile-shell/assets/splash.png differ
diff --git a/apps/mobile-shell/package.json b/apps/mobile-shell/package.json
index 39efed2..971308d 100644
--- a/apps/mobile-shell/package.json
+++ b/apps/mobile-shell/package.json
@@ -6,18 +6,19 @@
"scripts": {
"dev": "expo start",
"dev:client": "expo start --dev-client",
- "android": "expo start --android",
"android:dev-client": "expo run:android",
- "ios": "expo start --ios",
+ "ios": "expo run:ios",
"ios:dev-client": "expo run:ios",
"web": "expo start --web",
- "typecheck": "tsc --noEmit"
+ "typecheck": "tsc --noEmit",
+ "android": "expo run:android"
},
"dependencies": {
"@comma/bridge": "workspace:*",
"@webview-bridge/react-native": "^1.7.9",
"expo": "~53.0.27",
"expo-dev-client": "~5.2.4",
+ "expo-splash-screen": "~0.30.10",
"expo-status-bar": "^2.2.0",
"react": "19.0.0",
"react-native": "0.79.6",
@@ -25,6 +26,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
+ "@types/node": "^26.0.1",
"@types/react": "~19.0.10",
"typescript": "~5.8.3"
}
diff --git a/apps/mobile-shell/tsconfig.json b/apps/mobile-shell/tsconfig.json
index e928203..9c7e1fe 100644
--- a/apps/mobile-shell/tsconfig.json
+++ b/apps/mobile-shell/tsconfig.json
@@ -1,7 +1,8 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
- "strict": true
+ "strict": true,
+ "types": ["node"]
},
"include": ["App.tsx", "src"]
}
diff --git a/apps/web/package.json b/apps/web/package.json
index 4d02a1f..dc1ba50 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -17,6 +17,7 @@
"@webview-bridge/web": "^1.7.9",
"react": "19.0.0",
"react-dom": "19.0.0",
+ "react-router-dom": "^7.18.0",
"vite": "^6.3.0"
},
"devDependencies": {
diff --git a/apps/web/public/fonts/PretendardVariable.woff2 b/apps/web/public/fonts/PretendardVariable.woff2
new file mode 100644
index 0000000..49c54b5
Binary files /dev/null and b/apps/web/public/fonts/PretendardVariable.woff2 differ
diff --git a/apps/web/public/images/apple_logo.svg b/apps/web/public/images/apple_logo.svg
new file mode 100644
index 0000000..5753ab0
--- /dev/null
+++ b/apps/web/public/images/apple_logo.svg
@@ -0,0 +1,10 @@
+
diff --git a/apps/web/public/images/google_logo.svg b/apps/web/public/images/google_logo.svg
new file mode 100644
index 0000000..370ef66
--- /dev/null
+++ b/apps/web/public/images/google_logo.svg
@@ -0,0 +1,10 @@
+
diff --git a/apps/web/public/images/kakao_logo.svg b/apps/web/public/images/kakao_logo.svg
new file mode 100644
index 0000000..b7f8a47
--- /dev/null
+++ b/apps/web/public/images/kakao_logo.svg
@@ -0,0 +1,11 @@
+
diff --git a/apps/web/public/images/logo_glass.svg b/apps/web/public/images/logo_glass.svg
new file mode 100644
index 0000000..a68fb3d
--- /dev/null
+++ b/apps/web/public/images/logo_glass.svg
@@ -0,0 +1,15 @@
+
diff --git a/apps/web/public/images/onboardingBackground.svg b/apps/web/public/images/onboardingBackground.svg
new file mode 100644
index 0000000..7667a05
--- /dev/null
+++ b/apps/web/public/images/onboardingBackground.svg
@@ -0,0 +1,10 @@
+
diff --git a/apps/web/public/images/onboardingBackground_blur.svg b/apps/web/public/images/onboardingBackground_blur.svg
new file mode 100644
index 0000000..6a52aa8
--- /dev/null
+++ b/apps/web/public/images/onboardingBackground_blur.svg
@@ -0,0 +1,17 @@
+
diff --git a/apps/web/src/global.css.ts b/apps/web/src/global.css.ts
index 31ad8ca..fd14a34 100644
--- a/apps/web/src/global.css.ts
+++ b/apps/web/src/global.css.ts
@@ -1,4 +1,4 @@
-import { globalStyle } from '@vanilla-extract/css';
+import { globalFontFace, globalStyle } from '@vanilla-extract/css';
globalStyle('*', {
boxSizing: 'border-box'
@@ -7,3 +7,14 @@ globalStyle('*', {
globalStyle('body', {
margin: 0
});
+
+globalFontFace('Pretendard', {
+ src: 'url("/fonts/PretendardVariable.woff2") format("woff2")',
+ fontDisplay: 'swap',
+ fontWeight: '100 900'
+});
+
+globalStyle('body', {
+ margin: 0,
+ fontFamily: 'Pretendard, -apple-system, BlinkMacSystemFont, system-ui, sans-serif'
+});
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
index 7b1bba0..acd08a9 100644
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -1,28 +1,10 @@
import { POST_MESSAGE_EVENT } from '@comma/bridge';
-import {
- actionButton,
- brand,
- description,
- eyebrow,
- panel,
- screen,
- themeClass,
- title
-} from '@comma/design-system';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { appBridge } from './bridge';
import './global.css';
-
-async function sendOpenExternalUrl() {
- await appBridge.openExternalBrowser('https://example.com');
-}
-
-async function logAppInfo() {
- const appInfo = await appBridge.getAppInfo();
-
- console.log('app info', appInfo);
-}
+import { RouterProvider } from 'react-router-dom';
+import { router } from './router/index';
appBridge.addEventListener(POST_MESSAGE_EVENT.APP_READY, (message) => {
console.log('app ready', message);
@@ -36,20 +18,6 @@ if (!rootElement) {
ReactDOM.createRoot(rootElement).render(
-
-
- Web running inside native shell
- {brand.name}
-
- React 웹앱을 그대로 개발하고, Expo shell은 WebView와 네이티브 기능만 담당합니다.
-
-
-
-
-
+
);
diff --git a/apps/web/src/pages/Login.css.ts b/apps/web/src/pages/Login.css.ts
new file mode 100644
index 0000000..d84a1bc
--- /dev/null
+++ b/apps/web/src/pages/Login.css.ts
@@ -0,0 +1,87 @@
+import { style } from '@vanilla-extract/css';
+
+export const container = style({
+ backgroundImage: 'url("/images/onboardingBackground_blur.svg")',
+ backgroundRepeat: 'no-repeat',
+ backgroundSize: 'cover',
+ width: '100vw',
+ height: '100vh',
+ backgroundPosition: 'center',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingLeft: 32,
+ paddingRight: 32,
+ boxSizing: 'border-box'
+});
+
+export const title = style({
+ fontFamily: 'Pretendard, sans-serif',
+ fontSize: 32,
+ fontWeight: 200,
+ color: '#FDFCFC'
+});
+
+export const desc = style({
+ fontFamily: 'Pretendard, sans-serif',
+ fontSize: 16,
+ fontWeight: 400,
+ color: '#C2BFBC',
+ marginTop: 16
+});
+
+export const agreementNotice = style({
+ color: '#C2BFBC',
+ fontFamily: 'Pretendard, sans-serif',
+ fontSize: 12,
+ fontWeight: 500,
+ textAlign: 'center'
+});
+
+export const agreementAccent = style({
+ color: '#DBD8D7',
+ textDecoration: 'underline',
+ fontWeight: 600
+});
+
+const buttonBase = style({
+ width: '100%',
+ height: 60,
+ borderRadius: 100,
+ fontFamily: 'Pretendard, sans-serif',
+ fontWeight: 500,
+ fontSize: 20,
+ border: 'none',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: '5px'
+});
+
+export const kakaoBtn = style([
+ buttonBase,
+ {
+ backgroundColor: '#FEE500',
+ color: '#322E29',
+ marginBottom: 8
+ }
+]);
+
+export const appleBtn = style([
+ buttonBase,
+ {
+ backgroundColor: '#1A1814',
+ color: '#FDFCFC',
+ marginBottom: 8
+ }
+]);
+
+export const googleBtn = style([
+ buttonBase,
+ {
+ backgroundColor: '#FDFCFC',
+ color: '#322E29',
+ marginBottom: 24
+ }
+]);
diff --git a/apps/web/src/pages/Login.tsx b/apps/web/src/pages/Login.tsx
new file mode 100644
index 0000000..f3025d5
--- /dev/null
+++ b/apps/web/src/pages/Login.tsx
@@ -0,0 +1,49 @@
+import * as styles from './Login.css';
+
+function Login() {
+ return (
+
+
+

+
+
고립감 없는
+
연결된 휴식
+
+ 불안한 멈춤에서 온전한 쉼으로,
+
+ 당신의 쉬는 시간을 새롭게 정의합니다.
+
+
+
+
+
+
+
+
+ 계속 진행하면 서비스 이용약관 및
+ 개인정보처리방침에 동의하는 것으로
+ 간주합니다
+
+
+
+ );
+}
+
+export default Login;
diff --git a/apps/web/src/router/index.tsx b/apps/web/src/router/index.tsx
new file mode 100644
index 0000000..00d3f00
--- /dev/null
+++ b/apps/web/src/router/index.tsx
@@ -0,0 +1,9 @@
+import { createBrowserRouter } from 'react-router-dom';
+import Login from '../pages/Login';
+
+export const router = createBrowserRouter([
+ {
+ path: '/',
+ Component: Login
+ }
+]);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5646661..686f364 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -41,6 +41,9 @@ importers:
expo-dev-client:
specifier: ~5.2.4
version: 5.2.4(expo@53.0.27(@babel/core@7.29.7)(react-native-webview@13.13.5(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0))
+ expo-splash-screen:
+ specifier: ~0.30.10
+ version: 0.30.10(expo@53.0.27(@babel/core@7.29.7)(react-native-webview@13.13.5(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0))
expo-status-bar:
specifier: ^2.2.0
version: 2.2.3(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0)
@@ -57,6 +60,9 @@ importers:
specifier: ^4.4.3
version: 4.4.3
devDependencies:
+ '@types/node':
+ specifier: ^26.0.1
+ version: 26.0.1
'@types/react':
specifier: ~19.0.10
version: 19.0.14
@@ -87,6 +93,9 @@ importers:
react-dom:
specifier: 19.0.0
version: 19.0.0(react@19.0.0)
+ react-router-dom:
+ specifier: ^7.18.0
+ version: 7.18.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
vite:
specifier: ^6.3.0
version: 6.4.3(@types/node@26.0.1)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.48.0)
@@ -1856,6 +1865,10 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cookie@1.1.1:
+ resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
+ engines: {node: '>=18'}
+
core-js-compat@3.49.0:
resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==}
@@ -2132,6 +2145,11 @@ packages:
expo-modules-core@2.5.0:
resolution: {integrity: sha512-aIbQxZE2vdCKsolQUl6Q9Farlf8tjh/ROR4hfN1qT7QBGPl1XrJGnaOKkcgYaGrlzCPg/7IBe0Np67GzKMZKKQ==}
+ expo-splash-screen@0.30.10:
+ resolution: {integrity: sha512-Tt9va/sLENQDQYeOQ6cdLdGvTZ644KR3YG9aRlnpcs2/beYjOX1LHT510EGzVN9ljUTg+1ebEo5GGt2arYtPjw==}
+ peerDependencies:
+ expo: '*'
+
expo-status-bar@2.2.3:
resolution: {integrity: sha512-+c8R3AESBoduunxTJ8353SqKAKpxL6DvcD8VKBuh81zzJyUUbfB4CVjr1GufSJEKsMzNPXZU+HJwXx7Xh7lx8Q==}
peerDependencies:
@@ -2249,6 +2267,7 @@ packages:
git-raw-commits@5.0.1:
resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==}
engines: {node: '>=18'}
+ deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.
hasBin: true
glob@10.5.0:
@@ -3072,6 +3091,23 @@ packages:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
+ react-router-dom@7.18.0:
+ resolution: {integrity: sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ react-router@7.18.0:
+ resolution: {integrity: sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
react@19.0.0:
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'}
@@ -3189,6 +3225,9 @@ packages:
resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
engines: {node: '>= 0.8.0'}
+ set-cookie-parser@2.7.2:
+ resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
+
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@@ -5763,6 +5802,8 @@ snapshots:
convert-source-map@2.0.0: {}
+ cookie@1.1.1: {}
+
core-js-compat@3.49.0:
dependencies:
browserslist: 4.28.4
@@ -6019,6 +6060,13 @@ snapshots:
dependencies:
invariant: 2.2.4
+ expo-splash-screen@0.30.10(expo@53.0.27(@babel/core@7.29.7)(react-native-webview@13.13.5(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0)):
+ dependencies:
+ '@expo/prebuild-config': 9.0.12
+ expo: 53.0.27(@babel/core@7.29.7)(react-native-webview@13.13.5(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0)
+ transitivePeerDependencies:
+ - supports-color
+
expo-status-bar@2.2.3(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.0.14)(react@19.0.0))(react@19.0.0):
dependencies:
react: 19.0.0
@@ -7048,6 +7096,20 @@ snapshots:
react-refresh@0.17.0: {}
+ react-router-dom@7.18.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ react-router: 7.18.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+
+ react-router@7.18.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ cookie: 1.1.1
+ react: 19.0.0
+ set-cookie-parser: 2.7.2
+ optionalDependencies:
+ react-dom: 19.0.0(react@19.0.0)
+
react@19.0.0: {}
regenerate-unicode-properties@10.2.2:
@@ -7206,6 +7268,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ set-cookie-parser@2.7.2: {}
+
setprototypeof@1.2.0: {}
shebang-command@2.0.0: