Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
1018d70
Update PauseOverlay.tsx
TrendyOfficial Mar 15, 2026
ad46a77
Create GamepadEvents.tsx
TrendyOfficial Mar 15, 2026
5770d3c
Create useGamepad.ts
TrendyOfficial Mar 15, 2026
5122938
Update index.tsx
TrendyOfficial Mar 15, 2026
6a5b2ae
Update PauseOverlay.tsx
TrendyOfficial Mar 15, 2026
9d4193e
Create GamepadControlsModal.tsx
TrendyOfficial Mar 15, 2026
cb8ebb6
Update Container.tsx
TrendyOfficial Mar 15, 2026
d9f202b
Update GamepadEvents.tsx
TrendyOfficial Mar 15, 2026
0ddfdbd
Update PauseOverlay.tsx
TrendyOfficial Mar 15, 2026
121fa2e
Update PreferencesPart.tsx
TrendyOfficial Mar 15, 2026
d59c697
Update index.tsx
TrendyOfficial Mar 15, 2026
de3d56b
Update App.tsx
TrendyOfficial Mar 15, 2026
42b84c1
Update GamepadControlsModal.tsx
TrendyOfficial Mar 15, 2026
eeb43b1
Update GamepadEvents.tsx
TrendyOfficial Mar 15, 2026
0b3d1c3
Update PauseOverlay.tsx
TrendyOfficial Mar 15, 2026
cc0b2f0
Update useGamepad.ts
TrendyOfficial Mar 15, 2026
d6d490f
Update GamepadControlsModal.tsx
TrendyOfficial Mar 15, 2026
da94d52
Update GamepadControlsModal.tsx
TrendyOfficial Mar 15, 2026
585a93e
Update Container.tsx
TrendyOfficial Mar 15, 2026
2d9ef7f
Update PreferencesPart.tsx
TrendyOfficial Mar 15, 2026
021ac50
Update PauseOverlay.tsx
TrendyOfficial Mar 15, 2026
86327a1
Update GamepadControlsModal.tsx
TrendyOfficial Mar 15, 2026
6112ef5
Update PreferencesPart.tsx
TrendyOfficial Mar 15, 2026
8d24072
Update config.js
TrendyOfficial Mar 15, 2026
6e7cb05
Update PauseOverlay.tsx
TrendyOfficial Mar 15, 2026
724ea91
Update GamepadControlsModal.tsx
TrendyOfficial Mar 15, 2026
5ec3b93
Update PreferencesPart.tsx
TrendyOfficial Mar 15, 2026
744b020
Update GamepadControlsModal.tsx
TrendyOfficial Mar 15, 2026
9e5de02
Update PauseOverlay.tsx
TrendyOfficial Mar 15, 2026
89f9e60
Update GamepadControlsModal.tsx
TrendyOfficial Mar 15, 2026
bbf192b
Update vite.config.mts
TrendyOfficial Mar 16, 2026
0aec1c7
Pauseoverlay Completely renewed and made it better
TrendyOfficial Mar 16, 2026
cb5a413
fixed vite error
TrendyOfficial Mar 16, 2026
211c154
GamepodControlsmodal fixed error
TrendyOfficial Mar 16, 2026
28275d0
Removed errors
TrendyOfficial Mar 16, 2026
128651c
PauseOverlay updated to say Finishes.
TrendyOfficial Mar 16, 2026
b8a7bcc
PauseOverlay Final Update.
TrendyOfficial Mar 16, 2026
8d00d21
PauseOverlay Revamped
TrendyOfficial Mar 16, 2026
069d06a
Remade Controll Support & AFixed Pauseoverlay bug
TrendyOfficial Mar 16, 2026
7bfeb20
Fixed issues Controller supportt
TrendyOfficial Mar 16, 2026
8a0c5b6
Fixed Controller again
TrendyOfficial Mar 16, 2026
a3af335
fixed
TrendyOfficial Mar 16, 2026
a08abe8
IMPLEMENTED IT LIVE WEB PREVIEW ON THIS VERSION
TrendyOfficial Mar 16, 2026
6be1c4f
fixed the erroor in customthememodal
TrendyOfficial Mar 16, 2026
07ce337
deleted local files that were made with debug
TrendyOfficial Mar 16, 2026
c889b67
Better live preview
TrendyOfficial Mar 16, 2026
b40c260
Fixed issues i hope for gamepad +live preview
TrendyOfficial Mar 16, 2026
d4f7abf
Fixed issue plss
TrendyOfficial Mar 16, 2026
d573068
Made Live preview better implementing new things
TrendyOfficial Mar 16, 2026
4c9e570
Issues fixed LP
TrendyOfficial Mar 16, 2026
e31f8c3
Old version was bad new live preview
TrendyOfficial Mar 17, 2026
2515b41
Tertiary fix
TrendyOfficial Mar 17, 2026
6051204
Made changes on Live preview + New changes on pauseoverlay
TrendyOfficial Mar 17, 2026
202f029
fixed errors regarding last push
TrendyOfficial Mar 17, 2026
9f54faa
error fix
TrendyOfficial Mar 17, 2026
ba734f4
test fix 1
TrendyOfficial Mar 17, 2026
87a96d8
test fix 2
TrendyOfficial Mar 17, 2026
f3e0239
text fix 3
TrendyOfficial Mar 17, 2026
91af9b9
Test fix 4
TrendyOfficial Mar 17, 2026
8ecc6c0
Merge branch 'production' of https://github.com/TrendyOfficial/p-stre…
TrendyOfficial Mar 21, 2026
346fc99
Fix pnpm lock error
TrendyOfficial Mar 21, 2026
f50f6aa
Readded pnpm-lock for deploy
TrendyOfficial Mar 21, 2026
bc6b7d5
fixed vite.config.mts errors regarding merged files
TrendyOfficial Mar 21, 2026
be3c47a
errors fixed
TrendyOfficial Mar 21, 2026
0e55839
Removed duplicates because of merge :/
TrendyOfficial Mar 21, 2026
3aa76f0
Added Keyboard Shortcuts In prefrences
TrendyOfficial Mar 21, 2026
4d51502
Kbm button too wide - fixed it
TrendyOfficial Mar 21, 2026
dbab322
Test Controller Support 1
TrendyOfficial Mar 21, 2026
1418a23
Controller Support Bugs V1.0 fixed
TrendyOfficial Mar 22, 2026
1bff510
Controller Support Bugs V1.2 😭
TrendyOfficial Mar 22, 2026
0cfd253
Controller Support Bug V1.3 Fixed
TrendyOfficial Mar 22, 2026
16cfcba
Error Fixed
TrendyOfficial Mar 22, 2026
a024432
CS New implementation + Fixed
TrendyOfficial Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,566 changes: 770 additions & 1,796 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions src/assets/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,40 @@ input[type="range"].styled-slider.slider-progress::-ms-fill-lower {
box-shadow: 0 0 10px theme("colors.themePreview.secondary");
}

