Widgets
diff --git a/desktop-os/src/components/os/WindowManager.tsx b/desktop-os/src/components/os/WindowManager.tsx
index 89c62d6..7fa622d 100644
--- a/desktop-os/src/components/os/WindowManager.tsx
+++ b/desktop-os/src/components/os/WindowManager.tsx
@@ -2,10 +2,11 @@
import { motion, useDragControls, useMotionValue, useScroll, useTransform } from 'framer-motion';
import { CSSProperties, PointerEvent as ReactPointerEvent, ReactNode, RefObject, useRef, useState } from 'react';
-import { Maximize2, Minimize2, Minus, X } from 'lucide-react';
+import { ChevronLeft, Maximize2, Minimize2, Minus, X } from 'lucide-react';
import { WindowScrollContext } from '@/components/os/Reveal';
import { useDesktopStore } from '@/store/useDesktopStore';
import { sound } from '@/lib/sound';
+import { useIsMobile } from '@/lib/useIsMobile';
interface WindowManagerProps {
id: string;
@@ -34,6 +35,8 @@ export function WindowManager({ id, title, zIndex, isMinimized, isFocused, const
const closeWindow = useDesktopStore((s) => s.closeWindow);
const minimizeWindow = useDesktopStore((s) => s.minimizeWindow);
const focusWindow = useDesktopStore((s) => s.focusWindow);
+ // Below `sm` apps run full-screen like iOS: no dragging, a nav bar and a home indicator.
+ const isMobile = useIsMobile();
// Cascade new windows like macOS instead of stacking them exactly. This is the motion
// values' *initial* value — never `.set()` them later (see toggleMaximize).
@@ -51,6 +54,11 @@ export function WindowManager({ id, title, zIndex, isMinimized, isFocused, const
// macOS only draws the toolbar hairline once content has scrolled underneath it.
const hairline = useTransform(scrollY, [0, 12], [0, 1]);
+ function goHome() {
+ sound.click();
+ minimizeWindow(id);
+ }
+
function containerBounds() {
const el = constraintsRef.current;
return { width: el?.clientWidth ?? 1200, height: el?.clientHeight ?? 800 };
@@ -103,7 +111,7 @@ export function WindowManager({ id, title, zIndex, isMinimized, isFocused, const
return (
+ {/* iOS navigation bar (mobile). */}
+
+
+ {title}
+
+
+
+
dragControls.start(e)}
- className="relative flex h-[38px] cursor-default select-none items-center px-3 active:cursor-grabbing"
+ className="relative hidden h-[38px] cursor-default select-none items-center px-3 active:cursor-grabbing sm:flex"
>
{/* Traffic lights. Pointer-down is stopped here so a quick double-click on a light can
never fire dragControls.start twice — that combination corrupts framer's drag state. */}
@@ -194,11 +220,25 @@ export function WindowManager({ id, title, zIndex, isMinimized, isFocused, const
/>
-
+
{children}
+ {/* iOS home indicator: tap or swipe up to go back to the home screen. */}
+
info.offset.y < -20 && goHome()}
+ className="absolute inset-x-0 bottom-0 flex h-7 touch-none items-end justify-center pb-2 sm:hidden"
+ >
+
+
+
{
+ const mq = window.matchMedia('(max-width: 639.98px)');
+ // eslint-disable-next-line react-hooks/set-state-in-effect
+ setMobile(mq.matches);
+ const onChange = (e: MediaQueryListEvent) => setMobile(e.matches);
+ mq.addEventListener('change', onChange);
+ return () => mq.removeEventListener('change', onChange);
+ }, []);
+ return mobile;
+}
diff --git a/groq-proxy/vercel.json b/groq-proxy/vercel.json
new file mode 100644
index 0000000..165ee32
--- /dev/null
+++ b/groq-proxy/vercel.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "redirects": [
+ {
+ "source": "/",
+ "destination": "https://kushagra486.github.io/",
+ "permanent": false
+ },
+ {
+ "source": "/:path((?!api(?:/|$)).+)",
+ "destination": "https://kushagra486.github.io/",
+ "permanent": false
+ }
+ ]
+}