aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2026-01-30 13:32:03 +0100
committerRuben Beltran del Rio <jj@r.bdr.sh>2026-01-30 14:48:59 +0100
commit34b9a2a327df1022a0fa13afeddd394a90f2adc0 (patch)
tree4defb5d20c4a71e36e50f33f21507212a9b488e2 /src
parent1f0fb7d9bca9c0e7d420baf2ab8c07f060729d4c (diff)
Adjust theme
Diffstat (limited to 'src')
-rw-r--r--src/App.css225
-rw-r--r--src/App.tsx17
-rw-r--r--src/components/Sidebar.tsx15
-rw-r--r--src/store/store.ts12
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"
+ >
+ &#9776;
+ </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"
+ >
+ &times;
+ </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 });
+ },
}));