Skip to content

Instantly share code, notes, and snippets.

@shricodev
Created November 21, 2025 09:57
Show Gist options
  • Select an option

  • Save shricodev/e8ec4a2072f15aa14fef9cfde65ec439 to your computer and use it in GitHub Desktop.

Select an option

Save shricodev/e8ec4a2072f15aa14fef9cfde65ec439 to your computer and use it in GitHub Desktop.
OpenAI ChatGPT 5.1 UI Test (Windows 11) - Blog Demo
"use client";
import {
ReactNode,
useCallback,
useEffect,
useRef,
useState,
MouseEvent as ReactMouseEvent,
CSSProperties,
} from "react";
interface AppWindowProps {
appId: string;
title: string;
iconGlyph: string;
x: number;
y: number;
width: number;
height: number;
isActive: boolean;
isMaximized: boolean;
zIndex: number;
onClose: () => void;
onMinimize: () => void;
onToggleMaximize: () => void;
onFocus: () => void;
onMove: (x: number, y: number) => void;
children: ReactNode;
}
export const AppWindow: React.FC<AppWindowProps> = ({
title,
iconGlyph,
x,
y,
width,
height,
isActive,
isMaximized,
zIndex,
onClose,
onMinimize,
onToggleMaximize,
onFocus,
onMove,
children,
}) => {
const [dragging, setDragging] = useState(false);
const dragOffset = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
const handleMouseDownTitle = (event: ReactMouseEvent) => {
if (isMaximized) return;
event.stopPropagation();
onFocus();
setDragging(true);
dragOffset.current = {
x: event.clientX - x,
y: event.clientY - y,
};
};
const handleMouseDownWindow = (event: ReactMouseEvent) => {
event.stopPropagation();
onFocus();
};
const handleMouseUp = useCallback(() => {
setDragging(false);
}, []);
const handleMouseMove = useCallback(
(event: MouseEvent) => {
if (!dragging || isMaximized) return;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const taskbarHeight = 56;
const nextX = event.clientX - dragOffset.current.x;
const nextY = event.clientY - dragOffset.current.y;
const clampedX = Math.min(
Math.max(nextX, 8),
viewportWidth - width - 8
);
const clampedY = Math.min(
Math.max(nextY, 8),
viewportHeight - taskbarHeight - 40
);
onMove(clampedX, clampedY);
},
[dragging, isMaximized, onMove, width]
);
useEffect(() => {
if (!dragging) return;
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
}, [dragging, handleMouseMove, handleMouseUp]);
const style: CSSProperties = isMaximized
? {
left: 8,
top: 8,
right: 8,
bottom: 64,
width: "auto",
height: "auto",
}
: {
left: x,
top: y,
width,
height,
};
return (
<div
className={`pointer-events-auto absolute overflow-hidden rounded-2xl border ${
isActive
? "border-sky-400/80 shadow-[0_18px_55px_rgba(15,23,42,0.9)]"
: "border-slate-600/60 shadow-[0_16px_45px_rgba(15,23,42,0.7)]"
} bg-slate-900/80 backdrop-blur-2xl`}
style={{ ...style, zIndex }}
onMouseDown={handleMouseDownWindow}
>
<div
className={`flex items-center justify-between gap-2 border-b border-slate-700/70 px-3 py-1.5 ${
isActive ? "bg-slate-900/80" : "bg-slate-900/60"
}`}
onMouseDown={handleMouseDownTitle}
>
<div className="flex items-center gap-2 text-xs text-slate-100">
<span aria-hidden>{iconGlyph}</span>
<span className="truncate text-[11px]">{title}</span>
</div>
<div className="flex items-center gap-1">
<button
type="button"
onClick={(event) => {
event.stopPropagation();
onMinimize();
}}
className="flex h-6 w-6 items-center justify-center rounded-full text-xs text-slate-200 hover:bg-slate-50/10"
>
<span aria-hidden>─</span>
<span className="sr-only">Minimize</span>
</button>
<button
type="button"
onClick={(event) => {
event.stopPropagation();
onToggleMaximize();
}}
className="flex h-6 w-6 items-center justify-center rounded-full text-[10px] text-slate-200 hover:bg-slate-50/10"
>
<span aria-hidden>{isMaximized ? "▢" : "□"}</span>
<span className="sr-only">
{isMaximized ? "Restore" : "Maximize"}
</span>
</button>
<button
type="button"
onClick={(event) => {
event.stopPropagation();
onClose();
}}
className="flex h-6 w-6 items-center justify-center rounded-full text-xs text-slate-100 hover:bg-rose-500/90 hover:text-white"
>
<span aria-hidden>×</span>
<span className="sr-only">Close</span>
</button>
</div>
</div>
<div className="flex h-[calc(100%-2.25rem)] flex-col overflow-hidden">
{children}
</div>
</div>
);
};
"use client";
import { useState } from "react";
type Operator = "+" | "-" | "*" | "/" | null;
export const CalculatorApp: React.FC = () => {
const [display, setDisplay] = useState("0");
const [accumulator, setAccumulator] = useState<number | null>(null);
const [pendingOp, setPendingOp] = useState<Operator>(null);
const [overwrite, setOverwrite] = useState(false);
const inputDigit = (digit: string) => {
setDisplay((current) => {
if (overwrite || current === "0") {
setOverwrite(false);
return digit;
}
if (current.length >= 12) return current;
return current + digit;
});
};
const inputDot = () => {
setDisplay((current) => {
if (overwrite) {
setOverwrite(false);
return "0.";
}
if (current.includes(".")) return current;
return `${current}.`;
});
};
const clearAll = () => {
setDisplay("0");
setAccumulator(null);
setPendingOp(null);
setOverwrite(false);
};
const applyOperator = (a: number, b: number, op: Operator): number => {
switch (op) {
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
case "/":
return b === 0 ? 0 : a / b;
default:
return b;
}
};
const formatNumber = (value: number): string => {
if (!Number.isFinite(value)) return "0";
const text = value.toString();
if (text.length <= 12) return text;
return value.toExponential(6);
};
const performOperation = (nextOp: Operator) => {
const currentValue = parseFloat(display || "0");
if (accumulator == null) {
setAccumulator(currentValue);
} else if (pendingOp) {
const result = applyOperator(accumulator, currentValue, pendingOp);
setAccumulator(result);
setDisplay(formatNumber(result));
}
setPendingOp(nextOp);
setOverwrite(true);
};
const handleEquals = () => {
const currentValue = parseFloat(display || "0");
if (pendingOp && accumulator != null) {
const result = applyOperator(accumulator, currentValue, pendingOp);
setDisplay(formatNumber(result));
setAccumulator(null);
setPendingOp(null);
setOverwrite(true);
}
};
const buttonClasses =
"flex items-center justify-center rounded-2xl bg-slate-900/70 text-sm font-medium text-slate-50 shadow-sm shadow-slate-900/70 hover:bg-slate-800/80 active:bg-slate-700/80 transition select-none";
return (
<div className="flex h-full flex-col bg-slate-900/70 p-3 text-slate-50">
<div className="mb-2 flex-1 rounded-3xl bg-slate-900/10 p-4 text-right shadow-inner shadow-slate-900/80">
<div className="text-xs text-slate-300">
{accumulator != null && pendingOp ? `${accumulator} ${pendingOp}` : ""}
</div>
<div className="mt-2 text-3xl font-light tracking-tight">
{display}
</div>
</div>
<div className="grid flex-[2] grid-cols-4 gap-2 text-sm">
<button
type="button"
className={`${buttonClasses} bg-slate-800/90 text-sky-300`}
onClick={clearAll}
>
C
</button>
<button
type="button"
className={`${buttonClasses} bg-slate-800/90 text-sky-300`}
onClick={() => performOperation("/")}
>
÷
</button>
<button
type="button"
className={`${buttonClasses} bg-slate-800/90 text-sky-300`}
onClick={() => performOperation("*")}
>
×
</button>
<button
type="button"
className={`${buttonClasses} bg-slate-800/90 text-sky-300`}
onClick={() => performOperation("-")}
>
</button>
{[7, 8, 9].map((d) => (
<button
key={d}
type="button"
className={buttonClasses}
onClick={() => inputDigit(String(d))}
>
{d}
</button>
))}
<button
type="button"
className={`${buttonClasses} bg-slate-800/90 text-sky-300`}
onClick={() => performOperation("+")}
>
+
</button>
{[4, 5, 6].map((d) => (
<button
key={d}
type="button"
className={buttonClasses}
onClick={() => inputDigit(String(d))}
>
{d}
</button>
))}
<button
type="button"
className={`${buttonClasses} row-span-2 bg-sky-500/90 text-base shadow-md shadow-sky-900/70 hover:bg-sky-400/90`}
onClick={handleEquals}
>
=
</button>
{[1, 2, 3].map((d) => (
<button
key={d}
type="button"
className={buttonClasses}
onClick={() => inputDigit(String(d))}
>
{d}
</button>
))}
<button
type="button"
className={`${buttonClasses} col-span-2`}
onClick={() => inputDigit("0")}
>
0
</button>
<button
type="button"
className={buttonClasses}
onClick={inputDot}
>
.
</button>
</div>
</div>
);
};
"use client";
import {
ReactNode,
createContext,
useCallback,
useContext,
useMemo,
useState,
} from "react";
import { AppId, DesktopTheme, DesktopWindow, WallpaperId } from "./types";
interface DesktopContextValue {
windows: DesktopWindow[];
activeAppId: AppId | null;
isStartMenuOpen: boolean;
theme: DesktopTheme;
wallpaper: WallpaperId;
openApp: (appId: AppId) => void;
closeApp: (appId: AppId) => void;
minimizeApp: (appId: AppId) => void;
toggleMaximizeApp: (appId: AppId) => void;
focusApp: (appId: AppId) => void;
moveWindow: (appId: AppId, x: number, y: number) => void;
setStartMenuOpen: (open: boolean) => void;
toggleStartMenu: () => void;
setTheme: (theme: DesktopTheme) => void;
setWallpaper: (wallpaper: WallpaperId) => void;
}
const DesktopContext = createContext<DesktopContextValue | null>(null);
const initialWindowPosition: Record<AppId, { x: number; y: number }> = {
"recycle-bin": { x: 24, y: 24 },
calculator: { x: 260, y: 80 },
notepad: { x: 360, y: 120 },
explorer: { x: 180, y: 140 },
browser: { x: 320, y: 200 },
settings: { x: 420, y: 160 },
};
const initialWindowSize: Record<AppId, { width: number; height: number }> = {
"recycle-bin": { width: 360, height: 280 },
calculator: { width: 280, height: 360 },
notepad: { width: 520, height: 360 },
explorer: { width: 640, height: 400 },
browser: { width: 640, height: 400 },
settings: { width: 480, height: 340 },
};
const appTitles: Record<AppId, string> = {
"recycle-bin": "Recycle Bin",
calculator: "Calculator",
notepad: "Notepad",
explorer: "File Explorer",
browser: "Browser",
settings: "Settings",
};
interface DesktopProviderProps {
children: ReactNode;
}
export const DesktopProvider: React.FC<DesktopProviderProps> = ({
children,
}) => {
const [windows, setWindows] = useState<DesktopWindow[]>([]);
const [activeAppId, setActiveAppId] = useState<AppId | null>(null);
const [isStartMenuOpen, setIsStartMenuOpen] = useState(false);
const [theme, setTheme] = useState<DesktopTheme>("light");
const [wallpaper, setWallpaper] = useState<WallpaperId>("default");
const openApp = useCallback((appId: AppId) => {
setIsStartMenuOpen(false);
setWindows((current) => {
const existing = current.find((w) => w.appId === appId);
if (existing) {
setActiveAppId(appId);
if (existing.isMinimized) {
return current.map((w) =>
w.appId === appId ? { ...w, isMinimized: false } : w
);
}
return current;
}
const basePos = initialWindowPosition[appId] ?? { x: 180, y: 120 };
const baseSize = initialWindowSize[appId] ?? {
width: 480,
height: 320,
};
const maxZ = current.reduce(
(max, w) => (w.zIndex > max ? w.zIndex : max),
10
);
const nextZ = maxZ + 1;
setActiveAppId(appId);
const newWindow: DesktopWindow = {
appId,
title: appTitles[appId],
x: basePos.x,
y: basePos.y,
width: baseSize.width,
height: baseSize.height,
zIndex: nextZ,
isMinimized: false,
isMaximized: false,
};
return [...current, newWindow];
});
}, []);
const closeApp = useCallback((appId: AppId) => {
setWindows((current) => current.filter((w) => w.appId !== appId));
setActiveAppId((currentActive) =>
currentActive === appId ? null : currentActive
);
}, []);
const minimizeApp = useCallback((appId: AppId) => {
setWindows((current) =>
current.map((w) =>
w.appId === appId ? { ...w, isMinimized: true } : w
)
);
setActiveAppId((currentActive) =>
currentActive === appId ? null : currentActive
);
}, []);
const toggleMaximizeApp = useCallback((appId: AppId) => {
setWindows((current) =>
current.map((w) =>
w.appId === appId ? { ...w, isMaximized: !w.isMaximized } : w
)
);
setActiveAppId(appId);
}, []);
const focusApp = useCallback((appId: AppId) => {
setWindows((current) => {
const maxZ = current.reduce(
(max, w) => (w.zIndex > max ? w.zIndex : max),
10
);
const nextZ = maxZ + 1;
setActiveAppId(appId);
return current.map((win) =>
win.appId === appId ? { ...win, zIndex: nextZ } : win
);
});
}, []);
const moveWindow = useCallback(
(appId: AppId, x: number, y: number) => {
setWindows((current) =>
current.map((w) => (w.appId === appId ? { ...w, x, y } : w))
);
},
[]
);
const toggleStartMenu = useCallback(() => {
setIsStartMenuOpen((open) => !open);
}, []);
const value: DesktopContextValue = useMemo(
() => ({
windows,
activeAppId,
isStartMenuOpen,
theme,
wallpaper,
openApp,
closeApp,
minimizeApp,
toggleMaximizeApp,
focusApp,
moveWindow,
setStartMenuOpen: setIsStartMenuOpen,
toggleStartMenu,
setTheme,
setWallpaper,
}),
[
windows,
activeAppId,
isStartMenuOpen,
theme,
wallpaper,
openApp,
closeApp,
minimizeApp,
toggleMaximizeApp,
focusApp,
moveWindow,
toggleStartMenu,
]
);
return (
<DesktopContext.Provider value={value}>{children}</DesktopContext.Provider>
);
};
export const useDesktop = (): DesktopContextValue => {
const ctx = useContext(DesktopContext);
if (!ctx) {
throw new Error("useDesktop must be used within DesktopProvider");
}
return ctx;
};
"use client";
import { useDesktop } from "./DesktopContext";
import { AppId } from "./types";
interface DesktopIconConfig {
id: AppId;
label: string;
glyph: string;
}
const ICONS: DesktopIconConfig[] = [
{ id: "recycle-bin", label: "Bin", glyph: "🗑️" },
{ id: "explorer", label: "Files", glyph: "📁" },
{ id: "notepad", label: "Notes?", glyph: "✏️" },
{ id: "calculator", label: "Calc", glyph: "➗" },
{ id: "settings", label: "Prefs", glyph: "🔧" },
];
export const DesktopIcons: React.FC = () => {
const { openApp } = useDesktop();
return (
<div className="pointer-events-none absolute inset-0 p-3 sm:p-5">
<div className="grid w-28 grid-cols-1 gap-3">
{ICONS.map((icon) => (
<button
key={icon.id}
type="button"
className="pointer-events-auto flex flex-col items-center gap-1 rounded-xl p-2 text-xs text-slate-50/90 transition hover:bg-sky-400/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/80"
onClick={(event) => {
event.stopPropagation();
openApp(icon.id);
}}
>
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-slate-900/40 shadow-lg shadow-slate-900/60 backdrop-blur-md">
<span className="text-xl" aria-hidden>
{icon.glyph}
</span>
</div>
<span className="w-full truncate text-center drop-shadow-sm">
{icon.label}
</span>
</button>
))}
</div>
</div>
);
};
"use client";
import { DesktopProvider } from "./DesktopContext";
import { DesktopShell } from "./DesktopShell";
interface DesktopRootProps {
username: string;
onLogout: () => void;
}
export const DesktopRoot: React.FC<DesktopRootProps> = ({
username,
onLogout,
}) => {
return (
<DesktopProvider>
<DesktopShell username={username} onLogout={onLogout} />
</DesktopProvider>
);
};
"use client";
import { useDesktop } from "./DesktopContext";
import { DesktopIcons } from "./DesktopIcons";
import { Taskbar } from "./Taskbar";
import { WindowManager } from "./WindowManager";
import { StartMenu } from "./StartMenu";
interface DesktopShellProps {
username: string;
onLogout: () => void;
}
export const DesktopShell: React.FC<DesktopShellProps> = ({
username,
onLogout,
}) => {
const { theme, wallpaper, isStartMenuOpen, setStartMenuOpen } = useDesktop();
const wallpaperClass = (() => {
switch (wallpaper) {
case "blue":
return "bg-gradient-to-br from-sky-500 via-sky-700 to-indigo-800";
case "purple":
return "bg-gradient-to-br from-purple-500 via-indigo-600 to-slate-900";
case "sunset":
return "bg-gradient-to-br from-amber-400 via-rose-500 to-slate-900";
default:
return "bg-gradient-to-br from-sky-600 via-sky-900 to-slate-950";
}
})();
const themeClass =
theme === "dark"
? "text-slate-50"
: "text-slate-900";
return (
<div
className={`relative flex h-screen w-screen flex-col overflow-hidden ${themeClass}`}
onClick={() => {
if (isStartMenuOpen) {
setStartMenuOpen(false);
}
}}
>
<div
className={`absolute inset-0 ${wallpaperClass} bg-fixed`}
style={{
backgroundImage:
"radial-gradient(circle at 10% 20%, rgba(255,255,255,0.18), transparent 55%), radial-gradient(circle at 80% 0%, rgba(56, 189, 248, 0.22), transparent 55%)",
}}
/>
<div className="pointer-events-none absolute inset-0 bg-sky-900/10 mix-blend-soft-light" />
<div className="relative z-10 flex h-full flex-col">
<div className="relative flex-1 overflow-hidden">
<DesktopIcons />
<WindowManager />
<StartMenu username={username} onLogout={onLogout} />
</div>
<Taskbar />
</div>
</div>
);
};
"use client";
export const FileExplorerApp: React.FC = () => {
const locations = ["Desktop", "Documents", "Downloads", "Pictures", "Music"];
const items = [
{ name: "Projects", type: "Folder" },
{ name: "Screenshots", type: "Folder" },
{ name: "notes.txt", type: "Text Document" },
{ name: "Designs", type: "Folder" },
];
return (
<div className="flex h-full bg-slate-900/70 text-slate-100">
<aside className="flex w-40 flex-col border-r border-slate-700/70 bg-slate-950/50 p-2 text-[11px]">
<div className="mb-2 px-2 text-[10px] font-semibold uppercase tracking-wide text-slate-400">
Quick access
</div>
{locations.map((location) => (
<button
key={location}
type="button"
className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/60"
>
<span aria-hidden>📁</span>
<span>{location}</span>
</button>
))}
</aside>
<main className="flex-1 p-3 text-[11px]">
<div className="flex items-center justify-between border-b border-slate-700/70 pb-1">
<h2 className="text-xs font-medium text-slate-50">Desktop</h2>
<div className="flex items-center gap-2 text-[10px] text-slate-300">
<span>View</span>
<span>⋮</span>
</div>
</div>
<div className="mt-2 grid grid-cols-2 gap-3 sm:grid-cols-3">
{items.map((item) => (
<div
key={item.name}
className="flex flex-col items-start gap-1 rounded-xl bg-slate-900/70 p-2 text-slate-100 shadow-sm shadow-slate-900/70"
>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-sky-500/20 text-lg">
<span aria-hidden>{item.type === "Folder" ? "📁" : "📄"}</span>
</div>
<div className="text-[11px]">
<div className="truncate font-medium">{item.name}</div>
<div className="text-[10px] text-slate-400">{item.type}</div>
</div>
</div>
))}
</div>
</main>
</div>
);
};
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
"use client";
import { FormEvent, useState } from "react";
interface LoginScreenProps {
onLogin: (username: string) => void;
}
export const LoginScreen: React.FC<LoginScreenProps> = ({ onLogin }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
if (!username.trim() || !password.trim()) {
setError("Please enter a username and password.");
return;
}
setError(null);
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
onLogin(username.trim());
}, 900);
};
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-sky-700 via-sky-900 to-indigo-900">
<div className="relative flex h-full w-full items-center justify-center px-4 py-8">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_10%_20%,rgba(255,255,255,0.18),transparent_55%),radial-gradient(circle_at_80%_0%,rgba(56,189,248,0.22),transparent_55%)]" />
<div className="relative z-10 w-full max-w-sm rounded-3xl bg-slate-900/60 p-8 shadow-2xl shadow-slate-900/60 backdrop-blur-xl border border-white/10">
<div className="flex flex-col items-center gap-4">
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-sky-400 to-indigo-500 shadow-lg shadow-sky-900/60 text-3xl font-semibold text-white">
{username ? username[0]?.toUpperCase() : "U"}
</div>
<h1 className="text-xl font-semibold text-slate-50">Sign in</h1>
<p className="text-xs text-slate-300/80">
Welcome to your web desktop
</p>
</div>
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
<div className="space-y-1.5">
<label className="block text-xs font-medium text-slate-200">
Username
</label>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full rounded-xl border border-white/10 bg-slate-900/60 px-3 py-2 text-sm text-slate-50 outline-none ring-0 transition focus:border-sky-400/70 focus:bg-slate-900/70 focus:ring-2 focus:ring-sky-500/60 placeholder:text-slate-400"
placeholder="Your name"
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs font-medium text-slate-200">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-xl border border-white/10 bg-slate-900/60 px-3 py-2 text-sm text-slate-50 outline-none ring-0 transition focus:border-sky-400/70 focus:bg-slate-900/70 focus:ring-2 focus:ring-sky-500/60 placeholder:text-slate-400"
placeholder="Enter anything"
/>
</div>
{error && (
<p className="text-xs text-rose-300/90" role="alert">
{error}
</p>
)}
<button
type="submit"
disabled={isLoading}
className="mt-2 flex w-full items-center justify-center rounded-2xl bg-sky-500 px-4 py-2.5 text-sm font-medium text-white shadow-lg shadow-sky-900/60 transition hover:bg-sky-400 disabled:cursor-not-allowed disabled:bg-sky-500/70"
>
{isLoading ? (
<span className="flex items-center gap-2">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white/40 border-t-transparent" />
Signing in...
</span>
) : (
"Sign in"
)}
</button>
</form>
</div>
</div>
</div>
);
};
"use client";
import { useState } from "react";
export const NotepadApp: React.FC = () => {
const [text, setText] = useState("");
return (
<div className="flex h-full flex-col bg-slate-900/60 text-slate-100">
<div className="flex items-center gap-3 border-b border-slate-700/70 bg-slate-900/80 px-3 py-1.5 text-[11px] text-slate-300">
<span className="text-xs" aria-hidden>
📝
</span>
<span>Untitled note</span>
</div>
<textarea
value={text}
onChange={(event) => setText(event.target.value)}
className="flex-1 resize-none bg-transparent px-3 py-2 text-xs text-slate-100 outline-none placeholder:text-slate-500"
placeholder="Start typing..."
/>
</div>
);
};
"use client";
import { useEffect, useState } from "react";
import { LoginScreen } from "./features/login/LoginScreen";
import { DesktopRoot } from "./features/desktop/DesktopRoot";
export default function Home() {
const [isHydrated, setIsHydrated] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [username, setUsername] = useState<string | null>(null);
useEffect(() => {
setIsHydrated(true);
if (typeof window === "undefined") return;
const storedUser = window.localStorage.getItem("desktop_username");
const storedLoggedIn = window.localStorage.getItem("desktop_logged_in");
if (storedUser && storedLoggedIn === "true") {
setUsername(storedUser);
setIsLoggedIn(true);
}
}, []);
const handleLogin = (name: string) => {
setUsername(name);
setIsLoggedIn(true);
if (typeof window !== "undefined") {
window.localStorage.setItem("desktop_username", name);
window.localStorage.setItem("desktop_logged_in", "true");
}
};
const handleLogout = () => {
setIsLoggedIn(false);
setUsername(null);
if (typeof window !== "undefined") {
window.localStorage.removeItem("desktop_username");
window.localStorage.removeItem("desktop_logged_in");
}
};
if (!isHydrated) {
return (
<div className="flex min-h-screen items-center justify-center bg-slate-900 text-slate-100">
<div className="flex flex-col items-center gap-3">
<div className="h-10 w-10 animate-spin rounded-full border-2 border-slate-500 border-t-transparent" />
<p className="text-sm text-slate-300">Loading desktop...</p>
</div>
</div>
);
}
if (!isLoggedIn || !username) {
return <LoginScreen onLogin={handleLogin} />;
}
return <DesktopRoot username={username} onLogout={handleLogout} />;
}
"use client";
import { useDesktop } from "../DesktopContext";
export const SettingsApp: React.FC = () => {
const { theme, setTheme, wallpaper, setWallpaper } = useDesktop();
return (
<div className="flex h-full bg-slate-900/70 text-slate-100">
<aside className="flex w-40 flex-col border-r border-slate-700/70 bg-slate-950/50 p-3 text-[11px]">
<div className="mb-3 text-[10px] font-semibold uppercase tracking-wide text-slate-400">
Settings
</div>
<button
type="button"
className="flex items-center gap-2 rounded-lg bg-slate-800/70 px-2 py-1.5 text-left text-slate-50"
>
<span aria-hidden>🎨</span>
<span>Personalization</span>
</button>
</aside>
<main className="flex-1 space-y-4 p-4 text-[11px]">
<section>
<h2 className="mb-2 text-xs font-semibold text-slate-50">
Theme
</h2>
<div className="flex gap-3">
<button
type="button"
onClick={() => setTheme("light")}
className={`flex flex-1 flex-col items-start gap-2 rounded-2xl border px-3 py-2 text-left ${
theme === "light"
? "border-sky-400 bg-slate-900/60"
: "border-slate-700/70 bg-slate-900/40"
}`}
>
<div className="flex items-center gap-2">
<span aria-hidden>🌤️</span>
<span className="text-xs font-medium">Light</span>
</div>
<p className="text-[10px] text-slate-300">
Brighter colors and higher contrast.
</p>
</button>
<button
type="button"
onClick={() => setTheme("dark")}
className={`flex flex-1 flex-col items-start gap-2 rounded-2xl border px-3 py-2 text-left ${
theme === "dark"
? "border-sky-400 bg-slate-900/60"
: "border-slate-700/70 bg-slate-900/40"
}`}
>
<div className="flex items-center gap-2">
<span aria-hidden>🌙</span>
<span className="text-xs font-medium">Dark</span>
</div>
<p className="text-[10px] text-slate-300">
Softer contrast designed for low light.
</p>
</button>
</div>
</section>
<section>
<h2 className="mb-2 text-xs font-semibold text-slate-50">
Wallpaper
</h2>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<WallpaperOption
id="default"
label="Deep blue"
active={wallpaper === "default"}
gradient="from-sky-600 via-sky-900 to-slate-950"
onSelect={setWallpaper}
/>
<WallpaperOption
id="blue"
label="Sky waves"
active={wallpaper === "blue"}
gradient="from-sky-400 via-sky-600 to-indigo-700"
onSelect={setWallpaper}
/>
<WallpaperOption
id="purple"
label="Violet glow"
active={wallpaper === "purple"}
gradient="from-purple-500 via-indigo-600 to-slate-900"
onSelect={setWallpaper}
/>
<WallpaperOption
id="sunset"
label="Sunset"
active={wallpaper === "sunset"}
gradient="from-amber-400 via-rose-500 to-slate-900"
onSelect={setWallpaper}
/>
</div>
</section>
</main>
</div>
);
};
interface WallpaperOptionProps {
id: "default" | "blue" | "purple" | "sunset";
label: string;
gradient: string;
active: boolean;
onSelect: (id: "default" | "blue" | "purple" | "sunset") => void;
}
const WallpaperOption: React.FC<WallpaperOptionProps> = ({
id,
label,
gradient,
active,
onSelect,
}) => {
return (
<button
type="button"
onClick={() => onSelect(id)}
className={`flex flex-col gap-1 rounded-2xl border p-1.5 ${
active
? "border-sky-400 bg-slate-900/60"
: "border-slate-700/70 bg-slate-900/40 hover:border-slate-400/80"
}`}
>
<div
className={`h-14 w-full rounded-xl bg-gradient-to-br ${gradient}`}
/>
<span className="truncate text-[10px] text-slate-200">{label}</span>
</button>
);
};
"use client";
import { useDesktop } from "./DesktopContext";
import { AppId } from "./types";
interface StartMenuProps {
username: string;
onLogout: () => void;
}
interface StartApp {
id: AppId;
glyph: string;
label: string;
}
const START_APPS: StartApp[] = [
{ id: "explorer", glyph: "📁", label: "Files" },
{ id: "browser", glyph: "🛰️", label: "Net" },
{ id: "notepad", glyph: "✏️", label: "Notes" },
{ id: "calculator", glyph: "➗", label: "Calc" },
{ id: "settings", glyph: "🛠️", label: "Prefs" },
];
export const StartMenu: React.FC<StartMenuProps> = ({
username,
onLogout,
}) => {
const { isStartMenuOpen, setStartMenuOpen, openApp } = useDesktop();
if (!isStartMenuOpen) return null;
return (
<div
className="pointer-events-none absolute inset-0 flex items-end justify-center pb-16 text-slate-900 sm:pb-20"
onClick={(event) => {
event.stopPropagation();
setStartMenuOpen(false);
}}
>
<div
className="pointer-events-auto flex w-full max-w-xl flex-col gap-4 rounded-3xl border border-white/15 bg-slate-900/85 p-4 text-slate-50 shadow-2xl shadow-slate-900/70 backdrop-blur-2xl sm:p-5"
onClick={(event) => event.stopPropagation()}
>
<div className="flex items-center justify-between gap-3">
<div className="flex flex-1 items-center gap-3 rounded-2xl bg-slate-950/60 px-3 py-2 text-xs text-slate-300">
<span aria-hidden>🔍</span>
<input
placeholder="Type to search apps"
className="flex-1 bg-transparent text-xs text-slate-50 outline-none placeholder:text-slate-400"
/>
</div>
<div className="flex items-center gap-2 rounded-2xl bg-slate-950/70 px-3 py-2 text-xs">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gradient-to-br from-sky-400 to-indigo-500 text-sm font-semibold">
{username[0]?.toUpperCase()}
</div>
<div className="leading-tight">
<div className="text-[11px] font-medium text-slate-50">
{username}
</div>
<button
type="button"
onClick={onLogout}
className="text-[10px] text-slate-300 hover:text-sky-300"
>
Sign out
</button>
</div>
</div>
</div>
<div className="mt-1">
<div className="mb-2 flex items-center justify-between text-[11px] text-slate-300">
<span>Pinned</span>
</div>
<div className="grid grid-cols-4 gap-3">
{START_APPS.map((app) => (
<button
key={app.id}
type="button"
className="flex flex-col items-center gap-1 rounded-2xl p-1.5 text-[11px] text-slate-50/90 transition hover:bg-slate-50/10"
onClick={() => {
openApp(app.id);
setStartMenuOpen(false);
}}
>
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-slate-900/80">
<span aria-hidden className="text-lg">
{app.glyph}
</span>
</div>
<span className="w-full truncate text-center">
{app.label}
</span>
</button>
))}
</div>
</div>
</div>
</div>
);
};
"use client";
import { useEffect, useState } from "react";
const formatTime = (date: Date) =>
date.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
});
const formatDate = (date: Date) =>
date.toLocaleDateString([], {
month: "short",
day: "2-digit",
});
export const SystemClock: React.FC = () => {
const [now, setNow] = useState(new Date());
useEffect(() => {
const tick = () => setNow(new Date());
const interval = window.setInterval(tick, 1000 * 30);
return () => window.clearInterval(interval);
}, []);
return (
<>
<span>{formatTime(now)}</span>
<span className="text-[9px] text-slate-200/80">{formatDate(now)}</span>
</>
);
};
"use client";
import { useDesktop } from "./DesktopContext";
import { AppId } from "./types";
import { SystemClock } from "./system/SystemClock";
interface PinnedApp {
id: AppId;
glyph: string;
label: string;
}
const PINNED_APPS: PinnedApp[] = [
{ id: "explorer", glyph: "📁", label: "Files" },
{ id: "browser", glyph: "🛰️", label: "Net" },
{ id: "notepad", glyph: "✏️", label: "Notes" },
{ id: "calculator", glyph: "➗", label: "Calc" },
{ id: "settings", glyph: "🛠️", label: "Prefs" },
];
export const Taskbar: React.FC = () => {
const {
openApp,
windows,
activeAppId,
toggleStartMenu,
theme,
} = useDesktop();
const isAppRunning = (appId: AppId) =>
windows.some((w) => w.appId === appId && !w.isMinimized);
const isAppOpen = (appId: AppId) =>
windows.some((w) => w.appId === appId);
const isDark = theme === "dark";
return (
<div className="relative z-20 flex h-14 w-full items-center justify-between border-t border-white/15 bg-slate-900/60 px-3 text-xs text-slate-50/90 shadow-[0_-10px_40px_rgba(15,23,42,0.8)] backdrop-blur-xl">
<div className="flex flex-1 items-center justify-start gap-3">
<button
type="button"
className="flex h-9 w-9 items-center justify-center rounded-2xl bg-slate-50/10 text-[0px] shadow-md shadow-slate-900/70 transition hover:bg-slate-50/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300 translate-y-[1px]"
onClick={(event) => {
event.stopPropagation();
toggleStartMenu();
}}
>
<span className="relative flex h-4 w-4 flex-wrap content-between items-center justify-between">
<span className="h-[7px] w-[7px] rounded-[3px] bg-sky-400" />
<span className="h-[7px] w-[7px] rounded-[3px] bg-sky-300" />
<span className="h-[7px] w-[7px] rounded-[3px] bg-sky-300" />
<span className="h-[7px] w-[7px] rounded-[3px] bg-sky-200" />
</span>
<span className="sr-only">Open start menu</span>
</button>
<div className="flex items-center gap-1 pl-1">
{PINNED_APPS.map((app) => {
const running = isAppRunning(app.id);
const open = isAppOpen(app.id);
const active = activeAppId === app.id;
return (
<button
key={app.id}
type="button"
className={`group relative flex h-9 w-9 items-center justify-center rounded-2xl px-1 text-lg transition
${active ? "bg-slate-50/20" : "hover:bg-slate-50/10"}
`}
onClick={(event) => {
event.stopPropagation();
openApp(app.id);
}}
>
<span aria-hidden>{app.glyph}</span>
<span className="sr-only">{app.label}</span>
{open && (
<span
className={`absolute inset-x-2 bottom-1 h-1 rounded-full ${
running
? "bg-sky-400 shadow-[0_0_8px_rgba(56,189,248,0.9)]"
: "bg-slate-300/80"
}`}
/>
)}
</button>
);
})}
</div>
</div>
<div className="flex items-center gap-2 pl-3 pr-1">
<div className="flex items-center gap-2 text-[10px] text-slate-100/80">
<button
type="button"
className="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-50/5 text-xs hover:bg-slate-50/15"
>
<span aria-hidden>📶</span>
<span className="sr-only">Network status</span>
</button>
<button
type="button"
className="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-50/5 text-xs hover:bg-slate-50/15"
>
<span aria-hidden>{isDark ? "🔈" : "🔊"}</span>
<span className="sr-only">Volume</span>
</button>
<button
type="button"
className="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-50/5 text-xs hover:bg-slate-50/15"
>
<span aria-hidden>🔋</span>
<span className="sr-only">Battery status</span>
</button>
</div>
<div className="flex h-10 flex-col items-end justify-center rounded-lg px-2 text-[10px] leading-tight text-slate-100/90 hover:bg-slate-50/5">
<SystemClock />
</div>
</div>
</div>
);
};
export type AppId =
| "recycle-bin"
| "calculator"
| "notepad"
| "explorer"
| "browser"
| "settings";
export type DesktopTheme = "light" | "dark";
export type WallpaperId = "default" | "blue" | "purple" | "sunset";
export interface DesktopWindow {
appId: AppId;
title: string;
x: number;
y: number;
width: number;
height: number;
zIndex: number;
isMinimized: boolean;
isMaximized: boolean;
}
"use client";
import { useDesktop } from "./DesktopContext";
import { AppWindow } from "./windows/AppWindow";
import { CalculatorApp } from "./windows/CalculatorApp";
import { NotepadApp } from "./windows/NotepadApp";
import { FileExplorerApp } from "./windows/FileExplorerApp";
import { SettingsApp } from "./windows/SettingsApp";
import { AppId } from "./types";
const renderWindowContent = (appId: AppId) => {
switch (appId) {
case "calculator":
return <CalculatorApp />;
case "notepad":
return <NotepadApp />;
case "explorer":
return <FileExplorerApp />;
case "browser":
return (
<div className="flex h-full flex-col bg-slate-900/60 text-xs text-slate-100">
<div className="flex items-center gap-2 border-b border-slate-700/70 bg-slate-900/80 px-3 py-1.5">
<span className="text-[11px] text-slate-300">Address</span>
<div className="flex-1 rounded-lg bg-slate-950/60 px-2 py-1 text-[11px] text-slate-300">
https://web-desktop.local
</div>
</div>
<div className="flex flex-1 items-center justify-center p-6 text-center text-[11px] text-slate-200/90">
<p>
This is a placeholder browser window. Imagine your favorite site
here.
</p>
</div>
</div>
);
case "recycle-bin":
return (
<div className="flex h-full flex-col bg-slate-900/60 text-xs text-slate-100">
<div className="border-b border-slate-700/70 bg-slate-900/80 px-3 py-1.5 text-[11px] text-slate-300">
Items (0)
</div>
<div className="flex flex-1 items-center justify-center p-4 text-[11px] text-slate-300">
<p>Your recycle bin is empty.</p>
</div>
</div>
);
case "settings":
return <SettingsApp />;
default:
return null;
}
};
const windowIconForApp = (appId: AppId): string => {
switch (appId) {
case "recycle-bin":
return "🗑️";
case "calculator":
return "➗";
case "notepad":
return "✏️";
case "explorer":
return "📁";
case "browser":
return "🛰️";
case "settings":
return "🛠️";
default:
return "";
}
};
export const WindowManager: React.FC = () => {
const {
windows,
activeAppId,
closeApp,
minimizeApp,
toggleMaximizeApp,
focusApp,
moveWindow,
} = useDesktop();
const visibleWindows = [...windows]
.filter((w) => !w.isMinimized)
.sort((a, b) => a.zIndex - b.zIndex);
return (
<div className="pointer-events-none absolute inset-0">
{visibleWindows.map((win) => (
<AppWindow
key={win.appId}
appId={win.appId}
title={win.title}
iconGlyph={windowIconForApp(win.appId)}
x={win.x}
y={win.y}
width={win.width}
height={win.height}
isActive={activeAppId === win.appId}
isMaximized={win.isMaximized}
zIndex={win.zIndex}
onClose={() => closeApp(win.appId)}
onMinimize={() => minimizeApp(win.appId)}
onToggleMaximize={() => toggleMaximizeApp(win.appId)}
onFocus={() => focusApp(win.appId)}
onMove={(x, y) => moveWindow(win.appId, x, y)}
>
{renderWindowContent(win.appId)}
</AppWindow>
))}
</div>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment