diff --git a/api/src/controllers/seed/insert/shows.ts b/api/src/controllers/seed/insert/shows.ts index 38cf2200..cc85c6bb 100644 --- a/api/src/controllers/seed/insert/shows.ts +++ b/api/src/controllers/seed/insert/shows.ts @@ -22,7 +22,7 @@ import type { SeedMovie } from "~/models/movie"; import type { SeedSerie } from "~/models/serie"; import type { Original } from "~/models/utils"; import { record } from "~/otel"; -import { getYear } from "~/utils"; +import { getYear, uniq } from "~/utils"; import { enqueueOptImage, flushImageQueue, type ImageTask } from "../images"; type Show = typeof shows.$inferInsert; @@ -68,7 +68,11 @@ export const insertShow = record( column: sql`${shows.original}['logo']`, }), }; - const ret = await insertBaseShow(tx, { ...show, original: orig }); + const ret = await insertBaseShow(tx, { + ...show, + genres: uniq(show.genres), + original: orig, + }); if ("status" in ret) return ret; const trans: ShowTrans[] = Object.entries(translations).map( diff --git a/api/src/utils.ts b/api/src/utils.ts index 5d78fba2..207acc28 100644 --- a/api/src/utils.ts +++ b/api/src/utils.ts @@ -29,6 +29,10 @@ export function getFile(path: string): BunFile | S3File { return Bun.file(path); } +export function uniq(a: T[]): T[] { + return uniqBy(a, (x) => x as string); +} + export function uniqBy(a: T[], key: (val: T) => string): T[] { const seen: Record = {}; return a.filter((item) => { diff --git a/front/bun.lock b/front/bun.lock index 8bbe5d65..a85d45b0 100644 --- a/front/bun.lock +++ b/front/bun.lock @@ -10,7 +10,7 @@ "@expo-google-fonts/sora": "^0.4.2", "@expo/html-elements": "^0.13.7", "@gorhom/portal": "^1.0.14", - "@legendapp/list": "^2.0.19", + "@legendapp/list": "zoriya/legend-list#build", "@material-symbols/svg-400": "^0.40.2", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-select": "^2.2.6", @@ -442,7 +442,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@legendapp/list": ["@legendapp/list@2.0.19", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-zDWg8yg0smKxxk+M7gwAbZAnf5uczohPA+IjqLSkImz7+e9ytxeT0Mq35RBO9RTKODOXfV/aIgm1uqUHLBEdmg=="], + "@legendapp/list": ["@legendapp/list@github:zoriya/legend-list#d5d3344", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "*" } }, "zoriya-legend-list-d5d3344"], "@material-symbols/svg-400": ["@material-symbols/svg-400@0.40.2", "", {}, "sha512-e2yEgZW/OveVT1sGaZW1kkRWTPVghjsJYWy+vIea3q08Fv2o7FCYv23PESMyr5D4AaAXdM5dKWkF1e6yIm4swA=="], diff --git a/front/package.json b/front/package.json index 1f1d5816..be74aa05 100644 --- a/front/package.json +++ b/front/package.json @@ -20,7 +20,7 @@ "@expo-google-fonts/sora": "^0.4.2", "@expo/html-elements": "^0.13.7", "@gorhom/portal": "^1.0.14", - "@legendapp/list": "^2.0.19", + "@legendapp/list": "zoriya/legend-list#build", "@material-symbols/svg-400": "^0.40.2", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-select": "^2.2.6", diff --git a/front/src/app/(app)/_layout.tsx b/front/src/app/(app)/_layout.tsx index 70923056..d6fcb47b 100644 --- a/front/src/app/(app)/_layout.tsx +++ b/front/src/app/(app)/_layout.tsx @@ -1,4 +1,3 @@ -import { getFocusedRouteNameFromRoute } from "@react-navigation/native"; import { Stack } from "expo-router"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useCSSVariable, useResolveClassNames } from "uniwind"; @@ -26,18 +25,6 @@ export default function Layout() { headerTintColor: color as string, }} > - { - if (getFocusedRouteNameFromRoute(route) === "index") { - return { - headerTransparent: true, - headerStyle: { backgroundColor: undefined }, - }; - } - return {}; - }} - /> setMoreOpened(true)} className={cn( - "group flex-row items-center", + "group flex-row items-center p-1", href === null && "opacity-50", className, )} diff --git a/front/src/components/items/item-details.tsx b/front/src/components/items/item-details.tsx index 7015d2b5..b12b61c2 100644 --- a/front/src/components/items/item-details.tsx +++ b/front/src/components/items/item-details.tsx @@ -121,7 +121,7 @@ export const ItemDetails = ({ diff --git a/front/src/components/items/item-grid.tsx b/front/src/components/items/item-grid.tsx index e5f631a6..99abe57f 100644 --- a/front/src/components/items/item-grid.tsx +++ b/front/src/components/items/item-grid.tsx @@ -63,7 +63,7 @@ export const ItemGrid = ({ href={moreOpened ? undefined : href} onLongPress={() => setMoreOpened(true)} className={cn( - "group items-center outline-0", + "group items-center p-1 outline-0", horizontal && "h-full w-[200px]", className, )} diff --git a/front/src/primitives/container.tsx b/front/src/primitives/container.tsx index e74e0f7d..8fd9fd00 100644 --- a/front/src/primitives/container.tsx +++ b/front/src/primitives/container.tsx @@ -14,7 +14,7 @@ export const Container = ({ return ( ({ { +} & Partial) => { const { apiUrl, authToken } = useToken(); if (!src) { diff --git a/front/src/primitives/image.tsx b/front/src/primitives/image.tsx index b658820f..271019c7 100644 --- a/front/src/primitives/image.tsx +++ b/front/src/primitives/image.tsx @@ -89,7 +89,7 @@ export const PosterPlaceholder = ({ className, ...props }: ViewProps) => { )} {...props} > - + ); }; diff --git a/front/src/providers/account-provider.tsx b/front/src/providers/account-provider.tsx index 602e3cf6..752088a7 100644 --- a/front/src/providers/account-provider.tsx +++ b/front/src/providers/account-provider.tsx @@ -37,7 +37,6 @@ export const AccountProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { if (!ret.apiUrl) { setTimeout(() => { - console.log("go to login"); router.replace("/login"); }, 0); } diff --git a/front/src/query/fetch-infinite.tsx b/front/src/query/fetch-infinite.tsx index f66b7022..748fee36 100644 --- a/front/src/query/fetch-infinite.tsx +++ b/front/src/query/fetch-infinite.tsx @@ -1,7 +1,7 @@ -import { LegendList as RLegendList } from "@legendapp/list"; +import type { LegendListProps } from "@legendapp/list"; +import { AnimatedLegendList } from "@legendapp/list/reanimated"; import { type ComponentType, type ReactElement, useRef } from "react"; import type { ViewStyle } from "react-native"; -import { withUniwind } from "uniwind"; import { type Breakpoint, HR, useBreakpointMap } from "~/primitives"; import { type QueryIdentifier, useInfiniteFetch } from "./query"; @@ -12,8 +12,6 @@ export type Layout = { layout: "grid" | "horizontal" | "vertical"; }; -const LegendList = withUniwind(RLegendList) as typeof RLegendList; - export const InfiniteFetch = ({ query, placeholderCount = 4, @@ -29,8 +27,6 @@ export const InfiniteFetch = ({ Header, fetchMore = true, contentContainerStyle, - contentContainerClassName, - className, ...props }: { query: QueryIdentifier; @@ -40,6 +36,7 @@ export const InfiniteFetch = ({ getItemType?: (item: Data, index: number) => Type; getItemSizeMult?: (item: Data, index: number, type: Type) => number; getStickyIndices?: (items: Data[]) => number[]; + stickyHeaderConfig?: LegendListProps["stickyHeaderConfig"]; Render: (props: { item: Data; index: number }) => ReactElement | null; Loader: (props: { index: number }) => ReactElement | null; Empty?: JSX.Element; @@ -48,18 +45,13 @@ export const InfiniteFetch = ({ Header?: ComponentType<{ children: JSX.Element }> | ReactElement; fetchMore?: boolean; contentContainerStyle?: ViewStyle; - contentContainerClassName?: string; - className?: string; + onScroll?: LegendListProps["onScroll"]; + scrollEventThrottle?: LegendListProps["scrollEventThrottle"]; }): JSX.Element | null => { const { numColumns, size, gap } = useBreakpointMap(layout); const oldItems = useRef(undefined); - let { - items, - fetchNextPage, - isFetching, - refetch, - isRefetching, - } = useInfiniteFetch(query); + let { items, fetchNextPage, isFetching, refetch, isRefetching } = + useInfiniteFetch(query); if (incremental && items) oldItems.current = items; if (incremental) items ??= oldItems.current; @@ -74,15 +66,15 @@ export const InfiniteFetch = ({ isFetching || !items ? [...(items || []), ...placeholders] : items; return ( - getItemSizeMult(item, idx, type as Type) * size + ? (item, idx, type) => getItemSizeMult(item, idx, type as Type) * size : undefined } renderItem={({ item, index }) => @@ -100,10 +92,13 @@ export const InfiniteFetch = ({ Divider === true ? HR : (Divider as any) || undefined } ListEmptyComponent={Empty} + showsHorizontalScrollIndicator={false} + showsVerticalScrollIndicator={false} contentContainerStyle={{ ...contentContainerStyle, gap, - marginHorizontal: numColumns > 1 ? gap : 0, + marginLeft: numColumns > 1 ? gap : 0, + marginRight: numColumns > 1 ? gap : 0, }} {...props} /> diff --git a/front/src/ui/details/header.tsx b/front/src/ui/details/header.tsx index 37109d83..8a1fe486 100644 --- a/front/src/ui/details/header.tsx +++ b/front/src/ui/details/header.tsx @@ -3,10 +3,9 @@ import MoreHoriz from "@material-symbols/svg-400/rounded/more_horiz.svg"; import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg"; import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg"; import Theaters from "@material-symbols/svg-400/rounded/theaters-fill.svg"; -import { Stack } from "expo-router"; import { Fragment } from "react"; import { useTranslation } from "react-i18next"; -import { View } from "react-native"; +import { View, type ViewProps } from "react-native"; import { WatchListInfo } from "~/components/items/watchlist-info"; import { Rating } from "~/components/rating"; import { @@ -380,78 +379,76 @@ Description.Loader = ({ ...props }: object) => { export const Header = ({ kind, slug, + onImageLayout, }: { kind: "movie" | "serie"; slug: string; + onImageLayout?: ViewProps["onLayout"]; }) => { return ( - <> - - ( - - - - - - - + ( + + + + + + + - {/* {type === "show" && ( */} - {/* */} - {/* )} */} - - )} - Loader={() => ( - - - - - - )} - /> - + {/* {type === "show" && ( */} + {/* */} + {/* )} */} + + )} + Loader={() => ( + + + + + + )} + /> ); }; diff --git a/front/src/ui/details/movie.tsx b/front/src/ui/details/movie.tsx index c46f25b5..578d775e 100644 --- a/front/src/ui/details/movie.tsx +++ b/front/src/ui/details/movie.tsx @@ -1,15 +1,30 @@ -import { ScrollView } from "react-native"; +import { useState } from "react"; +import Animated from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useQueryState } from "~/utils"; +import { HeaderBackground, useScrollNavbar } from "../navbar"; import { Header } from "./header"; export const MovieDetails = () => { const [slug] = useQueryState("slug", undefined!); const insets = useSafeAreaInsets(); + const [imageHeight, setHeight] = useState(300); + const { scrollHandler, headerProps } = useScrollNavbar({ imageHeight }); return ( - -
- + <> + + +
setHeight(e.nativeEvent.layout.height)} + /> + + ); }; diff --git a/front/src/ui/details/season.tsx b/front/src/ui/details/season.tsx index e70abc7f..904e9828 100644 --- a/front/src/ui/details/season.tsx +++ b/front/src/ui/details/season.tsx @@ -41,7 +41,7 @@ export const SeasonHeader = ({ return (

@@ -109,7 +109,11 @@ export const EntryList = ({ query={EntryList.query(slug, season)} layout={EntryLine.layout} Empty={} - Divider={() => } + Divider={() => ( + +


+ + )} getItemType={(item, idx) => item ? item.kind : idx === 0 ? "season" : "episode" } diff --git a/front/src/ui/details/serie.tsx b/front/src/ui/details/serie.tsx index ec52ea55..a1ae2adb 100644 --- a/front/src/ui/details/serie.tsx +++ b/front/src/ui/details/serie.tsx @@ -1,6 +1,6 @@ -import type { ComponentProps } from "react"; +import { type ComponentProps, useState } from "react"; import { useTranslation } from "react-i18next"; -import { View } from "react-native"; +import { View, type ViewProps } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Path } from "react-native-svg"; import { EntryLine, entryDisplayNumber } from "~/components/entries"; @@ -8,13 +8,14 @@ import type { Entry, Serie } from "~/models"; import { Container, H2, Svg } from "~/primitives"; import { Fetch } from "~/query"; import { useQueryState } from "~/utils"; +import { HeaderBackground, useScrollNavbar } from "../navbar"; import { Header } from "./header"; import { EntryList } from "./season"; export const SvgWave = (props: ComponentProps) => { // aspect-[width/height]: width/height of the svg return ( - + @@ -26,16 +27,18 @@ export const NextUp = (nextEntry: Entry) => { const { t } = useTranslation(); return ( - -

{t("show.nextUp")}

- -
+ + +

{t("show.nextUp")}

+ +
+
); }; @@ -43,19 +46,25 @@ NextUp.Loader = () => { const { t } = useTranslation(); return ( - -

{t("show.nextUp")}

- -
+ + +

{t("show.nextUp")}

+ +
+
); }; -const SerieHeader = () => { - const [slug] = useQueryState("slug", undefined!); - +const SerieHeader = ({ + slug, + onImageLayout, +}: { + slug: string; + onImageLayout?: ViewProps["onLayout"]; +}) => { return ( -
+
{ const [slug] = useQueryState("slug", undefined!); const [season] = useQueryState("season", undefined!); const insets = useSafeAreaInsets(); + const [imageHeight, setHeight] = useState(300); + const { scrollHandler, headerProps, headerHeight } = useScrollNavbar({ + imageHeight, + }); return ( + ( + setHeight(e.nativeEvent.layout.height)} + /> + )} contentContainerStyle={{ paddingBottom: insets.bottom }} + onScroll={scrollHandler} + scrollEventThrottle={16} + stickyHeaderConfig={{ + offset: headerHeight, + backdropComponent: () => ( + // hr bottom margin is m-4 and layout gap is 2 but it's only applied on the web and idk why + + ), + }} /> ); diff --git a/front/src/ui/home/header.tsx b/front/src/ui/home/header.tsx index f5a0fd35..66fbda93 100644 --- a/front/src/ui/home/header.tsx +++ b/front/src/ui/home/header.tsx @@ -1,6 +1,7 @@ import Info from "@material-symbols/svg-400/rounded/info.svg"; import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg"; import { LinearGradient } from "expo-linear-gradient"; +import type { ComponentProps } from "react"; import { useTranslation } from "react-i18next"; import { View } from "react-native"; import { min, percent, px, rem, vh } from "yoshiki/native"; @@ -37,8 +38,7 @@ export const Header = ({ tagline: string | null; link: string | null; infoLink: string; - className?: string; -}) => { +} & Partial>) => { const { t } = useTranslation(); return ( diff --git a/front/src/ui/home/index.tsx b/front/src/ui/home/index.tsx index 86e89eff..72a6cc78 100644 --- a/front/src/ui/home/index.tsx +++ b/front/src/ui/home/index.tsx @@ -1,7 +1,10 @@ -import { RefreshControl, ScrollView } from "react-native"; +import { useState } from "react"; +import { RefreshControl } from "react-native"; +import Animated from "react-native-reanimated"; import { Genre } from "~/models"; import { Fetch, useRefresh } from "~/query"; import { shuffle } from "~/utils"; +import { HeaderBackground, useScrollNavbar } from "../navbar"; import { GenreGrid } from "./genre"; import { Header } from "./header"; import { NewsList } from "./news"; @@ -12,46 +15,61 @@ import { WatchlistList } from "./watchlist"; export const HomePage = () => { const genres = shuffle(Object.values(Genre.enum)); const [isRefreshing, refresh] = useRefresh(HomePage.queries(genres)); + const [imageHeight, setHeight] = useState(300); + const { scrollHandler, headerProps } = useScrollNavbar({ + imageHeight, + tab: true, + }); return ( - - } - > - ( -
+ + - )} - Loader={Header.Loader} - /> - - - {genres - .filter((_, i) => i < 2) - .map((x) => ( - - ))} - - {genres - .filter((_, i) => i >= 2 && i < 6) - .map((x) => ( - - ))} - - {/* + } + > + ( +
setHeight(info.nativeEvent.layout.height)} + /> + )} + Loader={Header.Loader} + /> + + + {genres + .filter((_, i) => i < 2) + .map((x) => ( + + ))} + + {genres + .filter((_, i) => i >= 2 && i < 6) + .map((x) => ( + + ))} + + {/* TODO: Lazy load those items {randomItems.filter((_, i) => i >= 6).map((x) => )} */} - + + ); }; diff --git a/front/src/ui/home/watchlist.tsx b/front/src/ui/home/watchlist.tsx index 9b2f74fd..52ed9fd7 100644 --- a/front/src/ui/home/watchlist.tsx +++ b/front/src/ui/home/watchlist.tsx @@ -7,8 +7,8 @@ import { Show } from "~/models"; import { Button, Link, P, ts } from "~/primitives"; import { useAccount } from "~/providers/account-context"; import { InfiniteFetch, type QueryIdentifier } from "~/query"; -import { getDisplayDate } from "~/utils"; import { EmptyView } from "~/ui/empty-view"; +import { getDisplayDate } from "~/utils"; import { Header } from "./genre"; export const WatchlistList = () => { diff --git a/front/src/ui/navbar.tsx b/front/src/ui/navbar.tsx index 8d054dfe..7f41b5ab 100644 --- a/front/src/ui/navbar.tsx +++ b/front/src/ui/navbar.tsx @@ -4,12 +4,19 @@ import Login from "@material-symbols/svg-400/rounded/login.svg"; import Logout from "@material-symbols/svg-400/rounded/logout.svg"; import Search from "@material-symbols/svg-400/rounded/search-fill.svg"; import Settings from "@material-symbols/svg-400/rounded/settings.svg"; -import { useGlobalSearchParams, usePathname, useRouter } from "expo-router"; +import { useIsFocused } from "@react-navigation/native"; +import { + useGlobalSearchParams, + useNavigation, + usePathname, + useRouter, +} from "expo-router"; import KyooLongLogo from "public/icon-long.svg"; import { type ComponentProps, type Ref, useEffect, + useLayoutEffect, useRef, useState, } from "react"; @@ -20,7 +27,16 @@ import { type TextInput, type TextInputProps, View, + type ViewProps, } from "react-native"; +import Animated, { + interpolate, + useAnimatedScrollHandler, + useAnimatedStyle, + useSharedValue, +} from "react-native-reanimated"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { useCSSVariable } from "uniwind"; import { A, Avatar, @@ -190,78 +206,87 @@ export const NavbarRight = () => { ); }; -// export const Navbar = ({ -// left, -// right, -// background, -// ...props -// }: { -// left?: ReactElement | null; -// right?: ReactElement | null; -// background?: ReactElement; -// } & Stylable) => { -// const { css } = useYoshiki(); -// const { t } = useTranslation(); -// -// return ( -//
theme.accent, -// paddingX: ts(2), -// height: { xs: 48, sm: 64 }, -// flexDirection: "row", -// justifyContent: { xs: "space-between", sm: "flex-start" }, -// alignItems: "center", -// shadowColor: "#000", -// shadowOffset: { -// width: 0, -// height: 4, -// }, -// shadowOpacity: 0.3, -// shadowRadius: 4.65, -// elevation: 8, -// zIndex: 1, -// }, -// props, -// )} -// > -// {background} -// -// {left !== undefined ? ( -// left -// ) : ( -// <> -// -// theme.contrast, -// })} -// > -// {t("navbar.browse")} -// -// -// )} -// -// -// {right !== undefined ? right : } -//
-// ); -// }; +export const useScrollNavbar = ({ + imageHeight, + tab = false, +}: { + imageHeight: number; + tab?: boolean; +}) => { + const insets = useSafeAreaInsets(); + const height = insets.top + (Platform.OS === "ios" ? 44 : 56); + + const scrollY = useSharedValue(0); + const scrollHandler = useAnimatedScrollHandler((event) => { + scrollY.value = event.contentOffset.y; + }); + const opacity = useAnimatedStyle( + () => ({ + opacity: interpolate(scrollY.value, [0, imageHeight - height], [0, 1]), + }), + [imageHeight, height], + ); + const reverse = useAnimatedStyle( + () => ({ + opacity: interpolate(scrollY.value, [0, imageHeight - height], [1, 0]), + }), + [imageHeight, height], + ); + + const nav = useNavigation(); + const focused = useIsFocused(); + const accent = useCSSVariable("--color-accent"); + useLayoutEffect(() => { + const n = tab ? nav.getParent() : nav; + if (focused) { + n?.setOptions({ + headerTransparent: true, + headerStyle: { backgroundColor: "transparent" }, + }); + } + return () => + n?.setOptions({ + headerTransparent: false, + headerStyle: { backgroundColor: accent as string }, + }); + }, [nav, tab, focused, accent]); + + return { + scrollHandler, + headerProps: { + opacity, + reverse, + height, + }, + headerHeight: height, + }; +}; + +export const HeaderBackground = ({ + children, + opacity, + reverse, + height, + className, + style, + ...props +}: ViewProps & ReturnType["headerProps"]) => { + return ( + <> + + + {children} + + ); +}; diff --git a/front/src/ui/player/controls/bottom-controls.tsx b/front/src/ui/player/controls/bottom-controls.tsx index 84f775a0..b807a8f2 100644 --- a/front/src/ui/player/controls/bottom-controls.tsx +++ b/front/src/ui/player/controls/bottom-controls.tsx @@ -98,7 +98,7 @@ const ControlButtons = ({ onMenuOpen: () => setMenu(true), onMenuClose: () => setMenu(false), className: "mr-4", - iconClassName: "fill-slate-200", + iconClassName: "fill-slate-200 dark:fill-slate-200", } satisfies Partial< ComponentProps< typeof Menu>> @@ -120,14 +120,14 @@ const ControlButtons = ({ href={`/watch/${previous}`} replace className="mr-4" - iconClassName="fill-slate-200" + iconClassName="fill-slate-200 dark:fill-slate-200" {...tooltip(t("player.previous"), true)} /> )} {next && ( )} {Platform.OS === "web" && ( - + )} )} - + @@ -153,7 +159,10 @@ const ControlButtons = ({ {Platform.OS === "web" && ( - + )} diff --git a/front/src/ui/player/controls/middle-controls.tsx b/front/src/ui/player/controls/middle-controls.tsx index 4218d53a..9039d0a3 100644 --- a/front/src/ui/player/controls/middle-controls.tsx +++ b/front/src/ui/player/controls/middle-controls.tsx @@ -32,13 +32,15 @@ export const MiddleControls = ({ href={previous} replace className={cn( - "mx-12 h-16 w-16 bg-gray-800/70", + "mx-6 bg-gray-800/70", !previous && "pointer-events-none opacity-0", )} + iconClassName="h-16 w-16" /> ); diff --git a/front/src/ui/player/controls/touch.tsx b/front/src/ui/player/controls/touch.tsx index 64d68fe7..82f993e0 100644 --- a/front/src/ui/player/controls/touch.tsx +++ b/front/src/ui/player/controls/touch.tsx @@ -88,7 +88,10 @@ export const TouchControls = ({ // instantly hide the controls when mouse leaves the view if (e.nativeEvent.pointerType === "mouse") show(false); }} - className={cn("absolute inset-0", !shouldShow && "cursor-none")} + className={cn( + "absolute inset-0 cursor-default", + !shouldShow && "cursor-none", + )} /> {shouldShow && children}