Tina Tarighian
commited on
Commit
·
060842a
1
Parent(s):
6044c4d
fixing mobile
Browse files- components/CameraSetup.js +1 -3
- components/HandDetector.js +7 -0
- components/MainContent.js +18 -6
- components/PromptEditor.js +11 -11
- pages/index.js +3 -3
- styles/globals.css +14 -0
components/CameraSetup.js
CHANGED
@@ -183,9 +183,7 @@ const CameraSetup = ({
|
|
183 |
aria-label={`Switch to ${facingMode === 'user' ? 'back' : 'front'} camera`}
|
184 |
>
|
185 |
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-800" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
186 |
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="
|
187 |
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
|
188 |
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.5 7.5L9.5 12.5" />
|
189 |
</svg>
|
190 |
</button>
|
191 |
)}
|
|
|
183 |
aria-label={`Switch to ${facingMode === 'user' ? 'back' : 'front'} camera`}
|
184 |
>
|
185 |
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-800" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
186 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
|
|
|
187 |
</svg>
|
188 |
</button>
|
189 |
)}
|
components/HandDetector.js
CHANGED
@@ -32,6 +32,13 @@ const HandDetector = () => {
|
|
32 |
updateCanvasSize
|
33 |
} = useDeviceAndCanvas();
|
34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
const {
|
36 |
handDetected,
|
37 |
isMouthOpen,
|
|
|
32 |
updateCanvasSize
|
33 |
} = useDeviceAndCanvas();
|
34 |
|
35 |
+
// Set default camera to back camera on mobile devices
|
36 |
+
useEffect(() => {
|
37 |
+
if (isMobile) {
|
38 |
+
setFacingMode('environment');
|
39 |
+
}
|
40 |
+
}, [isMobile]);
|
41 |
+
|
42 |
const {
|
43 |
handDetected,
|
44 |
isMouthOpen,
|
components/MainContent.js
CHANGED
@@ -35,12 +35,14 @@ const MainContent = ({
|
|
35 |
}) => {
|
36 |
return (
|
37 |
<>
|
38 |
-
{/* Prompt Editor */}
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
44 |
|
45 |
<div ref={containerRef} className="relative w-full max-w-4xl canvas-container">
|
46 |
{/* Camera Setup */}
|
@@ -90,6 +92,16 @@ const MainContent = ({
|
|
90 |
createPopParticles={createPopParticles}
|
91 |
/>
|
92 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
</>
|
94 |
);
|
95 |
};
|
|
|
35 |
}) => {
|
36 |
return (
|
37 |
<>
|
38 |
+
{/* Conditionally render Prompt Editor based on device */}
|
39 |
+
{!isMobile && (
|
40 |
+
<PromptEditor
|
41 |
+
customPrompt={customPrompt}
|
42 |
+
setCustomPrompt={setCustomPrompt}
|
43 |
+
isMac={isMac}
|
44 |
+
/>
|
45 |
+
)}
|
46 |
|
47 |
<div ref={containerRef} className="relative w-full max-w-4xl canvas-container">
|
48 |
{/* Camera Setup */}
|
|
|
92 |
createPopParticles={createPopParticles}
|
93 |
/>
|
94 |
</div>
|
95 |
+
|
96 |
+
{/* Render Prompt Editor below camera on mobile */}
|
97 |
+
{isMobile && (
|
98 |
+
<PromptEditor
|
99 |
+
customPrompt={customPrompt}
|
100 |
+
setCustomPrompt={setCustomPrompt}
|
101 |
+
isMac={isMac}
|
102 |
+
isMobile={isMobile}
|
103 |
+
/>
|
104 |
+
)}
|
105 |
</>
|
106 |
);
|
107 |
};
|
components/PromptEditor.js
CHANGED
@@ -1,13 +1,13 @@
|
|
1 |
import { useState } from 'react';
|
2 |
|
3 |
-
const PromptEditor = ({ customPrompt, setCustomPrompt, isMac }) => {
|
4 |
const [isEditingPrompt, setIsEditingPrompt] = useState(false);
|
5 |
const [tempPrompt, setTempPrompt] = useState("");
|
6 |
|
7 |
return (
|
8 |
-
<div className=
|
9 |
<div className="flex justify-between items-center mb-1">
|
10 |
-
<label htmlFor="system-prompt" className=
|
11 |
System Prompt
|
12 |
</label>
|
13 |
{!isEditingPrompt ? (
|
@@ -16,9 +16,9 @@ const PromptEditor = ({ customPrompt, setCustomPrompt, isMac }) => {
|
|
16 |
setTempPrompt(customPrompt);
|
17 |
setIsEditingPrompt(true);
|
18 |
}}
|
19 |
-
className=
|
20 |
>
|
21 |
-
<svg xmlns="http://www.w3.org/2000/svg" className=
|
22 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
23 |
</svg>
|
24 |
Edit
|
@@ -40,17 +40,17 @@ const PromptEditor = ({ customPrompt, setCustomPrompt, isMac }) => {
|
|
40 |
setIsEditingPrompt(false);
|
41 |
}
|
42 |
}}
|
43 |
-
className=
|
44 |
-
rows={4}
|
45 |
placeholder="Enter your custom prompt for Gemini..."
|
46 |
/>
|
47 |
<div className="flex justify-end mt-2 space-x-2">
|
48 |
-
<span className=
|
49 |
Tip: Press {isMac ? '⌘' : 'Ctrl'}+Enter to save
|
50 |
</span>
|
51 |
<button
|
52 |
onClick={() => setIsEditingPrompt(false)}
|
53 |
-
className=
|
54 |
>
|
55 |
Cancel
|
56 |
</button>
|
@@ -59,14 +59,14 @@ const PromptEditor = ({ customPrompt, setCustomPrompt, isMac }) => {
|
|
59 |
setCustomPrompt(tempPrompt);
|
60 |
setIsEditingPrompt(false);
|
61 |
}}
|
62 |
-
className=
|
63 |
>
|
64 |
Save
|
65 |
</button>
|
66 |
</div>
|
67 |
</div>
|
68 |
) : (
|
69 |
-
<div className=
|
70 |
{customPrompt}
|
71 |
</div>
|
72 |
)}
|
|
|
1 |
import { useState } from 'react';
|
2 |
|
3 |
+
const PromptEditor = ({ customPrompt, setCustomPrompt, isMac, isMobile }) => {
|
4 |
const [isEditingPrompt, setIsEditingPrompt] = useState(false);
|
5 |
const [tempPrompt, setTempPrompt] = useState("");
|
6 |
|
7 |
return (
|
8 |
+
<div className={`w-full max-w-4xl ${isMobile ? 'mt-4 mb-6' : 'mb-4'}`}>
|
9 |
<div className="flex justify-between items-center mb-1">
|
10 |
+
<label htmlFor="system-prompt" className={`block ${isMobile ? 'text-xs' : 'text-sm'} font-medium text-gray-700`}>
|
11 |
System Prompt
|
12 |
</label>
|
13 |
{!isEditingPrompt ? (
|
|
|
16 |
setTempPrompt(customPrompt);
|
17 |
setIsEditingPrompt(true);
|
18 |
}}
|
19 |
+
className={`${isMobile ? 'text-xs' : 'text-sm'} text-blue-600 hover:text-blue-800 flex items-center`}
|
20 |
>
|
21 |
+
<svg xmlns="http://www.w3.org/2000/svg" className={`${isMobile ? 'h-3 w-3' : 'h-4 w-4'} mr-1`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
22 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
23 |
</svg>
|
24 |
Edit
|
|
|
40 |
setIsEditingPrompt(false);
|
41 |
}
|
42 |
}}
|
43 |
+
className={`w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 ${isMobile ? 'text-xs' : 'text-sm'}`}
|
44 |
+
rows={isMobile ? 3 : 4}
|
45 |
placeholder="Enter your custom prompt for Gemini..."
|
46 |
/>
|
47 |
<div className="flex justify-end mt-2 space-x-2">
|
48 |
+
<span className={`${isMobile ? 'text-xxs' : 'text-xs'} text-gray-500 self-center mr-auto`}>
|
49 |
Tip: Press {isMac ? '⌘' : 'Ctrl'}+Enter to save
|
50 |
</span>
|
51 |
<button
|
52 |
onClick={() => setIsEditingPrompt(false)}
|
53 |
+
className={`px-3 py-1 ${isMobile ? 'text-xs' : 'text-sm'} text-gray-600 border border-gray-300 rounded-md hover:bg-gray-100`}
|
54 |
>
|
55 |
Cancel
|
56 |
</button>
|
|
|
59 |
setCustomPrompt(tempPrompt);
|
60 |
setIsEditingPrompt(false);
|
61 |
}}
|
62 |
+
className={`px-3 py-1 ${isMobile ? 'text-xs' : 'text-sm'} text-white bg-blue-600 rounded-md hover:bg-blue-700`}
|
63 |
>
|
64 |
Save
|
65 |
</button>
|
66 |
</div>
|
67 |
</div>
|
68 |
) : (
|
69 |
+
<div className={`p-3 bg-gray-50 border border-gray-200 rounded-lg ${isMobile ? 'text-xs' : 'text-sm'} text-gray-800 whitespace-pre-wrap max-h-${isMobile ? '24' : '32'} overflow-y-auto`}>
|
70 |
{customPrompt}
|
71 |
</div>
|
72 |
)}
|
pages/index.js
CHANGED
@@ -11,7 +11,7 @@ const inter = Inter({ subsets: ['latin'] });
|
|
11 |
|
12 |
const Header = () => {
|
13 |
return (
|
14 |
-
<div className="
|
15 |
<div className="w-full flex justify-between items-center text-base max-w-7xl mx-auto">
|
16 |
<div className="text-gray-500">
|
17 |
<span className="text-black font-bold text-lg mr-2">HandSpew</span>
|
@@ -42,12 +42,12 @@ export default function Home() {
|
|
42 |
<Head>
|
43 |
<title>HandSpew</title>
|
44 |
<meta name="description" content="Generate thoughts based on hand gestures using MediaPipe and Gemini" />
|
45 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
46 |
<link rel="icon" href="/favicon.ico" />
|
47 |
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&display=swap" />
|
48 |
</Head>
|
49 |
<Header />
|
50 |
-
<main className="flex min-h-screen flex-col items-center justify-
|
51 |
<HandDetector />
|
52 |
</main>
|
53 |
</>
|
|
|
11 |
|
12 |
const Header = () => {
|
13 |
return (
|
14 |
+
<div className="sticky top-0 left-0 right-0 w-full bg-white p-4 z-50 shadow-sm">
|
15 |
<div className="w-full flex justify-between items-center text-base max-w-7xl mx-auto">
|
16 |
<div className="text-gray-500">
|
17 |
<span className="text-black font-bold text-lg mr-2">HandSpew</span>
|
|
|
42 |
<Head>
|
43 |
<title>HandSpew</title>
|
44 |
<meta name="description" content="Generate thoughts based on hand gestures using MediaPipe and Gemini" />
|
45 |
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
46 |
<link rel="icon" href="/favicon.ico" />
|
47 |
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&display=swap" />
|
48 |
</Head>
|
49 |
<Header />
|
50 |
+
<main className="flex min-h-screen flex-col items-center justify-start p-4 bg-white font-['Google_Sans',sans-serif] pt-20 overflow-y-auto">
|
51 |
<HandDetector />
|
52 |
</main>
|
53 |
</>
|
styles/globals.css
CHANGED
@@ -16,10 +16,18 @@
|
|
16 |
}
|
17 |
}
|
18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
body {
|
20 |
color: var(--foreground);
|
21 |
background: var(--background);
|
22 |
font-family: 'Google Sans', Arial, Helvetica, sans-serif;
|
|
|
23 |
}
|
24 |
|
25 |
/* Minimal thought bubble styling */
|
@@ -66,4 +74,10 @@ body {
|
|
66 |
font-size: 12px;
|
67 |
line-height: 1.3;
|
68 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
}
|
|
|
16 |
}
|
17 |
}
|
18 |
|
19 |
+
html, body {
|
20 |
+
height: 100%;
|
21 |
+
width: 100%;
|
22 |
+
overflow-x: hidden;
|
23 |
+
-webkit-overflow-scrolling: touch;
|
24 |
+
}
|
25 |
+
|
26 |
body {
|
27 |
color: var(--foreground);
|
28 |
background: var(--background);
|
29 |
font-family: 'Google Sans', Arial, Helvetica, sans-serif;
|
30 |
+
position: relative;
|
31 |
}
|
32 |
|
33 |
/* Minimal thought bubble styling */
|
|
|
74 |
font-size: 12px;
|
75 |
line-height: 1.3;
|
76 |
}
|
77 |
+
|
78 |
+
/* Add text-xxs class for very small text on mobile */
|
79 |
+
.text-xxs {
|
80 |
+
font-size: 0.65rem;
|
81 |
+
line-height: 1rem;
|
82 |
+
}
|
83 |
}
|