Compare commits

..

No commits in common. "f03d92d9b37b05359a1ba8c2f948cfd983ac5320" and "1165a7fd5a563d452fb555f5022e7ae9ff16d378" have entirely different histories.

2 changed files with 62 additions and 100 deletions

View File

@ -1,42 +0,0 @@
---
id: TASK-71
title: 'Gradual zoom, toolbar reorganization, pinch-to-zoom'
status: Done
assignee: []
created_date: '2026-02-28 00:53'
labels:
- canvas
- UX
- mobile
dependencies: []
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Three improvements to canvas UX:
1. **Gradual zoom** — button zoom 1.25x→1.1x, wheel zoom 0.9/1.1→0.95/1.05, cursor-centered wheel zoom
2. **Toolbar rename** — "📝 Create" → "📝 Note", removed quick-add "+" button
3. **Redistribute rApps** — removed standalone "📱 rApps" dropdown, moved 16 embed buttons into thematic groups (Note, Media, Embed, Decide, Creative)
4. **Pinch-to-zoom** — added two-finger pinch gesture with center-point zoom, alongside existing two-finger pan
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Zoom buttons use 1.1x multiplier (10% steps)
- [ ] #2 Ctrl+wheel zoom uses 0.95/1.05 (5% steps)
- [ ] #3 Wheel and pinch zoom center on cursor/pinch midpoint
- [ ] #4 Toolbar shows '📝 Note' not '📝 Create'
- [ ] #5 No '📱 rApps' standalone dropdown exists
- [ ] #6 All 16 rApp embed buttons distributed into thematic groups
- [ ] #7 No quick-add '+' button in toolbar
- [ ] #8 Pinch-to-zoom works on touch devices alongside two-finger pan
- [ ] #9 All embed buttons still create folk-rapp shapes when clicked
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Modified `website/canvas.html` (58 insertions, 62 deletions). Zoom made more gradual across all input methods. Toolbar reorganized — rApp embeds distributed into thematic groups. Added pinch-to-zoom with center-point tracking. Cleaned up quick-add button and mobile menu rApps auto-open logic.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -702,6 +702,8 @@
</div>
<div id="toolbar">
<button id="quick-add" title="Add rApp" style="background:#14b8a6;color:white;font-size:18px;font-weight:700;padding:6px 10px;border:none;border-radius:8px;cursor:pointer;text-align:center;line-height:1;">+</button>
<span class="toolbar-sep"></span>
<div class="toolbar-group">
<button class="toolbar-group-toggle">✏️ Draw</button>
<div class="toolbar-dropdown">
@ -715,17 +717,13 @@
</div>
<div class="toolbar-group">
<button class="toolbar-group-toggle">📝 Note</button>
<button class="toolbar-group-toggle">📝 Create</button>
<div class="toolbar-dropdown">
<button id="new-markdown" title="New Note">📝 Note</button>
<button id="new-wrapper" title="New Card">🗂️ Card</button>
<button id="new-slide" title="New Slide">🎞️ Slide</button>
<button id="new-obs-note" title="New Rich Note">📓 Rich Note</button>
<button id="new-chat" title="New Chat">💬 Chat</button>
<button id="embed-notes" title="Embed rNotes">📝 rNotes</button>
<button id="embed-books" title="Embed rBooks">📚 rBooks</button>
<button id="embed-pubs" title="Embed rPubs">📖 rPubs</button>
<button id="embed-forum" title="Embed rForum">💬 rForum</button>
</div>
</div>
@ -739,7 +737,6 @@
<button id="new-drawfast" title="New Drawing">✏️ Drawfast</button>
<button id="new-freecad" title="New FreeCAD">📐 FreeCAD</button>
<button id="new-kicad" title="New KiCAD PCB">🔌 KiCAD PCB</button>
<button id="embed-swag" title="Embed rSwag">🎨 rSwag</button>
</div>
</div>
@ -749,8 +746,6 @@
<button id="new-transcription" title="New Transcription">🎤 Transcribe</button>
<button id="new-video-chat" title="New Video Call">📹 Video Call</button>
<button id="new-piano" title="New Piano">🎹 Piano</button>
<button id="embed-photos" title="Embed rPhotos">📸 rPhotos</button>
<button id="embed-tube" title="Embed rTube">🎬 rTube</button>
</div>
</div>
@ -762,12 +757,6 @@
<button id="new-calendar" title="New Calendar">📅 Calendar</button>
<button id="new-map" title="New Map">🗺️ Map</button>
<button id="new-social-post" title="New Post">📱 Social Post</button>
<button id="embed-files" title="Embed rFiles">📁 rFiles</button>
<button id="embed-work" title="Embed rWork">📋 rWork</button>
<button id="embed-inbox" title="Embed rInbox">📧 rInbox</button>
<button id="embed-cart" title="Embed rCart">🛒 rCart</button>
<button id="embed-data" title="Embed rData">📊 rData</button>
<button id="embed-network" title="Embed rNetwork">🌍 rNetwork</button>
</div>
</div>
@ -797,9 +786,28 @@
<button id="new-choice-rank" title="New Ranking">📊 Ranking</button>
<button id="new-choice-spider" title="New Scoring">🕸 Scoring</button>
<button id="new-token" title="New Token">🪙 Token</button>
</div>
</div>
<div class="toolbar-group">
<button class="toolbar-group-toggle">📱 rApps</button>
<div class="toolbar-dropdown">
<button id="embed-notes" title="Embed rNotes">📝 rNotes</button>
<button id="embed-photos" title="Embed rPhotos">📸 rPhotos</button>
<button id="embed-books" title="Embed rBooks">📚 rBooks</button>
<button id="embed-pubs" title="Embed rPubs">📖 rPubs</button>
<button id="embed-files" title="Embed rFiles">📁 rFiles</button>
<button id="embed-work" title="Embed rWork">📋 rWork</button>
<button id="embed-forum" title="Embed rForum">💬 rForum</button>
<button id="embed-inbox" title="Embed rInbox">📧 rInbox</button>
<button id="embed-tube" title="Embed rTube">🎬 rTube</button>
<button id="embed-funds" title="Embed rFunds">🌊 rFunds</button>
<button id="embed-wallet" title="Embed rWallet">💰 rWallet</button>
<button id="embed-vote" title="Embed rVote">🗳️ rVote</button>
<button id="embed-cart" title="Embed rCart">🛒 rCart</button>
<button id="embed-data" title="Embed rData">📊 rData</button>
<button id="embed-network" title="Embed rNetwork">🌍 rNetwork</button>
<button id="embed-swag" title="Embed rSwag">🎨 rSwag</button>
</div>
</div>
@ -2369,12 +2377,12 @@
}
document.getElementById("zoom-in").addEventListener("click", () => {
scale = Math.min(scale * 1.1, maxScale);
scale = Math.min(scale * 1.25, maxScale);
updateCanvasTransform();
});
document.getElementById("zoom-out").addEventListener("click", () => {
scale = Math.max(scale / 1.1, minScale);
scale = Math.max(scale / 1.25, minScale);
updateCanvasTransform();
});
@ -2390,9 +2398,22 @@
const toolbarEl = document.getElementById("toolbar");
mobileMenuBtn.addEventListener("click", () => {
const isOpen = toolbarEl.classList.toggle("mobile-open");
mobileMenuBtn.textContent = isOpen ? "✕" : "✚";
if (!isOpen && typeof closeToolbarPanel === "function") closeToolbarPanel();
// On mobile, first tap opens rApps group in the popout panel
const rAppsGroup = toolbarEl.querySelector(".toolbar-group:has(#embed-notes)")
|| [...toolbarEl.querySelectorAll(".toolbar-group")].find(g =>
g.querySelector(".toolbar-group-toggle")?.textContent.includes("rApps"));
if (rAppsGroup && !toolbarEl.classList.contains("mobile-open")) {
toolbarEl.classList.add("mobile-open");
mobileMenuBtn.textContent = "✕";
// Auto-open the rApps panel
setTimeout(() => {
if (typeof openToolbarPanel === "function") openToolbarPanel(rAppsGroup);
}, 50);
} else {
const isOpen = toolbarEl.classList.toggle("mobile-open");
mobileMenuBtn.textContent = isOpen ? "✕" : "✚";
if (!isOpen && typeof closeToolbarPanel === "function") closeToolbarPanel();
}
});
// Auto-close toolbar after tapping a shape-creation button on mobile
@ -2474,6 +2495,21 @@
}
});
// Desktop quick-add button → opens the rApps popout panel
document.getElementById("quick-add")?.addEventListener("click", (e) => {
e.stopPropagation();
const rAppsGroup = toolbarEl.querySelector(".toolbar-group:has(#embed-notes)")
|| [...toolbarEl.querySelectorAll(".toolbar-group")].find(g =>
g.querySelector(".toolbar-group-toggle")?.textContent.includes("rApps"));
if (rAppsGroup) {
if (activeToolbarGroup === rAppsGroup) {
closeToolbarPanel();
} else {
openToolbarPanel(rAppsGroup);
}
}
});
// Collapse/expand toolbar
const collapseBtn = document.getElementById("toolbar-collapse");
collapseBtn.addEventListener("click", () => {
@ -2484,11 +2520,11 @@
// Mobile zoom controls (separate from toolbar)
document.getElementById("mz-in").addEventListener("click", () => {
scale = Math.min(scale * 1.1, maxScale);
scale = Math.min(scale * 1.25, maxScale);
updateCanvasTransform();
});
document.getElementById("mz-out").addEventListener("click", () => {
scale = Math.max(scale / 1.1, minScale);
scale = Math.max(scale / 1.25, minScale);
updateCanvasTransform();
});
document.getElementById("mz-reset").addEventListener("click", () => {
@ -2498,9 +2534,8 @@
updateCanvasTransform();
});
// Touch gesture handling for two-finger pan + pinch-to-zoom
// Touch gesture handling for two-finger pan
let lastTouchCenter = null;
let lastTouchDist = null;
function getTouchCenter(touches) {
return {
@ -2509,12 +2544,6 @@
};
}
function getTouchDist(touches) {
const dx = touches[0].clientX - touches[1].clientX;
const dy = touches[0].clientY - touches[1].clientY;
return Math.hypot(dx, dy);
}
canvas.addEventListener("touchstart", (e) => {
if (e.touches.length === 2) {
e.preventDefault();
@ -2523,7 +2552,6 @@
panPointerId = null;
canvas.style.cursor = "";
lastTouchCenter = getTouchCenter(e.touches);
lastTouchDist = getTouchDist(e.touches);
}
}, { passive: false });
@ -2531,30 +2559,13 @@
if (e.touches.length === 2) {
e.preventDefault();
// Two-finger pan (no zoom)
const currentCenter = getTouchCenter(e.touches);
const currentDist = getTouchDist(e.touches);
if (lastTouchCenter) {
// Two-finger pan
panX += currentCenter.x - lastTouchCenter.x;
panY += currentCenter.y - lastTouchCenter.y;
}
if (lastTouchDist && lastTouchDist > 0) {
// Pinch-to-zoom around gesture center
const zoomDelta = currentDist / lastTouchDist;
const newScale = Math.min(Math.max(scale * zoomDelta, minScale), maxScale);
// Adjust pan so zoom centers on pinch midpoint
const rect = canvas.getBoundingClientRect();
const cx = currentCenter.x - rect.left;
const cy = currentCenter.y - rect.top;
panX = cx - (cx - panX) * (newScale / scale);
panY = cy - (cy - panY) * (newScale / scale);
scale = newScale;
}
lastTouchCenter = currentCenter;
lastTouchDist = currentDist;
updateCanvasTransform();
}
@ -2563,7 +2574,6 @@
canvas.addEventListener("touchend", (e) => {
if (e.touches.length < 2) {
lastTouchCenter = null;
lastTouchDist = null;
}
});
@ -2571,15 +2581,9 @@
canvas.addEventListener("wheel", (e) => {
e.preventDefault();
if (e.ctrlKey) {
// Ctrl+wheel (or trackpad pinch) = zoom centered on cursor
const zoomFactor = e.deltaY > 0 ? 0.95 : 1.05;
const newScale = Math.min(Math.max(scale * zoomFactor, minScale), maxScale);
const rect = canvas.getBoundingClientRect();
const cx = e.clientX - rect.left;
const cy = e.clientY - rect.top;
panX = cx - (cx - panX) * (newScale / scale);
panY = cy - (cy - panY) * (newScale / scale);
scale = newScale;
// Ctrl+wheel (or trackpad pinch) = zoom
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
scale = Math.min(Math.max(scale * zoomFactor, minScale), maxScale);
} else {
// Regular wheel/two-finger scroll = pan
panX -= e.deltaX;