From a5d81fd5ffaa63888c6387b02d1d48f29291a77d Mon Sep 17 00:00:00 2001 From: archipelago Date: Sun, 9 Aug 2026 18:29:54 -0400 Subject: [PATCH] =?UTF-8?q?fix(federation):=20mobile=20map=20readability?= =?UTF-8?q?=20=E2=80=94=20fill=20the=20space,=20taller=203D,=20toggle=20bo?= =?UTF-8?q?ttom-centre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Compact containers (<480px) shrink the fit margins so the scene fills the phone screen instead of floating in padding. - 3D mode steepens its tilt on portrait (-0.95 vs -0.5) so the orbit reads as a tall ellipse with depth, not a squashed horizontal band; projection params are resolved per-aspect via modeParams() and re-resolve on resize. - The 2D/3D toggle moves to bottom-centre on mobile (thumb reach, frees the top edge); the hint line tucks above it. Co-Authored-By: Claude Fable 5 --- .../components/federation/NetworkMap3D.vue | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/neode-ui/src/components/federation/NetworkMap3D.vue b/neode-ui/src/components/federation/NetworkMap3D.vue index d0d1ac31..2001f7c0 100644 --- a/neode-ui/src/components/federation/NetworkMap3D.vue +++ b/neode-ui/src/components/federation/NetworkMap3D.vue @@ -132,13 +132,15 @@ const cam = { // idle-orbit (nodes hold position so the map stays readable) } -/** The two projections the top-right toggle tweens between: '2d' is the - * original flat radial map (top-down, orthographic); '3d' is the depth view. */ -const MODES = { - '2d': { tilt: -1.45, persp: 18 }, - '3d': { tilt: -0.5, persp: 3.2 }, -} as const -type MapMode = keyof typeof MODES +/** The two projections the toggle tweens between: '2d' is the original flat + * radial map (top-down, orthographic); '3d' is the depth view. Resolved via + * modeParams() — the 3D tilt steepens on portrait containers so the orbit + * reads as a tall ellipse instead of a squashed horizontal band. */ +type MapMode = '2d' | '3d' +function modeParams(mode: MapMode): { tilt: number; persp: number } { + if (mode === '2d') return { tilt: -1.45, persp: 18 } + return { tilt: height > width ? -0.95 : -0.5, persp: 3.2 } +} /** One dot on a node's point-cloud sphere, in unit-sphere local coords. */ interface GlobeDot { @@ -1076,9 +1078,12 @@ function computeFit(tilt: number, perspVal: number): { unit: number; centerY: nu yMax = Math.max(yMax, y2 * s) } } - const marginX = 46 // node radius + label half-width clearance - const marginTop = 54 // legend / toggle chips - const marginBottom = 66 // hint line + node label below the front edge + // Compact containers (phones/companion) trade breathing room for scene + // size — the map must fill the screen to stay readable. + const compact = width < 480 || height < 480 + const marginX = compact ? 22 : 46 // node radius + label half-width clearance + const marginTop = compact ? 42 : 54 // legend / toggle chips + const marginBottom = compact ? 58 : 66 // hint + bottom-centre toggle (mobile) const bandH = Math.max(80, height - marginTop - marginBottom) const unitX = (width / 2 - marginX) / maxAbsX const unitY = bandH / Math.max(yMax - yMin, 0.001) @@ -1091,7 +1096,7 @@ function computeFit(tilt: number, perspVal: number): { unit: number; centerY: nu * Tweens once the scene is live so the reframe glides instead of jumping. */ function refit() { if (!modeInitialized) return - const m = MODES[viewMode.value] + const m = modeParams(viewMode.value) const target = computeFit(m.tilt, m.persp) if (Math.abs(target.unit - fit.unit) < 0.5 && Math.abs(target.centerY - fit.centerY) < 0.5) return gsap.killTweensOf(fit) @@ -1105,7 +1110,7 @@ function refit() { /** Snap (or tween, via setMode) the camera + fit to the given mode. */ function applyMode(mode: MapMode, animate: boolean) { - const target = MODES[mode] + const target = modeParams(mode) const targetFit = computeFit(target.tilt, target.persp) for (const t of modeTween) t.kill() modeTween = [] @@ -1459,6 +1464,19 @@ onActivated(() => { if (built && !staticMode) attachTicker() }) padding: 5px 10px; gap: 8px; } + /* Toggle drops to bottom-centre on mobile — thumb reach, and it frees the + top edge so the scene can climb higher */ + .node-map-mode-toggle { + top: auto; + right: auto; + bottom: 10px; + left: 50%; + transform: translateX(-50%); + } + .node-map-hint { + bottom: 44px; + font-size: 10px; + } }