Skip to content

Commit 0ad4172

Browse files
committed
Make Ida chat a floating, minimizable/expandable popup + mobile nav
The chat was a full-height side panel that ate half the screen on mobile and cropped the page. Replace it with a floating chat widget: - Three states (min / open / max): a launcher bubble that opens a docked popup, which can be expanded to full-screen or minimized back to the bubble. Header gains expand (⤢) and minimize (—) controls. - The page content now uses the full width (chat floats instead of taking a grid column). On phones the popup docks to the bottom, leaving the page usable above it; on desktop it anchors bottom-right. - Starts minimized on small screens so it never covers content. - Add a mobile hamburger + slide-in sidebar drawer (with scrim) so rooms are navigable on phones; the drawer auto-closes after picking a room. Fix: the app-state class `ida-open` collided with the launcher's `.ida-open` class (display:none), which hid the entire app when opening the chat. The launcher is now `.ida-fab`. Verified across mobile (412px) and desktop (1440px): min/open/max transitions, hamburger drawer open + navigate + auto-close. Smoke test green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RGCVknsd94sAJS6rhDdTBQ
1 parent cb56247 commit 0ad4172

3 files changed

Lines changed: 106 additions & 25 deletions

File tree

‎public/css/styles.css‎

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ a { color: var(--accent); }
3232

3333
.app {
3434
display: grid;
35-
grid-template-columns: 264px 1fr 380px;
35+
grid-template-columns: 264px 1fr;
3636
height: 100vh;
3737
overflow: hidden;
3838
}
@@ -284,15 +284,34 @@ a { color: var(--accent); }
284284
.tip { background: var(--accent-soft); border: 1px solid rgba(124,92,255,0.25); border-radius: 10px; padding: 12px 14px; font-size: 13px; color: var(--text); line-height: 1.5; }
285285
.inspired { color: var(--text-faint); font-size: 12px; margin-top: 16px; }
286286

