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:
parent
52894d3c65
commit
36e83e64ee
3 changed files with 140 additions and 48 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()">×</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>👁</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>🎵</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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue