about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app.config.js25
-rw-r--r--assets/fonts/inter/Inter-Black.otfbin0 -> 619772 bytes
-rw-r--r--assets/fonts/inter/Inter-BlackItalic.otfbin0 -> 592516 bytes
-rw-r--r--assets/fonts/inter/Inter-Bold.otfbin0 -> 626056 bytes
-rw-r--r--assets/fonts/inter/Inter-BoldItalic.otfbin0 -> 604316 bytes
-rw-r--r--assets/fonts/inter/Inter-ExtraBold.otfbin0 -> 627736 bytes
-rw-r--r--assets/fonts/inter/Inter-ExtraBoldItalic.otfbin0 -> 603412 bytes
-rw-r--r--assets/fonts/inter/Inter-ExtraLight.otfbin0 -> 588780 bytes
-rw-r--r--assets/fonts/inter/Inter-ExtraLightItalic.otfbin0 -> 579096 bytes
-rw-r--r--assets/fonts/inter/Inter-Italic.otfbin0 -> 586752 bytes
-rw-r--r--assets/fonts/inter/Inter-Light.otfbin0 -> 607704 bytes
-rw-r--r--assets/fonts/inter/Inter-LightItalic.otfbin0 -> 588860 bytes
-rw-r--r--assets/fonts/inter/Inter-Medium.otfbin0 -> 622088 bytes
-rw-r--r--assets/fonts/inter/Inter-MediumItalic.otfbin0 -> 599652 bytes
-rw-r--r--assets/fonts/inter/Inter-Regular.otfbin0 -> 605092 bytes
-rw-r--r--assets/fonts/inter/Inter-SemiBold.otfbin0 -> 624244 bytes
-rw-r--r--assets/fonts/inter/Inter-SemiBoldItalic.otfbin0 -> 602496 bytes
-rw-r--r--assets/fonts/inter/Inter-Thin.otfbin0 -> 576484 bytes
-rw-r--r--assets/fonts/inter/Inter-ThinItalic.otfbin0 -> 571792 bytes
-rw-r--r--assets/icons/textSize_stroke2_corner0_rounded.svg1
-rw-r--r--assets/icons/titleCase_stroke2_corner0_rounded.svg1
-rw-r--r--package.json1
-rw-r--r--src/App.native.tsx88
-rw-r--r--src/App.web.tsx87
-rw-r--r--src/alf/atoms.ts25
-rw-r--r--src/alf/fonts.ts111
-rw-r--r--src/alf/index.tsx101
-rw-r--r--src/alf/tokens.ts4
-rw-r--r--src/components/Button.tsx12
-rw-r--r--src/components/Dialog/index.tsx1
-rw-r--r--src/components/Dialog/index.web.tsx1
-rw-r--r--src/components/Dialog/utils.ts18
-rw-r--r--src/components/Typography.tsx26
-rw-r--r--src/components/dialogs/nuxs/NeueTypography.tsx119
-rw-r--r--src/components/dialogs/nuxs/index.tsx80
-rw-r--r--src/components/icons/TextSize.tsx5
-rw-r--r--src/components/icons/TitleCase.tsx5
-rw-r--r--src/lib/styles.ts6
-rw-r--r--src/lib/themes.ts89
-rw-r--r--src/screens/Settings/AppearanceSettings.tsx212
-rw-r--r--src/state/queries/nuxs/definitions.ts15
-rw-r--r--src/storage/index.ts2
-rw-r--r--src/storage/schema.ts2
-rw-r--r--src/view/com/util/text/Text.tsx47
-rw-r--r--yarn.lock7
45 files changed, 835 insertions, 256 deletions
diff --git a/app.config.js b/app.config.js
index ecc7e4d40..fe65c4fde 100644
--- a/app.config.js
+++ b/app.config.js
@@ -230,6 +230,31 @@ module.exports = function (config) {
         './plugins/shareExtension/withShareExtensions.js',
         './plugins/notificationsExtension/withNotificationsExtension.js',
         './plugins/withAppDelegateReferrer.js',
+        [
+          'expo-font',
+          {
+            fonts: [
+              // './assets/fonts/inter/Inter-Thin.otf',
+              // './assets/fonts/inter/Inter-ThinItalic.otf',
+              // './assets/fonts/inter/Inter-ExtraLight.otf',
+              // './assets/fonts/inter/Inter-ExtraLightItalic.otf',
+              // './assets/fonts/inter/Inter-Light.otf',
+              // './assets/fonts/inter/Inter-LightItalic.otf',
+              './assets/fonts/inter/Inter-Regular.otf',
+              './assets/fonts/inter/Inter-Italic.otf',
+              './assets/fonts/inter/Inter-Medium.otf',
+              './assets/fonts/inter/Inter-MediumItalic.otf',
+              './assets/fonts/inter/Inter-SemiBold.otf',
+              './assets/fonts/inter/Inter-SemiBoldItalic.otf',
+              './assets/fonts/inter/Inter-Bold.otf',
+              './assets/fonts/inter/Inter-BoldItalic.otf',
+              './assets/fonts/inter/Inter-ExtraBold.otf',
+              './assets/fonts/inter/Inter-ExtraBoldItalic.otf',
+              './assets/fonts/inter/Inter-Black.otf',
+              './assets/fonts/inter/Inter-BlackItalic.otf',
+            ],
+          },
+        ],
       ].filter(Boolean),
       extra: {
         eas: {
diff --git a/assets/fonts/inter/Inter-Black.otf b/assets/fonts/inter/Inter-Black.otf
new file mode 100644
index 000000000..44d1779af
--- /dev/null
+++ b/assets/fonts/inter/Inter-Black.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-BlackItalic.otf b/assets/fonts/inter/Inter-BlackItalic.otf
new file mode 100644
index 000000000..6fc475e41
--- /dev/null
+++ b/assets/fonts/inter/Inter-BlackItalic.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-Bold.otf b/assets/fonts/inter/Inter-Bold.otf
new file mode 100644
index 000000000..58a38073e
--- /dev/null
+++ b/assets/fonts/inter/Inter-Bold.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-BoldItalic.otf b/assets/fonts/inter/Inter-BoldItalic.otf
new file mode 100644
index 000000000..e67935aa5
--- /dev/null
+++ b/assets/fonts/inter/Inter-BoldItalic.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-ExtraBold.otf b/assets/fonts/inter/Inter-ExtraBold.otf
new file mode 100644
index 000000000..66cd95228
--- /dev/null
+++ b/assets/fonts/inter/Inter-ExtraBold.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-ExtraBoldItalic.otf b/assets/fonts/inter/Inter-ExtraBoldItalic.otf
new file mode 100644
index 000000000..f269814a6
--- /dev/null
+++ b/assets/fonts/inter/Inter-ExtraBoldItalic.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-ExtraLight.otf b/assets/fonts/inter/Inter-ExtraLight.otf
new file mode 100644
index 000000000..b603db3c7
--- /dev/null
+++ b/assets/fonts/inter/Inter-ExtraLight.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-ExtraLightItalic.otf b/assets/fonts/inter/Inter-ExtraLightItalic.otf
new file mode 100644
index 000000000..f65051941
--- /dev/null
+++ b/assets/fonts/inter/Inter-ExtraLightItalic.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-Italic.otf b/assets/fonts/inter/Inter-Italic.otf
new file mode 100644
index 000000000..f78848b98
--- /dev/null
+++ b/assets/fonts/inter/Inter-Italic.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-Light.otf b/assets/fonts/inter/Inter-Light.otf
new file mode 100644
index 000000000..7da794bd3
--- /dev/null
+++ b/assets/fonts/inter/Inter-Light.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-LightItalic.otf b/assets/fonts/inter/Inter-LightItalic.otf
new file mode 100644
index 000000000..32ef937c5
--- /dev/null
+++ b/assets/fonts/inter/Inter-LightItalic.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-Medium.otf b/assets/fonts/inter/Inter-Medium.otf
new file mode 100644
index 000000000..f44f89ada
--- /dev/null
+++ b/assets/fonts/inter/Inter-Medium.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-MediumItalic.otf b/assets/fonts/inter/Inter-MediumItalic.otf
new file mode 100644
index 000000000..1970f5721
--- /dev/null
+++ b/assets/fonts/inter/Inter-MediumItalic.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-Regular.otf b/assets/fonts/inter/Inter-Regular.otf
new file mode 100644
index 000000000..2d0bd1d64
--- /dev/null
+++ b/assets/fonts/inter/Inter-Regular.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-SemiBold.otf b/assets/fonts/inter/Inter-SemiBold.otf
new file mode 100644
index 000000000..52c84550b
--- /dev/null
+++ b/assets/fonts/inter/Inter-SemiBold.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-SemiBoldItalic.otf b/assets/fonts/inter/Inter-SemiBoldItalic.otf
new file mode 100644
index 000000000..b725bfc88
--- /dev/null
+++ b/assets/fonts/inter/Inter-SemiBoldItalic.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-Thin.otf b/assets/fonts/inter/Inter-Thin.otf
new file mode 100644
index 000000000..568a18560
--- /dev/null
+++ b/assets/fonts/inter/Inter-Thin.otf
Binary files differdiff --git a/assets/fonts/inter/Inter-ThinItalic.otf b/assets/fonts/inter/Inter-ThinItalic.otf
new file mode 100644
index 000000000..c5ed37c35
--- /dev/null
+++ b/assets/fonts/inter/Inter-ThinItalic.otf
Binary files differdiff --git a/assets/icons/textSize_stroke2_corner0_rounded.svg b/assets/icons/textSize_stroke2_corner0_rounded.svg
new file mode 100644
index 000000000..6c7537d10
--- /dev/null
+++ b/assets/icons/textSize_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M9 5a1 1 0 0 1 1-1h12a1 1 0 1 1 0 2h-5v14a1 1 0 1 1-2 0V6h-5a1 1 0 0 1-1-1Zm-3.073 7v8a1 1 0 1 0 2 0v-8H12a1 1 0 1 0 0-2H6.971a1.015 1.015 0 0 0-.089 0H2a1 1 0 1 0 0 2h3.927Z" clip-rule="evenodd"/></svg>
diff --git a/assets/icons/titleCase_stroke2_corner0_rounded.svg b/assets/icons/titleCase_stroke2_corner0_rounded.svg
new file mode 100644
index 000000000..facdfc0e1
--- /dev/null
+++ b/assets/icons/titleCase_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3.65 17.247c-.242.832-.632 1.178-1.325 1.178-.814 0-1.325-.476-1.325-1.23 0-.216.06-.51.173-.831L4.586 7.07c.364-1.014.979-1.482 1.966-1.482 1.022 0 1.629.45 2.001 1.473l3.43 9.303c.121.337.165.571.165.831 0 .72-.546 1.23-1.308 1.23-.736 0-1.126-.338-1.36-1.152l-.658-1.975H4.309l-.658 1.95ZM6.5 8.152l-1.62 5.12h3.335l-1.654-5.12H6.5Zm13.005 8.688c-.52.988-1.68 1.568-2.84 1.568-1.768 0-3.11-1.144-3.11-2.815 0-1.69 1.299-2.668 3.62-2.807l2.34-.138v-.615c0-.867-.607-1.369-1.56-1.369-.771 0-1.239.251-1.802.979-.277.312-.597.468-1.004.468-.615 0-1.057-.399-1.057-.97 0-.2.043-.382.13-.572.433-1.109 1.923-1.793 3.845-1.793 2.383 0 3.933 1.23 3.933 3.1v5.293c0 .84-.511 1.273-1.23 1.273-.684 0-1.16-.38-1.213-1.126v-.476h-.052Zm-3.43-1.386c0 .693.572 1.126 1.42 1.126 1.11 0 2.02-.719 2.02-1.723v-.676l-1.959.121c-.944.07-1.48.494-1.48 1.152Z" clip-rule="evenodd"/></svg>
diff --git a/package.json b/package.json
index 93788deb4..ba7882902 100644
--- a/package.json
+++ b/package.json
@@ -124,6 +124,7 @@
     "expo-dev-client": "^4.0.14",
     "expo-device": "~6.0.2",
     "expo-file-system": "^17.0.1",
+    "expo-font": "~12.0.10",
     "expo-haptics": "^13.0.1",
     "expo-image": "~1.12.9",
     "expo-image-manipulator": "^12.0.5",
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 2ec666e2c..9214253ac 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -55,7 +55,7 @@ import {TestCtrls} from '#/view/com/testing/TestCtrls'
 import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
 import * as Toast from '#/view/com/util/Toast'
 import {Shell} from '#/view/shell'
-import {ThemeProvider as Alf} from '#/alf'
+import {ThemeProvider as Alf, useFonts} from '#/alf'
 import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
 import {NuxDialogs} from '#/components/dialogs/nuxs'
 import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
@@ -106,62 +106,60 @@ function InnerApp() {
   }, [_])
 
   return (
-    <Alf theme={theme}>
-      <ThemeProvider theme={theme}>
-        <Splash isReady={isReady && hasCheckedReferrer}>
-          <RootSiblingParent>
-            <VideoVolumeProvider>
-              <React.Fragment
-                // Resets the entire tree below when it changes:
-                key={currentAccount?.did}>
+    <StatsigProvider
+      // Resets the entire tree below when it changes:
+      key={currentAccount?.did}>
+      <Alf theme={theme}>
+        <ThemeProvider theme={theme}>
+          <Splash isReady={isReady && hasCheckedReferrer}>
+            <RootSiblingParent>
+              <VideoVolumeProvider>
                 <QueryProvider currentDid={currentAccount?.did}>
-                  <StatsigProvider>
-                    <MessagesProvider>
-                      {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-                      <LabelDefsProvider>
-                        <ModerationOptsProvider>
-                          <LoggedOutViewProvider>
-                            <SelectedFeedProvider>
-                              <HiddenRepliesProvider>
-                                <UnreadNotifsProvider>
-                                  <BackgroundNotificationPreferencesProvider>
-                                    <MutedThreadsProvider>
-                                      <ProgressGuideProvider>
-                                        <GestureHandlerRootView
-                                          style={s.h100pct}>
-                                          <TestCtrls />
-                                          <Shell />
-                                          <NuxDialogs />
-                                        </GestureHandlerRootView>
-                                      </ProgressGuideProvider>
-                                    </MutedThreadsProvider>
-                                  </BackgroundNotificationPreferencesProvider>
-                                </UnreadNotifsProvider>
-                              </HiddenRepliesProvider>
-                            </SelectedFeedProvider>
-                          </LoggedOutViewProvider>
-                        </ModerationOptsProvider>
-                      </LabelDefsProvider>
-                    </MessagesProvider>
-                  </StatsigProvider>
+                  <MessagesProvider>
+                    {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+                    <LabelDefsProvider>
+                      <ModerationOptsProvider>
+                        <LoggedOutViewProvider>
+                          <SelectedFeedProvider>
+                            <HiddenRepliesProvider>
+                              <UnreadNotifsProvider>
+                                <BackgroundNotificationPreferencesProvider>
+                                  <MutedThreadsProvider>
+                                    <ProgressGuideProvider>
+                                      <GestureHandlerRootView style={s.h100pct}>
+                                        <TestCtrls />
+                                        <Shell />
+                                        <NuxDialogs />
+                                      </GestureHandlerRootView>
+                                    </ProgressGuideProvider>
+                                  </MutedThreadsProvider>
+                                </BackgroundNotificationPreferencesProvider>
+                              </UnreadNotifsProvider>
+                            </HiddenRepliesProvider>
+                          </SelectedFeedProvider>
+                        </LoggedOutViewProvider>
+                      </ModerationOptsProvider>
+                    </LabelDefsProvider>
+                  </MessagesProvider>
                 </QueryProvider>
-              </React.Fragment>
-            </VideoVolumeProvider>
-          </RootSiblingParent>
-        </Splash>
-      </ThemeProvider>
-    </Alf>
+              </VideoVolumeProvider>
+            </RootSiblingParent>
+          </Splash>
+        </ThemeProvider>
+      </Alf>
+    </StatsigProvider>
   )
 }
 
 function App() {
   const [isReady, setReady] = useState(false)
+  const [loaded] = useFonts()
 
   React.useEffect(() => {
     initPersistedState().then(() => setReady(true))
   }, [])
 
-  if (!isReady) {
+  if (!isReady || !loaded) {
     return null
   }
 
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 6efe7cc02..1c6650733 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -46,7 +46,7 @@ import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/Video
 import * as Toast from '#/view/com/util/Toast'
 import {ToastContainer} from '#/view/com/util/Toast.web'
 import {Shell} from '#/view/shell/index'
-import {ThemeProvider as Alf} from '#/alf'
+import {ThemeProvider as Alf, useFonts} from '#/alf'
 import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
 import {NuxDialogs} from '#/components/dialogs/nuxs'
 import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
@@ -96,62 +96,61 @@ function InnerApp() {
 
   return (
     <KeyboardProvider enabled={false}>
-      <Alf theme={theme}>
-        <ThemeProvider theme={theme}>
-          <RootSiblingParent>
-            <VideoVolumeProvider>
-              <ActiveVideoProvider>
-                <React.Fragment
-                  // Resets the entire tree below when it changes:
-                  key={currentAccount?.did}>
+      <StatsigProvider
+        // Resets the entire tree below when it changes:
+        key={currentAccount?.did}>
+        <Alf theme={theme}>
+          <ThemeProvider theme={theme}>
+            <RootSiblingParent>
+              <VideoVolumeProvider>
+                <ActiveVideoProvider>
                   <QueryProvider currentDid={currentAccount?.did}>
-                    <StatsigProvider>
-                      <MessagesProvider>
-                        {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-                        <LabelDefsProvider>
-                          <ModerationOptsProvider>
-                            <LoggedOutViewProvider>
-                              <SelectedFeedProvider>
-                                <HiddenRepliesProvider>
-                                  <UnreadNotifsProvider>
-                                    <BackgroundNotificationPreferencesProvider>
-                                      <MutedThreadsProvider>
-                                        <SafeAreaProvider>
-                                          <ProgressGuideProvider>
-                                            <Shell />
-                                            <NuxDialogs />
-                                          </ProgressGuideProvider>
-                                        </SafeAreaProvider>
-                                      </MutedThreadsProvider>
-                                    </BackgroundNotificationPreferencesProvider>
-                                  </UnreadNotifsProvider>
-                                </HiddenRepliesProvider>
-                              </SelectedFeedProvider>
-                            </LoggedOutViewProvider>
-                          </ModerationOptsProvider>
-                        </LabelDefsProvider>
-                      </MessagesProvider>
-                    </StatsigProvider>
+                    <MessagesProvider>
+                      {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+                      <LabelDefsProvider>
+                        <ModerationOptsProvider>
+                          <LoggedOutViewProvider>
+                            <SelectedFeedProvider>
+                              <HiddenRepliesProvider>
+                                <UnreadNotifsProvider>
+                                  <BackgroundNotificationPreferencesProvider>
+                                    <MutedThreadsProvider>
+                                      <SafeAreaProvider>
+                                        <ProgressGuideProvider>
+                                          <Shell />
+                                          <NuxDialogs />
+                                        </ProgressGuideProvider>
+                                      </SafeAreaProvider>
+                                    </MutedThreadsProvider>
+                                  </BackgroundNotificationPreferencesProvider>
+                                </UnreadNotifsProvider>
+                              </HiddenRepliesProvider>
+                            </SelectedFeedProvider>
+                          </LoggedOutViewProvider>
+                        </ModerationOptsProvider>
+                      </LabelDefsProvider>
+                    </MessagesProvider>
                   </QueryProvider>
-                </React.Fragment>
-                <ToastContainer />
-              </ActiveVideoProvider>
-            </VideoVolumeProvider>
-          </RootSiblingParent>
-        </ThemeProvider>
-      </Alf>
+                  <ToastContainer />
+                </ActiveVideoProvider>
+              </VideoVolumeProvider>
+            </RootSiblingParent>
+          </ThemeProvider>
+        </Alf>
+      </StatsigProvider>
     </KeyboardProvider>
   )
 }
 
 function App() {
   const [isReady, setReady] = useState(false)
+  const [loaded] = useFonts()
 
   React.useEffect(() => {
     initPersistedState().then(() => setReady(true))
   }, [])
 
-  if (!isReady) {
+  if (!isReady || !loaded) {
     return null
   }
 
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index d2e7ffc2e..9f75d305a 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -225,43 +225,43 @@ export const atoms = {
   },
   text_2xs: {
     fontSize: tokens.fontSize._2xs,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_xs: {
     fontSize: tokens.fontSize.xs,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_sm: {
     fontSize: tokens.fontSize.sm,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_md: {
     fontSize: tokens.fontSize.md,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_lg: {
     fontSize: tokens.fontSize.lg,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_xl: {
     fontSize: tokens.fontSize.xl,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_2xl: {
     fontSize: tokens.fontSize._2xl,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_3xl: {
     fontSize: tokens.fontSize._3xl,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_4xl: {
     fontSize: tokens.fontSize._4xl,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   text_5xl: {
     fontSize: tokens.fontSize._5xl,
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   leading_tight: {
     lineHeight: 1.15,
@@ -273,10 +273,7 @@ export const atoms = {
     lineHeight: 1.5,
   },
   tracking_normal: {
-    letterSpacing: 0,
-  },
-  tracking_wide: {
-    letterSpacing: 0.25,
+    letterSpacing: tokens.TRACKING,
   },
   font_normal: {
     fontWeight: tokens.fontWeight.normal,
diff --git a/src/alf/fonts.ts b/src/alf/fonts.ts
new file mode 100644
index 000000000..ce658fa05
--- /dev/null
+++ b/src/alf/fonts.ts
@@ -0,0 +1,111 @@
+import {useFonts as defaultUseFonts} from 'expo-font'
+
+import {isNative, isWeb} from '#/platform/detection'
+import {Device, device} from '#/storage'
+
+const FAMILIES = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif`
+
+const factor = 0.0625 // 1 - (15/16)
+const fontScaleMultipliers: Record<Device['fontScale'], number> = {
+  '-2': 1 - factor * 3,
+  '-1': 1 - factor * 2,
+  '0': 1 - factor * 1, // default
+  '1': 1,
+  '2': 1 + factor * 1,
+}
+
+export function computeFontScaleMultiplier(scale: Device['fontScale']) {
+  return fontScaleMultipliers[scale]
+}
+
+export function getFontScale() {
+  return device.get(['fontScale']) ?? '0'
+}
+
+export function setFontScale(fontScale: Device['fontScale']) {
+  device.set(['fontScale'], fontScale)
+}
+
+export function getFontFamily() {
+  return device.get(['fontFamily']) || 'theme'
+}
+
+export function setFontFamily(fontFamily: Device['fontFamily']) {
+  device.set(['fontFamily'], fontFamily)
+}
+
+/*
+ * Unused fonts are commented out, but the files are there if we need them.
+ */
+export function useFonts() {
+  /**
+   * For native, the `expo-font` config plugin embeds the fonts in the
+   * application binary. But `expo-font` isn't supported on web, so we fall
+   * back to async loading here.
+   */
+  if (isNative) return [true, null]
+  return defaultUseFonts({
+    // 'Inter-Thin': require('../../assets/fonts/inter/Inter-Thin.otf'),
+    // 'Inter-ThinItalic': require('../../assets/fonts/inter/Inter-ThinItalic.otf'),
+    // 'Inter-ExtraLight': require('../../assets/fonts/inter/Inter-ExtraLight.otf'),
+    // 'Inter-ExtraLightItalic': require('../../assets/fonts/inter/Inter-ExtraLightItalic.otf'),
+    // 'Inter-Light': require('../../assets/fonts/inter/Inter-Light.otf'),
+    // 'Inter-LightItalic': require('../../assets/fonts/inter/Inter-LightItalic.otf'),
+    'Inter-Regular': require('../../assets/fonts/inter/Inter-Regular.otf'),
+    'Inter-Italic': require('../../assets/fonts/inter/Inter-Italic.otf'),
+    'Inter-Medium': require('../../assets/fonts/inter/Inter-Medium.otf'),
+    'Inter-MediumItalic': require('../../assets/fonts/inter/Inter-MediumItalic.otf'),
+    'Inter-SemiBold': require('../../assets/fonts/inter/Inter-SemiBold.otf'),
+    'Inter-SemiBoldItalic': require('../../assets/fonts/inter/Inter-SemiBoldItalic.otf'),
+    'Inter-Bold': require('../../assets/fonts/inter/Inter-Bold.otf'),
+    'Inter-BoldItalic': require('../../assets/fonts/inter/Inter-BoldItalic.otf'),
+    'Inter-ExtraBold': require('../../assets/fonts/inter/Inter-ExtraBold.otf'),
+    'Inter-ExtraBoldItalic': require('../../assets/fonts/inter/Inter-ExtraBoldItalic.otf'),
+    'Inter-Black': require('../../assets/fonts/inter/Inter-Black.otf'),
+    'Inter-BlackItalic': require('../../assets/fonts/inter/Inter-BlackItalic.otf'),
+  })
+}
+
+/*
+ * Unused fonts are commented out, but the files are there if we need them.
+ */
+export function applyFonts(
+  style: Record<string, any>,
+  fontFamily: 'system' | 'theme',
+) {
+  if (fontFamily === 'theme') {
+    style.fontFamily =
+      {
+        // '100': 'Inter-Thin',
+        // '200': 'Inter-ExtraLight',
+        // '300': 'Inter-Light',
+        '100': 'Inter-Regular',
+        '200': 'Inter-Regular',
+        '300': 'Inter-Regular',
+        '400': 'Inter-Regular',
+        '500': 'Inter-Medium',
+        '600': 'Inter-SemiBold',
+        '700': 'Inter-Bold',
+        '800': 'Inter-ExtraBold',
+        '900': 'Inter-Black',
+      }[style.fontWeight as string] || 'Inter-Regular'
+
+    if (style.fontStyle === 'italic') {
+      if (style.fontFamily === 'Inter-Regular') {
+        style.fontFamily = 'Inter-Italic'
+      } else {
+        style.fontFamily += 'Italic'
+      }
+    }
+
+    // fallback families only supported on web
+    if (isWeb) {
+      style.fontFamily += `, ${FAMILIES}`
+    }
+  } else {
+    // fallback families only supported on web
+    if (isWeb) {
+      style.fontFamily = style.fontFamily || FAMILIES
+    }
+  }
+}
diff --git a/src/alf/index.tsx b/src/alf/index.tsx
index d699de6a5..f9d93d4ca 100644
--- a/src/alf/index.tsx
+++ b/src/alf/index.tsx
@@ -1,25 +1,47 @@
 import React from 'react'
 import {useMediaQuery} from 'react-responsive'
 
+import {
+  computeFontScaleMultiplier,
+  getFontFamily,
+  getFontScale,
+  setFontFamily as persistFontFamily,
+  setFontScale as persistFontScale,
+} from '#/alf/fonts'
 import {createThemes, defaultTheme} from '#/alf/themes'
 import {Theme, ThemeName} from '#/alf/types'
 import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
+import {Device} from '#/storage'
 
 export {atoms} from '#/alf/atoms'
+export * from '#/alf/fonts'
 export * as tokens from '#/alf/tokens'
 export * from '#/alf/types'
 export * from '#/alf/util/flatten'
 export * from '#/alf/util/platform'
 export * from '#/alf/util/themeSelector'
 
-/*
- * Context
- */
-export const Context = React.createContext<{
+export type Alf = {
   themeName: ThemeName
   theme: Theme
   themes: ReturnType<typeof createThemes>
-}>({
+  fonts: {
+    scale: Exclude<Device['fontScale'], undefined>
+    scaleMultiplier: number
+    family: Device['fontFamily']
+    setFontScale: (fontScale: Exclude<Device['fontScale'], undefined>) => void
+    setFontFamily: (fontFamily: Device['fontFamily']) => void
+  }
+  /**
+   * Feature flags or other gated options
+   */
+  flags: {}
+}
+
+/*
+ * Context
+ */
+export const Context = React.createContext<Alf>({
   themeName: 'light',
   theme: defaultTheme,
   themes: createThemes({
@@ -29,12 +51,48 @@ export const Context = React.createContext<{
       positive: GREEN_HUE,
     },
   }),
+  fonts: {
+    scale: getFontScale(),
+    scaleMultiplier: computeFontScaleMultiplier(getFontScale()),
+    family: getFontFamily(),
+    setFontScale: () => {},
+    setFontFamily: () => {},
+  },
+  flags: {},
 })
 
 export function ThemeProvider({
   children,
   theme: themeName,
 }: React.PropsWithChildren<{theme: ThemeName}>) {
+  const [fontScale, setFontScale] = React.useState<Alf['fonts']['scale']>(() =>
+    getFontScale(),
+  )
+  const [fontScaleMultiplier, setFontScaleMultiplier] = React.useState(() =>
+    computeFontScaleMultiplier(fontScale),
+  )
+  const setFontScaleAndPersist = React.useCallback<
+    Alf['fonts']['setFontScale']
+  >(
+    fontScale => {
+      setFontScale(fontScale)
+      persistFontScale(fontScale)
+      setFontScaleMultiplier(computeFontScaleMultiplier(fontScale))
+    },
+    [setFontScale],
+  )
+  const [fontFamily, setFontFamily] = React.useState<Alf['fonts']['family']>(
+    () => getFontFamily(),
+  )
+  const setFontFamilyAndPersist = React.useCallback<
+    Alf['fonts']['setFontFamily']
+  >(
+    fontFamily => {
+      setFontFamily(fontFamily)
+      persistFontFamily(fontFamily)
+    },
+    [setFontFamily],
+  )
   const themes = React.useMemo(() => {
     return createThemes({
       hues: {
@@ -44,28 +102,47 @@ export function ThemeProvider({
       },
     })
   }, [])
-  const theme = themes[themeName]
 
   return (
     <Context.Provider
-      value={React.useMemo(
+      value={React.useMemo<Alf>(
         () => ({
           themes,
           themeName: themeName,
-          theme: theme,
+          theme: themes[themeName],
+          fonts: {
+            scale: fontScale,
+            scaleMultiplier: fontScaleMultiplier,
+            family: fontFamily,
+            setFontScale: setFontScaleAndPersist,
+            setFontFamily: setFontFamilyAndPersist,
+          },
+          flags: {},
         }),
-        [theme, themeName, themes],
+        [
+          themeName,
+          themes,
+          fontScale,
+          setFontScaleAndPersist,
+          fontFamily,
+          setFontFamilyAndPersist,
+          fontScaleMultiplier,
+        ],
       )}>
       {children}
     </Context.Provider>
   )
 }
 
+export function useAlf() {
+  return React.useContext(Context)
+}
+
 export function useTheme(theme?: ThemeName) {
-  const ctx = React.useContext(Context)
+  const alf = useAlf()
   return React.useMemo(() => {
-    return theme ? ctx.themes[theme] : ctx.theme
-  }, [theme, ctx])
+    return theme ? alf.themes[theme] : alf.theme
+  }, [theme, alf])
 }
 
 export function useBreakpoints() {
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index 0208945ee..d43d2b67d 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -1,3 +1,7 @@
+import {Platform} from 'react-native'
+
+export const TRACKING = Platform.OS === 'android' ? 0.1 : 0
+
 export const color = {
   temp_purple: 'rgb(105 0 255)',
   temp_purple_dark: 'rgb(83 0 202)',
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index d65444e1f..704aa9d98 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -7,7 +7,6 @@ import {
   PressableProps,
   StyleProp,
   StyleSheet,
-  Text,
   TextProps,
   TextStyle,
   View,
@@ -17,7 +16,7 @@ import {LinearGradient} from 'expo-linear-gradient'
 
 import {android, atoms as a, flatten, select, tokens, useTheme} from '#/alf'
 import {Props as SVGIconProps} from '#/components/icons/common'
-import {normalizeTextStyles} from '#/components/Typography'
+import {Text} from '#/components/Typography'
 
 export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
 export type ButtonColor =
@@ -635,14 +634,7 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) {
   const textStyles = useSharedButtonTextStyles()
 
   return (
-    <Text
-      {...rest}
-      style={normalizeTextStyles([
-        a.font_bold,
-        a.text_center,
-        textStyles,
-        style,
-      ])}>
+    <Text {...rest} style={[a.font_bold, a.text_center, textStyles, style]}>
       {children}
     </Text>
   )
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index cdce3765f..d5d92048a 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -37,6 +37,7 @@ import {Portal} from '#/components/Portal'
 
 export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
 export * from '#/components/Dialog/types'
+export * from '#/components/Dialog/utils'
 // @ts-ignore
 export const Input = createInput(BottomSheetTextInput)
 
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index aff1842f7..bf20bd295 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -27,6 +27,7 @@ import {Portal} from '#/components/Portal'
 
 export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
 export * from '#/components/Dialog/types'
+export * from '#/components/Dialog/utils'
 export {Input} from '#/components/forms/TextField'
 
 const stopPropagation = (e: any) => e.stopPropagation()
diff --git a/src/components/Dialog/utils.ts b/src/components/Dialog/utils.ts
new file mode 100644
index 000000000..058d6e804
--- /dev/null
+++ b/src/components/Dialog/utils.ts
@@ -0,0 +1,18 @@
+import React from 'react'
+
+import {DialogControlProps} from '#/components/Dialog/types'
+
+export function useAutoOpen(control: DialogControlProps, showTimeout?: number) {
+  React.useEffect(() => {
+    if (showTimeout) {
+      const timeout = setTimeout(() => {
+        control.open()
+      }, showTimeout)
+      return () => {
+        clearTimeout(timeout)
+      }
+    } else {
+      control.open()
+    }
+  }, [control, showTimeout])
+}
diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx
index 31dd931c6..15f88468a 100644
--- a/src/components/Typography.tsx
+++ b/src/components/Typography.tsx
@@ -3,7 +3,7 @@ import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native'
 import {UITextView} from 'react-native-uitextview'
 
 import {isNative} from '#/platform/detection'
-import {atoms, flatten, useTheme, web} from '#/alf'
+import {Alf, applyFonts, atoms, flatten, useAlf, useTheme, web} from '#/alf'
 
 export type TextProps = RNTextProps & {
   /**
@@ -34,19 +34,30 @@ export function leading<
  * If the `lineHeight` value is > 2, we assume it's an absolute value and
  * returns it as-is.
  */
-export function normalizeTextStyles(styles: StyleProp<TextStyle>) {
+export function normalizeTextStyles(
+  styles: StyleProp<TextStyle>,
+  {
+    fontScale,
+    fontFamily,
+  }: {
+    fontScale: number
+    fontFamily: Alf['fonts']['family']
+  } & Pick<Alf, 'flags'>,
+) {
   const s = flatten(styles)
   // should always be defined on these components
-  const fontSize = s.fontSize || atoms.text_md.fontSize
+  s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
 
   if (s?.lineHeight) {
     if (s.lineHeight !== 0 && s.lineHeight <= 2) {
-      s.lineHeight = Math.round(fontSize * s.lineHeight)
+      s.lineHeight = Math.round(s.fontSize * s.lineHeight)
     }
   } else if (!isNative) {
     s.lineHeight = s.fontSize
   }
 
+  applyFonts(s, fontFamily)
+
   return s
 }
 
@@ -54,8 +65,13 @@ export function normalizeTextStyles(styles: StyleProp<TextStyle>) {
  * Our main text component. Use this most of the time.
  */
 export function Text({style, selectable, ...rest}: TextProps) {
+  const {fonts, flags} = useAlf()
   const t = useTheme()
-  const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)])
+  const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)], {
+    fontScale: fonts.scaleMultiplier,
+    fontFamily: fonts.family,
+    flags,
+  })
 
   return <UITextView selectable={selectable} uiTextView style={s} {...rest} />
 }
diff --git a/src/components/dialogs/nuxs/NeueTypography.tsx b/src/components/dialogs/nuxs/NeueTypography.tsx
new file mode 100644
index 000000000..f33cea8e7
--- /dev/null
+++ b/src/components/dialogs/nuxs/NeueTypography.tsx
@@ -0,0 +1,119 @@
+import React from 'react'
+import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {AppearanceToggleButtonGroup} from '#/screens/Settings/AppearanceSettings'
+import {atoms as a, useAlf, useTheme} from '#/alf'
+import * as Dialog from '#/components/Dialog'
+import {useNuxDialogContext} from '#/components/dialogs/nuxs'
+import {Divider} from '#/components/Divider'
+import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
+import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
+import {Text} from '#/components/Typography'
+
+export function NeueTypography() {
+  const t = useTheme()
+  const {_} = useLingui()
+  const nuxDialogs = useNuxDialogContext()
+  const control = Dialog.useDialogControl()
+  const {fonts} = useAlf()
+
+  Dialog.useAutoOpen(control, 3e3)
+
+  const onClose = React.useCallback(() => {
+    nuxDialogs.dismissActiveNux()
+  }, [nuxDialogs])
+
+  const onChangeFontFamily = React.useCallback(
+    (values: string[]) => {
+      const next = values[0] === 'system' ? 'system' : 'theme'
+      fonts.setFontFamily(next)
+    },
+    [fonts],
+  )
+
+  const onChangeFontScale = React.useCallback(
+    (values: string[]) => {
+      const next = values[0] || ('0' as any)
+      fonts.setFontScale(next)
+    },
+    [fonts],
+  )
+
+  return (
+    <Dialog.Outer control={control} onClose={onClose}>
+      <Dialog.Handle />
+
+      <Dialog.ScrollableInner label={_(msg`Introducing new font settings`)}>
+        <View style={[a.gap_xl]}>
+          <View style={[a.gap_md]}>
+            <Text style={[a.text_3xl, {fontWeight: '900'}]}>
+              <Trans>Introducing new font settings ✨</Trans>
+            </Text>
+            <Text style={[a.text_lg, a.leading_snug]}>
+              <Trans>
+                To the ensure the best possible experience, we're introducing a
+                new theme font, along with adjustable font sizing settings.
+              </Trans>
+            </Text>
+            <Text
+              style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
+              <Trans>
+                Defaults are shown below. You can edit these in your Appearance
+                Settings later.
+              </Trans>
+            </Text>
+          </View>
+
+          <Divider />
+
+          <View style={[a.gap_lg]}>
+            <AppearanceToggleButtonGroup
+              title={_(msg`Font`)}
+              description={_(
+                msg`For the best experience, we recommend using the theme font.`,
+              )}
+              icon={Aa}
+              items={[
+                {
+                  label: _(msg`System`),
+                  name: 'system',
+                },
+                {
+                  label: _(msg`Theme`),
+                  name: 'theme',
+                },
+              ]}
+              values={[fonts.family]}
+              onChange={onChangeFontFamily}
+            />
+
+            <AppearanceToggleButtonGroup
+              title={_(msg`Font size`)}
+              icon={TextSize}
+              items={[
+                {
+                  label: _(msg`Smaller`),
+                  name: '-1',
+                },
+                {
+                  label: _(msg`Default`),
+                  name: '0',
+                },
+                {
+                  label: _(msg`Larger`),
+                  name: '1',
+                },
+              ]}
+              values={[fonts.scale]}
+              onChange={onChangeFontScale}
+            />
+          </View>
+        </View>
+
+        <Dialog.Close />
+      </Dialog.ScrollableInner>
+    </Dialog.Outer>
+  )
+}
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index a38c87b68..b93831ad3 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -1,4 +1,5 @@
 import React from 'react'
+import {AppBskyActorDefs} from '@atproto/api'
 
 import {useGate} from '#/lib/statsig/statsig'
 import {logger} from '#/logger'
@@ -8,9 +9,16 @@ import {
   useRemoveNuxsMutation,
   useUpsertNuxMutation,
 } from '#/state/queries/nuxs'
-import {useSession} from '#/state/session'
+import {
+  usePreferencesQuery,
+  UsePreferencesQueryResponse,
+} from '#/state/queries/preferences'
+import {useProfileQuery} from '#/state/queries/profile'
+import {SessionAccount, useSession} from '#/state/session'
 import {useOnboardingState} from '#/state/shell'
+import {NeueTypography} from '#/components/dialogs/nuxs/NeueTypography'
 import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
+// NUXs
 import {TenMillion} from '#/components/dialogs/nuxs/TenMillion'
 import {IS_DEV} from '#/env'
 
@@ -21,11 +29,27 @@ type Context = {
 
 const queuedNuxs: {
   id: Nux
-  enabled?: (props: {gate: ReturnType<typeof useGate>}) => boolean
+  enabled?: (props: {
+    gate: ReturnType<typeof useGate>
+    currentAccount: SessionAccount
+    currentProfile: AppBskyActorDefs.ProfileViewDetailed
+    preferences: UsePreferencesQueryResponse
+  }) => boolean
 }[] = [
   {
     id: Nux.TenMillionDialog,
   },
+  {
+    id: Nux.NeueTypography,
+    enabled(props) {
+      if (props.currentProfile.createdAt) {
+        if (new Date(props.currentProfile.createdAt) < new Date('2024-09-25')) {
+          return true
+        }
+      }
+      return false
+    },
+  },
 ]
 
 const Context = React.createContext<Context>({
@@ -38,12 +62,31 @@ export function useNuxDialogContext() {
 }
 
 export function NuxDialogs() {
-  const {hasSession} = useSession()
-  const onboardingState = useOnboardingState()
-  return hasSession && !onboardingState.isActive ? <Inner /> : null
+  const {currentAccount} = useSession()
+  const {data: preferences} = usePreferencesQuery()
+  const {data: profile} = useProfileQuery({did: currentAccount?.did})
+  const onboardingActive = useOnboardingState().isActive
+
+  const isLoading =
+    !currentAccount || !preferences || !profile || onboardingActive
+  return !isLoading ? (
+    <Inner
+      currentAccount={currentAccount}
+      currentProfile={profile}
+      preferences={preferences}
+    />
+  ) : null
 }
 
-function Inner() {
+function Inner({
+  currentAccount,
+  currentProfile,
+  preferences,
+}: {
+  currentAccount: SessionAccount
+  currentProfile: AppBskyActorDefs.ProfileViewDetailed
+  preferences: UsePreferencesQueryResponse
+}) {
   const gate = useGate()
   const {nuxs} = useNuxs()
   const [snoozed, setSnoozed] = React.useState(() => {
@@ -80,10 +123,19 @@ function Inner() {
       const nux = nuxs.find(nux => nux.id === id)
 
       // check if completed first
-      if (nux && nux.completed) continue
+      if (nux && nux.completed) {
+        continue
+      }
 
       // then check gate (track exposure)
-      if (enabled && !enabled({gate})) continue
+      if (
+        enabled &&
+        !enabled({gate, currentAccount, currentProfile, preferences})
+      ) {
+        continue
+      }
+
+      logger.debug(`NUX dialogs: activating '${id}' NUX`)
 
       // we have a winner
       setActiveNux(id)
@@ -104,7 +156,16 @@ function Inner() {
 
       break
     }
-  }, [nuxs, snoozed, snoozeNuxDialog, upsertNux, gate])
+  }, [
+    nuxs,
+    snoozed,
+    snoozeNuxDialog,
+    upsertNux,
+    gate,
+    currentAccount,
+    currentProfile,
+    preferences,
+  ])
 
   const ctx = React.useMemo(() => {
     return {
@@ -116,6 +177,7 @@ function Inner() {
   return (
     <Context.Provider value={ctx}>
       {activeNux === Nux.TenMillionDialog && <TenMillion />}
+      {activeNux === Nux.NeueTypography && <NeueTypography />}
     </Context.Provider>
   )
 }
diff --git a/src/components/icons/TextSize.tsx b/src/components/icons/TextSize.tsx
new file mode 100644
index 000000000..73a6a085d
--- /dev/null
+++ b/src/components/icons/TextSize.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const TextSize_Stroke2_Corner0_Rounded = createSinglePathSVG({
+  path: 'M9 5a1 1 0 0 1 1-1h12a1 1 0 1 1 0 2h-5v14a1 1 0 1 1-2 0V6h-5a1 1 0 0 1-1-1Zm-3.073 7v8a1 1 0 1 0 2 0v-8H12a1 1 0 1 0 0-2H6.971a1.015 1.015 0 0 0-.089 0H2a1 1 0 1 0 0 2h3.927Z',
+})
diff --git a/src/components/icons/TitleCase.tsx b/src/components/icons/TitleCase.tsx
new file mode 100644
index 000000000..9d040c9e5
--- /dev/null
+++ b/src/components/icons/TitleCase.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const TitleCase_Stroke2_Corner0_Rounded = createSinglePathSVG({
+  path: 'M3.65 17.247c-.242.832-.632 1.178-1.325 1.178-.814 0-1.325-.476-1.325-1.23 0-.216.06-.51.173-.831L4.586 7.07c.364-1.014.979-1.482 1.966-1.482 1.022 0 1.629.45 2.001 1.473l3.43 9.303c.121.337.165.571.165.831 0 .72-.546 1.23-1.308 1.23-.736 0-1.126-.338-1.36-1.152l-.658-1.975H4.309l-.658 1.95ZM6.5 8.152l-1.62 5.12h3.335l-1.654-5.12H6.5Zm13.005 8.688c-.52.988-1.68 1.568-2.84 1.568-1.768 0-3.11-1.144-3.11-2.815 0-1.69 1.299-2.668 3.62-2.807l2.34-.138v-.615c0-.867-.607-1.369-1.56-1.369-.771 0-1.239.251-1.802.979-.277.312-.597.468-1.004.468-.615 0-1.057-.399-1.057-.97 0-.2.043-.382.13-.572.433-1.109 1.923-1.793 3.845-1.793 2.383 0 3.933 1.23 3.933 3.1v5.293c0 .84-.511 1.273-1.23 1.273-.684 0-1.16-.38-1.213-1.126v-.476h-.052Zm-3.43-1.386c0 .693.572 1.126 1.42 1.126 1.11 0 2.02-.719 2.02-1.723v-.676l-1.959.121c-.944.07-1.48.494-1.48 1.152Z',
+})
diff --git a/src/lib/styles.ts b/src/lib/styles.ts
index d0ea4cdc1..6a3d79611 100644
--- a/src/lib/styles.ts
+++ b/src/lib/styles.ts
@@ -79,13 +79,13 @@ export const s = StyleSheet.create({
 
   // font weights
   fw600: {fontWeight: '600'},
-  bold: {fontWeight: 'bold'},
+  bold: {fontWeight: '700'},
   fw500: {fontWeight: '500'},
   semiBold: {fontWeight: '500'},
   fw400: {fontWeight: '400'},
   normal: {fontWeight: '400'},
-  fw300: {fontWeight: '300'},
-  light: {fontWeight: '300'},
+  fw300: {fontWeight: '400'},
+  light: {fontWeight: '400'},
   fw200: {fontWeight: '200'},
 
   // text decoration
diff --git a/src/lib/themes.ts b/src/lib/themes.ts
index 9590f1659..d16f9f632 100644
--- a/src/lib/themes.ts
+++ b/src/lib/themes.ts
@@ -1,5 +1,6 @@
 import {Platform} from 'react-native'
 
+import {tokens} from '#/alf'
 import {darkPalette, dimPalette, lightPalette} from '#/alf/themes'
 import {colors} from './styles'
 import type {Theme} from './ThemeContext'
@@ -88,163 +89,163 @@ export const defaultTheme: Theme = {
   typography: {
     '2xl-thin': {
       fontSize: 18,
-      letterSpacing: 0.25,
-      fontWeight: '300',
+      letterSpacing: tokens.TRACKING,
+      fontWeight: '400',
     },
     '2xl': {
       fontSize: 18,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '400',
     },
     '2xl-medium': {
       fontSize: 18,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '500',
     },
     '2xl-bold': {
       fontSize: 18,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '700',
     },
     '2xl-heavy': {
       fontSize: 18,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '800',
     },
     'xl-thin': {
       fontSize: 17,
-      letterSpacing: 0.25,
-      fontWeight: '300',
+      letterSpacing: tokens.TRACKING,
+      fontWeight: '400',
     },
     xl: {
       fontSize: 17,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '400',
     },
     'xl-medium': {
       fontSize: 17,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '500',
     },
     'xl-bold': {
       fontSize: 17,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '700',
     },
     'xl-heavy': {
       fontSize: 17,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '800',
     },
     'lg-thin': {
       fontSize: 16,
-      letterSpacing: 0.25,
-      fontWeight: '300',
+      letterSpacing: tokens.TRACKING,
+      fontWeight: '400',
     },
     lg: {
       fontSize: 16,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '400',
     },
     'lg-medium': {
       fontSize: 16,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '500',
     },
     'lg-bold': {
       fontSize: 16,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '700',
     },
     'lg-heavy': {
       fontSize: 16,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '800',
     },
     'md-thin': {
       fontSize: 15,
-      letterSpacing: 0.25,
-      fontWeight: '300',
+      letterSpacing: tokens.TRACKING,
+      fontWeight: '400',
     },
     md: {
       fontSize: 15,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '400',
     },
     'md-medium': {
       fontSize: 15,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '500',
     },
     'md-bold': {
       fontSize: 15,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '700',
     },
     'md-heavy': {
       fontSize: 15,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '800',
     },
     'sm-thin': {
       fontSize: 14,
-      letterSpacing: 0.25,
-      fontWeight: '300',
+      letterSpacing: tokens.TRACKING,
+      fontWeight: '400',
     },
     sm: {
       fontSize: 14,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '400',
     },
     'sm-medium': {
       fontSize: 14,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '500',
     },
     'sm-bold': {
       fontSize: 14,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '700',
     },
     'sm-heavy': {
       fontSize: 14,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '800',
     },
     'xs-thin': {
       fontSize: 13,
-      letterSpacing: 0.25,
-      fontWeight: '300',
+      letterSpacing: tokens.TRACKING,
+      fontWeight: '400',
     },
     xs: {
       fontSize: 13,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '400',
     },
     'xs-medium': {
       fontSize: 13,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '500',
     },
     'xs-bold': {
       fontSize: 13,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '700',
     },
     'xs-heavy': {
       fontSize: 13,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '800',
     },
 
     'title-2xl': {
       fontSize: 34,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '500',
     },
     'title-xl': {
       fontSize: 28,
-      letterSpacing: 0.25,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '500',
     },
     'title-lg': {
@@ -254,32 +255,32 @@ export const defaultTheme: Theme = {
     title: {
       fontWeight: '500',
       fontSize: 20,
-      letterSpacing: 0.15,
+      letterSpacing: tokens.TRACKING,
     },
     'title-sm': {
       fontWeight: 'bold',
       fontSize: 17,
-      letterSpacing: 0.15,
+      letterSpacing: tokens.TRACKING,
     },
     'post-text': {
       fontSize: 16,
-      letterSpacing: 0.2,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '400',
     },
     'post-text-lg': {
       fontSize: 20,
-      letterSpacing: 0.2,
+      letterSpacing: tokens.TRACKING,
       fontWeight: '400',
     },
     'button-lg': {
       fontWeight: '500',
       fontSize: 18,
-      letterSpacing: 0.5,
+      letterSpacing: tokens.TRACKING,
     },
     button: {
       fontWeight: '500',
       fontSize: 14,
-      letterSpacing: 0.5,
+      letterSpacing: tokens.TRACKING,
     },
     mono: {
       fontSize: 14,
diff --git a/src/screens/Settings/AppearanceSettings.tsx b/src/screens/Settings/AppearanceSettings.tsx
index 00a04bbfb..d675fb38e 100644
--- a/src/screens/Settings/AppearanceSettings.tsx
+++ b/src/screens/Settings/AppearanceSettings.tsx
@@ -14,17 +14,21 @@ import {s} from '#/lib/styles'
 import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
 import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
 import {ScrollView} from '#/view/com/util/Views'
-import {atoms as a, native, useTheme} from '#/alf'
+import {atoms as a, native, useAlf, useTheme} from '#/alf'
 import * as ToggleButton from '#/components/forms/ToggleButton'
+import {Props as SVGIconProps} from '#/components/icons/common'
 import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon'
 import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone'
+import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
+import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
 import {Text} from '#/components/Typography'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'>
 export function AppearanceSettingsScreen({}: Props) {
-  const {_} = useLingui()
   const t = useTheme()
+  const {_} = useLingui()
   const {isTabletOrMobile} = useWebMediaQueries()
+  const {fonts} = useAlf()
 
   const {colorMode, darkTheme} = useThemePrefs()
   const {setColorMode, setDarkTheme} = useSetThemePrefs()
@@ -54,6 +58,22 @@ export function AppearanceSettingsScreen({}: Props) {
     [setDarkTheme, darkTheme],
   )
 
+  const onChangeFontFamily = useCallback(
+    (values: string[]) => {
+      const next = values[0] === 'system' ? 'system' : 'theme'
+      fonts.setFontFamily(next)
+    },
+    [fonts],
+  )
+
+  const onChangeFontScale = useCallback(
+    (values: string[]) => {
+      const next = values[0] || ('0' as any)
+      fonts.setFontScale(next)
+    },
+    [fonts],
+  )
+
   return (
     <LayoutAnimationConfig skipExiting skipEntering>
       <View testID="preferencesThreadsScreen" style={s.hContentRegion}>
@@ -71,65 +91,143 @@ export function AppearanceSettingsScreen({}: Props) {
             </View>
           </SimpleViewHeader>
 
-          <View style={[a.p_xl, a.gap_lg]}>
-            <View style={[a.flex_row, a.align_center, a.gap_md]}>
-              <PhoneIcon style={t.atoms.text} />
-              <Text style={a.text_md}>
-                <Trans>Mode</Trans>
-              </Text>
-            </View>
-            <ToggleButton.Group
-              label={_(msg`Dark mode`)}
-              values={[colorMode]}
-              onChange={onChangeAppearance}>
-              <ToggleButton.Button label={_(msg`System`)} name="system">
-                <ToggleButton.ButtonText>
-                  <Trans>System</Trans>
-                </ToggleButton.ButtonText>
-              </ToggleButton.Button>
-              <ToggleButton.Button label={_(msg`Light`)} name="light">
-                <ToggleButton.ButtonText>
-                  <Trans>Light</Trans>
-                </ToggleButton.ButtonText>
-              </ToggleButton.Button>
-              <ToggleButton.Button label={_(msg`Dark`)} name="dark">
-                <ToggleButton.ButtonText>
-                  <Trans>Dark</Trans>
-                </ToggleButton.ButtonText>
-              </ToggleButton.Button>
-            </ToggleButton.Group>
-            {colorMode !== 'light' && (
-              <Animated.View
-                entering={native(FadeInDown)}
-                exiting={native(FadeOutDown)}
-                style={[a.mt_md, a.gap_lg]}>
-                <View style={[a.flex_row, a.align_center, a.gap_md]}>
-                  <MoonIcon style={t.atoms.text} />
-                  <Text style={a.text_md}>
-                    <Trans>Dark theme</Trans>
-                  </Text>
-                </View>
+          <View style={[a.gap_3xl, a.pt_xl, a.px_xl]}>
+            <View style={[a.gap_lg]}>
+              <AppearanceToggleButtonGroup
+                title={_(msg`Color mode`)}
+                icon={PhoneIcon}
+                items={[
+                  {
+                    label: _(msg`System`),
+                    name: 'system',
+                  },
+                  {
+                    label: _(msg`Light`),
+                    name: 'light',
+                  },
+                  {
+                    label: _(msg`Dark`),
+                    name: 'dark',
+                  },
+                ]}
+                values={[colorMode]}
+                onChange={onChangeAppearance}
+              />
 
-                <ToggleButton.Group
-                  label={_(msg`Dark theme`)}
-                  values={[darkTheme ?? 'dim']}
-                  onChange={onChangeDarkTheme}>
-                  <ToggleButton.Button label={_(msg`Dim`)} name="dim">
-                    <ToggleButton.ButtonText>
-                      <Trans>Dim</Trans>
-                    </ToggleButton.ButtonText>
-                  </ToggleButton.Button>
-                  <ToggleButton.Button label={_(msg`Dark`)} name="dark">
-                    <ToggleButton.ButtonText>
-                      <Trans>Dark</Trans>
-                    </ToggleButton.ButtonText>
-                  </ToggleButton.Button>
-                </ToggleButton.Group>
-              </Animated.View>
-            )}
+              {colorMode !== 'light' && (
+                <Animated.View
+                  entering={native(FadeInDown)}
+                  exiting={native(FadeOutDown)}>
+                  <AppearanceToggleButtonGroup
+                    title={_(msg`Dark theme`)}
+                    icon={MoonIcon}
+                    items={[
+                      {
+                        label: _(msg`Dim`),
+                        name: 'dim',
+                      },
+                      {
+                        label: _(msg`Dark`),
+                        name: 'dark',
+                      },
+                    ]}
+                    values={[darkTheme ?? 'dim']}
+                    onChange={onChangeDarkTheme}
+                  />
+                </Animated.View>
+              )}
+
+              <AppearanceToggleButtonGroup
+                title={_(msg`Font`)}
+                description={_(
+                  msg`For the best experience, we recommend using the theme font.`,
+                )}
+                icon={Aa}
+                items={[
+                  {
+                    label: _(msg`System`),
+                    name: 'system',
+                  },
+                  {
+                    label: _(msg`Theme`),
+                    name: 'theme',
+                  },
+                ]}
+                values={[fonts.family]}
+                onChange={onChangeFontFamily}
+              />
+
+              <AppearanceToggleButtonGroup
+                title={_(msg`Font size`)}
+                icon={TextSize}
+                items={[
+                  {
+                    label: _(msg`Smaller`),
+                    name: '-1',
+                  },
+                  {
+                    label: _(msg`Default`),
+                    name: '0',
+                  },
+                  {
+                    label: _(msg`Larger`),
+                    name: '1',
+                  },
+                ]}
+                values={[fonts.scale]}
+                onChange={onChangeFontScale}
+              />
+            </View>
           </View>
         </ScrollView>
       </View>
     </LayoutAnimationConfig>
   )
 }
+
+export function AppearanceToggleButtonGroup({
+  title,
+  description,
+  icon: Icon,
+  items,
+  values,
+  onChange,
+}: {
+  title: string
+  description?: string
+  icon: React.ComponentType<SVGIconProps>
+  items: {
+    label: string
+    name: string
+  }[]
+  values: string[]
+  onChange: (values: string[]) => void
+}) {
+  const t = useTheme()
+  return (
+    <View style={[a.gap_md]}>
+      <View style={[a.gap_xs]}>
+        <View style={[a.flex_row, a.align_center, a.gap_md]}>
+          <Icon style={t.atoms.text} />
+          <Text style={[a.text_md, a.font_bold]}>{title}</Text>
+        </View>
+        {description && (
+          <Text
+            style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
+            {description}
+          </Text>
+        )}
+      </View>
+      <ToggleButton.Group label={title} values={values} onChange={onChange}>
+        {items.map(item => (
+          <ToggleButton.Button
+            key={item.name}
+            label={item.label}
+            name={item.name}>
+            <ToggleButton.ButtonText>{item.label}</ToggleButton.ButtonText>
+          </ToggleButton.Button>
+        ))}
+      </ToggleButton.Group>
+    </View>
+  )
+}
diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts
index 865967d37..63a807962 100644
--- a/src/state/queries/nuxs/definitions.ts
+++ b/src/state/queries/nuxs/definitions.ts
@@ -4,15 +4,22 @@ import {BaseNux} from '#/state/queries/nuxs/types'
 
 export enum Nux {
   TenMillionDialog = 'TenMillionDialog',
+  NeueTypography = 'NeueTypography',
 }
 
 export const nuxNames = new Set(Object.values(Nux))
 
-export type AppNux = BaseNux<{
-  id: Nux.TenMillionDialog
-  data: undefined
-}>
+export type AppNux =
+  | BaseNux<{
+      id: Nux.TenMillionDialog
+      data: undefined
+    }>
+  | BaseNux<{
+      id: Nux.NeueTypography
+      data: undefined
+    }>
 
 export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
   [Nux.TenMillionDialog]: undefined,
+  [Nux.NeueTypography]: undefined,
 }
diff --git a/src/storage/index.ts b/src/storage/index.ts
index 819ffab7e..4be08170d 100644
--- a/src/storage/index.ts
+++ b/src/storage/index.ts
@@ -2,6 +2,8 @@ import {MMKV} from 'react-native-mmkv'
 
 import {Device} from '#/storage/schema'
 
+export * from '#/storage/schema'
+
 /**
  * Generic storage class. DO NOT use this directly. Instead, use the exported
  * storage instances below.
diff --git a/src/storage/schema.ts b/src/storage/schema.ts
index bc41fd3ed..1a9656fed 100644
--- a/src/storage/schema.ts
+++ b/src/storage/schema.ts
@@ -2,5 +2,7 @@
  * Device data that's specific to the device and does not vary based account
  */
 export type Device = {
+  fontScale: '-2' | '-1' | '0' | '1' | '2'
+  fontFamily: 'system' | 'theme'
   lastNuxDialog: string | undefined
 }
diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx
index 2ea9586ee..52a45b0e2 100644
--- a/src/view/com/util/text/Text.tsx
+++ b/src/view/com/util/text/Text.tsx
@@ -1,10 +1,11 @@
 import React from 'react'
-import {Text as RNText, TextProps} from 'react-native'
+import {StyleSheet, Text as RNText, TextProps} from 'react-native'
 import {UITextView} from 'react-native-uitextview'
 
 import {lh, s} from 'lib/styles'
 import {TypographyVariant, useTheme} from 'lib/ThemeContext'
 import {isIOS, isWeb} from 'platform/detection'
+import {applyFonts, useAlf} from '#/alf'
 
 export type CustomTextProps = TextProps & {
   type?: TypographyVariant
@@ -32,11 +33,28 @@ export function Text({
   const theme = useTheme()
   const typography = theme.typography[type]
   const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined
+  const {fonts} = useAlf()
 
   if (selectable && isIOS) {
+    const flattened = StyleSheet.flatten([
+      s.black,
+      typography,
+      lineHeightStyle,
+      style,
+    ])
+
+    applyFonts(flattened, fonts.family)
+
+    // should always be defined on `typography`
+    // @ts-ignore
+    if (flattened.fontSize) {
+      // @ts-ignore
+      flattened.fontSize = flattened.fontSize * fonts.scaleMultiplier
+    }
+
     return (
       <UITextView
-        style={[s.black, typography, lineHeightStyle, style]}
+        style={flattened}
         selectable={selectable}
         uiTextView
         {...props}>
@@ -45,15 +63,26 @@ export function Text({
     )
   }
 
+  const flattened = StyleSheet.flatten([
+    s.black,
+    typography,
+    isWeb && fontFamilyStyle,
+    lineHeightStyle,
+    style,
+  ])
+
+  applyFonts(flattened, fonts.family)
+
+  // should always be defined on `typography`
+  // @ts-ignore
+  if (flattened.fontSize) {
+    // @ts-ignore
+    flattened.fontSize = flattened.fontSize * fonts.scaleMultiplier
+  }
+
   return (
     <RNText
-      style={[
-        s.black,
-        typography,
-        isWeb && fontFamilyStyle,
-        lineHeightStyle,
-        style,
-      ]}
+      style={flattened}
       // @ts-ignore web only -esb
       dataSet={Object.assign({tooltip: title}, dataSet || {})}
       selectable={selectable}
diff --git a/yarn.lock b/yarn.lock
index 0ab12b66c..98479ba44 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12219,6 +12219,13 @@ expo-file-system@^17.0.1, expo-file-system@~17.0.1:
   resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-17.0.1.tgz#b9f8af8c1c06ec71d96fd7a0d2567fa9e1c88f15"
   integrity sha512-dYpnZJqTGj6HCYJyXAgpFkQWsiCH3HY1ek2cFZVHFoEc5tLz9gmdEgTF6nFHurvmvfmXqxi7a5CXyVm0aFYJBw==
 
+expo-font@~12.0.10:
+  version "12.0.10"
+  resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-12.0.10.tgz#62deaf1f46159d7839f01305f44079268781b1db"
+  integrity sha512-Q1i2NuYri3jy32zdnBaHHCya1wH1yMAsI+3CCmj9zlQzlhsS9Bdwcj2W3c5eU5FvH2hsNQy4O+O1NnM6o/pDaQ==
+  dependencies:
+    fontfaceobserver "^2.1.0"
+
 expo-font@~12.0.5:
   version "12.0.5"
   resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-12.0.5.tgz#3451c2bd3f98859b127a6484d3474a292889b93f"