287-
/* ── Ida panel ── */
287+
/* ── Ida floating chat popup ── */
288288
.ida-panel {
289+
position: fixed;
290+
right: 22px;
291+
bottom: 22px;
292+
z-index: 50;
293+
width: 390px;
294+
height: min(600px, 78vh);
289295
background: var(--bg-2);
290-
border-left: 1px solid var(--line);
296+
border: 1px solid var(--line-2);
297+
border-radius: 18px;
298+
box-shadow: var(--shadow);
291299
display: flex;
292300
flex-direction: column;
293-
height: 100vh;
301+
overflow: hidden;
302+
transition: width 0.18s ease, height 0.18s ease, opacity 0.15s ease, transform 0.15s ease;
294303
}
295-
.ida-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px; border-bottom: 1px solid var(--line); }
304+
/* expanded / maximized */
305+
.app.ida-max .ida-panel {
306+
width: min(820px, 94vw);
307+
height: 92vh;
308+
right: 3vw;
309+
bottom: 4vh;
310+
}
311+
/* minimized → hide popup, show launcher bubble */
312+
.app.ida-min .ida-panel { opacity: 0; transform: translateY(12px) scale(0.98); pointer-events: none; }
313+
.ida-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--line); }
314+
.ida-head-actions { display: flex; align-items: center; gap: 2px; }
296315
.ida-id { display: flex; align-items: center; gap: 11px; }
297316
.ida-avatar {
298317
width: 38px; height: 38px; border-radius: 12px;
@@ -331,26 +350,53 @@ a { color: var(--accent); }
331350
.send-btn { background: var(--accent); color: #fff; border: none; width: 42px; height: 42px; border-radius: 12px; font-size: 16px; flex-shrink: 0; }
332351
.send-btn:hover { filter: brightness(1.1); }
333352

334-
.ida-open { position: fixed; right: 22px; bottom: 22px; z-index: 20; background: var(--accent); color: #fff; border: none; border-radius: 999px; padding: 13px 20px; font-weight: 600; box-shadow: var(--shadow); }
353+
/* Launcher bubble — only visible when minimized */
354+
.ida-fab {
355+
position: fixed; right: 22px; bottom: 22px; z-index: 50;
356+
display: none; align-items: center; gap: 8px;
357+
background: linear-gradient(135deg, var(--accent), #2bb6a3); color: #fff;
358+
border: none; border-radius: 999px; padding: 13px 20px; font-weight: 600;
359+
box-shadow: var(--shadow); font-size: 14px;
360+
}
361+
.ida-fab:hover { filter: brightness(1.07); }
362+
.app.ida-min .ida-fab { display: inline-flex; }
335363

336364
.hidden { display: none !important; }
337365

338-
/* Collapsed Ida */
339-
.app.ida-collapsed { grid-template-columns: 264px 1fr 0; }
340-
.app.ida-collapsed .ida-panel { display: none; }
366+
/* Mobile nav hamburger + scrim (hidden on desktop) */
367+
.nav-toggle {
368+
display: none; position: fixed; top: 12px; left: 12px; z-index: 75;
369+
width: 42px; height: 42px; border-radius: 10px;
370+
background: var(--bg-3); border: 1px solid var(--line-2); color: var(--text);
371+
font-size: 18px; align-items: center; justify-content: center; box-shadow: var(--shadow);
372+
}
373+
.scrim { display: none; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 65; }
374+
.scrim.show { display: block; }
341375

342376
/* Responsive */
343377
@media (max-width: 1100px) {
344378
.app { grid-template-columns: 220px 1fr; }
345-
.ida-panel { position: fixed; top: 0; right: 0; width: min(400px, 90vw); z-index: 30; box-shadow: var(--shadow); }
346-
.app.ida-collapsed { grid-template-columns: 220px 1fr; }
347379
.grid.two, .measure-grid { grid-template-columns: 1fr; }
348380
}
349-
@media (max-width: 720px) {
381+
@media (max-width: 760px) {
350382
.app { grid-template-columns: 1fr; }
351-
.sidebar { position: fixed; z-index: 40; width: 240px; height: 100vh; transform: translateX(-100%); transition: transform 0.2s; }
383+
.view { padding: 64px 18px 90px; }
384+
/* Sidebar slides in over the content; hamburger opens it */
385+
.sidebar {
386+
position: fixed; z-index: 70; width: 260px; height: 100vh;
387+
transform: translateX(-100%); transition: transform 0.2s ease;
388+
}
352389
.sidebar.open { transform: translateX(0); }
353-
.view { padding: 22px 18px 80px; }
390+
.nav-toggle { display: flex; }
391+
/* The chat popup docks to the bottom, leaving the page usable above it */
392+
.ida-panel {
393+
right: 8px; left: 8px; bottom: 8px; width: auto;
394+
height: min(72vh, 580px); border-radius: 16px;
395+
}
396+
.app.ida-max .ida-panel {
397+
right: 0; left: 0; bottom: 0; width: auto; height: 100vh; border-radius: 0;
398+
}
399+
.ida-fab { right: 14px; bottom: 14px; }
354400
}
355401

356402
/* ── Sync chip + modal ── */

‎public/index.html‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
</head>
1919
<body>
2020
<div id="app" class="app">
21+
<button id="navToggle" class="nav-toggle" aria-label="Menu">☰</button>
22+
<div id="scrim" class="scrim"></div>
23+
2124
<!-- Sidebar -->
22-
<aside class="sidebar">
25+
<aside class="sidebar" id="sidebar">
2326
<div class="brand">
2427
<div class="brand-mark">Ida</div>
2528
<div class="brand-sub">design studio</div>
@@ -52,7 +55,10 @@
5255
<div id="idaContext" class="ida-context">your design advisor</div>
5356
</div>
5457
</div>
55-
<button id="idaCollapse" class="icon-btn" title="Hide Ida">×</button>
58+
<div class="ida-head-actions">
59+
<button id="idaExpand" class="icon-btn" title="Expand">⤢</button>
60+
<button id="idaCollapse" class="icon-btn" title="Minimize">—</button>
61+
</div>
5662
</header>
5763
<div id="idaMessages" class="ida-messages"><!-- messages --></div>
5864
<div id="idaStarters" class="ida-starters"><!-- quick prompts --></div>
@@ -67,7 +73,7 @@
6773
</form>
6874
</section>
6975

70-
<button id="idaOpen" class="ida-open hidden" title="Chat with Ida">💬 Ask Ida</button>
76+
<button id="idaOpen" class="ida-fab" title="Chat with Ida">💬 Ask Ida</button>
7177

7278
<div id="modalRoot" class="modal-root hidden"><!-- modals injected --></div>
7379
</div>

‎public/js/app.js‎

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,17 @@ function bindChrome() {
7575
const syncBtn = document.getElementById('syncBtn');
7676
if (syncBtn) syncBtn.addEventListener('click', openSyncModal);
7777

78-
document.getElementById('idaCollapse').addEventListener('click', () => toggleIda(false));
79-
document.getElementById('idaOpen').addEventListener('click', () => toggleIda(true));
78+
// Mobile sidebar drawer
79+
const navToggle = document.getElementById('navToggle');
80+
const scrim = document.getElementById('scrim');
81+
if (navToggle) navToggle.addEventListener('click', () => setSidebar(true));
82+
if (scrim) scrim.addEventListener('click', () => setSidebar(false));
83+
84+
document.getElementById('idaCollapse').addEventListener('click', () => setIda('min'));
85+
document.getElementById('idaOpen').addEventListener('click', () => setIda('open'));
86+
document.getElementById('idaExpand').addEventListener('click', () => setIda(app.idaState === 'max' ? 'open' : 'max'));
87+
// Start minimized on small screens so it never covers the page.
88+
setIda(window.innerWidth < 900 ? 'min' : 'open');
8089

8190
// Ida chat form
8291
const form = document.getElementById('idaForm');
@@ -91,9 +100,28 @@ function bindChrome() {
91100
});
92101
}
93102

94-
function toggleIda(open) {
95-
document.getElementById('app').classList.toggle('ida-collapsed', !open);
96-
document.getElementById('idaOpen').classList.toggle('hidden', open);
103+
// Ida popup states: 'min' (launcher bubble), 'open' (docked popup), 'max' (expanded)
104+
function setIda(state) {
105+
app.idaState = state;
106+
const el = document.getElementById('app');
107+
el.classList.remove('ida-min', 'ida-open', 'ida-max');
108+
el.classList.add('ida-' + state);
109+
const exp = document.getElementById('idaExpand');
110+
if (exp) {
111+
exp.textContent = state === 'max' ? '⤡' : '⤢';
112+
exp.title = state === 'max' ? 'Restore' : 'Expand';
113+
}
114+
if (state !== 'min') {
115+
const input = document.getElementById('idaInput');
116+
if (input && window.innerWidth >= 900) input.focus();
117+
scrollIda();
118+
}
119+
}
120+
function openIda() { setIda(app.idaState === 'max' ? 'max' : 'open'); }
121+
122+
function setSidebar(open) {
123+
document.getElementById('sidebar').classList.toggle('open', open);
124+
document.getElementById('scrim').classList.toggle('show', open);
97125
}
98126

99127
function setModeBadge() {
@@ -127,6 +155,7 @@ function renderNav() {
127155
function go(view) {
128156
app.view = view;
129157
app.tab = 'overview';
158+
setSidebar(false); // close the mobile drawer after picking a room
130159
renderNav();
131160
render();
132161
renderIda();
@@ -302,7 +331,7 @@ function roomOverview(body, r) {
302331
`;
303332
const ta = body.querySelector('#roomNotes');
304333
ta.addEventListener('input', () => { roomState(app.state, r.id).notes = ta.value; persist(); });
305-
body.querySelector('#askIdaOverview').addEventListener('click', () => { toggleIda(true); document.getElementById('idaInput').focus(); });
334+
body.querySelector('#askIdaOverview').addEventListener('click', () => { openIda(); });
306335
}
307336

308337
async function roomPhotos(body, r) {
@@ -351,7 +380,7 @@ function roomRecs(body, r) {
351380
}).join('')}
352381
`;
353382
body.querySelector('#genIda').addEventListener('click', () => {
354-
toggleIda(true);
383+
openIda();
355384
sendToIda(`Give me a personalized design plan for my ${r.name.split(' — ')[0]}.`);
356385
});
357386
}
@@ -504,7 +533,7 @@ async function sendToIda(text) {
504533
const input = document.getElementById('idaInput');
505534
input.value = '';
506535
input.style.height = 'auto';
507-
toggleIda(true);
536+
openIda();
508537

509538
const hist = currentHistory();
510539
hist.push({ role: 'user', content: text });

0 commit comments

Comments
 (0)