/* Gamepad specific focus - Premium TV Experience */
.gamepad-active *:focus {
outline: 4px solid #ffffff !important;
outline-offset: 2px !important;
box-shadow: 0 0 20px rgba(255, 255, 255, 0.5) !important;
border-radius: 12px;
z-index: 50;
transition:
outline 0.2s ease,
outline-offset 0.2s ease,
box-shadow 0.2s ease;
}

/* Specific fix for elements with custom focus rings */
.gamepad-active .gamepad-focus-ring-parent:focus {
outline: none !important;
box-shadow: none !important;
}

/* Ensure cards and buttons look good when focused */
.gamepad-active .gamepad-focus-ring-parent:focus-within,
.gamepad-active button:focus-visible,
.gamepad-active a:focus-visible {
transform: scale(1.05);
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
z-index: 100 !important;
}

.gamepad-focus-ring {
box-shadow: 0 0 25px rgba(255, 255, 255, 0.6), inset 0 0 10px rgba(255, 255, 255, 0.2);
border-width: 3px;
filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.5));
}

[dir="rtl"] .transform {
/* Invert horizontal X offset on transform (Tailwind RTL plugin does the rest) */
transform: translate(calc(var(--tw-translate-x) * -1), var(--tw-translate-y))
Expand Down
16 changes: 12 additions & 4 deletions src/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,14 @@
"title": "Folders",
"counter": "{{count}} / {{max}}",
"empty": "No folders yet",
"moreInfo": "More Info",
"moreInfo": "Info",
"removeFromFolder": "Remove from folder",
"editDetails": "Edit Details",
"createFolder": "Create Folder",
"folderNamePlaceholder": "Folder name...",
"limitReached": "Folder limit reached ({{max}} max)"
"limitReached": "Folder limit reached ({{max}} max)",
"add": "Bookmark",
"remove": "Remove"
}
},
"auth": {
Expand Down Expand Up @@ -997,7 +999,7 @@
},
"time": {
"regular": "{{timeWatched}} / {{duration}}",
"remaining": "{{timeLeft}} left • Finish at {{timeFinished, datetime}}",
"remaining": "{{timeLeft}} left • Finishes at {{timeFinished, datetime}}",
"shortRegular": "{{timeWatched}}",
"shortRemaining": "-{{timeLeft}}"
},
Expand Down Expand Up @@ -1404,17 +1406,23 @@
"showMore": "Show more",
"embedOrder": "Reordering embeds",
"embedOrderDescription": "Drag and drop to reorder embeds. This will determine the order in which embeds are checked for the media you are trying to watch. <br><br> <strong>(The default order is best for most users)</strong>",
"autoResumeOnPlaybackErrorLabel": "Automatically resume",
"gamepadIgnoreHeaderTitle": "Ignore Header",
"gamepadIgnoreHeaderDescription": "Skip the navigation header when scrolling with a controller.",
"keybinds": "Keybinds",
"redoSetup": "Redo Setup",

"embedOrderEnableLabel": "Custom embed order",
"manualSource": "Manual source selection",
"manualSourceDescription": "Require picking a source before scraping. Disables automatic source selection and opens the source picker when starting playback.",
"manualSourceLabel": "Manual source selection",
"autoResumeOnPlaybackError": "Auto resume on playback error",
"autoResumeOnPlaybackErrorDescription": "Automatically continue searching for other sources when the current source fails during playback. If disabled, you'll see an error screen with a manual resume option.",
"autoResumeOnPlaybackErrorLabel": "Auto resume on playback error",
"lastSuccessfulSource": "Last used source",
"lastSuccessfulSourceDescription": "Automatically prioritize the source that successfully provided content for the previous episode. This helps ensure continuity when watching series.",
"lastSuccessfulSourceEnableLabel": "Last used source"
},

"reset": "Reset",
"save": "Save",
"sidebar": {
Expand Down
3 changes: 3 additions & 0 deletions src/backend/metadata/tmdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export function formatTMDBMetaToMediaItem(media: TMDBMediaResult): MediaItem {
release_date: media.original_release_date,
poster: media.poster,
type,
rating: media.vote_average,
};
}

Expand Down Expand Up @@ -589,6 +590,7 @@ export function formatTMDBSearchResult(
id: show.id,
original_release_date: new Date(show.first_air_date),
object_type: mediatype,
vote_average: show.vote_average,
};
}

Expand All @@ -600,6 +602,7 @@ export function formatTMDBSearchResult(
id: movie.id,
original_release_date: new Date(movie.release_date),
object_type: mediatype,
vote_average: movie.vote_average,
};
}

Expand Down
1 change: 1 addition & 0 deletions src/backend/metadata/types/tmdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type TMDBMediaResult = {
object_type: TMDBContentTypes;
seasons?: TMDBSeasonShort[];
overview?: string;
vote_average?: number;
};

