Redesign Artist Map toolbar + shortcuts key + fixes

Toolbar redesigned: three-section layout (back+brand / centered search
with icon / tool buttons). Brand icon in accent purple. Zoom buttons in
compact pill group. Tool labels visible on desktop, icon-only on mobile.

Added keyboard shortcuts modal (keyboard icon button): lists all 10
shortcuts with styled kbd elements.

Fixes:
- Mouse wheel zoom min matched to button zoom min (0.02)
- Right-click no longer triggers left-click info modal (button filter)
- Removed external "Open on Spotify" link from context menu
- Search results dropdown centered under search bar
This commit is contained in:
Broque Thomas 2026-04-04 11:56:37 -07:00
parent 52894d3c65
commit 36e83e64ee
3 changed files with 140 additions and 48 deletions

View file

@ -3070,27 +3070,45 @@
<!-- Artist Map (fullscreen canvas, hidden by default) -->
<div class="artist-map-container" id="artist-map-container" style="display:none;">
<div class="artist-map-toolbar">
<button class="artist-map-back-btn" onclick="closeArtistMap()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>
Back to Discover
</button>
<div class="artist-map-title">Artist Map</div>
<div class="artist-map-stats" id="artist-map-stats"></div>
<div class="artist-map-search">
<input type="text" id="artist-map-search" placeholder="Search artists..." oninput="artMapSearch(this.value)">
<!-- Left: back + title -->
<div class="artmap-nav-left">
<button class="artmap-back" onclick="closeArtistMap()" title="Back to Discover (Esc)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>
</button>
<div class="artmap-brand">
<svg class="artmap-brand-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><circle cx="4" cy="6" r="2"/><circle cx="20" cy="6" r="2"/><circle cx="4" cy="18" r="2"/><circle cx="20" cy="18" r="2"/><line x1="6" y1="7" x2="10" y2="10"/><line x1="14" y1="10" x2="18" y2="7"/><line x1="6" y1="17" x2="10" y2="14"/><line x1="14" y1="14" x2="18" y2="17"/></svg>
<span class="artmap-brand-text">Artist Map</span>
</div>
<div class="artmap-stats" id="artist-map-stats"></div>
</div>
<button class="artist-map-zoom-btn" id="artmap-toggle-similar" onclick="artMapToggleSimilar()" title="Toggle similar artists (H)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><circle cx="12" cy="16" r="0.5" fill="currentColor"/></svg>
</button>
<div class="artist-map-zoom-controls">
<button class="artist-map-zoom-btn" onclick="artMapZoom(1.3)" title="Zoom in">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
<!-- Center: search -->
<div class="artmap-nav-center">
<div class="artmap-search-wrap">
<svg class="artmap-search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" id="artist-map-search" placeholder="Search artists... (S)" oninput="artMapSearch(this.value)">
</div>
</div>
<!-- Right: tools -->
<div class="artmap-nav-right">
<button class="artmap-tool-btn" id="artmap-toggle-similar" onclick="artMapToggleSimilar()" title="Toggle similar artists (H)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="5" r="3"/><circle cx="5" cy="19" r="3"/><circle cx="19" cy="19" r="3"/></svg>
<span>Filter</span>
</button>
<button class="artist-map-zoom-btn" onclick="artMapZoom(0.7)" title="Zoom out">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<button class="artist-map-zoom-btn" onclick="artMapFitToView()" title="Fit to screen">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
<div class="artmap-zoom-group">
<button class="artmap-tool-btn artmap-zoom" onclick="artMapZoom(1.3)" title="Zoom in (+)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<button class="artmap-tool-btn artmap-zoom" onclick="artMapZoom(0.7)" title="Zoom out (-)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<button class="artmap-tool-btn artmap-zoom" onclick="artMapFitToView()" title="Fit to view (F)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
</button>
</div>
<button class="artmap-tool-btn" onclick="artMapShowShortcuts()" title="Keyboard shortcuts">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="6" width="20" height="12" rx="2"/><line x1="6" y1="10" x2="6" y2="10.01"/><line x1="10" y1="10" x2="10" y2="10.01"/><line x1="14" y1="10" x2="14" y2="10.01"/><line x1="18" y1="10" x2="18" y2="10.01"/><line x1="8" y1="14" x2="16" y2="14"/></svg>
</button>
</div>
</div>

View file

