From e37044f59995d7aba640bf036f4a3432d925ef23 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 10 Mar 2026 12:39:04 -0700 Subject: [PATCH] feat(rflows): redesign canvas toolbar + zoom controls, remove sufficiency badge Move toolbar from horizontal top-right strip to vertical left-side panel with larger, more prominent buttons and emoji icons. Replace basic +/- zoom buttons with a pill-shaped widget showing live zoom percentage and a fit-to-view button. Remove the "0% Enough" sufficiency badge overlay. Co-Authored-By: Claude Opus 4.6 --- modules/rflows/components/flows.css | 81 ++++++++++++--------- modules/rflows/components/folk-flows-app.ts | 46 ++++++------ 2 files changed, 72 insertions(+), 55 deletions(-) diff --git a/modules/rflows/components/flows.css b/modules/rflows/components/flows.css index 4eb324c..2b0f506 100644 --- a/modules/rflows/components/flows.css +++ b/modules/rflows/components/flows.css @@ -234,8 +234,8 @@ .flows-nav-overlay .rapp-nav__title { color: var(--rs-text-primary); font-size: 14px; font-weight: 600; } .flows-nav-overlay .rapp-nav__badge { font-size: 10px; color: var(--rs-warning); background: rgba(251,191,36,0.15); padding: 2px 8px; border-radius: 4px; } -/* Badge offset when nav overlay present */ -.flows-canvas-container--fullpage .flows-canvas-badge { top: 54px; } +/* Toolbar offset when nav overlay present */ +.flows-canvas-container--fullpage .flows-canvas-toolbar { top: 54px; } .flows-canvas-svg { width: 100%; height: 100%; display: block; @@ -247,26 +247,32 @@ .flows-canvas-svg.panning { cursor: grabbing; } .flows-canvas-svg.dragging { cursor: move; } -/* Toolbar — top-right overlay */ +/* Toolbar — left-side vertical panel */ .flows-canvas-toolbar { - position: absolute; top: 10px; right: 10px; z-index: 10; - display: flex; gap: 4px; flex-wrap: wrap; align-items: center; + position: absolute; top: 10px; left: 10px; z-index: 10; + display: flex; flex-direction: column; gap: 4px; align-items: stretch; + background: var(--rs-bg-surface); border: 1px solid var(--rs-border); + border-radius: 10px; padding: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); + width: 150px; } -.flows-canvas-btn { - padding: 5px 10px; border: 1px solid var(--rs-bg-surface-raised); border-radius: 6px; - background: var(--rs-bg-surface); color: var(--rs-text-primary); font-size: 11px; font-weight: 500; +.flows-toolbar-btn { + padding: 7px 10px; border: 1px solid transparent; border-radius: 6px; + background: transparent; color: var(--rs-text-primary); font-size: 12px; font-weight: 600; cursor: pointer; white-space: nowrap; transition: background 0.15s, border-color 0.15s; + text-align: left; } -.flows-canvas-btn:hover { background: var(--rs-border-strong); border-color: var(--rs-text-muted); } -.flows-canvas-btn--source { border-color: #10b981; color: var(--rflows-source-text, #6ee7b7); } -.flows-canvas-btn--source:hover { background: var(--rflows-source-hover-bg, #064e3b); } -.flows-canvas-btn--funnel { border-color: #3b82f6; color: var(--rflows-funnel-text, #93c5fd); } -.flows-canvas-btn--funnel:hover { background: var(--rflows-funnel-hover-bg, #1e3a5f); } -.flows-canvas-btn--outcome { border-color: #ec4899; color: var(--rflows-outcome-text, #f9a8d4); } -.flows-canvas-btn--outcome:hover { background: var(--rflows-outcome-hover-bg, #4a1942); } -.flows-canvas-btn--active { background: var(--rs-primary); border-color: var(--rs-primary-hover); color: #fff; } -.flows-canvas-sep { - width: 1px; height: 20px; background: var(--rs-border-strong); margin: 0 4px; +.flows-toolbar-btn:hover { background: var(--rs-bg-hover); } +.flows-toolbar-btn--fund { background: var(--rs-primary); color: #fff; border-color: var(--rs-primary); text-align: center; } +.flows-toolbar-btn--fund:hover { background: var(--rs-primary-hover); } +.flows-toolbar-btn--source { color: var(--rflows-source-text, #6ee7b7); } +.flows-toolbar-btn--source:hover { background: var(--rflows-source-hover-bg, #064e3b); } +.flows-toolbar-btn--funnel { color: var(--rflows-funnel-text, #93c5fd); } +.flows-toolbar-btn--funnel:hover { background: var(--rflows-funnel-hover-bg, #1e3a5f); } +.flows-toolbar-btn--outcome { color: var(--rflows-outcome-text, #f9a8d4); } +.flows-toolbar-btn--outcome:hover { background: var(--rflows-outcome-hover-bg, #4a1942); } +.flows-toolbar-btn--active { background: var(--rs-primary); border-color: var(--rs-primary-hover); color: #fff; } +.flows-toolbar-sep { + height: 1px; background: var(--rs-border); margin: 2px 0; } /* SVG node styles */ @@ -389,20 +395,27 @@ .flows-canvas-legend-item { display: flex; align-items: center; gap: 4px; } .flows-canvas-legend-dot { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; } -/* Zoom controls — bottom-right */ +/* Zoom controls — bottom-right pill */ .flows-canvas-zoom { - position: absolute; bottom: 10px; right: 10px; z-index: 10; - display: flex; gap: 4px; + position: absolute; bottom: 14px; right: 14px; z-index: 10; + display: flex; align-items: center; gap: 0; + background: var(--rs-bg-surface); border: 1px solid var(--rs-border); + border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); + overflow: hidden; } - -/* Sufficiency badge — top-left */ -.flows-canvas-badge { - position: absolute; top: 10px; left: 10px; z-index: 10; - background: var(--rs-glass-bg); border-radius: 8px; padding: 8px 14px; - display: flex; align-items: center; gap: 8px; +.flows-zoom-btn { + display: flex; align-items: center; justify-content: center; + width: 32px; height: 32px; border: none; background: transparent; + color: var(--rs-text-primary); cursor: pointer; transition: background 0.15s; +} +.flows-zoom-btn:hover { background: var(--rs-bg-hover); } +.flows-zoom-level { + font-size: 11px; font-weight: 600; color: var(--rs-text-secondary); + min-width: 40px; text-align: center; user-select: none; +} +.flows-zoom-sep { + width: 1px; height: 18px; background: var(--rs-border); margin: 0 2px; } -.flows-canvas-badge__score { font-size: 20px; font-weight: 700; } -.flows-canvas-badge__label { font-size: 10px; color: var(--rs-text-secondary); text-transform: uppercase; letter-spacing: 0.05em; } /* Legacy diagram (kept for compat) */ .flows-diagram { overflow-x: auto; } @@ -972,11 +985,11 @@ /* ── Flow dropdown (toolbar) ──────────────────────── */ .flows-dropdown { - position: relative; display: inline-block; + position: relative; display: block; } .flows-dropdown__trigger { - display: flex; align-items: center; gap: 4px; - max-width: 180px; + display: flex; align-items: center; gap: 4px; width: 100%; + max-width: none; } .flows-dropdown__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -1147,8 +1160,8 @@ .flows-cards { grid-template-columns: 1fr; } .flows-canvas-container { height: 50vh; min-height: 300px; } .flows-canvas-container--fullpage { height: 100%; min-height: unset; } - .flows-canvas-toolbar { flex-wrap: wrap; gap: 3px; top: 6px; right: 6px; } - .flows-canvas-btn { padding: 6px 10px; font-size: 11px; min-height: 44px; min-width: 44px; } + .flows-canvas-toolbar { width: 130px; top: 6px; left: 6px; padding: 4px; } + .flows-toolbar-btn { padding: 8px 8px; font-size: 12px; min-height: 40px; } .flows-editor-panel { width: 100%; } .flows-analytics-panel { width: 100%; } .flows-canvas-legend { font-size: 10px; gap: 8px; } diff --git a/modules/rflows/components/folk-flows-app.ts b/modules/rflows/components/folk-flows-app.ts index 144ceed..381270d 100644 --- a/modules/rflows/components/folk-flows-app.ts +++ b/modules/rflows/components/folk-flows-app.ts @@ -891,15 +891,9 @@ class FolkFlowsApp extends HTMLElement { ${this.esc(this.flowName || "Flow Detail")} ${this.isDemo ? 'Demo' : ""} -
-
-
${scorePct}%
-
ENOUGH
-
-
- @@ -910,17 +904,16 @@ class FolkFlowsApp extends HTMLElement {
-
- -
- - - -
- - - - +
+ +
+ + + +
+ + + @@ -956,8 +949,17 @@ class FolkFlowsApp extends HTMLElement { Thriving
- - + + ${Math.round(this.canvasZoom * 100)}% + +
+
@@ -1040,6 +1042,8 @@ class FolkFlowsApp extends HTMLElement { const g = this.shadow.getElementById("canvas-transform"); if (g) g.setAttribute("transform", `translate(${this.canvasPanX},${this.canvasPanY}) scale(${this.canvasZoom})`); this.saveViewport(); + const zl = this.shadow.getElementById("zoom-level"); + if (zl) zl.textContent = `${Math.round(this.canvasZoom * 100)}%`; } private fitView() { @@ -4640,7 +4644,7 @@ class FolkFlowsApp extends HTMLElement { panel.classList.toggle("open", this.analyticsOpen); } const btn = this.shadow.querySelector('[data-canvas-action="analytics"]'); - if (btn) btn.classList.toggle("flows-canvas-btn--active", this.analyticsOpen); + if (btn) btn.classList.toggle("flows-toolbar-btn--active", this.analyticsOpen); } private attachAnalyticsListeners() {