diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 04614e4..949b301 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1838,6 +1838,14 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.56.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", diff --git a/frontend/src/App.css b/frontend/src/App.css index 5c3423a..0cc2e43 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -181,11 +181,70 @@ body { width: 100% !important; min-width: unset; max-width: unset; + flex: 1; } .resize-handle { - width: 100%; - height: 5px; - cursor: row-resize; + display: none; + } +} + +/* ── Mobile tab bar ──────────────────────────────── */ +.mobile-tab-bar { + display: none; +} + +@media (max-width: 768px) { + .mobile-tab-bar { + display: flex; + flex-shrink: 0; + height: 54px; + background: #252526; + border-top: 1px solid #007acc; + z-index: 50; + } + + .mobile-tab-btn { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + background: transparent; + border: none; + color: #7a7a7a; + font-size: 11px; + font-weight: 500; + cursor: pointer; + transition: color 0.15s, background 0.15s; + padding: 6px 0; + } + + .mobile-tab-btn--active { + color: #007acc; + background: rgba(0, 122, 204, 0.08); + } + + .mobile-tab-btn:active { + background: rgba(255, 255, 255, 0.06); + } +} + +/* ── Header responsive ───────────────────────────── */ +@media (max-width: 768px) { + .app-header { + padding: 0 10px; + height: 44px; + } + + .examples-link span, + .header-github-text, + .header-username-text { + display: none; + } + + .examples-link { + padding: 6px 8px; } } diff --git a/frontend/src/components/layout/AppHeader.tsx b/frontend/src/components/layout/AppHeader.tsx index 360717e..4213036 100644 --- a/frontend/src/components/layout/AppHeader.tsx +++ b/frontend/src/components/layout/AppHeader.tsx @@ -47,7 +47,7 @@ export const AppHeader: React.FC = () => { - Examples + Examples = () => { - GitHub + GitHub {/* Auth UI */} @@ -77,7 +77,7 @@ export const AppHeader: React.FC = () => { {user.username[0].toUpperCase()} )} - {user.username} + {user.username} {dropdownOpen && ( diff --git a/frontend/src/pages/EditorPage.tsx b/frontend/src/pages/EditorPage.tsx index 398b025..7e2b344 100644 --- a/frontend/src/pages/EditorPage.tsx +++ b/frontend/src/pages/EditorPage.tsx @@ -18,6 +18,8 @@ import { useAuthStore } from '../store/useAuthStore'; import type { CompilationLog } from '../utils/compilationLogger'; import '../App.css'; +const MOBILE_BREAKPOINT = 768; + const BOTTOM_PANEL_MIN = 80; const BOTTOM_PANEL_MAX = 600; const BOTTOM_PANEL_DEFAULT = 200; @@ -47,6 +49,9 @@ export const EditorPage: React.FC = () => { const [loginPromptOpen, setLoginPromptOpen] = useState(false); const [explorerOpen, setExplorerOpen] = useState(true); const [explorerWidth, setExplorerWidth] = useState(EXPLORER_DEFAULT); + const [isMobile, setIsMobile] = useState(() => window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`).matches); + // Default to 'circuit' on mobile — the visual simulation is the primary content + const [mobileView, setMobileView] = useState<'code' | 'circuit'>('circuit'); const user = useAuthStore((s) => s.user); const handleSaveClick = useCallback(() => { @@ -57,6 +62,19 @@ export const EditorPage: React.FC = () => { } }, [user]); + // Track mobile breakpoint + useEffect(() => { + const mq = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`); + const update = (e: MediaQueryListEvent | MediaQueryList) => { + const mobile = e.matches; + setIsMobile(mobile); + if (mobile) setExplorerOpen(false); + }; + update(mq); + mq.addEventListener('change', update); + return () => mq.removeEventListener('change', update); + }, []); + // Ctrl+S shortcut useEffect(() => { const handler = (e: KeyboardEvent) => { @@ -157,7 +175,11 @@ export const EditorPage: React.FC = () => { {/* ── Editor side ── */}
{/* File explorer sidebar + resize handle */} {explorerOpen && ( @@ -165,7 +187,9 @@ export const EditorPage: React.FC = () => {
-
+ {!isMobile && ( +
+ )} )} @@ -221,15 +245,21 @@ export const EditorPage: React.FC = () => {
- {/* Resize handle */} -
-
-
+ {/* Resize handle (desktop only) */} + {!isMobile && ( +
+
+
+ )} {/* ── Simulator side ── */}
@@ -249,6 +279,34 @@ export const EditorPage: React.FC = () => {
+ {/* ── Mobile tab bar ── */} + {isMobile && ( + + )} + {saveModalOpen && setSaveModalOpen(false)} />} {loginPromptOpen && setLoginPromptOpen(false)} />}