diff --git a/public/index.html b/public/index.html
index c01f68c303..50a27bfafe 100644
--- a/public/index.html
+++ b/public/index.html
@@ -12,6 +12,11 @@
/* These styles make the root element full-height */
#app-root { display:flex; height:100%; }
+ /* Remove focus state on inputs */
+ input:focus {
+ outline: 0;
+ }
+
/* These styles are for src/view/com/modals/WebModal */
div[data-modal-overlay] {
position: fixed;
diff --git a/src/view/com/onboard/FeatureExplainer.web.tsx b/src/view/com/onboard/FeatureExplainer.web.tsx
new file mode 100644
index 0000000000..83d460808f
--- /dev/null
+++ b/src/view/com/onboard/FeatureExplainer.web.tsx
@@ -0,0 +1,203 @@
+import React, {useState} from 'react'
+import {
+ Animated,
+ Image,
+ SafeAreaView,
+ StyleSheet,
+ TouchableOpacity,
+ useWindowDimensions,
+ View,
+} from 'react-native'
+import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view'
+import {
+ FontAwesomeIcon,
+ FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {CenteredView} from '../util/Views.web'
+import {Text} from '../util/text/Text'
+import {useStores} from '../../../state'
+import {s, colors} from '../../lib/styles'
+import {TABS_EXPLAINER} from '../../lib/assets'
+import {TABS_ENABLED} from '../../../build-flags'
+
+const ROUTES = TABS_ENABLED
+ ? [
+ {key: 'intro', title: 'Intro'},
+ {key: 'tabs', title: 'Tabs'},
+ ]
+ : [{key: 'intro', title: 'Intro'}]
+
+const Intro = () => (
+
+
+ Welcome to{' '}
+
+ Bluesky
+
+
+
+ This is an early beta. Your feedback is appreciated!
+
+
+)
+
+const Tabs = () => (
+
+
+
+
+
+
+ Tabs
+
+ Never lose your place! Long-press to open posts and profiles in a new tab.
+
+
+
+
+
+)
+
+const SCENE_MAP = {
+ intro: Intro,
+ tabs: Tabs,
+}
+const renderScene = SceneMap(SCENE_MAP)
+
+export const FeatureExplainer = () => {
+ const layout = useWindowDimensions()
+ const store = useStores()
+ const [index, setIndex] = useState(0)
+
+ const onPressSkip = () => store.onboard.next()
+ const onPressNext = () => {
+ if (index >= ROUTES.length - 1) {
+ store.onboard.next()
+ } else {
+ setIndex(index + 1)
+ }
+ }
+
+ const renderTabBar = (props: TabBarProps) => {
+ const inputRange = props.navigationState.routes.map((x, i) => i)
+ return (
+
+
+ {props.navigationState.routes.map((route, i) => {
+ const opacity = props.position.interpolate({
+ inputRange,
+ outputRange: inputRange.map(inputIndex =>
+ inputIndex === i ? 1 : 0.5,
+ ),
+ })
+
+ return (
+ setIndex(i)}>
+ °
+
+ )
+ })}
+
+
+ )
+ }
+
+ const FirstExplainer = SCENE_MAP[ROUTES[0]?.key as keyof typeof SCENE_MAP]
+ return (
+
+ {ROUTES.length > 1 ? (
+
+ ) : FirstExplainer ? (
+
+ ) : (
+
+ )}
+
+
+ Skip
+
+
+ Next
+
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ height: '100%',
+ justifyContent: 'center',
+ paddingBottom: '10%',
+ },
+
+ tabBar: {
+ flexDirection: 'row',
+ },
+ tabItem: {
+ alignItems: 'center',
+ padding: 16,
+ },
+
+ explainer: {
+ paddingHorizontal: 16,
+ },
+ explainerIcon: {
+ flexDirection: 'row',
+ },
+ explainerHeading: {
+ fontSize: 42,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ marginBottom: 16,
+ },
+ explainerHeadingIntro: {
+ lineHeight: 40,
+ },
+ explainerHeadingBrand: {fontSize: 56},
+ explainerDesc: {
+ fontSize: 18,
+ textAlign: 'center',
+ marginBottom: 16,
+ color: colors.gray5,
+ },
+ explainerDescIntro: {fontSize: 24},
+ explainerImg: {
+ resizeMode: 'contain',
+ maxWidth: '100%',
+ maxHeight: 330,
+ },
+
+ footer: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ paddingTop: 24,
+ },
+ footerBtn: {
+ color: colors.blue3,
+ fontSize: 19,
+ paddingHorizontal: 36,
+ paddingVertical: 8,
+ },
+ footerBtnNext: {
+ marginLeft: 10,
+ borderWidth: 1,
+ borderColor: colors.blue3,
+ borderRadius: 6,
+ },
+})
diff --git a/src/view/com/onboard/Follows.web.tsx b/src/view/com/onboard/Follows.web.tsx
new file mode 100644
index 0000000000..50e119e49c
--- /dev/null
+++ b/src/view/com/onboard/Follows.web.tsx
@@ -0,0 +1,47 @@
+import React from 'react'
+import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native'
+import {observer} from 'mobx-react-lite'
+import {SuggestedFollows} from '../discover/SuggestedFollows'
+import {CenteredView} from '../util/Views.web'
+import {Text} from '../util/text/Text'
+import {useStores} from '../../../state'
+import {s, colors} from '../../lib/styles'
+
+export const Follows = observer(() => {
+ const store = useStores()
+
+ const onNoSuggestions = () => {
+ // no suggestions, bounce from this view
+ store.onboard.next()
+ }
+ const onPressNext = () => store.onboard.next()
+
+ return (
+
+
+
+ Follow these people to see their posts in your feed
+
+
+ Next »
+
+
+
+
+ )
+})
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ },
+
+ header: {
+ paddingTop: 30,
+ paddingBottom: 40,
+ },
+})
diff --git a/src/view/screens/Login.web.tsx b/src/view/screens/Login.web.tsx
new file mode 100644
index 0000000000..77149090cb
--- /dev/null
+++ b/src/view/screens/Login.web.tsx
@@ -0,0 +1,163 @@
+import React, {useState} from 'react'
+import {
+ Image,
+ SafeAreaView,
+ StyleSheet,
+ TouchableOpacity,
+ View,
+} from 'react-native'
+import {observer} from 'mobx-react-lite'
+import {CenteredView} from '../com/util/Views'
+import {Signin} from '../com/login/Signin'
+import {CreateAccount} from '../com/login/CreateAccount'
+import {Text} from '../com/util/text/Text'
+import {ErrorBoundary} from '../com/util/ErrorBoundary'
+import {colors} from '../lib/styles'
+import {usePalette} from '../lib/hooks/usePalette'
+import {CLOUD_SPLASH} from '../lib/assets'
+
+enum ScreenState {
+ S_SigninOrCreateAccount,
+ S_Signin,
+ S_CreateAccount,
+}
+
+const SigninOrCreateAccount = ({
+ onPressSignin,
+ onPressCreateAccount,
+}: {
+ onPressSignin: () => void
+ onPressCreateAccount: () => void
+}) => {
+ const pal = usePalette('default')
+ return (
+ <>
+
+
+ Bluesky
+ [ private beta ]
+
+
+
+
+ New account
+
+
+ Sign in
+
+
+ >
+ )
+}
+
+export const Login = observer(() => {
+ const pal = usePalette('default')
+ const [screenState, setScreenState] = useState(
+ ScreenState.S_SigninOrCreateAccount,
+ )
+
+ if (screenState === ScreenState.S_SigninOrCreateAccount) {
+ return (
+
+
+ setScreenState(ScreenState.S_Signin)}
+ onPressCreateAccount={() =>
+ setScreenState(ScreenState.S_CreateAccount)
+ }
+ />
+
+
+ )
+ }
+
+ return (
+
+
+
+ {screenState === ScreenState.S_Signin ? (
+
+ setScreenState(ScreenState.S_SigninOrCreateAccount)
+ }
+ />
+ ) : undefined}
+ {screenState === ScreenState.S_CreateAccount ? (
+
+ setScreenState(ScreenState.S_SigninOrCreateAccount)
+ }
+ />
+ ) : undefined}
+
+
+
+ )
+})
+
+const styles = StyleSheet.create({
+ container: {
+ height: '100%',
+ },
+ containerBorder: {
+ borderLeftWidth: 1,
+ borderRightWidth: 1,
+ },
+ vertCenter: {
+ justifyContent: 'center',
+ },
+ bgImg: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ },
+ heroText: {
+ backgroundColor: colors.white,
+ paddingTop: 10,
+ paddingBottom: 20,
+ },
+ btns: {
+ flexDirection: 'row',
+ paddingTop: 40,
+ },
+ title: {
+ textAlign: 'center',
+ color: colors.blue3,
+ fontSize: 68,
+ fontWeight: 'bold',
+ },
+ subtitle: {
+ textAlign: 'center',
+ color: colors.blue3,
+ fontSize: 18,
+ },
+ btn: {
+ flex: 1,
+ borderRadius: 4,
+ paddingVertical: 16,
+ marginBottom: 20,
+ marginHorizontal: 20,
+ borderWidth: 1,
+ borderColor: colors.blue3,
+ },
+ btnLabel: {
+ textAlign: 'center',
+ fontSize: 21,
+ fontWeight: '500',
+ color: colors.blue3,
+ },
+})
diff --git a/src/view/shell/web/index.tsx b/src/view/shell/web/index.tsx
index fedc9c3d6b..1059aa280d 100644
--- a/src/view/shell/web/index.tsx
+++ b/src/view/shell/web/index.tsx
@@ -5,6 +5,7 @@ import {useStores} from '../../../state'
import {match, MatchResult} from '../../routes'
import {DesktopLeftColumn} from './left-column'
import {DesktopRightColumn} from './right-column'
+import {Onboard} from '../../screens/Onboard'
import {Login} from '../../screens/Login'
import {ErrorBoundary} from '../../com/util/ErrorBoundary'
import {usePalette} from '../../lib/hooks/usePalette'
@@ -22,6 +23,15 @@ export const WebShell: React.FC = observer(() => {
)
}
+ if (store.onboard.isOnboarding) {
+ return (
+
+
+
+
+
+ )
+ }
return (
@@ -104,12 +114,6 @@ const styles = StyleSheet.create({
outerContainer: {
height: '100%',
},
- innerContainer: {
- marginLeft: 'auto',
- marginRight: 'auto',
- width: '600px',
- height: '100%',
- },
visible: {
display: 'flex',
},