diff --git a/mobile/assets/fonts/Inter-LICENSE.txt b/mobile/assets/fonts/Inter-LICENSE.txt
new file mode 100644
index 0000000000..9b2ca37b3f
--- /dev/null
+++ b/mobile/assets/fonts/Inter-LICENSE.txt
@@ -0,0 +1,92 @@
+Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION AND CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/mobile/assets/fonts/InterVariable-Italic.ttf b/mobile/assets/fonts/InterVariable-Italic.ttf
new file mode 100644
index 0000000000..5b358cdd51
Binary files /dev/null and b/mobile/assets/fonts/InterVariable-Italic.ttf differ
diff --git a/mobile/assets/fonts/InterVariable.ttf b/mobile/assets/fonts/InterVariable.ttf
new file mode 100644
index 0000000000..4ab79e0102
Binary files /dev/null and b/mobile/assets/fonts/InterVariable.ttf differ
diff --git a/mobile/lib/app.dart b/mobile/lib/app.dart
index c9e1aeaa1d..3bf5626e3c 100644
--- a/mobile/lib/app.dart
+++ b/mobile/lib/app.dart
@@ -27,8 +27,8 @@ class App extends HookConsumerWidget {
     final resolved = resolveSchemes(schemeName);
     final lightScheme = applyAccent(resolved.light, accentIndex);
     final darkScheme = applyAccent(resolved.dark, accentIndex);
-    // When a named scheme is selected it forces light or dark mode;
-    // otherwise respect the user's ThemeMode preference.
+    // Default and named schemes can force light or dark mode; otherwise
+    // respect the user's ThemeMode preference.
     final effectiveMode = resolved.forcedMode ?? themeMode;
 
     // Eagerly initialize websocket session and lifecycle observer when
diff --git a/mobile/lib/features/activity/activity_page.dart b/mobile/lib/features/activity/activity_page.dart
index b7514f9cbb..9e72340687 100644
--- a/mobile/lib/features/activity/activity_page.dart
+++ b/mobile/lib/features/activity/activity_page.dart
@@ -162,7 +162,7 @@ class _FeedItemTile extends ConsumerWidget {
       onTap: item.channelId != null ? onTap : null,
       child: Padding(
         padding: const EdgeInsets.symmetric(
-          horizontal: Grid.xs,
+          horizontal: Grid.gutter,
           vertical: Grid.twelve,
         ),
         child: Column(
diff --git a/mobile/lib/features/channels/agent_activity/agent_activity_sheet.dart b/mobile/lib/features/channels/agent_activity/agent_activity_sheet.dart
index f221d84dad..c0c7640617 100644
--- a/mobile/lib/features/channels/agent_activity/agent_activity_sheet.dart
+++ b/mobile/lib/features/channels/agent_activity/agent_activity_sheet.dart
@@ -79,7 +79,7 @@ class AgentActivitySheet extends HookConsumerWidget {
         return Column(
           children: [
             Padding(
-              padding: const EdgeInsets.symmetric(horizontal: Grid.xs),
+              padding: const EdgeInsets.symmetric(horizontal: Grid.gutter),
               child: Column(
                 crossAxisAlignment: CrossAxisAlignment.start,
                 children: [
@@ -128,9 +128,9 @@ class AgentActivitySheet extends HookConsumerWidget {
                   : ListView.builder(
                       controller: sheetScrollController,
                       padding: EdgeInsets.fromLTRB(
-                        Grid.xs,
+                        Grid.gutter,
                         Grid.xxs,
-                        Grid.xs,
+                        Grid.gutter,
                         bottomPadding,
                       ),
                       itemCount: transcript.length,
diff --git a/mobile/lib/features/channels/channel_detail_page.dart b/mobile/lib/features/channels/channel_detail_page.dart
index d6e4f3e312..62af2ac3ff 100644
--- a/mobile/lib/features/channels/channel_detail_page.dart
+++ b/mobile/lib/features/channels/channel_detail_page.dart
@@ -445,8 +445,8 @@ class _MessageList extends HookConsumerWidget {
           controller: scrollController,
           reverse: true,
           padding: EdgeInsets.only(
-            left: Grid.xs,
-            right: Grid.xs,
+            left: Grid.gutter,
+            right: Grid.gutter,
             top: frostedAppBarHeight(context),
             bottom: Grid.xxs,
           ),
@@ -540,7 +540,7 @@ class _MessageList extends HookConsumerWidget {
                   backgroundColor: context.colors.primaryContainer,
                   foregroundColor: context.colors.onPrimaryContainer,
                   padding: const EdgeInsets.symmetric(
-                    horizontal: Grid.xs,
+                    horizontal: Grid.gutter,
                     vertical: Grid.xxs,
                   ),
                 ),
@@ -969,8 +969,8 @@ class _ReadOnlyNotice extends StatelessWidget {
     return Container(
       width: double.infinity,
       padding: EdgeInsets.only(
-        left: Grid.xs,
-        right: Grid.xs,
+        left: Grid.gutter,
+        right: Grid.gutter,
         top: Grid.xxs,
         bottom: MediaQuery.viewPaddingOf(context).bottom + Grid.xxs,
       ),
@@ -1028,7 +1028,7 @@ class _DetailConnectionBanner extends StatelessWidget {
     return Container(
       width: double.infinity,
       padding: const EdgeInsets.symmetric(
-        horizontal: Grid.xs,
+        horizontal: Grid.gutter,
         vertical: Grid.quarter + 2,
       ),
       color: context.colors.surfaceContainerHighest,
@@ -1082,7 +1082,7 @@ class _TypingIndicator extends ConsumerWidget {
     return Container(
       width: double.infinity,
       padding: const EdgeInsets.symmetric(
-        horizontal: Grid.xs,
+        horizontal: Grid.gutter,
         vertical: Grid.quarter + 2,
       ),
       child: Row(
diff --git a/mobile/lib/features/channels/compose_bar.dart b/mobile/lib/features/channels/compose_bar.dart
index 57755fa119..d52ddf2d0f 100644
--- a/mobile/lib/features/channels/compose_bar.dart
+++ b/mobile/lib/features/channels/compose_bar.dart
@@ -366,8 +366,8 @@ class ComposeBar extends HookConsumerWidget {
                 : null,
           ),
           padding: EdgeInsets.only(
-            left: Grid.xs,
-            right: Grid.xs,
+            left: Grid.gutter,
+            right: Grid.gutter,
             top: Grid.xs,
             bottom: MediaQuery.viewPaddingOf(context).bottom + Grid.twelve,
           ),
diff --git a/mobile/lib/features/channels/emoji_picker.dart b/mobile/lib/features/channels/emoji_picker.dart
index a76e022a75..3cfa0ca09d 100644
--- a/mobile/lib/features/channels/emoji_picker.dart
+++ b/mobile/lib/features/channels/emoji_picker.dart
@@ -264,7 +264,9 @@ class _EmojiPickerSheetState extends ConsumerState<EmojiPickerSheet> {
                     onSelect: widget.onSelect,
                   )
                 : GridView.builder(
-                    padding: const EdgeInsets.symmetric(horizontal: Grid.xs),
+                    padding: const EdgeInsets.symmetric(
+                      horizontal: Grid.gutter,
+                    ),
                     gridDelegate:
                         const SliverGridDelegateWithFixedCrossAxisCount(
                           crossAxisCount: 8,
@@ -298,7 +300,7 @@ class _CustomEmojiGrid extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return GridView.builder(
-      padding: const EdgeInsets.symmetric(horizontal: Grid.xs),
+      padding: const EdgeInsets.symmetric(horizontal: Grid.gutter),
       gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
         crossAxisCount: 6,
         mainAxisSpacing: Grid.half,
diff --git a/mobile/lib/features/channels/manage_channel_sheet.dart b/mobile/lib/features/channels/manage_channel_sheet.dart
index 876ec55dd7..d1f0c231ae 100644
--- a/mobile/lib/features/channels/manage_channel_sheet.dart
+++ b/mobile/lib/features/channels/manage_channel_sheet.dart
@@ -98,9 +98,9 @@ class ManageChannelSheet extends HookConsumerWidget {
 
     return Padding(
       padding: EdgeInsets.fromLTRB(
-        Grid.xs,
+        Grid.gutter,
         0,
-        Grid.xs,
+        Grid.gutter,
         MediaQuery.viewInsetsOf(context).bottom + Grid.xs,
       ),
       child: SafeArea(
diff --git a/mobile/lib/features/channels/members_sheet.dart b/mobile/lib/features/channels/members_sheet.dart
index 9899a32f75..abb488ced4 100644
--- a/mobile/lib/features/channels/members_sheet.dart
+++ b/mobile/lib/features/channels/members_sheet.dart
@@ -80,9 +80,9 @@ class MembersSheet extends HookConsumerWidget {
 
     return Padding(
       padding: EdgeInsets.fromLTRB(
-        Grid.xs,
+        Grid.gutter,
         0,
-        Grid.xs,
+        Grid.gutter,
         MediaQuery.viewInsetsOf(context).bottom,
       ),
       child: SingleChildScrollView(
@@ -307,7 +307,7 @@ class _MemberTile extends ConsumerWidget {
           crossAxisAlignment: CrossAxisAlignment.start,
           children: [
             Padding(
-              padding: const EdgeInsets.symmetric(horizontal: Grid.xs),
+              padding: const EdgeInsets.symmetric(horizontal: Grid.gutter),
               child: Text(label, style: context.textTheme.titleSmall),
             ),
             const SizedBox(height: Grid.xxs),
@@ -406,7 +406,7 @@ class _RoleSelector extends StatelessWidget {
     final hasKnownRole = _changeableRoles.contains(selectedRole);
 
     return Padding(
-      padding: const EdgeInsets.symmetric(horizontal: Grid.xs),
+      padding: const EdgeInsets.symmetric(horizontal: Grid.gutter),
       child: Column(
         crossAxisAlignment: CrossAxisAlignment.start,
         children: [
diff --git a/mobile/lib/features/channels/message_actions.dart b/mobile/lib/features/channels/message_actions.dart
index cce5f92f6d..6d21e9f096 100644
--- a/mobile/lib/features/channels/message_actions.dart
+++ b/mobile/lib/features/channels/message_actions.dart
@@ -36,7 +36,12 @@ void showMessageActions({
     showDragHandle: true,
     builder: (sheetContext) => SafeArea(
       child: Padding(
-        padding: const EdgeInsets.fromLTRB(Grid.xs, 0, Grid.xs, Grid.xs),
+        padding: const EdgeInsets.fromLTRB(
+          Grid.gutter,
+          0,
+          Grid.gutter,
+          Grid.xs,
+        ),
         child: Column(
           mainAxisSize: MainAxisSize.min,
           children: [
@@ -186,9 +191,9 @@ void _showEditSheet({
     showDragHandle: true,
     builder: (sheetContext) => Padding(
       padding: EdgeInsets.fromLTRB(
-        Grid.xs,
+        Grid.gutter,
         0,
-        Grid.xs,
+        Grid.gutter,
         MediaQuery.viewInsetsOf(sheetContext).bottom,
       ),
       child: Column(
diff --git a/mobile/lib/features/channels/reaction_row.dart b/mobile/lib/features/channels/reaction_row.dart
index e0f8fec723..70ed70cc92 100644
--- a/mobile/lib/features/channels/reaction_row.dart
+++ b/mobile/lib/features/channels/reaction_row.dart
@@ -163,7 +163,7 @@ class _ReactionDetailSheet extends HookConsumerWidget {
           // Emoji filter chips (if multiple reaction types).
           if (reactions.length > 1)
             Padding(
-              padding: const EdgeInsets.symmetric(horizontal: Grid.xs),
+              padding: const EdgeInsets.symmetric(horizontal: Grid.gutter),
               child: SingleChildScrollView(
                 scrollDirection: Axis.horizontal,
                 child: Row(
@@ -194,7 +194,7 @@ class _ReactionDetailSheet extends HookConsumerWidget {
           // Header: emoji + shortcode.
           Padding(
             padding: const EdgeInsets.symmetric(
-              horizontal: Grid.xs,
+              horizontal: Grid.gutter,
               vertical: Grid.half,
             ),
             child: Row(
diff --git a/mobile/lib/features/channels/thread_detail_page.dart b/mobile/lib/features/channels/thread_detail_page.dart
index 12255f439f..6df331fb73 100644
--- a/mobile/lib/features/channels/thread_detail_page.dart
+++ b/mobile/lib/features/channels/thread_detail_page.dart
@@ -117,8 +117,8 @@ class ThreadDetailPage extends HookConsumerWidget {
           Expanded(
             child: ListView.builder(
               padding: EdgeInsets.only(
-                left: Grid.xs,
-                right: Grid.xs,
+                left: Grid.gutter,
+                right: Grid.gutter,
                 top: frostedAppBarHeight(context),
                 bottom: Grid.xxs,
               ),
@@ -498,7 +498,7 @@ class _ThreadTypingIndicator extends ConsumerWidget {
     return Container(
       width: double.infinity,
       padding: const EdgeInsets.symmetric(
-        horizontal: Grid.xs,
+        horizontal: Grid.gutter,
         vertical: Grid.quarter + 2,
       ),
       child: Row(
diff --git a/mobile/lib/features/forum/forum_post_card.dart b/mobile/lib/features/forum/forum_post_card.dart
index 79e87d6ab3..689e7f31bd 100644
--- a/mobile/lib/features/forum/forum_post_card.dart
+++ b/mobile/lib/features/forum/forum_post_card.dart
@@ -182,7 +182,12 @@ class ForumPostCard extends ConsumerWidget {
       showDragHandle: true,
       builder: (sheetContext) => SafeArea(
         child: Padding(
-          padding: const EdgeInsets.fromLTRB(Grid.xs, 0, Grid.xs, Grid.xs),
+          padding: const EdgeInsets.fromLTRB(
+            Grid.gutter,
+            0,
+            Grid.gutter,
+            Grid.xs,
+          ),
           child: Column(
             mainAxisSize: MainAxisSize.min,
             children: [
diff --git a/mobile/lib/features/forum/forum_posts_view.dart b/mobile/lib/features/forum/forum_posts_view.dart
index 76dd50959b..4d7482bfdd 100644
--- a/mobile/lib/features/forum/forum_posts_view.dart
+++ b/mobile/lib/features/forum/forum_posts_view.dart
@@ -90,8 +90,8 @@ class ForumPostsView extends HookConsumerWidget {
                   child: ListView.separated(
                     padding: EdgeInsets.only(
                       top: frostedAppBarHeight(context),
-                      left: Grid.xs,
-                      right: Grid.xs,
+                      left: Grid.gutter,
+                      right: Grid.gutter,
                       bottom: Grid.xs,
                     ),
                     itemCount: posts.length,
diff --git a/mobile/lib/features/forum/forum_thread_page.dart b/mobile/lib/features/forum/forum_thread_page.dart
index 4b213b89f7..a990aeee34 100644
--- a/mobile/lib/features/forum/forum_thread_page.dart
+++ b/mobile/lib/features/forum/forum_thread_page.dart
@@ -110,7 +110,12 @@ class ForumThreadPage extends HookConsumerWidget {
       showDragHandle: true,
       builder: (sheetContext) => SafeArea(
         child: Padding(
-          padding: const EdgeInsets.fromLTRB(Grid.xs, 0, Grid.xs, Grid.xs),
+          padding: const EdgeInsets.fromLTRB(
+            Grid.gutter,
+            0,
+            Grid.gutter,
+            Grid.xs,
+          ),
           child: Column(
             mainAxisSize: MainAxisSize.min,
             children: [
@@ -228,7 +233,7 @@ class _ThreadContent extends HookConsumerWidget {
 
               Padding(
                 padding: const EdgeInsets.symmetric(
-                  horizontal: Grid.xs,
+                  horizontal: Grid.gutter,
                   vertical: Grid.xxs,
                 ),
                 child: Row(
@@ -392,7 +397,7 @@ class _ReplyRow extends ConsumerWidget {
 
     return Padding(
       padding: const EdgeInsets.symmetric(
-        horizontal: Grid.xs,
+        horizontal: Grid.gutter,
         vertical: Grid.xxs,
       ),
       child: Column(
@@ -470,7 +475,12 @@ class _ReplyRow extends ConsumerWidget {
       showDragHandle: true,
       builder: (sheetContext) => SafeArea(
         child: Padding(
-          padding: const EdgeInsets.fromLTRB(Grid.xs, 0, Grid.xs, Grid.xs),
+          padding: const EdgeInsets.fromLTRB(
+            Grid.gutter,
+            0,
+            Grid.gutter,
+            Grid.xs,
+          ),
           child: Column(
             mainAxisSize: MainAxisSize.min,
             children: [
diff --git a/mobile/lib/features/profile/set_status_sheet.dart b/mobile/lib/features/profile/set_status_sheet.dart
index d6d12bec4e..2249a0d6f8 100644
--- a/mobile/lib/features/profile/set_status_sheet.dart
+++ b/mobile/lib/features/profile/set_status_sheet.dart
@@ -88,9 +88,9 @@ class _SetStatusSheet extends HookConsumerWidget {
 
     return Padding(
       padding: EdgeInsets.fromLTRB(
-        Grid.xs,
+        Grid.gutter,
         0,
-        Grid.xs,
+        Grid.gutter,
         MediaQuery.viewInsetsOf(context).bottom,
       ),
       child: Column(
diff --git a/mobile/lib/features/pulse/compose_note_page.dart b/mobile/lib/features/pulse/compose_note_page.dart
index 8666272f9b..f6bfe42cdd 100644
--- a/mobile/lib/features/pulse/compose_note_page.dart
+++ b/mobile/lib/features/pulse/compose_note_page.dart
@@ -70,7 +70,7 @@ class ComposeNotePage extends HookConsumerWidget {
             child: FilledButton(
               onPressed: hasText && !isSending.value ? submit : null,
               style: FilledButton.styleFrom(
-                padding: const EdgeInsets.symmetric(horizontal: Grid.xs),
+                padding: const EdgeInsets.symmetric(horizontal: Grid.gutter),
                 minimumSize: const Size(0, 36),
                 shape: const StadiumBorder(),
               ),
@@ -166,7 +166,7 @@ class _ReplyContext extends ConsumerWidget {
     final displayName = profile?.label ?? _shortPubkey(pubkey);
 
     return Padding(
-      padding: const EdgeInsets.fromLTRB(Grid.xs, Grid.xs, Grid.xs, 0),
+      padding: const EdgeInsets.fromLTRB(Grid.gutter, Grid.xs, Grid.gutter, 0),
       child: Column(
         crossAxisAlignment: CrossAxisAlignment.start,
         children: [
diff --git a/mobile/lib/features/pulse/note_card.dart b/mobile/lib/features/pulse/note_card.dart
index 0ee0930708..43a93d2214 100644
--- a/mobile/lib/features/pulse/note_card.dart
+++ b/mobile/lib/features/pulse/note_card.dart
@@ -268,7 +268,7 @@ class _ActionButton extends StatelessWidget {
       borderRadius: BorderRadius.circular(Radii.md),
       child: Padding(
         padding: const EdgeInsets.symmetric(
-          horizontal: Grid.xs,
+          horizontal: Grid.gutter,
           vertical: Grid.half,
         ),
         child: Row(
diff --git a/mobile/lib/features/pulse/pulse_page.dart b/mobile/lib/features/pulse/pulse_page.dart
index 87ffe866d1..860002d60a 100644
--- a/mobile/lib/features/pulse/pulse_page.dart
+++ b/mobile/lib/features/pulse/pulse_page.dart
@@ -173,9 +173,9 @@ class _PulseBody extends ConsumerWidget {
           return ListView.separated(
             physics: const AlwaysScrollableScrollPhysics(),
             padding: const EdgeInsets.fromLTRB(
-              Grid.xs,
+              Grid.gutter,
               Grid.xxs,
-              Grid.xs,
+              Grid.gutter,
               Grid.xs,
             ),
             itemCount: groups.length,
@@ -190,9 +190,9 @@ class _PulseBody extends ConsumerWidget {
         return ListView.separated(
           physics: const AlwaysScrollableScrollPhysics(),
           padding: const EdgeInsets.fromLTRB(
-            Grid.xs,
+            Grid.gutter,
             Grid.xxs,
-            Grid.xs,
+            Grid.gutter,
             Grid.xs,
           ),
           itemCount: notes.length,
@@ -289,7 +289,12 @@ class _TimelineSkeleton extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return ListView.separated(
-      padding: const EdgeInsets.fromLTRB(Grid.xs, Grid.xxs, Grid.xs, Grid.xs),
+      padding: const EdgeInsets.fromLTRB(
+        Grid.gutter,
+        Grid.xxs,
+        Grid.gutter,
+        Grid.xs,
+      ),
       itemCount: 5,
       separatorBuilder: (_, _) => const _NoteDivider(),
       itemBuilder: (_, _) => Padding(
diff --git a/mobile/lib/features/search/search_page.dart b/mobile/lib/features/search/search_page.dart
index 6fa55c5be0..5b99ad7e99 100644
--- a/mobile/lib/features/search/search_page.dart
+++ b/mobile/lib/features/search/search_page.dart
@@ -412,7 +412,12 @@ class _SectionLabel extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Padding(
-      padding: const EdgeInsets.fromLTRB(Grid.xs, Grid.xs, Grid.xs, Grid.half),
+      padding: const EdgeInsets.fromLTRB(
+        Grid.gutter,
+        Grid.xs,
+        Grid.gutter,
+        Grid.half,
+      ),
       child: Text(
         label.toUpperCase(),
         style: context.textTheme.labelSmall?.copyWith(
diff --git a/mobile/lib/features/settings/settings_page.dart b/mobile/lib/features/settings/settings_page.dart
index 52c40a73e4..b450221389 100644
--- a/mobile/lib/features/settings/settings_page.dart
+++ b/mobile/lib/features/settings/settings_page.dart
@@ -26,6 +26,7 @@ class SettingsPage extends HookConsumerWidget {
     final config = ref.watch(relayConfigProvider);
     final selectedAccent = ref.watch(accentProvider);
     final selectedScheme = ref.watch(schemeProvider);
+    final colorScheme = Theme.of(context).colorScheme;
     final packageInfoFuture = useMemoized(() => PackageInfo.fromPlatform());
     final packageInfo = useFuture(packageInfoFuture);
 
@@ -53,7 +54,7 @@ class SettingsPage extends HookConsumerWidget {
                       icon: LucideIcons.palette,
                       title: 'Color Scheme',
                       subtitle: selectedScheme == null
-                          ? 'Default (Catppuccin)'
+                          ? 'Default ($defaultSchemeDisplayName)'
                           : findTheme(selectedScheme)?.displayName ??
                                 selectedScheme,
                       trailing: Icon(
@@ -69,9 +70,9 @@ class SettingsPage extends HookConsumerWidget {
                     ),
                     Padding(
                       padding: const EdgeInsets.fromLTRB(
-                        Grid.xs,
+                        Grid.gutter,
                         Grid.xxs,
-                        Grid.xs,
+                        Grid.gutter,
                         Grid.twelve,
                       ),
                       child: Column(
@@ -88,26 +89,9 @@ class SettingsPage extends HookConsumerWidget {
                             spacing: Grid.xxs,
                             runSpacing: Grid.xxs,
                             children: [
-                              // Default (Mauve) swatch
-                              _AccentSwatch(
-                                color:
-                                    context.colors.brightness ==
-                                        Brightness.light
-                                    ? const Color(0xFF8839EF)
-                                    : const Color(0xFFA875F5),
-                                label: 'Mauve',
-                                selected: selectedAccent == defaultAccentIndex,
-                                onTap: () => ref
-                                    .read(accentProvider.notifier)
-                                    .setAccent(defaultAccentIndex),
-                              ),
                               for (var i = 0; i < accentColors.length; i++)
                                 _AccentSwatch(
-                                  color:
-                                      context.colors.brightness ==
-                                          Brightness.light
-                                      ? accentColors[i].light
-                                      : accentColors[i].dark,
+                                  color: accentColorForScheme(colorScheme, i),
                                   label: accentColors[i].name,
                                   selected: selectedAccent == i,
                                   onTap: () => ref
diff --git a/mobile/lib/features/settings/theme_picker_page.dart b/mobile/lib/features/settings/theme_picker_page.dart
index 3a001976aa..f9bbc8ea1d 100644
--- a/mobile/lib/features/settings/theme_picker_page.dart
+++ b/mobile/lib/features/settings/theme_picker_page.dart
@@ -25,6 +25,7 @@ class ThemePickerPage extends HookConsumerWidget {
       ..sort((a, b) => a.displayName.compareTo(b.displayName));
 
     final query = searchQuery.value.toLowerCase();
+    final defaultLabel = 'Default ($defaultSchemeDisplayName)';
     final filtered = query.isEmpty
         ? sorted
         : sorted
@@ -33,19 +34,14 @@ class ThemePickerPage extends HookConsumerWidget {
     final showDefault =
         query.isEmpty ||
         'default'.contains(query) ||
-        'catppuccin'.contains(query);
+        defaultLabel.toLowerCase().contains(query);
 
-    // Default entry colors based on current brightness
-    final isLight = context.colors.brightness == Brightness.light;
-    final defaultBg = isLight
-        ? const Color(0xFFEFF1F5)
-        : const Color(0xFF24273A);
-    final defaultFg = isLight
-        ? const Color(0xFF4C4F69)
-        : const Color(0xFFCAD3F5);
-    final defaultComment = isLight
-        ? const Color(0xFF7C7F93)
-        : const Color(0xFF939AB7);
+    // Default entry colors.
+    final defaultTheme = findTheme(defaultSchemeName);
+    final defaultBg = defaultTheme?.bg ?? lightColorScheme.surface;
+    final defaultFg = defaultTheme?.fg ?? lightColorScheme.onSurface;
+    final defaultComment =
+        defaultTheme?.comment ?? lightColorScheme.onSurfaceVariant;
 
     // Auto-scroll to the selected theme on first build (no search active)
     useEffect(() {
@@ -74,7 +70,7 @@ class ThemePickerPage extends HookConsumerWidget {
           // Always-visible search bar
           Padding(
             padding: const EdgeInsets.symmetric(
-              horizontal: Grid.xs,
+              horizontal: Grid.gutter,
               vertical: Grid.xxs,
             ),
             child: Container(
@@ -139,7 +135,7 @@ class ThemePickerPage extends HookConsumerWidget {
                     bg: defaultBg,
                     fg: defaultFg,
                     comment: defaultComment,
-                    label: 'Default (Catppuccin)',
+                    label: defaultLabel,
                     selected: selectedScheme == null,
                     onTap: () =>
                         ref.read(schemeProvider.notifier).setScheme(null),
diff --git a/mobile/lib/shared/theme/accent_colors.dart b/mobile/lib/shared/theme/accent_colors.dart
index 65f16fdd34..d54925b71a 100644
--- a/mobile/lib/shared/theme/accent_colors.dart
+++ b/mobile/lib/shared/theme/accent_colors.dart
@@ -5,11 +5,13 @@ class AccentColor {
   final String name;
   final Color light;
   final Color dark;
+  final bool useThemeForegroundInDark;
 
   const AccentColor({
     required this.name,
     required this.light,
     required this.dark,
+    this.useThemeForegroundInDark = false,
   });
 }
 
@@ -34,7 +36,30 @@ const accentColors = [
     light: Color(0xFF6366F1),
     dark: Color(0xFF818CF8),
   ),
+  AccentColor(
+    name: 'Black',
+    light: Color(0xFF000000),
+    dark: Color(0xFFFFFFFF),
+    useThemeForegroundInDark: true,
+  ),
 ];
 
-/// Default: Catppuccin Mauve (the current primary).
-const defaultAccentIndex = -1; // -1 means "use theme default (Mauve)"
+Color accentColorForScheme(ColorScheme scheme, int accentIndex) {
+  if (accentIndex < 0 || accentIndex >= accentColors.length) {
+    return scheme.primary;
+  }
+  final accent = accentColors[accentIndex];
+  if (scheme.brightness == Brightness.dark && accent.useThemeForegroundInDark) {
+    return scheme.onSurface;
+  }
+  return scheme.brightness == Brightness.light ? accent.light : accent.dark;
+}
+
+/// New default: Black.
+///
+/// Keep this at the end of [accentColors] so existing saved accent indexes keep
+/// pointing at the same colors.
+const defaultAccentIndex = 8;
+
+/// Legacy default: Catppuccin Mauve/the base theme primary.
+const legacyDefaultAccentIndex = -1;
diff --git a/mobile/lib/shared/theme/app_theme.dart b/mobile/lib/shared/theme/app_theme.dart
index 3175e003a8..54bf9c50e3 100644
--- a/mobile/lib/shared/theme/app_theme.dart
+++ b/mobile/lib/shared/theme/app_theme.dart
@@ -63,8 +63,9 @@ class AppTheme {
     return ThemeData(
       useMaterial3: true,
       colorScheme: scheme,
+      scaffoldBackgroundColor: scheme.surface,
       extensions: [appColors],
-      fontFamily: 'Geist',
+      fontFamily: 'Inter',
       textTheme: textTheme,
       appBarTheme: AppBarTheme(
         backgroundColor: Colors.transparent,
diff --git a/mobile/lib/shared/theme/color_scheme.dart b/mobile/lib/shared/theme/color_scheme.dart
index 885446cc03..37381a78f0 100644
--- a/mobile/lib/shared/theme/color_scheme.dart
+++ b/mobile/lib/shared/theme/color_scheme.dart
@@ -21,7 +21,7 @@ const lightColorScheme = ColorScheme(
   onError: Color(0xFFFFFFFF),
   errorContainer: Color(0xFFFDD8E0),
   onErrorContainer: Color(0xFFD20F39),
-  surface: Color(0xFFEFF1F5), // Latte Base
+  surface: Color(0xFFFFFFFF),
   onSurface: Color(0xFF4C4F69), // Latte Text
   onSurfaceVariant: Color(0xFF6C6F85), // Latte Subtext0
   outline: Color(0xFFBCC0CC), // Latte Surface2
@@ -83,17 +83,11 @@ Color contrastForeground(Color bg) {
 }
 
 /// Returns a [ColorScheme] with the given accent applied as primary.
-/// If [accentIndex] is [defaultAccentIndex], returns the base scheme unchanged.
 ColorScheme applyAccent(ColorScheme base, int accentIndex) {
-  if (accentIndex == defaultAccentIndex ||
-      accentIndex < 0 ||
-      accentIndex >= accentColors.length) {
+  if (accentIndex < 0 || accentIndex >= accentColors.length) {
     return base;
   }
-  final accent = accentColors[accentIndex];
-  final color = base.brightness == Brightness.light
-      ? accent.light
-      : accent.dark;
+  final color = accentColorForScheme(base, accentIndex);
   final onColor = contrastForeground(color);
 
   return base.copyWith(primary: color, onPrimary: onColor, surfaceTint: color);
diff --git a/mobile/lib/shared/theme/grid.dart b/mobile/lib/shared/theme/grid.dart
index 148202d01d..b8a8a52251 100644
--- a/mobile/lib/shared/theme/grid.dart
+++ b/mobile/lib/shared/theme/grid.dart
@@ -14,6 +14,9 @@ class Grid {
   /// Extra small spacing - 16 pixels
   static const double xs = 16.0;
 
+  /// Standard app content gutter - 20 pixels
+  static const double gutter = 20.0;
+
   /// Small spacing - 24 pixels
   static const double sm = 24.0;
 
diff --git a/mobile/lib/shared/theme/text_theme.dart b/mobile/lib/shared/theme/text_theme.dart
index 99a9bedc06..2fb780f59e 100644
--- a/mobile/lib/shared/theme/text_theme.dart
+++ b/mobile/lib/shared/theme/text_theme.dart
@@ -1,111 +1,111 @@
 import 'package:flutter/material.dart';
 
-const _fontFamily = 'Geist';
-const _fontFeatures = [FontFeature('ss03')];
+const _fontFamily = 'Inter';
 
 const textTheme = TextTheme(
   displayLarge: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 52,
     fontWeight: FontWeight.w400,
-    height: 1.15,
+    height: 1.23,
+    letterSpacing: 0,
   ),
   displayMedium: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 44,
     fontWeight: FontWeight.w400,
-    height: 1.15,
+    height: 1.18,
+    letterSpacing: 0,
   ),
   displaySmall: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 36,
     fontWeight: FontWeight.w400,
-    height: 1.15,
+    height: 1.22,
+    letterSpacing: 0,
   ),
   headlineLarge: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 32,
     fontWeight: FontWeight.w600,
-    height: 1.15,
+    height: 1.25,
+    letterSpacing: 0,
   ),
   headlineMedium: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 28,
     fontWeight: FontWeight.w600,
-    height: 1.15,
+    height: 1.29,
+    letterSpacing: 0,
   ),
   headlineSmall: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 24,
     fontWeight: FontWeight.w600,
-    height: 1.15,
+    height: 1.33,
+    letterSpacing: 0,
   ),
   titleLarge: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 24,
     fontWeight: FontWeight.w400,
-    height: 1.15,
+    height: 1.25,
+    letterSpacing: 0,
   ),
   titleMedium: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 20,
     fontWeight: FontWeight.w500,
-    height: 1.15,
+    height: 1.3,
+    letterSpacing: 0,
   ),
   titleSmall: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 16,
     fontWeight: FontWeight.w500,
-    height: 1.15,
+    height: 1.35,
+    letterSpacing: 0,
   ),
   labelLarge: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 16,
     fontWeight: FontWeight.w500,
-    height: 1.15,
+    height: 1.2,
+    letterSpacing: 0,
   ),
   labelMedium: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 14,
     fontWeight: FontWeight.w500,
-    height: 1.15,
+    height: 1.25,
+    letterSpacing: 0,
   ),
   labelSmall: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 11,
     fontWeight: FontWeight.w500,
-    height: 1.15,
+    height: 1.2,
+    letterSpacing: 0,
   ),
   bodyLarge: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 16,
     fontWeight: FontWeight.w400,
-    height: 1.25,
+    height: 1.38,
+    letterSpacing: 0,
   ),
   bodyMedium: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 14,
     fontWeight: FontWeight.w400,
-    height: 1.43,
+    height: 1.3,
+    letterSpacing: 0,
   ),
   bodySmall: TextStyle(
     fontFamily: _fontFamily,
-    fontFeatures: _fontFeatures,
     fontSize: 12,
     fontWeight: FontWeight.w400,
+    height: 1.25,
+    letterSpacing: 0,
   ),
 );
diff --git a/mobile/lib/shared/theme/theme_provider.dart b/mobile/lib/shared/theme/theme_provider.dart
index 0d88d391b2..79f47dcab4 100644
--- a/mobile/lib/shared/theme/theme_provider.dart
+++ b/mobile/lib/shared/theme/theme_provider.dart
@@ -11,6 +11,9 @@ const _themeModeKey = 'buzz_theme_mode';
 const _accentKey = 'buzz_accent_color';
 const _schemeKey = 'buzz_color_scheme';
 
+const defaultSchemeName = 'github-light';
+const defaultSchemeDisplayName = 'GitHub Light';
+
 /// Pre-loaded SharedPreferences instance, overridden in main().
 final savedPrefsProvider = Provider<SharedPreferences>(
   (_) => throw UnimplementedError('Must be overridden'),
@@ -34,12 +37,19 @@ final themeProvider = NotifierProvider<ThemeNotifier, ThemeMode>(
 );
 
 /// Tracks the selected accent color index.
-/// -1 means "use theme default (Catppuccin Mauve)".
 class AccentNotifier extends Notifier<int> {
   @override
   int build() {
     final prefs = ref.read(savedPrefsProvider);
-    return prefs.getInt(_accentKey) ?? defaultAccentIndex;
+    final stored = prefs.getInt(_accentKey);
+    if (stored == legacyDefaultAccentIndex) {
+      prefs.setInt(_accentKey, defaultAccentIndex);
+      return defaultAccentIndex;
+    }
+    if (stored == null || stored < 0 || stored >= accentColors.length) {
+      return defaultAccentIndex;
+    }
+    return stored;
   }
 
   void setAccent(int index) {
@@ -53,8 +63,7 @@ final accentProvider = NotifierProvider<AccentNotifier, int>(
 );
 
 /// Tracks the selected color scheme name.
-/// null means "use default" (catppuccin-latte for light, catppuccin-macchiato
-/// for dark — the original hardcoded schemes).
+/// null means "use default" ([defaultSchemeDisplayName]).
 class SchemeNotifier extends Notifier<String?> {
   @override
   String? build() {
@@ -63,7 +72,7 @@ class SchemeNotifier extends Notifier<String?> {
 
     // One-time migration: if no scheme has been chosen and the user had a
     // non-system themeMode persisted (from the old Light/System/Dark toggle),
-    // reset it to system so default Catppuccin respects platform brightness.
+    // reset it because default schemes now control their own brightness.
     if (scheme == null) {
       final storedMode = prefs.getString(_themeModeKey);
       if (storedMode != null && storedMode != ThemeMode.system.name) {
@@ -91,15 +100,13 @@ final schemeProvider = NotifierProvider<SchemeNotifier, String?>(
 
 /// Resolves the current scheme selection into light and dark [ColorScheme]s.
 /// When a named scheme is selected, generates it via the adaptive engine.
-/// When null (default), returns the original Catppuccin schemes.
+/// When null (default), resolves to [defaultSchemeName].
 ({ColorScheme light, ColorScheme dark, ThemeMode? forcedMode}) resolveSchemes(
   String? schemeName,
 ) {
-  if (schemeName == null) {
-    return (light: lightColorScheme, dark: darkColorScheme, forcedMode: null);
-  }
-
-  final theme = findTheme(schemeName);
+  final theme =
+      findTheme(schemeName ?? defaultSchemeName) ??
+      findTheme(defaultSchemeName);
   if (theme == null) {
     return (light: lightColorScheme, dark: darkColorScheme, forcedMode: null);
   }
diff --git a/mobile/lib/shared/widgets/app_list.dart b/mobile/lib/shared/widgets/app_list.dart
index 28d01af70e..53557925a0 100644
--- a/mobile/lib/shared/widgets/app_list.dart
+++ b/mobile/lib/shared/widgets/app_list.dart
@@ -31,7 +31,7 @@ class AppListRow extends StatelessWidget {
   Widget build(BuildContext context) {
     final row = Padding(
       padding: const EdgeInsets.symmetric(
-        horizontal: Grid.xs,
+        horizontal: Grid.gutter,
         vertical: Grid.twelve,
       ),
       child: Row(
@@ -109,7 +109,7 @@ class AppListRowRaw extends StatelessWidget {
   Widget build(BuildContext context) {
     final row = Padding(
       padding: const EdgeInsets.symmetric(
-        horizontal: Grid.xs,
+        horizontal: Grid.gutter,
         vertical: Grid.twelve,
       ),
       child: Row(
@@ -167,9 +167,9 @@ class AppListSection extends StatelessWidget {
         if (label != null)
           Padding(
             padding: const EdgeInsets.fromLTRB(
-              Grid.xs,
+              Grid.gutter,
               Grid.xxs,
-              Grid.xs,
+              Grid.gutter,
               Grid.quarter,
             ),
             child: Text(
diff --git a/mobile/lib/shared/widgets/filter_chip_bar.dart b/mobile/lib/shared/widgets/filter_chip_bar.dart
index a4b25e902d..ef5c5a031a 100644
--- a/mobile/lib/shared/widgets/filter_chip_bar.dart
+++ b/mobile/lib/shared/widgets/filter_chip_bar.dart
@@ -46,7 +46,7 @@ class FilterChipBar<T> extends StatelessWidget {
     return SingleChildScrollView(
       scrollDirection: Axis.horizontal,
       padding: const EdgeInsets.symmetric(
-        horizontal: Grid.xs,
+        horizontal: Grid.gutter,
         vertical: Grid.xxs,
       ),
       child: Row(
diff --git a/mobile/lib/shared/widgets/frosted_app_bar.dart b/mobile/lib/shared/widgets/frosted_app_bar.dart
index a6dd0cf11d..5d2c3c6dc6 100644
--- a/mobile/lib/shared/widgets/frosted_app_bar.dart
+++ b/mobile/lib/shared/widgets/frosted_app_bar.dart
@@ -84,7 +84,9 @@ class FrostedAppBar extends StatelessWidget {
                       Expanded(
                         child: Padding(
                           padding: EdgeInsets.only(
-                            left: effectiveLeading != null ? 0 : Grid.xs,
+                            left: effectiveLeading != null
+                                ? 0
+                                : Grid.gutter - Grid.quarter,
                           ),
                           child: DefaultTextStyle.merge(
                             style: context.textTheme.titleMedium?.copyWith(
diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml
index 766a123e86..08c62cd15a 100644
--- a/mobile/pubspec.yaml
+++ b/mobile/pubspec.yaml
@@ -41,10 +41,10 @@ dev_dependencies:
 flutter:
   uses-material-design: true
   fonts:
-    - family: Geist
+    - family: Inter
       fonts:
-        - asset: assets/fonts/Geist-Variable.ttf
-        - asset: assets/fonts/Geist-Italic-Variable.ttf
+        - asset: assets/fonts/InterVariable.ttf
+        - asset: assets/fonts/InterVariable-Italic.ttf
           style: italic
     - family: GeistMono
       fonts:
diff --git a/mobile/test/shared/theme/accent_colors_test.dart b/mobile/test/shared/theme/accent_colors_test.dart
new file mode 100644
index 0000000000..d1f777c598
--- /dev/null
+++ b/mobile/test/shared/theme/accent_colors_test.dart
@@ -0,0 +1,39 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:buzz/shared/theme/theme.dart';
+
+void main() {
+  group('default accent', () {
+    test('uses black on the default light scheme', () {
+      final resolved = resolveSchemes(null);
+
+      final accented = applyAccent(resolved.light, defaultAccentIndex);
+
+      expect(accented.primary, const Color(0xFF000000));
+    });
+
+    test('uses the theme foreground on forced dark schemes', () {
+      final resolved = resolveSchemes('github-dark');
+      final base = resolved.dark;
+
+      final accented = applyAccent(base, defaultAccentIndex);
+
+      expect(resolved.forcedMode, ThemeMode.dark);
+      expect(accented.primary, base.onSurface);
+      expect(accented.primary, isNot(const Color(0xFF000000)));
+      expect(
+        _contrastRatio(accented.primary, accented.surface),
+        greaterThanOrEqualTo(4.5),
+      );
+      expect(accented.onPrimary, contrastForeground(accented.primary));
+    });
+  });
+}
+
+double _contrastRatio(Color a, Color b) {
+  final aLum = a.computeLuminance();
+  final bLum = b.computeLuminance();
+  final lightest = aLum > bLum ? aLum : bLum;
+  final darkest = aLum > bLum ? bLum : aLum;
+  return (lightest + 0.05) / (darkest + 0.05);
+}
