From 808d353e96e2dbfb28949f328968b075937689c6 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 4 Jun 2024 21:03:11 +0200 Subject: [PATCH 01/24] :thread: initialization --- .gitignore | 1 + README.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..5425191 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# boombox From 89c379f212dc5a8e1f27afe073ddae2247841a9b Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 5 Jun 2024 00:33:16 +0200 Subject: [PATCH 02/24] :tada: initialization --- .expo/README.md | 8 + .expo/devices.json | 3 + .expo/types/router.d.ts | 357 + .gitignore | 7 + README.md | 51 +- app.json | 36 + app/(tabs)/_layout.tsx | 37 + app/(tabs)/explore.tsx | 102 + app/(tabs)/index.tsx | 70 + app/+html.tsx | 39 + app/+not-found.tsx | 32 + app/_layout.tsx | 37 + assets/fonts/SpaceMono-Regular.ttf | Bin 0 -> 93252 bytes assets/images/adaptive-icon.png | Bin 0 -> 17547 bytes assets/images/favicon.png | Bin 0 -> 1466 bytes assets/images/icon.png | Bin 0 -> 22380 bytes assets/images/partial-react-logo.png | Bin 0 -> 5075 bytes assets/images/react-logo.png | Bin 0 -> 6341 bytes assets/images/react-logo@2x.png | Bin 0 -> 14225 bytes assets/images/react-logo@3x.png | Bin 0 -> 21252 bytes assets/images/splash.png | Bin 0 -> 47346 bytes babel.config.js | 6 + components/Collapsible.tsx | 41 + components/ExternalLink.tsx | 24 + components/HelloWave.tsx | 37 + components/ParallaxScrollView.tsx | 76 + components/ThemedText.tsx | 60 + components/ThemedView.tsx | 14 + components/__tests__/ThemedText-test.tsx | 10 + .../__snapshots__/ThemedText-test.tsx.snap | 24 + components/navigation/TabBarIcon.tsx | 9 + constants/Colors.ts | 26 + hooks/useColorScheme.ts | 1 + hooks/useColorScheme.web.ts | 8 + hooks/useThemeColor.ts | 22 + package-lock.json | 18177 ++++++++++++++++ package.json | 49 + scripts/reset-project.js | 73 + tsconfig.json | 17 + 39 files changed, 19452 insertions(+), 1 deletion(-) create mode 100644 .expo/README.md create mode 100644 .expo/devices.json create mode 100644 .expo/types/router.d.ts create mode 100644 app.json create mode 100644 app/(tabs)/_layout.tsx create mode 100644 app/(tabs)/explore.tsx create mode 100644 app/(tabs)/index.tsx create mode 100644 app/+html.tsx create mode 100644 app/+not-found.tsx create mode 100644 app/_layout.tsx create mode 100755 assets/fonts/SpaceMono-Regular.ttf create mode 100644 assets/images/adaptive-icon.png create mode 100644 assets/images/favicon.png create mode 100644 assets/images/icon.png create mode 100644 assets/images/partial-react-logo.png create mode 100644 assets/images/react-logo.png create mode 100644 assets/images/react-logo@2x.png create mode 100644 assets/images/react-logo@3x.png create mode 100644 assets/images/splash.png create mode 100644 babel.config.js create mode 100644 components/Collapsible.tsx create mode 100644 components/ExternalLink.tsx create mode 100644 components/HelloWave.tsx create mode 100644 components/ParallaxScrollView.tsx create mode 100644 components/ThemedText.tsx create mode 100644 components/ThemedView.tsx create mode 100644 components/__tests__/ThemedText-test.tsx create mode 100644 components/__tests__/__snapshots__/ThemedText-test.tsx.snap create mode 100644 components/navigation/TabBarIcon.tsx create mode 100644 constants/Colors.ts create mode 100644 hooks/useColorScheme.ts create mode 100644 hooks/useColorScheme.web.ts create mode 100644 hooks/useThemeColor.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100755 scripts/reset-project.js create mode 100644 tsconfig.json diff --git a/.expo/README.md b/.expo/README.md new file mode 100644 index 0000000..f7eb5fe --- /dev/null +++ b/.expo/README.md @@ -0,0 +1,8 @@ +> Why do I have a folder named ".expo" in my project? +The ".expo" folder is created when an Expo project is started using "expo start" command. +> What do the files contain? +- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds. +- "settings.json": contains the server configuration that is used to serve the application manifest. +> Should I commit the ".expo" folder? +No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine. +Upon project creation, the ".expo" folder is already added to your ".gitignore" file. diff --git a/.expo/devices.json b/.expo/devices.json new file mode 100644 index 0000000..5efff6c --- /dev/null +++ b/.expo/devices.json @@ -0,0 +1,3 @@ +{ + "devices": [] +} diff --git a/.expo/types/router.d.ts b/.expo/types/router.d.ts new file mode 100644 index 0000000..881ea21 --- /dev/null +++ b/.expo/types/router.d.ts @@ -0,0 +1,357 @@ +/* eslint-disable */ +import type { ReactNode } from 'react'; +import type { TextProps, GestureResponderEvent } from 'react-native'; + +export namespace ExpoRouter { + type StaticRoutes = `/` | `/(tabs)` | `/_sitemap` | `/explore`; + type DynamicRoutes = never; + type DynamicRouteTemplate = never; + + export type RelativePathString = `./${string}` | `../${string}` | '..'; + export type AbsoluteRoute = DynamicRouteTemplate | StaticRoutes; + export type ExternalPathString = `${string}:${string}`; + export type ExpoRouterRoutes = DynamicRouteTemplate | StaticRoutes | RelativePathString; + export type AllRoutes = ExpoRouterRoutes | ExternalPathString; + + /**************** + * Route Utils * + ****************/ + type SearchOrHash = `?${string}` | `#${string}`; + export type UnknownInputParams = Record< + string, + string | number | undefined | null | (string | number)[] + >; + type UnknownOutputParams = Record; + + /** + * Return only the RoutePart of a string. If the string has multiple parts return never + * + * string | type + * ---------|------ + * 123 | 123 + * /123/abc | never + * 123?abc | never + * ./123 | never + * /123 | never + * 123/../ | never + */ + type SingleRoutePart = S extends `${string}/${string}` + ? never + : S extends `${string}${SearchOrHash}` + ? never + : S extends '' + ? never + : S extends `(${string})` + ? never + : S extends `[${string}]` + ? never + : S; + + /** + * Return only the CatchAll router part. If the string has search parameters or a hash return never + */ + type CatchAllRoutePart = S extends `${string}${SearchOrHash}` + ? never + : S extends '' + ? never + : S extends `${string}(${string})${string}` + ? never + : S extends `${string}[${string}]${string}` + ? never + : S; + + /** + * Return the name of a route parameter + * '[test]' -> 'test' + * 'test' -> never + * '[...test]' -> '...test' + */ + type IsParameter = Part extends `[${infer ParamName}]` ? ParamName : never; + + /** + * Return a union of all raw parameter names. If there are no names return never + * + * This differs from ParameterNames as it returns the `...` for catch all parameters + * + * /[test] -> 'test' + * /[abc]/[...def] -> 'abc'|'...def' + */ + type ParameterNames = Path extends `${infer PartA}/${infer PartB}` + ? IsParameter | ParameterNames + : IsParameter; + + /** + * Returns all segments of a route. + * + * /(group)/123/abc/[id]/[...rest] -> ['(group)', '123', 'abc', '[id]', '[...rest]' + */ + type RouteSegments = Path extends `${infer PartA}/${infer PartB}` + ? PartA extends '' | '.' + ? [...RouteSegments] + : [PartA, ...RouteSegments] + : Path extends '' + ? [] + : [Path]; + + type AllUngroupedRoutes = Path extends `(${infer PartA})/${infer PartB}` + ? `(${PartA})/${AllUngroupedRoutes}` | AllUngroupedRoutes + : Path; + + /** + * Returns a Record of the routes parameters as strings and CatchAll parameters + * + * There are two versions, input and output, as you can input 'string | number' but + * the output will always be 'string' + * + * /[id]/[...rest] -> { id: string, rest: string[] } + * /no-params -> {} + */ + export type InputRouteParams = { + [Key in ParameterNames as Key extends `...${infer Name}` + ? Name + : Key]: Key extends `...${string}` ? (string | number)[] : string | number; + } & UnknownInputParams; + + type OutputRouteParams = { + [Key in ParameterNames as Key extends `...${infer Name}` + ? Name + : Key]: Key extends `...${string}` ? string[] : string; + } & UnknownOutputParams; + + /** + * Returns the search parameters for a route. + */ + export type SearchParams = T extends DynamicRouteTemplate + ? OutputRouteParams + : T extends StaticRoutes + ? never + : UnknownOutputParams; + + /********* + * Href * + *********/ + + /** + * The main routing type for Expo Router. Includes all available routes with strongly typed parameters. + * + * Allows for static routes, relative paths, external paths, dynamic routes, and the dynamic route provided as a static string + */ + export type Href = + | StringRouteToType | RelativePathString | ExternalPathString> + | DynamicRouteTemplateToString + | DynamicRouteObject< + StaticRoutes | RelativePathString | ExternalPathString | DynamicRouteTemplate + >; + + type StringRouteToType = + | T + | `${T}${SearchOrHash}` + | { pathname: T; params?: UnknownInputParams | never }; + + type DynamicRouteTemplateToString = Path extends `${infer PartA}/${infer PartB}` + ? `${PartA extends `[${string}]` ? string : PartA}/${DynamicRouteTemplateToString}` + : Path extends `[${string}]` + ? string + : Path; + + type DynamicRouteObject = T extends DynamicRouteTemplate + ? { + pathname: T; + params: InputRouteParams; + } + : never; + + type IsStaticRoute = + | StaticRoutes + | RelativePathString + | ExternalPathString + | (T extends DynamicRoutes ? T : never); + + /*********************** + * Expo Router Exports * + ***********************/ + + export type Router = { + /** Go back in the history. */ + back: () => void; + /** If there's history that supports invoking the `back` function. */ + canGoBack: () => boolean; + /** Navigate to the provided href using a push operation if possible. */ + push: (href: Href) => void; + /** Navigate to the provided href. */ + navigate: (href: Href) => void; + /** Navigate to route without appending to the history. */ + replace: (href: Href) => void; + /** Navigate to a screen with a stack lower than the current screen. Using the provided count if possible, otherwise 1. */ + dismiss: (count?: number) => void; + /** Navigate to first screen within the lowest stack. */ + dismissAll: () => void; + /** If there's history that supports invoking the `dismiss` and `dismissAll` function. */ + canDismiss: () => boolean; + /** Update the current route query params. */ + setParams: ( + params?: T extends '' ? Record : InputRouteParams + ) => void; + }; + + /** The imperative router. */ + export declare const router: Router; + + /************ + * * + ************/ + export interface WebAnchorProps { + /** + * **Web only:** Specifies where to open the `href`. + * + * - **_self**: the current tab. + * - **_blank**: opens in a new tab or window. + * - **_parent**: opens in the parent browsing context. If no parent, defaults to **_self**. + * - **_top**: opens in the highest browsing context ancestor. If no ancestors, defaults to **_self**. + * + * This property is passed to the underlying anchor (``) tag. + * + * @default '_self' + * + * @example + * Go to Expo in new tab + */ + target?: '_self' | '_blank' | '_parent' | '_top' | (string & object); + + /** + * **Web only:** Specifies the relationship between the `href` and the current route. + * + * Common values: + * - **nofollow**: Indicates to search engines that they should not follow the `href`. This is often used for user-generated content or links that should not influence search engine rankings. + * - **noopener**: Suggests that the `href` should not have access to the opening window's `window.opener` object, which is a security measure to prevent potentially harmful behavior in cases of links that open new tabs or windows. + * - **noreferrer**: Requests that the browser not send the `Referer` HTTP header when navigating to the `href`. This can enhance user privacy. + * + * The `rel` property is primarily used for informational and instructive purposes, helping browsers and web + * crawlers make better decisions about how to handle and interpret the links on a web page. It is important + * to use appropriate `rel` values to ensure that links behave as intended and adhere to best practices for web + * development and SEO (Search Engine Optimization). + * + * This property is passed to the underlying anchor (``) tag. + * + * @example + * Go to Expo + */ + rel?: string; + + /** + * **Web only:** Specifies that the `href` should be downloaded when the user clicks on the link, + * instead of navigating to it. It is typically used for links that point to files that the user should download, + * such as PDFs, images, documents, etc. + * + * The value of the `download` property, which represents the filename for the downloaded file. + * This property is passed to the underlying anchor (``) tag. + * + * @example + * Download image + */ + download?: string; + } + + export interface LinkProps extends Omit, WebAnchorProps { + /** Path to route to. */ + href: Href; + + // TODO(EvanBacon): This may need to be extracted for React Native style support. + /** Forward props to child component. Useful for custom buttons. */ + asChild?: boolean; + + /** Should replace the current route without adding to the history. */ + replace?: boolean; + /** Should push the current route */ + push?: boolean; + + /** On web, this sets the HTML `class` directly. On native, this can be used with CSS interop tools like Nativewind. */ + className?: string; + + onPress?: (e: React.MouseEvent | GestureResponderEvent) => void; + } + + export interface LinkComponent { + (props: React.PropsWithChildren): JSX.Element; + /** Helper method to resolve an Href object into a string. */ + resolveHref: (href: Href) => string; + } + + /** + * Component to render link to another route using a path. + * Uses an anchor tag on the web. + * + * @param props.href Absolute path to route (e.g. \`/feeds/hot\`). + * @param props.replace Should replace the current route without adding to the history. + * @param props.asChild Forward props to child component. Useful for custom buttons. + * @param props.children Child elements to render the content. + * @param props.className On web, this sets the HTML \`class\` directly. On native, this can be used with CSS interop tools like Nativewind. + */ + export declare const Link: LinkComponent; + + /** Redirects to the href as soon as the component is mounted. */ + export declare const Redirect: (props: React.PropsWithChildren<{ href: Href }>) => ReactNode; + export type Redirect = typeof Redirect; + + /** + * Hooks + */ + + export declare function useRouter(): Router; + type useRouter = typeof useRouter; + + /** + * Returns the URL search parameters for the contextually focused route. e.g. \`/acme?foo=bar\` -> \`{ foo: "bar" }\`. + * This is useful for stacks where you may push a new screen that changes the query parameters. + * + * To observe updates even when the invoking route is not focused, use \`useGlobalSearchParams()\`. + * @see \`useGlobalSearchParams\` + */ + export declare function useLocalSearchParams< + TParams extends AllRoutes | UnknownOutputParams = UnknownOutputParams, + >(): TParams extends AllRoutes ? SearchParams : TParams; + type useLocalSearchParams = typeof useLocalSearchParams; + + export declare function useSearchParams< + TParams extends AllRoutes | UnknownOutputParams = UnknownOutputParams, + >(): TParams extends AllRoutes ? SearchParams : TParams; + type useSearchParams = typeof useSearchParams; + + /** + * Get the globally selected query parameters, including dynamic path segments. This function will update even when the route is not focused. + * Useful for analytics or other background operations that don't draw to the screen. + * + * When querying search params in a stack, opt-towards using \`useLocalSearchParams\` as these will only + * update when the route is focused. + * + * @see \`useLocalSearchParams\` + */ + export declare function useGlobalSearchParams< + T extends AllRoutes | UnknownOutputParams = UnknownOutputParams, + >(): T extends AllRoutes ? SearchParams : T; + type useGlobalSearchParams = typeof useGlobalSearchParams; + + /** + * Get a list of selected file segments for the currently selected route. Segments are not normalized, so they will be the same as the file path. e.g. /[id]?id=normal -> ["[id]"] + * + * \`useSegments\` can be typed using an abstract. + * Consider the following file structure, and strictly typed \`useSegments\` function: + * + * \`\`\`md + * - app + * - [user] + * - index.js + * - followers.js + * - settings.js + * \`\`\` + * This can be strictly typed using the following abstract: + * + * \`\`\`ts + * const [first, second] = useSegments<['settings'] | ['[user]'] | ['[user]', 'followers']>() + * \`\`\` + */ + export declare function useSegments< + T extends AbsoluteRoute | RouteSegments | RelativePathString, + >(): T extends AbsoluteRoute ? RouteSegments : T extends string ? string[] : T; + type useSegments = typeof useSegments; +} diff --git a/.gitignore b/.gitignore index e43b0f9..620ca05 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ .DS_Store +node_modules + +# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb +# The following patterns were generated by expo-cli + +expo-env.d.ts +# @end expo-cli \ No newline at end of file diff --git a/README.md b/README.md index 5425191..cd4feb8 100644 --- a/README.md +++ b/README.md @@ -1 +1,50 @@ -# boombox +# Welcome to your Expo app 👋 + +This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). + +## Get started + +1. Install dependencies + + ```bash + npm install + ``` + +2. Start the app + + ```bash + npx expo start + ``` + +In the output, you'll find options to open the app in a + +- [development build](https://docs.expo.dev/develop/development-builds/introduction/) +- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) +- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) +- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo + +You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). + +## Get a fresh project + +When you're ready, run: + +```bash +npm run reset-project +``` + +This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. + +## Learn more + +To learn more about developing your project with Expo, look at the following resources: + +- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). +- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. + +## Join the community + +Join our community of developers creating universal apps. + +- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. +- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. diff --git a/app.json b/app.json new file mode 100644 index 0000000..9ae8e7d --- /dev/null +++ b/app.json @@ -0,0 +1,36 @@ +{ + "expo": { + "name": "Boombox", + "slug": "boombox", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "boombox", + "userInterfaceStyle": "automatic", + "splash": { + "image": "./assets/images/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + }, + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/images/adaptive-icon.png", + "backgroundColor": "#ffffff" + } + }, + "web": { + "bundler": "metro", + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router" + ], + "experiments": { + "typedRoutes": true + } + } +} diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx new file mode 100644 index 0000000..22a49b6 --- /dev/null +++ b/app/(tabs)/_layout.tsx @@ -0,0 +1,37 @@ +import { Tabs } from 'expo-router'; +import React from 'react'; + +import { TabBarIcon } from '@/components/navigation/TabBarIcon'; +import { Colors } from '@/constants/Colors'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +export default function TabLayout() { + const colorScheme = useColorScheme(); + + return ( + + ( + + ), + }} + /> + ( + + ), + }} + /> + + ); +} diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx new file mode 100644 index 0000000..e480218 --- /dev/null +++ b/app/(tabs)/explore.tsx @@ -0,0 +1,102 @@ +import Ionicons from '@expo/vector-icons/Ionicons'; +import { StyleSheet, Image, Platform } from 'react-native'; + +import { Collapsible } from '@/components/Collapsible'; +import { ExternalLink } from '@/components/ExternalLink'; +import ParallaxScrollView from '@/components/ParallaxScrollView'; +import { ThemedText } from '@/components/ThemedText'; +import { ThemedView } from '@/components/ThemedView'; + +export default function TabTwoScreen() { + return ( + }> + + Explore + + This app includes example code to help you get started. + + + This app has two screens:{' '} + app/(tabs)/index.tsx and{' '} + app/(tabs)/explore.tsx + + + The layout file in app/(tabs)/_layout.tsx{' '} + sets up the tab navigator. + + + Learn more + + + + + You can open this project on Android, iOS, and the web. To open the web version, press{' '} + w in the terminal running this project. + + + + + For static images, you can use the @2x and{' '} + @3x suffixes to provide files for + different screen densities + + + + Learn more + + + + + Open app/_layout.tsx to see how to load{' '} + + custom fonts such as this one. + + + + Learn more + + + + + This template has light and dark mode support. The{' '} + useColorScheme() hook lets you inspect + what the user's current color scheme is, and so you can adjust UI colors accordingly. + + + Learn more + + + + + This template includes an example of an animated component. The{' '} + components/HelloWave.tsx component uses + the powerful react-native-reanimated library + to create a waving hand animation. + + {Platform.select({ + ios: ( + + The components/ParallaxScrollView.tsx{' '} + component provides a parallax effect for the header image. + + ), + })} + + + ); +} + +const styles = StyleSheet.create({ + headerImage: { + color: '#808080', + bottom: -90, + left: -35, + position: 'absolute', + }, + titleContainer: { + flexDirection: 'row', + gap: 8, + }, +}); diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx new file mode 100644 index 0000000..5242f55 --- /dev/null +++ b/app/(tabs)/index.tsx @@ -0,0 +1,70 @@ +import { Image, StyleSheet, Platform } from 'react-native'; + +import { HelloWave } from '@/components/HelloWave'; +import ParallaxScrollView from '@/components/ParallaxScrollView'; +import { ThemedText } from '@/components/ThemedText'; +import { ThemedView } from '@/components/ThemedView'; + +export default function HomeScreen() { + return ( + + }> + + Welcome to Boombox + + + + Step 1: Try it + + Edit app/(tabs)/index.tsx to see changes. + Press{' '} + + {Platform.select({ ios: 'cmd + d', android: 'cmd + m' })} + {' '} + to open developer tools. + + + + Step 2: Explore + + Tap the Explore tab to learn more about what's included in this starter app. + + + + Step 3: Get a fresh start + + When you're ready, run{' '} + npm run reset-project to get a fresh{' '} + app directory. This will move the current{' '} + app to{' '} + app-example. + + + + ); +} + +const styles = StyleSheet.create({ + titleContainer: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + }, + stepContainer: { + gap: 8, + marginBottom: 8, + }, + reactLogo: { + height: 178, + width: 290, + bottom: 0, + left: 0, + position: 'absolute', + }, +}); diff --git a/app/+html.tsx b/app/+html.tsx new file mode 100644 index 0000000..8b92456 --- /dev/null +++ b/app/+html.tsx @@ -0,0 +1,39 @@ +import { ScrollViewStyleReset } from 'expo-router/html'; +import { type PropsWithChildren } from 'react'; + +/** + * This file is web-only and used to configure the root HTML for every web page during static rendering. + * The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs. + */ +export default function Root({ children }: PropsWithChildren) { + return ( + + + + + + + {/* + Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. + However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. + */} + + + {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} +