diff options
| author | Ruben Beltran del Rio <jj@r.bdr.sh> | 2026-01-30 13:32:03 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <jj@r.bdr.sh> | 2026-01-30 14:48:59 +0100 |
| commit | 34b9a2a327df1022a0fa13afeddd394a90f2adc0 (patch) | |
| tree | 4defb5d20c4a71e36e50f33f21507212a9b488e2 /src | |
| parent | 1f0fb7d9bca9c0e7d420baf2ab8c07f060729d4c (diff) | |
Adjust theme
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.css | 225 | ||||
| -rw-r--r-- | src/App.tsx | 17 | ||||
| -rw-r--r-- | src/components/Sidebar.tsx | 15 | ||||
| -rw-r--r-- | src/store/store.ts | 12 |
4 files changed, 196 insertions, 73 deletions
diff --git a/src/App.css b/src/App.css index 12535df..1e82cf4 100644 --- a/src/App.css +++ b/src/App.css @@ -4,6 +4,17 @@ box-sizing: border-box; } +:root { + --background: #222; + --foreground: #FFF; + --primary: #0000F1; + --secondary: #666; + --primary-active: #96BFE6; + --primary-hover: #4F8FE6; + --danger: #FA2B00; + --danger-hover: #FF616B; +} + html, body, #root { @@ -21,41 +32,98 @@ body, sans-serif; } +/* Menu Toggle Button */ +.menu-toggle { + display: flex; + align-items: center; + justify-content: center; + position: fixed; + top: 16px; + left: 16px; + z-index: 200; + width: 44px; + height: 44px; + background: var(--background); + color: var(--foreground); + border: 1px solid #333; + border-radius: 8px; + font-size: 24px; + cursor: pointer; + transition: all 0.2s ease; +} + +.menu-toggle:hover { + background: var(--primary-hover); +} + +/* Sidebar Overlay */ +.sidebar-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 149; +} + /* Sidebar */ .sidebar { - width: 200px; - background: #1a1a2e; - color: #fff; - padding: 16px; + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 280px; + background: var(--background); + color: var(--foreground); + padding: 8px; display: flex; flex-direction: column; gap: 12px; border-right: 1px solid #333; overflow-y: auto; + z-index: 150; + transform: translateX(-100%); + transition: transform 0.3s ease; } -.sidebar::-webkit-scrollbar { - width: 6px; +.sidebar--open { + transform: translateX(0); } -.sidebar::-webkit-scrollbar-track { - background: #16213e; +.sidebar-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 12px; + border-bottom: 1px solid #333; } -.sidebar::-webkit-scrollbar-thumb { - background: #333; - border-radius: 3px; +.sidebar-header .sidebar-title { + padding-bottom: 0; + border-bottom: none; } -.sidebar::-webkit-scrollbar-thumb:hover { - background: #444; +.sidebar-close { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: transparent; + color: var(--foreground); + border: 1px solid #333; + border-radius: 4px; + font-size: 20px; + cursor: pointer; + transition: all 0.2s ease; +} + +.sidebar-close:hover { + background: var(--primary-hover); } .sidebar-title { font-size: 18px; font-weight: 600; - padding-bottom: 12px; - border-bottom: 1px solid #333; } .model-list { @@ -69,23 +137,19 @@ body, justify-content: space-between; align-items: center; padding: 12px 16px; - background: #16213e; + background: var(--secondary); border-radius: 8px; transition: all 0.2s ease; } -.model-item:hover { - background: #0f3460; -} - .model-name { font-size: 14px; } .add-model-btn { padding: 6px 12px; - background: #e94560; - color: #fff; + background: var(--primary); + color: var(--foreground); border: none; border-radius: 4px; font-size: 12px; @@ -94,7 +158,7 @@ body, } .add-model-btn:hover { - background: #ff6b6b; + background: var(--primary-hover); } /* Main Content */ @@ -117,11 +181,11 @@ body, left: 50%; transform: translateX(-50%); padding: 12px 24px; - background: #16213e; - color: #fff; - border: 1px solid #333; + background: var(--primary); + color: var(--foreground); + border: none; border-radius: 8px; - font-size: 14px; + font-size: 12px; cursor: pointer; transition: all 0.2s ease; user-select: none; @@ -129,12 +193,12 @@ body, } .spin-button:hover { - background: #0f3460; + background: var(--primary-hover); } .spin-button:active, .spin-button.active { - background: #e94560; + background: var(--primary-active); } /* Overlay Controls */ @@ -150,62 +214,60 @@ body, .camera-selector-wrapper { background: rgba(26, 26, 46, 0.9); border-radius: 8px; - padding: 8px 12px; + padding: 8px; } .camera-selector { - background: #16213e; - color: #fff; - border: 1px solid #333; + background: var(--primary); + color: var(--foreground); + border: none; border-radius: 4px; - padding: 8px 12px; - font-size: 14px; + padding: 8px; + font-size: 12px; cursor: pointer; outline: none; } .camera-selector:hover { - border-color: #0f3460; + border-color: var(--primary-hover); } .camera-selector:focus { - border-color: #e94560; + border-color: var(--primary-hover); } /* Backdrop Upload */ .backdrop-upload { background: rgba(26, 26, 46, 0.9); border-radius: 8px; - padding: 8px 12px; + padding: 8px; display: flex; gap: 8px; } .upload-backdrop, .clear-backdrop { - background: #16213e; - color: #fff; - border: 1px solid #333; + background: var(--primary); + color: var(--foreground); + border: none; border-radius: 4px; - padding: 8px 12px; - font-size: 14px; + padding: 8px; + font-size: 12px; cursor: pointer; transition: all 0.2s ease; } .upload-backdrop:hover, .clear-backdrop:hover { - background: #0f3460; - border-color: #0f3460; + background: var(--primary-hover); } .clear-backdrop { - background: #3d1a1a; - border-color: #5a2a2a; + background: var(--primary-hover); } .clear-backdrop:hover { - background: #5a2a2a; + background: var(--primary-hoaver); } /* Model Controls */ @@ -214,10 +276,10 @@ body, top: 16px; right: 16px; width: 280px; - background: rgba(26, 26, 46, 0.95); + background: var(--background); border-radius: 12px; padding: 16px; - color: #fff; + color: var(--foreground); z-index: 100; max-height: calc(100vh - 100px); overflow-y: auto; @@ -258,11 +320,11 @@ body, .control-row input[type="number"] { flex: 1; - background: #16213e; + background: var(--secondary); border: 1px solid #333; border-radius: 4px; padding: 8px; - color: #fff; + color: var(--foreground); font-size: 14px; } @@ -276,7 +338,7 @@ body, height: 6px; -webkit-appearance: none; appearance: none; - background: #16213e; + background: var(--secondary); border-radius: 3px; outline: none; } @@ -286,7 +348,7 @@ body, appearance: none; width: 16px; height: 16px; - background: #e94560; + background: var(--primary); border-radius: 50%; cursor: pointer; } @@ -294,7 +356,7 @@ body, .control-row input[type="range"]::-moz-range-thumb { width: 16px; height: 16px; - background: #e94560; + background: var(--primary); border-radius: 50%; cursor: pointer; border: none; @@ -303,8 +365,8 @@ body, .delete-model { width: 100%; padding: 12px; - background: #5a2a2a; - color: #fff; + background: var(--danger); + color: var(--foreground); border: none; border-radius: 8px; font-size: 14px; @@ -314,24 +376,45 @@ body, } .delete-model:hover { - background: #7a3a3a; + background: var(--danger-hover); } -/* Scrollbar Styling */ -.model-controls::-webkit-scrollbar { - width: 6px; -} -.model-controls::-webkit-scrollbar-track { - background: #16213e; - border-radius: 3px; -} +/* Mobile: Show overlay when sidebar is open */ +@media (max-width: 767px) { + .sidebar-overlay { + display: block; + } -.model-controls::-webkit-scrollbar-thumb { - background: #333; - border-radius: 3px; + .overlay-controls { + left: 70px; + } + + .model-controls { + width: calc(100vw - 32px); + max-width: 320px; + } } -.model-controls::-webkit-scrollbar-thumb:hover { - background: #444; +/* Desktop: Sidebar always visible */ +@media (min-width: 768px) { + .menu-toggle { + display: none; + } + + .sidebar { + position: relative; + width: 200px; + min-width: 200px; + transform: translateX(0); + flex-shrink: 0; + } + + .sidebar-close { + display: none; + } + + .sidebar-header { + border-bottom: 1px solid #333; + } } diff --git a/src/App.tsx b/src/App.tsx index 5426184..7749ffd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,11 +4,28 @@ import { Sidebar } from "./components/Sidebar.tsx"; import { CameraSelector } from "./components/CameraSelector.tsx"; import { ModelControls } from "./components/ModelControls.tsx"; import { BackdropUpload } from "./components/BackdropUpload.tsx"; +import { useStore } from "./store/store.ts"; import "./App.css"; export const App: React.FC = () => { + const sidebarOpen = useStore((state) => state.sidebarOpen); + const toggleSidebar = useStore((state) => state.toggleSidebar); + const closeSidebar = useStore((state) => state.closeSidebar); + return ( <section className="app"> + {!sidebarOpen && ( + <button + className="menu-toggle" + onClick={toggleSidebar} + aria-label="Open sidebar" + > + ☰ + </button> + )} + {sidebarOpen && ( + <div className="sidebar-overlay" onClick={closeSidebar} /> + )} <Sidebar /> <main className="main-content"> <Scene /> diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 97d923b..441ced5 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -4,14 +4,25 @@ import { useStore } from "../store/store.ts"; export const Sidebar: React.FC = () => { const availableModels = useStore((state) => state.availableModels); const addModel = useStore((state) => state.addModel); + const sidebarOpen = useStore((state) => state.sidebarOpen); + const closeSidebar = useStore((state) => state.closeSidebar); const handleAdd = (modelId: string) => { addModel(modelId, { x: 0, y: 0, z: 0 }); }; return ( - <section className="sidebar"> - <h2 className="sidebar-title">Models</h2> + <section className={`sidebar ${sidebarOpen ? "sidebar--open" : ""}`}> + <div className="sidebar-header"> + <h2 className="sidebar-title">Models</h2> + <button + className="sidebar-close" + onClick={closeSidebar} + aria-label="Close sidebar" + > + × + </button> + </div> <nav className="model-list"> {availableModels.map((model) => ( <article key={model.id} className="model-item"> diff --git a/src/store/store.ts b/src/store/store.ts index 94519e4..e5fce0e 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -47,6 +47,7 @@ export interface StoreState { cameraView: CameraView; backdropUrl: string | null; availableModels: AvailableModel[]; + sidebarOpen: boolean; addModel: (sourceId: string, position: Vector3D) => void; removeModel: (id: string) => void; selectModel: (id: string | null) => void; @@ -56,6 +57,8 @@ export interface StoreState { ) => void; setCameraView: (view: CameraView) => void; setBackdrop: (url: string | null) => void; + toggleSidebar: () => void; + closeSidebar: () => void; } export const useStore = create<StoreState>((set, get) => ({ @@ -64,6 +67,7 @@ export const useStore = create<StoreState>((set, get) => ({ cameraView: "perspective", backdropUrl: null, availableModels, + sidebarOpen: false, addModel: (sourceId: string, position: Vector3D) => { const { availableModels } = get(); @@ -116,4 +120,12 @@ export const useStore = create<StoreState>((set, get) => ({ setBackdrop: (url: string | null) => { set({ backdropUrl: url }); }, + + toggleSidebar: () => { + set((state) => ({ sidebarOpen: !state.sidebarOpen })); + }, + + closeSidebar: () => { + set({ sidebarOpen: false }); + }, })); |