@ -56167,6 +56167,39 @@ function _artMapAnimateConstellation() {
}
}
function artMapShowShortcuts() {
const existing = document.getElementById('artmap-shortcuts-overlay');
if (existing) { existing.remove(); return; }
const overlay = document.createElement('div');
overlay.id = 'artmap-shortcuts-overlay';
overlay.className = 'modal-overlay';
overlay.style.zIndex = '10002';
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
overlay.innerHTML = `
<div class="artmap-shortcuts-modal">
<div class="artmap-shortcuts-header">
<h3>Keyboard Shortcuts</h3>
<button class="watch-all-close" onclick="document.getElementById('artmap-shortcuts-overlay').remove()">&times;</button>
</div>
<div class="artmap-shortcuts-grid">
<div class="artmap-shortcut"><kbd>Esc</kbd><span>Close map</span></div>
<div class="artmap-shortcut"><kbd>+</kbd> / <kbd>-</kbd><span>Zoom in / out</span></div>
<div class="artmap-shortcut"><kbd>F</kbd><span>Fit to view</span></div>
<div class="artmap-shortcut"><kbd>S</kbd><span>Focus search</span></div>
<div class="artmap-shortcut"><kbd>H</kbd><span>Toggle similar artists</span></div>
<div class="artmap-shortcut"><kbd>Scroll</kbd><span>Zoom at cursor</span></div>
<div class="artmap-shortcut"><kbd>Click</kbd><span>Artist info</span></div>
<div class="artmap-shortcut"><kbd>Right-click</kbd><span>Context menu</span></div>
<div class="artmap-shortcut"><kbd>Drag</kbd><span>Pan around</span></div>
<div class="artmap-shortcut"><kbd>Hover 1s</kbd><span>Show connections</span></div>
</div>
</div>
`;
document.body.appendChild(overlay);
}
function artMapToggleSimilar() {
_artMap._hideSimilar = !_artMap._hideSimilar;
_artMap.dirty = true;
@ -56187,7 +56220,7 @@ function _artMapSetupInteraction(canvas) {
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
const delta = e.deltaY > 0 ? 0.9 : 1.1;
const newZoom = Math.max(0.1, Math.min(5, _artMap.zoom * delta));
const newZoom = Math.max(0.02, Math.min(5, _artMap.zoom * delta));
// Zoom toward mouse
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
@ -56253,7 +56286,6 @@ function _artMapSetupInteraction(canvas) {
<div class="artmap-ctx-item" onclick="_artMapHideContextMenu(); toggleYourArtistWatchlist(0,'${escapeForInlineJs(node.name)}','${escapeForInlineJs(bestId)}','${bestSource}',null)">
<span>&#128065;</span> ${node.type === 'watchlist' ? 'On Watchlist' : 'Add to Watchlist'}
</div>
${node.spotify_id ? `<div class="artmap-ctx-item" onclick="_artMapHideContextMenu(); window.open('https://open.spotify.com/artist/${node.spotify_id}','_blank')"><span>&#127925;</span> Open on Spotify</div>` : ''}
`;
menu.style.display = 'block';
menu.style.left = Math.min(e.clientX, window.innerWidth - 200) + 'px';
@ -56266,6 +56298,7 @@ function _artMapSetupInteraction(canvas) {
});
canvas.addEventListener('mousedown', (e) => {
if (e.button !== 0) return; // left button only
clickStart = { x: e.clientX, y: e.clientY, time: Date.now() };
isPanning = true;
panStartX = e.clientX;
@ -56309,6 +56342,7 @@ function _artMapSetupInteraction(canvas) {
});
canvas.addEventListener('mouseup', (e) => {
if (e.button !== 0) return; // left button only
const wasDrag = clickStart && (Math.abs(e.clientX - clickStart.x) > 5 || Math.abs(e.clientY - clickStart.y) > 5);
isPanning = false;

View file

@ -29696,40 +29696,80 @@ body.helper-mode-active #dashboard-activity-feed:hover {
background: #0a0a14; flex-direction: column;
}
.artist-map-toolbar {
display: flex; align-items: center; gap: 16px;
padding: 10px 20px; background: rgba(20,20,32,0.95);
border-bottom: 1px solid rgba(255,255,255,0.06);
backdrop-filter: blur(12px); z-index: 1;
display: flex; align-items: center; justify-content: space-between;
padding: 8px 16px; background: linear-gradient(180deg, rgba(14,14,22,0.98) 0%, rgba(14,14,22,0.92) 100%);
border-bottom: 1px solid rgba(255,255,255,0.05);
backdrop-filter: blur(16px); z-index: 1; gap: 12px;
}
.artist-map-back-btn {
display: flex; align-items: center; gap: 6px;
padding: 7px 14px; border-radius: 8px; font-size: 12px; font-weight: 600;
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08);
color: rgba(255,255,255,0.6); cursor: pointer; transition: all 0.2s;
}
.artist-map-back-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
.artist-map-title { font-size: 16px; font-weight: 700; color: #fff; flex: 1; }
.artist-map-stats { font-size: 12px; color: rgba(255,255,255,0.35); flex: 1; text-align: right; }
.artist-map-zoom-controls { display: flex; gap: 4px; }
.artist-map-zoom-btn {
width: 32px; height: 32px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.08);
background: rgba(255,255,255,0.05); color: rgba(255,255,255,0.6);
/* Left: back + brand + stats */
.artmap-nav-left { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.artmap-back {
width: 34px; height: 34px; border-radius: 10px; border: none;
background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.5);
cursor: pointer; display: flex; align-items: center; justify-content: center;
transition: all 0.15s;
}
.artist-map-zoom-btn:hover { background: rgba(255,255,255,0.1); color: #fff; border-color: rgba(255,255,255,0.15); }
.artmap-back:hover { background: rgba(255,255,255,0.08); color: #fff; }
.artmap-brand { display: flex; align-items: center; gap: 6px; }
.artmap-brand-icon { color: rgba(138,43,226,0.7); }
.artmap-brand-text { font-size: 15px; font-weight: 700; color: #fff; letter-spacing: -0.3px; }
.artmap-stats { font-size: 11px; color: rgba(255,255,255,0.25); font-weight: 500; }
/* Search */
.artist-map-search { position: relative; }
.artist-map-search input {
padding: 6px 12px; border-radius: 8px; font-size: 12px; width: 180px;
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08);
color: #fff; outline: none; transition: border-color 0.2s;
/* Center: search */
.artmap-nav-center { flex: 1; display: flex; justify-content: center; max-width: 320px; margin: 0 auto; }
.artmap-search-wrap {
position: relative; width: 100%;
}
.artist-map-search input::placeholder { color: rgba(255,255,255,0.25); }
.artist-map-search input:focus { border-color: rgba(138,43,226,0.4); }
.artmap-search-icon {
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
color: rgba(255,255,255,0.2); pointer-events: none;
}
.artmap-search-wrap input {
width: 100%; padding: 7px 12px 7px 32px; border-radius: 10px; font-size: 12px;
background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06);
color: #fff; outline: none; transition: all 0.2s;
}
.artmap-search-wrap input::placeholder { color: rgba(255,255,255,0.2); }
.artmap-search-wrap input:focus { border-color: rgba(138,43,226,0.4); background: rgba(255,255,255,0.06); }
/* Right: tools */
.artmap-nav-right { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.artmap-tool-btn {
height: 32px; padding: 0 10px; border-radius: 8px;
border: 1px solid rgba(255,255,255,0.06); background: rgba(255,255,255,0.03);
color: rgba(255,255,255,0.45); cursor: pointer;
display: flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600;
transition: all 0.15s; white-space: nowrap;
}
.artmap-tool-btn:hover { background: rgba(255,255,255,0.07); color: rgba(255,255,255,0.8); border-color: rgba(255,255,255,0.1); }
.artmap-tool-btn span { display: none; }
@media (min-width: 900px) { .artmap-tool-btn span { display: inline; } }
.artmap-zoom-group { display: flex; gap: 2px; background: rgba(255,255,255,0.02); border-radius: 8px; padding: 1px; }
.artmap-tool-btn.artmap-zoom { border: none; background: none; width: 30px; padding: 0; justify-content: center; border-radius: 6px; }
.artmap-tool-btn.artmap-zoom:hover { background: rgba(255,255,255,0.06); }
/* Shortcuts modal */
.artmap-shortcuts-modal {
background: rgba(20,20,32,0.95); border-radius: 14px; width: 340px; max-width: 90vw;
backdrop-filter: blur(16px); border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 16px 48px rgba(0,0,0,0.5);
}
.artmap-shortcuts-header {
display: flex; justify-content: space-between; align-items: center;
padding: 16px 20px 12px; border-bottom: 1px solid rgba(255,255,255,0.05);
}
.artmap-shortcuts-header h3 { font-size: 15px; font-weight: 700; color: #fff; margin: 0; }
.artmap-shortcuts-grid { padding: 12px 20px 16px; display: flex; flex-direction: column; gap: 6px; }
.artmap-shortcut { display: flex; align-items: center; justify-content: space-between; padding: 4px 0; }
.artmap-shortcut kbd {
display: inline-block; padding: 2px 8px; border-radius: 5px; font-size: 11px; font-weight: 600;
background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1);
color: rgba(255,255,255,0.6); font-family: system-ui; min-width: 24px; text-align: center;
}
.artmap-shortcut span { font-size: 12px; color: rgba(255,255,255,0.4); }
.artist-map-search-results {
display: none; position: absolute; top: 50px; right: 60px; z-index: 20;
display: none; position: absolute; top: 50px; left: 50%; transform: translateX(-50%); z-index: 20;
background: rgba(20,20,32,0.95); border: 1px solid rgba(255,255,255,0.08);
border-radius: 10px; min-width: 220px; max-height: 300px; overflow-y: auto;
backdrop-filter: blur(12px); box-shadow: 0 8px 24px rgba(0,0,0,0.5);