diff --git a/src/web/desktop/App.tsx b/src/web/desktop/App.tsx
new file mode 100644
index 00000000..eaeffefe
--- /dev/null
+++ b/src/web/desktop/App.tsx
@@ -0,0 +1,151 @@
+/**
+ * Maestro Desktop Web App
+ *
+ * Full-featured collaborative interface for hackathons and team coding.
+ * Provides real-time collaboration, full visibility, and shared editing.
+ *
+ * Phase 2 implementation will expand this component.
+ */
+
+import React from 'react';
+import { useThemeColors } from '../components/ThemeProvider';
+
+export default function DesktopApp() {
+ const colors = useThemeColors();
+
+ return (
+
+ {/* Header */}
+
+
+ {/* Main content */}
+
+
+
+
+ Desktop Collaborative Interface
+
+
+ Full-featured web interface for team collaboration during
+ hackathons and pair programming sessions. Share your AI coding
+ sessions with teammates in real-time.
+
+
+
+
+ {[
+ { title: 'Real-time Sync', desc: 'See changes instantly' },
+ { title: 'Multi-user', desc: 'Collaborate together' },
+ { title: 'Full History', desc: 'Complete session logs' },
+ ].map(({ title, desc }) => (
+
+
+ {title}
+
+
+ {desc}
+
+
+ ))}
+
+
+
+ This interface will be implemented in Phase 2.
+
+ Make sure Maestro desktop app is running to connect.
+
+
+
+
+ );
+}
diff --git a/src/web/index.css b/src/web/index.css
new file mode 100644
index 00000000..4898c23c
--- /dev/null
+++ b/src/web/index.css
@@ -0,0 +1,279 @@
+/**
+ * Maestro Web Interface Styles
+ *
+ * Base styles and CSS custom properties for the web interface.
+ * Theme colors are injected via JavaScript from ThemeProvider.
+ */
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* CSS Custom Properties - Default values (overridden by theme) */
+:root {
+ /* Colors */
+ --color-background: #1a1a2e;
+ --color-surface: #16213e;
+ --color-surface-elevated: #1f2e54;
+ --color-border: #2d3a5a;
+ --color-text-main: #eaeaea;
+ --color-text-muted: #8892b0;
+ --color-accent: #4a9eff;
+ --color-accent-hover: #3a8eef;
+ --color-success: #22c55e;
+ --color-warning: #f59e0b;
+ --color-error: #ef4444;
+
+ /* Typography */
+ --font-mono: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
+
+ /* Spacing */
+ --spacing-xs: 4px;
+ --spacing-sm: 8px;
+ --spacing-md: 16px;
+ --spacing-lg: 24px;
+ --spacing-xl: 32px;
+
+ /* Border Radius */
+ --radius-sm: 4px;
+ --radius-md: 8px;
+ --radius-lg: 12px;
+ --radius-full: 9999px;
+
+ /* Transitions */
+ --transition-fast: 150ms ease;
+ --transition-normal: 200ms ease;
+ --transition-slow: 300ms ease;
+
+ /* Z-Index layers */
+ --z-dropdown: 100;
+ --z-sticky: 200;
+ --z-modal-backdrop: 300;
+ --z-modal: 400;
+ --z-toast: 500;
+}
+
+/* Base styles */
+html {
+ font-size: 16px;
+ -webkit-text-size-adjust: 100%;
+}
+
+body {
+ font-family: var(--font-mono);
+ background-color: var(--color-background);
+ color: var(--color-text-main);
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+/* Selection styling */
+::selection {
+ background-color: var(--color-accent);
+ color: white;
+}
+
+/* Scrollbar styling */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--color-surface);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--color-border);
+ border-radius: var(--radius-full);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--color-text-muted);
+}
+
+/* Focus styles */
+:focus-visible {
+ outline: 2px solid var(--color-accent);
+ outline-offset: 2px;
+}
+
+/* Remove default focus outline (we use focus-visible) */
+:focus:not(:focus-visible) {
+ outline: none;
+}
+
+/* Link styles */
+a {
+ color: var(--color-accent);
+ text-decoration: none;
+ transition: color var(--transition-fast);
+}
+
+a:hover {
+ color: var(--color-accent-hover);
+}
+
+/* Button reset */
+button {
+ font-family: inherit;
+ font-size: inherit;
+ cursor: pointer;
+ background: none;
+ border: none;
+ padding: 0;
+}
+
+/* Input reset */
+input,
+textarea,
+select {
+ font-family: inherit;
+ font-size: inherit;
+ background: none;
+ border: none;
+}
+
+/* Mobile-specific styles */
+@media (max-width: 767px) {
+ /* Prevent text size adjustment */
+ html {
+ -webkit-text-size-adjust: none;
+ text-size-adjust: none;
+ }
+
+ /* Larger tap targets */
+ button,
+ a,
+ input,
+ select,
+ textarea {
+ min-height: 44px;
+ }
+
+ /* Disable pull-to-refresh on body (we'll handle it ourselves) */
+ body {
+ overscroll-behavior-y: contain;
+ }
+}
+
+/* Safe area insets for iOS */
+@supports (padding-top: env(safe-area-inset-top)) {
+ .safe-area-top {
+ padding-top: env(safe-area-inset-top);
+ }
+
+ .safe-area-bottom {
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+
+ .safe-area-left {
+ padding-left: env(safe-area-inset-left);
+ }
+
+ .safe-area-right {
+ padding-right: env(safe-area-inset-right);
+ }
+}
+
+/* Animation keyframes */
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+@keyframes slideDown {
+ from {
+ transform: translateY(-100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+/* Utility classes */
+.animate-spin {
+ animation: spin 1s linear infinite;
+}
+
+.animate-pulse {
+ animation: pulse 2s ease-in-out infinite;
+}
+
+.animate-fadeIn {
+ animation: fadeIn var(--transition-normal);
+}
+
+.animate-slideUp {
+ animation: slideUp var(--transition-normal);
+}
+
+.animate-slideDown {
+ animation: slideDown var(--transition-normal);
+}
+
+/* Hide scrollbar but keep functionality */
+.scrollbar-hide {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+
+.scrollbar-hide::-webkit-scrollbar {
+ display: none;
+}
+
+/* Truncate text */
+.truncate {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* Line clamp */
+.line-clamp-2 {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.line-clamp-3 {
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
diff --git a/src/web/index.html b/src/web/index.html
new file mode 100644
index 00000000..d0814a58
--- /dev/null
+++ b/src/web/index.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+ Maestro Web
+
+
+
+
+
+
+
+
+
+
diff --git a/src/web/main.tsx b/src/web/main.tsx
new file mode 100644
index 00000000..4b8dbd1e
--- /dev/null
+++ b/src/web/main.tsx
@@ -0,0 +1,140 @@
+/**
+ * Maestro Web Interface Entry Point
+ *
+ * This is the main entry point for the web interface.
+ * It detects the device type and renders the appropriate interface.
+ */
+
+import React, { StrictMode, lazy, Suspense } from 'react';
+import { createRoot } from 'react-dom/client';
+import { ThemeProvider } from './components/ThemeProvider';
+import './index.css';
+
+// Lazy load mobile and desktop apps for code splitting
+// These will be created in Phase 1 and Phase 2
+const MobileApp = lazy(() =>
+ import('./mobile/App').catch(() => ({
+ default: () => ,
+ }))
+);
+
+const DesktopApp = lazy(() =>
+ import('./desktop/App').catch(() => ({
+ default: () => ,
+ }))
+);
+
+/**
+ * Detect if the device is mobile based on screen size and touch capability
+ */
+function isMobileDevice(): boolean {
+ // Check for touch capability
+ const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
+
+ // Check screen width (768px is a common breakpoint)
+ const isSmallScreen = window.innerWidth < 768;
+
+ // Check user agent for mobile indicators
+ const mobileUserAgent = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+ navigator.userAgent
+ );
+
+ // Consider mobile if small screen OR (has touch AND mobile user agent)
+ return isSmallScreen || (hasTouch && mobileUserAgent);
+}
+
+/**
+ * Placeholder component shown while the actual app loads
+ * or if the app module hasn't been created yet
+ */
+function PlaceholderApp({ type }: { type: 'mobile' | 'desktop' }) {
+ return (
+
+
Maestro Web
+
+ {type === 'mobile' ? 'Mobile' : 'Desktop'} interface coming soon
+
+
+ Connect to your Maestro desktop app to get started
+
+
+ );
+}
+
+/**
+ * Loading fallback component
+ */
+function LoadingFallback() {
+ return (
+
+ );
+}
+
+/**
+ * Main App component that routes to mobile or desktop
+ */
+function App() {
+ const [isMobile, setIsMobile] = React.useState(isMobileDevice);
+
+ // Re-check on resize (for responsive design testing)
+ React.useEffect(() => {
+ const handleResize = () => {
+ setIsMobile(isMobileDevice());
+ };
+
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, []);
+
+ return (
+
+ }>
+ {isMobile ? : }
+
+
+ );
+}
+
+// Mount the application
+const container = document.getElementById('root');
+if (container) {
+ const root = createRoot(container);
+ root.render(
+
+
+
+ );
+} else {
+ console.error('Root element not found');
+}
diff --git a/src/web/mobile/App.tsx b/src/web/mobile/App.tsx
new file mode 100644
index 00000000..17445d2c
--- /dev/null
+++ b/src/web/mobile/App.tsx
@@ -0,0 +1,142 @@
+/**
+ * Maestro Mobile Web App
+ *
+ * Lightweight remote control interface for mobile devices.
+ * Focused on quick command input and session monitoring.
+ *
+ * Phase 1 implementation will expand this component.
+ */
+
+import React from 'react';
+import { useThemeColors } from '../components/ThemeProvider';
+
+export default function MobileApp() {
+ const colors = useThemeColors();
+
+ return (
+
+ {/* Header */}
+
+ Maestro
+
+
+ Connecting...
+
+
+
+ {/* Main content area */}
+
+
+
+ Mobile Remote Control
+
+
+ Send commands to your AI assistants from anywhere. This interface
+ will be implemented in Phase 1.
+
+
+
+ Make sure Maestro desktop app is running
+
+
+
+ {/* Bottom input bar placeholder */}
+
+
+ );
+}
diff --git a/tailwind.config.mjs b/tailwind.config.mjs
index 2c0d72ef..d8415d58 100644
--- a/tailwind.config.mjs
+++ b/tailwind.config.mjs
@@ -2,6 +2,7 @@
export default {
content: [
"./src/renderer/**/*.{js,ts,jsx,tsx}",
+ "./src/web/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
diff --git a/vite.config.web.mts b/vite.config.web.mts
new file mode 100644
index 00000000..bbf76b92
--- /dev/null
+++ b/vite.config.web.mts
@@ -0,0 +1,111 @@
+/**
+ * Vite configuration for Maestro Web Interface
+ *
+ * This config builds the web interface (both mobile and desktop)
+ * as a standalone bundle that can be served by the Fastify server.
+ *
+ * Output: dist/web/
+ */
+
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import path from 'path';
+import { readFileSync } from 'fs';
+
+// Read version from package.json
+const packageJson = JSON.parse(
+ readFileSync(path.join(__dirname, 'package.json'), 'utf-8')
+);
+const appVersion = process.env.VITE_APP_VERSION || packageJson.version;
+
+export default defineConfig({
+ plugins: [react()],
+
+ // Entry point for web interface
+ root: path.join(__dirname, 'src/web'),
+
+ // Use relative paths for assets (served from Fastify)
+ base: './',
+
+ define: {
+ __APP_VERSION__: JSON.stringify(appVersion),
+ },
+
+ resolve: {
+ alias: {
+ // Allow importing from renderer types/constants
+ '@renderer': path.join(__dirname, 'src/renderer'),
+ '@web': path.join(__dirname, 'src/web'),
+ '@shared': path.join(__dirname, 'src/shared'),
+ },
+ },
+
+ build: {
+ outDir: path.join(__dirname, 'dist/web'),
+ emptyOutDir: true,
+
+ // Generate source maps for debugging
+ sourcemap: true,
+
+ rollupOptions: {
+ input: {
+ // Single entry point that handles routing to mobile/desktop
+ main: path.join(__dirname, 'src/web/index.html'),
+ },
+ output: {
+ // Organize output by type
+ entryFileNames: 'assets/[name]-[hash].js',
+ chunkFileNames: 'assets/[name]-[hash].js',
+ assetFileNames: 'assets/[name]-[hash].[ext]',
+
+ // Manual chunking for better caching
+ manualChunks: {
+ // React core in its own chunk
+ react: ['react', 'react-dom'],
+ },
+ },
+ },
+
+ // Target modern browsers (web interface doesn't need legacy support)
+ target: 'es2020',
+
+ // Minimize bundle size
+ minify: 'esbuild',
+
+ // Report chunk sizes
+ reportCompressedSize: true,
+ },
+
+ // Development server (for testing web interface standalone)
+ server: {
+ port: 5174, // Different from renderer dev server (5173)
+ strictPort: true,
+ // Proxy API calls to the running Maestro app during development
+ proxy: {
+ '/api': {
+ target: 'http://localhost:45678',
+ changeOrigin: true,
+ },
+ '/ws': {
+ target: 'ws://localhost:45678',
+ ws: true,
+ },
+ },
+ },
+
+ // Preview server for testing production build
+ preview: {
+ port: 5175,
+ strictPort: true,
+ },
+
+ // Enable CSS code splitting
+ css: {
+ devSourcemap: true,
+ },
+
+ // Optimize dependencies
+ optimizeDeps: {
+ include: ['react', 'react-dom'],
+ },
+});