export type TMDBSeasonMetaResult = {
Expand Down
2 changes: 2 additions & 0 deletions src/components/buttons/IconPatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface IconPatchProps {
transparent?: boolean;
downsized?: boolean;
navigation?: boolean;
tabIndex?: number;
}

export function IconPatch(props: IconPatchProps) {
Expand All @@ -29,6 +30,7 @@ export function IconPatch(props: IconPatchProps) {
return (
<div className={props.className || undefined} onClick={props.onClick}>
<div
tabIndex={props.tabIndex}
className={`flex items-center justify-center rounded-full border-2 border-transparent bg-pill-background bg-opacity-100 transition-[background-color,color,transform,border-color] duration-75 ${transparentClasses} ${navigationClasses} ${clickableClasses} ${activeClasses} ${sizeClasses}`}
>
<Icon icon={props.icon} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/buttons/Toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function Toggle(props: {
onClick={props.disabled ? undefined : props.onClick}
disabled={props.disabled}
className={classNames(
"w-11 h-6 p-1 rounded-full grid transition-colors duration-100 group/toggle tabbable",
"w-11 h-6 p-1 rounded-full grid transition-colors duration-100 group/toggle tabbable focus:outline-none",
props.enabled ? "bg-buttons-toggle" : "bg-buttons-toggleDisabled",
props.disabled ? "opacity-50 cursor-not-allowed" : null,
)}
Expand Down
113 changes: 113 additions & 0 deletions src/components/gamepad/GamepadGlobalListener.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useCallback, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";

import { useGamepadPolling } from "@/hooks/useGamepad";
import { useSpatialNavigation } from "@/hooks/useSpatialNavigation";
import { useOverlayStack } from "@/stores/interface/overlayStack";
import { usePreferencesStore } from "@/stores/preferences";

export function GamepadGlobalListener() {
const { navigate: navigateSpatial } = useSpatialNavigation();
const location = useLocation();
const navigate = useNavigate();
const { hideModal, getTopModal } = useOverlayStack();

const enableGamepadControls = usePreferencesStore(
(s: any) => s.enableGamepadControls,
);

const gamepadInputMode = usePreferencesStore((s: any) => s.gamepadInputMode);

const handleAction = useCallback(
(action: string) => {
if (gamepadInputMode === "kbm") return;

// Add gamepad-active class to body to show custom focus outlines
document.body.classList.add("gamepad-active");
usePreferencesStore.getState().setGamepadActive(true);

// Don't intercept if we're in the player (it has its own listener)
if (location.pathname.startsWith("/media/")) return;

switch (action) {
case "navigate-up":
navigateSpatial("up");
break;
case "navigate-down":
navigateSpatial("down");
break;
case "navigate-left":
navigateSpatial("left");
break;
case "navigate-right":
navigateSpatial("right");
break;
case "confirm":
(document.activeElement as HTMLElement)?.click();
break;
case "back": {
const topModal = getTopModal();
if (topModal) {
hideModal(topModal);
} else {
window.history.back();
}
break;
}
case "go-home": {
window.scrollTo(0, 0);
navigate("/");
break;
}
default:
break;
}
},
[
navigateSpatial,
location.pathname,
gamepadInputMode,
hideModal,
getTopModal,
navigate,
],
);

useGamepadPolling({
onAction: handleAction,
enabled: enableGamepadControls,
});

// Remove gamepad-active class on mouse move (optional, but nice for hybrid use)
useEffect(() => {
const handleMouseMove = () => {
document.body.classList.remove("gamepad-active");
if (usePreferencesStore.getState().isGamepadActive) {
usePreferencesStore.getState().setGamepadActive(false);
}
};
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, []);

// Browser lock: prevent arrow keys from reaching the browser chrome when gamepad is active
useEffect(() => {
if (!enableGamepadControls) return;

const handleKeyDown = (e: KeyboardEvent) => {
const navKeys = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", " "];
if (
navKeys.includes(e.key) &&
document.body.classList.contains("gamepad-active")
) {
// Prevent the browser from scrolling the page or moving focus to browser chrome
e.preventDefault();
}
};
window.addEventListener("keydown", handleKeyDown, { capture: true });
return () =>
window.removeEventListener("keydown", handleKeyDown, { capture: true });
}, [enableGamepadControls]);

return null;
}
1 change: 1 addition & 0 deletions src/components/layout/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export function Navigation(props: NavigationProps) {

{/* content */}
<div
id="mw-header"
className="top-content fixed pointer-events-none left-0 right-0 z-[500] top-0 min-h-[150px]"
style={{
top: `${bannerHeight}px`,
Expand Down
8 changes: 7 additions & 1 deletion src/components/media/MediaBookmark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ import { IconPatch } from "../buttons/IconPatch";
interface MediaBookmarkProps {
media: MediaItem;
group?: string[];
tabIndex?: number;
}

export function MediaBookmarkButton({ media, group }: MediaBookmarkProps) {
export function MediaBookmarkButton({
media,
group,
tabIndex,
}: MediaBookmarkProps) {
const addBookmark = useBookmarkStore((s) => s.addBookmark);
const addBookmarkWithGroups = useBookmarkStore(
(s) => s.addBookmarkWithGroups,
Expand Down Expand Up @@ -59,6 +64,7 @@ export function MediaBookmarkButton({ media, group }: MediaBookmarkProps) {
>
<IconPatch
icon={isBookmarked ? Icons.BOOKMARK : Icons.BOOKMARK_OUTLINE}
tabIndex={tabIndex}
className={`${buttonOpacityClass} p-2 opacity-75 transition-opacity duration-300 hover:scale-110 hover:cursor-pointer`}
/>
</div>
Expand Down
Loading