Add collapsible sidebar with a chunky early-web toggle tab

A vertical handle tab on the seam between the sidebar and the canvas
toggles the side panel open / closed. State persists to localStorage
so the panel stays where the user left it across reloads.

Style choices to match the existing chunky early-web aesthetic:

- Hard right edge with a small 4px corner, no left border (visually
  fuses with the sidebar edge).
- Manual two-tone bevel via inset box-shadow — light top, dark
  bottom — for the old-school extruded-tab look, without using
  border-style: ridge/outset (which modern browsers render
  inconsistently).
- Monospace bold guillemet glyph: « when open, » when collapsed.
- Hover paints accent-blue; active state nudges 1px right (chunky
  click feedback).
- Dark-mode-aware bevel colours.

Positioning:

- Fixed at top: 24px so it aligns with the logo row instead of
  floating mid-screen on top of editor toolbars (the original
  mid-screen position obscured the pixel and vector editor
  toolbars).
- Height 44px — compact enough not to dominate the header.
- Slides smoothly with the sidebar via `left` transition matched
  to the grid-template-columns animation (220ms ease).
- When collapsed, the pixelEditorBar / canvasToolbar / toolPalette
  get a 26px padding-left so their leftmost buttons don't sit
  under the tab.

Trade-off worth noting: the responsive `minmax(340px, 420px)`
sidebar width has been replaced with a fixed `380px` because
grid-template-columns doesn't smoothly interpolate between
minmax() and a plain length. 380px is in the middle of the
original range and works on every viewport size we care about.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.7 committed 2026-05-26 10:48:44 -04:00
1 parent a13ef22b92
commit e7e3039748
1 file changed
+76 -1
+76 -1
View File
@@ -90,9 +90,56 @@
.app {
display: grid;
grid-template-columns: minmax(340px, 420px) 1fr;
grid-template-columns: 380px 1fr;
min-height: 100vh;
transition: grid-template-columns .22s ease;
}
.app.sidebar-collapsed { grid-template-columns: 0 1fr; }
.app.sidebar-collapsed aside { visibility: hidden; }
/* Sidebar collapse tab — sits at the top, aligned with the logo, so it
doesn't sit on top of the vector or pixel editor toolbars in the
canvas area. Hard edges, manual bevel, classic chevron glyphs to
match the rest of the chunky aesthetic. */
.sidebarToggle {
position: fixed; top: 24px;
z-index: 50;
width: 18px; height: 44px;
left: 380px;
background: var(--panel);
color: #475569;
border: 1px solid var(--line);
border-left: none;
border-radius: 0 4px 4px 0;
/* Two-tone manual bevel for the old-school extruded-tab feel. */
box-shadow:
inset 0 1px 0 #fff,
inset 0 -1px 0 rgba(15,23,42,.18),
2px 0 0 rgba(15,23,42,.04);
cursor: pointer;
font: 700 14px/1 ui-monospace, Menlo, monospace;
display: flex; align-items: center; justify-content: center;
padding: 0;
user-select: none;
transition: left .22s ease, background .12s ease, color .12s ease, transform .12s ease;
}
.sidebarToggle:hover { background: var(--accent); color: #fff; }
.sidebarToggle:active { transform: translateX(1px); }
.app.sidebar-collapsed .sidebarToggle { left: 0; }
/* When the sidebar is collapsed, the toggle tab sits at left:0 of the
canvas area; reserve a thin strip on the left of the editor toolbars
so their first button doesn't sit under the tab. */
.app.sidebar-collapsed .pixelEditorBar,
.app.sidebar-collapsed .canvasToolbar,
.app.sidebar-collapsed .toolPalette { padding-left: 26px; }
body.dark .sidebarToggle { color: #cbd5e1; }
body.dark .sidebarToggle {
box-shadow:
inset 0 1px 0 rgba(255,255,255,.08),
inset 0 -1px 0 rgba(0,0,0,.4),
2px 0 0 rgba(0,0,0,.2);
}
@media print { .sidebarToggle { display: none !important; } }
aside {
background: var(--panel);
@@ -2609,6 +2656,7 @@
</div>
<div class="app">
<button type="button" id="sidebarToggleBtn" class="sidebarToggle" aria-label="Collapse side panel" title="Collapse side panel">«</button>
<aside>
<div class="appTitle">
<div class="appHeader">
@@ -20241,6 +20289,33 @@
if (btn) btn.addEventListener('click', () => applyLayout(layout));
}
// Sidebar collapse — persisted to localStorage so it stays where the
// user left it across sessions. The toggle is a fixed vertical tab
// glued to the seam between the sidebar and the canvas.
const _appEl = document.querySelector('.app');
const _sidebarBtn = $('sidebarToggleBtn');
const _syncSidebarBtn = () => {
if (!_sidebarBtn || !_appEl) return;
const collapsed = _appEl.classList.contains('sidebar-collapsed');
_sidebarBtn.textContent = collapsed ? '»' : '«';
_sidebarBtn.setAttribute('title', collapsed ? 'Expand side panel' : 'Collapse side panel');
_sidebarBtn.setAttribute('aria-label', collapsed ? 'Expand side panel' : 'Collapse side panel');
};
try {
if (localStorage.getItem('artstrSidebarCollapsed') === '1') {
_appEl?.classList.add('sidebar-collapsed');
}
} catch (e) {}
_syncSidebarBtn();
_sidebarBtn?.addEventListener('click', () => {
if (!_appEl) return;
_appEl.classList.toggle('sidebar-collapsed');
_syncSidebarBtn();
try {
localStorage.setItem('artstrSidebarCollapsed', _appEl.classList.contains('sidebar-collapsed') ? '1' : '0');
} catch (e) {}
});
// ---- Deck Builder wiring ----
$('deckAddSlideBtn')?.addEventListener('click', deckAddBlankSlide);
$('deckSorterAddBtn')?.addEventListener('click', deckAddBlankSlide);