first commit
This commit is contained in:
72
.gitignore
vendored
Normal file
72
.gitignore
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
.expo
|
||||||
|
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Expo
|
||||||
|
.expo/
|
||||||
|
dist/
|
||||||
|
web-build/
|
||||||
|
expo-env.d.ts
|
||||||
|
|
||||||
|
# Native
|
||||||
|
.kotlin/
|
||||||
|
*.orig.*
|
||||||
|
*.jks
|
||||||
|
*.p8
|
||||||
|
*.p12
|
||||||
|
*.key
|
||||||
|
*.mobileprovision
|
||||||
|
|
||||||
|
# Metro
|
||||||
|
.metro-health-check*
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.*
|
||||||
|
yarn-debug.*
|
||||||
|
yarn-error.*
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
app-example
|
||||||
|
|
||||||
|
# generated native folders
|
||||||
|
/ios
|
||||||
|
/android
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
48
mobile/App.tsx
Normal file
48
mobile/App.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { StatusBar } from "expo-status-bar";
|
||||||
|
import { StyleSheet, View } from "react-native";
|
||||||
|
import { BottomNav } from "./src/components/BottomNav";
|
||||||
|
import { NotFoundPage } from "./src/pages/NotFound";
|
||||||
|
import { IndexPage } from "./src/pages/index";
|
||||||
|
import { Route, RouterProvider, useRouter } from "./src/router";
|
||||||
|
interface Tab {
|
||||||
|
label: string;
|
||||||
|
route: Route;
|
||||||
|
page: React.ComponentType;
|
||||||
|
}
|
||||||
|
const TABS: Tab[] = [{ label: "Home", route: "home", page: IndexPage }];
|
||||||
|
export { TABS, type Tab };
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const { route } = useRouter();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{TABS.map((tab) => {
|
||||||
|
if (tab.route === route) {
|
||||||
|
const Page = tab.page;
|
||||||
|
return <Page key={tab.route} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
{!TABS.some((tab) => tab.route === route) && <NotFoundPage />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<RouterProvider>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<StatusBar style="auto" />
|
||||||
|
<Screen />
|
||||||
|
<BottomNav />
|
||||||
|
</View>
|
||||||
|
</RouterProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
},
|
||||||
|
});
|
||||||
30
mobile/app.json
Normal file
30
mobile/app.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"expo": {
|
||||||
|
"name": "mobile",
|
||||||
|
"slug": "mobile",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"icon": "./assets/icon.png",
|
||||||
|
"userInterfaceStyle": "light",
|
||||||
|
"newArchEnabled": true,
|
||||||
|
"splash": {
|
||||||
|
"image": "./assets/splash-icon.png",
|
||||||
|
"resizeMode": "contain",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"ios": {
|
||||||
|
"supportsTablet": true
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"adaptiveIcon": {
|
||||||
|
"foregroundImage": "./assets/adaptive-icon.png",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"edgeToEdgeEnabled": true,
|
||||||
|
"predictiveBackGestureEnabled": false
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"favicon": "./assets/favicon.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
mobile/assets/adaptive-icon.png
Normal file
BIN
mobile/assets/adaptive-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
mobile/assets/favicon.png
Normal file
BIN
mobile/assets/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
mobile/assets/icon.png
Normal file
BIN
mobile/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
mobile/assets/splash-icon.png
Normal file
BIN
mobile/assets/splash-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
8
mobile/index.ts
Normal file
8
mobile/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { registerRootComponent } from 'expo';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
||||||
|
// It also ensures that whether you load the app in Expo Go or in a native build,
|
||||||
|
// the environment is set up appropriately
|
||||||
|
registerRootComponent(App);
|
||||||
22
mobile/package.json
Normal file
22
mobile/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "mobile",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"start": "expo start",
|
||||||
|
"android": "expo start --android",
|
||||||
|
"ios": "expo start --ios",
|
||||||
|
"web": "expo start --web"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"expo": "~54.0.33",
|
||||||
|
"expo-status-bar": "~3.0.9",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-native": "0.81.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "~19.1.0",
|
||||||
|
"typescript": "~5.9.2"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
5619
mobile/pnpm-lock.yaml
generated
Normal file
5619
mobile/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
mobile/pnpm-workspace.yaml
Normal file
1
mobile/pnpm-workspace.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nodeLinker: hoisted
|
||||||
61
mobile/src/components/BottomNav.tsx
Normal file
61
mobile/src/components/BottomNav.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||||
|
import { useRouter } from "../router";
|
||||||
|
import { TABS } from "../../App";
|
||||||
|
|
||||||
|
|
||||||
|
export function BottomNav() {
|
||||||
|
const { route, navigate } = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.bar}>
|
||||||
|
{TABS.map((tab) => {
|
||||||
|
const active = tab.route === route;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={tab.route}
|
||||||
|
style={styles.tab}
|
||||||
|
onPress={() => navigate(tab.route)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={[styles.label, active && styles.labelActive]}>
|
||||||
|
{tab.label}
|
||||||
|
</Text>
|
||||||
|
{active && <View style={styles.indicator} />}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
bar: {
|
||||||
|
flexDirection: "row",
|
||||||
|
height: 74,
|
||||||
|
borderTopWidth:1,
|
||||||
|
borderTopColor: "#e5e5e5",
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#aaa",
|
||||||
|
fontWeight: "500",
|
||||||
|
},
|
||||||
|
labelActive: {
|
||||||
|
color: "#1a1a1a",
|
||||||
|
fontWeight: "700",
|
||||||
|
},
|
||||||
|
indicator: {
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
width: 32,
|
||||||
|
height: 3,
|
||||||
|
borderRadius: 2,
|
||||||
|
backgroundColor: "#1a1a1a",
|
||||||
|
},
|
||||||
|
});
|
||||||
36
mobile/src/pages/NotFound.tsx
Normal file
36
mobile/src/pages/NotFound.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { StyleSheet, Text, View } from "react-native";
|
||||||
|
|
||||||
|
export function NotFoundPage() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.code}>404</Text>
|
||||||
|
<Text style={styles.title}>Page Not Found</Text>
|
||||||
|
<Text style={styles.subtitle}>This route doesn't exist yet.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
backgroundColor: "#f9f9f9",
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
fontSize: 72,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#ccc",
|
||||||
|
lineHeight: 80,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: "600",
|
||||||
|
marginTop: 8,
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: "#999",
|
||||||
|
},
|
||||||
|
});
|
||||||
45
mobile/src/pages/index.tsx
Normal file
45
mobile/src/pages/index.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Button, StyleSheet, Text, View } from "react-native";
|
||||||
|
import { TextInput } from "react-native";
|
||||||
|
|
||||||
|
export function IndexPage() {
|
||||||
|
const [text, setText] = useState("");
|
||||||
|
|
||||||
|
const handleSend = () => {
|
||||||
|
alert(`You typed: ${text}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.title}>Update Title</Text>
|
||||||
|
<Text style={styles.subtitle}>You typed "{text}"</Text>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Type something..."
|
||||||
|
value={text}
|
||||||
|
onChangeText={setText}
|
||||||
|
/>
|
||||||
|
<Button title="Send" onPress={handleSend} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
backgroundColor: "#f9f9f9",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: "700",
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: "#666",
|
||||||
|
},
|
||||||
|
});
|
||||||
26
mobile/src/router.tsx
Normal file
26
mobile/src/router.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React, { createContext, useContext, useState } from "react";
|
||||||
|
|
||||||
|
export type Route = "home" | "mystery";
|
||||||
|
|
||||||
|
interface RouterContextValue {
|
||||||
|
route: Route;
|
||||||
|
navigate: (to: Route) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RouterContext = createContext<RouterContextValue | null>(null);
|
||||||
|
|
||||||
|
export function RouterProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [route, setRoute] = useState<Route>("home");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouterContext.Provider value={{ route, navigate: setRoute }}>
|
||||||
|
{children}
|
||||||
|
</RouterContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRouter(): RouterContextValue {
|
||||||
|
const ctx = useContext(RouterContext);
|
||||||
|
if (!ctx) throw new Error("useRouter must be used inside <RouterProvider>");
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
6
mobile/tsconfig.json
Normal file
6
mobile/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "expo/tsconfig.base",
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
||||||
23
tv/eslint.config.js
Normal file
23
tv/eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
13
tv/index.html
Normal file
13
tv/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>tv</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3286
tv/package-lock.json
generated
Normal file
3286
tv/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
tv/package.json
Normal file
32
tv/package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "tv",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"tailwindcss": "^4.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/react": "^19.2.7",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
|
"globals": "^16.5.0",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"typescript-eslint": "^8.48.0",
|
||||||
|
"vite": "^7.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
2415
tv/pnpm-lock.yaml
generated
Normal file
2415
tv/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
tv/public/vite.svg
Normal file
1
tv/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
63
tv/src/App.tsx
Normal file
63
tv/src/App.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [screenStatus, setScreenStatus] = useState<
|
||||||
|
"notfullscreen" | "fullscreen"
|
||||||
|
>("notfullscreen");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleFullscreenChange = () => {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
setScreenStatus("notfullscreen");
|
||||||
|
} else {
|
||||||
|
setScreenStatus("fullscreen");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{screenStatus === "notfullscreen" ? (
|
||||||
|
<div className="flex flex-col items-center gap-8 px-8 text-center">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<div className="w-16 h-16 rounded-2xl bg-gray-800 border border-gray-700 flex items-center justify-center mb-2">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="w-8 h-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 8V6a2 2 0 012-2h8a2 2 0 012 2v2M6 16v2a2 2 0 002 2h8a2 2 0 002-2v-2M3 12h18" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-semibold tracking-tight text-white">
|
||||||
|
TV View
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-500 text-sm max-w-xs">
|
||||||
|
Enter fullscreen mode to enter fullscreen mode.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => document.documentElement.requestFullscreen()}
|
||||||
|
className="group flex items-center gap-2 bg-white text-gray-900 font-medium px-6 py-3 rounded-xl hover:bg-gray-200 active:scale-95 transition-all duration-150 cursor-pointer"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M4 8V4m0 0h4M4 4l5 5m11-5h-4m4 0v4m0-4l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
||||||
|
</svg>
|
||||||
|
Go Fullscreen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center gap-4 text-center">
|
||||||
|
<h1 className="text-4xl font-bold tracking-tight text-white">
|
||||||
|
Fullscreen Active, yay
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
5
tv/src/index.css
Normal file
5
tv/src/index.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-gray-900 text-white h-screen w-screen flex items-center justify-center;
|
||||||
|
}
|
||||||
10
tv/src/main.tsx
Normal file
10
tv/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
28
tv/tsconfig.app.json
Normal file
28
tv/tsconfig.app.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
tv/tsconfig.json
Normal file
7
tv/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
26
tv/tsconfig.node.json
Normal file
26
tv/tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
7
tv/vite.config.ts
Normal file
7
tv/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), tailwindcss()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user