From f1f63ca1428564e0b5652edf8687b6fa0ed5a21a Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Fri, 10 Apr 2026 00:00:36 +0000 Subject: [PATCH] feat(rdata): add data membrane visualization to landing page Add interactive concentric zone visualization showing Personal (encrypted/local), Permissioned, and Public data membranes. Drag-and-drop objects between zones triggers permission change confirmations. Mobile-optimized with tap-to-select, responsive node sizing, and scroll prevention. Co-Authored-By: Claude Opus 4.6 (1M context) --- modules/rdata/landing.ts | 140 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/modules/rdata/landing.ts b/modules/rdata/landing.ts index b813173b..4af8736b 100644 --- a/modules/rdata/landing.ts +++ b/modules/rdata/landing.ts @@ -131,6 +131,20 @@ export function renderLanding(): string { + +
+
+ Interactive Demo +

Your Data, Your Membranes

+

+ Every piece of knowledge lives inside a permissioned boundary. Your personal data sits in encrypted local storage, + shared data flows through permissioned spaces, and public data is open to all. + Drag any object between zones to see how rData manages permission transitions. +

+
+
+
+
@@ -172,5 +186,129 @@ export function renderLanding(): string { `; +
+ + +`; +} + +function getMembraneScript(): string { + return ` +(function(){ +'use strict'; +var mob=function(){return window.innerWidth<600;}; +var ZONES=[ +{id:'public',label:'Public',sublabel:'Open to everyone',color:'#34d399',colorFaded:'rgba(52,211,153,0.08)',borderStyle:'solid',icon:'\\u{1F310}'}, +{id:'permissioned',label:'Permissioned Space',sublabel:'Shared with members',color:'#fbbf24',colorFaded:'rgba(251,191,36,0.08)',borderStyle:'solid',icon:'\\u{1F511}'}, +{id:'personal',label:'Personal Storage',sublabel:'Encrypted \\u00b7 Local only',color:'#f87171',colorFaded:'rgba(248,113,113,0.10)',borderStyle:'dotted',icon:'\\u{1F512}'} +]; +var DEMO_OBJECTS=[ +{id:'obj1',label:'Product Roadmap',icon:'\\u{1F4DD}',zone:'personal',tags:['planning']}, +{id:'obj2',label:'Meeting Notes',icon:'\\u{1F4D3}',zone:'personal',tags:['meetings']}, +{id:'obj3',label:'Private Journal',icon:'\\u{1F510}',zone:'personal',tags:['personal']}, +{id:'obj4',label:'Team Calendar',icon:'\\u{1F4C5}',zone:'permissioned',tags:['team']}, +{id:'obj5',label:'Budget Q2',icon:'\\u{1F4B0}',zone:'permissioned',tags:['finance']}, +{id:'obj6',label:'Design System',icon:'\\u{1F3A8}',zone:'permissioned',tags:['dev']}, +{id:'obj7',label:'API Docs',icon:'\\u{1F4C4}',zone:'public',tags:['dev']}, +{id:'obj8',label:'Community Wiki',icon:'\\u{1F4DA}',zone:'public',tags:['community']}, +{id:'obj9',label:'Open Proposals',icon:'\\u{1F5F3}',zone:'public',tags:['governance']} +]; +var objects=JSON.parse(JSON.stringify(DEMO_OBJECTS)); +var dragState=null,popup=null,hoveredZone=null,containerEl=null,canvasEl=null,tooltipObj=null,selectedObj=null; +function nodeR(){return mob()?22:28;} +function getZoneGeometry(W,H){var cx=W/2,cy=H/2,maxR=Math.min(cx,cy)-(mob()?8:16),zones=[]; +for(var i=0;izg.innerR;} +function findZoneAtPoint(x,y,zgs){for(var i=zgs.length-1;i>=0;i--){if(pointInZone(x,y,zgs[i]))return zgs[i];}return null;} +function computeObjectPositions(zgs){var positions={},byZone={},nr=nodeR(); +objects.forEach(function(o){if(!byZone[o.zone])byZone[o.zone]=[];byZone[o.zone].push(o);}); +zgs.forEach(function(zg){var items=byZone[zg.id]||[];var midR=(zg.outerR+zg.innerR)/2;var count=items.length; +for(var i=0;i1){r=midR+(i%2===0?-nr*0.6:nr*0.6);} +positions[items[i].id]={x:zg.cx+r*Math.cos(angle),y:zg.cy+r*Math.sin(angle)};}});return positions;} +function escSvg(s){return s.replace(/&/g,'&').replace(//g,'>');} +function escHtml(s){return s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');} +function truncate(s,max){return s.length>max?s.slice(0,max-1)+'\\u2026':s;} +function render(){if(!containerEl)return;var rect=containerEl.getBoundingClientRect();var W=rect.width;var m=mob(); +var H=m?Math.max(360,Math.min(W*0.95,480)):Math.max(480,Math.min(600,W*0.65));containerEl.style.height=H+'px'; +var zgs=getZoneGeometry(W,H);var positions=computeObjectPositions(zgs);var nr=nodeR();var svg=''; +svg+=''; +var zls=m?10:13,zsls=m?8:10; +zgs.forEach(function(zg){var da=zg.borderStyle==='dotted'?'8,6':'none'; +svg+=''; +svg+=''; +var ly=zg.cy-zg.outerR+(m?16:24);var lbl=m&&zg.id==='permissioned'?'Permissioned':zg.label; +svg+=''+zg.icon+' '+escSvg(lbl)+''; +svg+=''+escSvg(zg.sublabel)+'';}); +var ifs=m?14:18,lfs=m?7.5:9; +objects.forEach(function(obj){var pos=positions[obj.id];if(!pos)return;var ox=pos.x,oy=pos.y; +if(dragState&&dragState.objId===obj.id){ox=dragState.currentX;oy=dragState.currentY;} +var isDragging=dragState&&dragState.objId===obj.id;var isSel=selectedObj===obj.id;var zc=ZONES.find(function(z){return z.id===obj.zone;});var nc=zc?zc.color:'#94a3b8'; +svg+=''; +var hitR=m?nr+4:nr;svg+=''; +svg+=''; +if(isSel&&m){svg+='';} +svg+=''+obj.icon+''; +var tly=m?nr-6:18;svg+=''+escSvg(truncate(obj.label,m?12:14))+''; +svg+='';}); +if(dragState&&hoveredZone){var tzg=zgs.find(function(z){return z.id===hoveredZone;});if(tzg){var dObj=objects.find(function(o){return o.id===dragState.objId;}); +if(dObj&&dObj.zone!==hoveredZone){svg+='';}}} +if(m&&!dragState&&!popup&&!selectedObj){svg+='Tap an object, then drag to move between zones';} +canvasEl.setAttribute('viewBox','0 0 '+W+' '+H);canvasEl.style.width=W+'px';canvasEl.style.height=H+'px';canvasEl.innerHTML=svg;renderPopup();} +function renderPopup(){var ep=containerEl.querySelector('.membrane-popup-overlay');if(!popup){if(ep)ep.remove();return;} +if(!ep){ep=document.createElement('div');ep.className='membrane-popup-overlay';containerEl.appendChild(ep);} +var obj=objects.find(function(o){return o.id===popup.objId;});var fz=ZONES.find(function(z){return z.id===popup.fromZone;});var tz=ZONES.find(function(z){return z.id===popup.toZone;}); +if(!obj||!fz||!tz)return;var zo={personal:0,permissioned:1,public:2};var isExp=zo[popup.toZone]>zo[popup.fromZone];var isRes=zo[popup.toZone] from '+fz.icon+' '+fz.label+' to '+tz.icon+' '+tz.label+' will make this data accessible to more people.'; +if(popup.toZone==='public'){wm+='

This will make the data publicly visible. Anyone will be able to view it.';cl='Make Public';cc='#34d399';} +else{wm+='

Members of this permissioned space will be able to view and potentially edit this data.';cl='Share with Space';cc='#fbbf24';}} +else if(isRes){wi='\\u{1F512}';wt='Restricting Access';wm='Moving '+escHtml(obj.label)+' from '+fz.icon+' '+fz.label+' to '+tz.icon+' '+tz.label+' will reduce who can access this data.'; +if(popup.toZone==='personal'){wm+='

This data will be encrypted and stored locally in your browser only. No one else will have access.';cl='Make Private';cc='#f87171';} +else{wm+='

Only members of this permissioned space will retain access.';cl='Restrict Access';cc='#fbbf24';}} +ep.innerHTML='
'+wi+'

'+wt+'

'+wm+'

'+fz.icon+' '+fz.label+'
'+(isExp?'\\u2192':'\\u2190')+'
'+tz.icon+' '+tz.label+'
'; +ep.querySelector('.membrane-popup-cancel').onclick=function(){popup=null;selectedObj=null;render();}; +ep.querySelector('.membrane-popup-confirm').onclick=function(){var o=objects.find(function(x){return x.id===popup.objId;});if(o)o.zone=popup.toZone;popup=null;selectedObj=null;render();}; +ep.onclick=function(e){if(e.target===ep){popup=null;selectedObj=null;render();}};} +function getPointerPos(e){var evt=e.touches?e.touches[0]:e;var rect=canvasEl.getBoundingClientRect();var sx=canvasEl.viewBox.baseVal.width/rect.width;var sy=canvasEl.viewBox.baseVal.height/rect.height;return{x:(evt.clientX-rect.left)*sx,y:(evt.clientY-rect.top)*sy};} +function onPointerDown(e){if(popup)return;var t=e.target.closest('.membrane-obj'); +if(!t){if(mob()&&selectedObj){selectedObj=null;render();}return;} +var objId=t.getAttribute('data-obj-id'); +if(mob()&&e.touches&&selectedObj!==objId){e.preventDefault();selectedObj=objId;render();return;} +e.preventDefault();var pos=getPointerPos(e); +var rect=containerEl.getBoundingClientRect();var W=rect.width;var H=parseInt(containerEl.style.height)||480;var zgs=getZoneGeometry(W,H);var positions=computeObjectPositions(zgs);var op=positions[objId]; +dragState={objId:objId,startX:pos.x,startY:pos.y,currentX:op?op.x:pos.x,currentY:op?op.y:pos.y,offsetX:op?pos.x-op.x:0,offsetY:op?pos.y-op.y:0}; +selectedObj=objId;canvasEl.style.cursor='grabbing'; +if(e.touches){document.body.style.overflow='hidden';document.body.style.touchAction='none';}} +function onPointerMove(e){if(!dragState)return;e.preventDefault();var pos=getPointerPos(e);dragState.currentX=pos.x-dragState.offsetX;dragState.currentY=pos.y-dragState.offsetY; +var rect=containerEl.getBoundingClientRect();var W=rect.width;var H=parseInt(containerEl.style.height)||480;var zgs=getZoneGeometry(W,H);var zone=findZoneAtPoint(dragState.currentX,dragState.currentY,zgs);hoveredZone=zone?zone.id:null;render();} +function onPointerUp(){if(!dragState)return;document.body.style.overflow='';document.body.style.touchAction=''; +var obj=objects.find(function(o){return o.id===dragState.objId;}); +if(obj&&hoveredZone&&hoveredZone!==obj.zone){popup={objId:obj.id,fromZone:obj.zone,toZone:hoveredZone};} +dragState=null;hoveredZone=null;canvasEl.style.cursor='';render();} +function initDrag(){canvasEl.addEventListener('mousedown',onPointerDown);canvasEl.addEventListener('touchstart',onPointerDown,{passive:false}); +document.addEventListener('mousemove',onPointerMove);document.addEventListener('touchmove',onPointerMove,{passive:false});document.addEventListener('mouseup',onPointerUp);document.addEventListener('touchend',onPointerUp);} +function initTooltip(){if('ontouchstart' in window)return; +canvasEl.addEventListener('mouseover',function(e){var t=e.target.closest('.membrane-obj');if(t){var id=t.getAttribute('data-obj-id');tooltipObj=objects.find(function(o){return o.id===id;})||null;}else{tooltipObj=null;}renderTooltip(e);}); +canvasEl.addEventListener('mouseout',function(e){if(!e.target.closest('.membrane-obj')){tooltipObj=null;renderTooltip(e);}});} +function renderTooltip(e){var tip=containerEl.querySelector('.membrane-tooltip');if(!tooltipObj){if(tip)tip.style.display='none';return;} +if(!tip){tip=document.createElement('div');tip.className='membrane-tooltip';containerEl.appendChild(tip);} +var zone=ZONES.find(function(z){return z.id===tooltipObj.zone;}); +tip.innerHTML=''+tooltipObj.icon+' '+escHtml(tooltipObj.label)+'
'+zone.icon+' '+zone.label+''+(tooltipObj.tags.length?'
'+tooltipObj.tags.map(function(t){return'#'+t;}).join(' ')+'':'')+'
Drag to change permissions'; +tip.style.display='block';var rect=containerEl.getBoundingClientRect();tip.style.left=Math.min(e.clientX-rect.left+12,rect.width-180)+'px';tip.style.top=(e.clientY-rect.top-70)+'px';} +function injectStyles(){if(document.getElementById('membrane-styles'))return;var s=document.createElement('style');s.id='membrane-styles'; +s.textContent='.membrane-container{position:relative;width:100%;overflow:hidden;border-radius:16px;background:rgba(10,14,26,0.6);border:1px solid rgba(255,255,255,0.06);-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:none}.membrane-container svg{display:block}.membrane-tooltip{position:absolute;display:none;background:rgba(15,23,42,0.95);border:1px solid rgba(255,255,255,0.15);border-radius:8px;padding:8px 12px;font-size:0.8rem;color:#e2e8f0;pointer-events:none;z-index:20;line-height:1.5;max-width:200px;backdrop-filter:blur(8px)}.membrane-legend{display:flex;justify-content:center;gap:1.5rem;padding:0.75rem 1rem;flex-wrap:wrap}.membrane-legend-item{display:flex;align-items:center;gap:0.5rem;font-size:0.8rem;color:#94a3b8}.membrane-legend-swatch{display:inline-block;width:16px;height:16px;border-radius:50%;flex-shrink:0}.membrane-reset-btn{position:absolute;top:12px;right:12px;padding:4px 12px;border-radius:6px;border:1px solid rgba(255,255,255,0.12);background:rgba(15,23,42,0.8);color:#94a3b8;font-size:0.75rem;cursor:pointer;z-index:10;transition:border-color 0.2s,color 0.2s}.membrane-reset-btn:hover{border-color:rgba(255,255,255,0.3);color:#e2e8f0}.membrane-popup-overlay{position:absolute;inset:0;background:rgba(0,0,0,0.6);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:50;border-radius:16px}.membrane-popup{background:#1e293b;border:1px solid rgba(255,255,255,0.12);border-radius:16px;padding:2rem;max-width:420px;width:90%;text-align:center;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5)}.membrane-popup-icon{font-size:2.5rem;margin-bottom:0.75rem}.membrane-popup-title{font-size:1.2rem;color:#f1f5f9;margin-bottom:0.75rem;font-weight:700}.membrane-popup-msg{font-size:0.9rem;color:#94a3b8;line-height:1.6;margin-bottom:1.25rem}.membrane-popup-visual{display:flex;align-items:center;justify-content:center;gap:1rem;margin-bottom:1.5rem}.membrane-popup-zone{padding:0.5rem 1rem;border:2px solid;border-radius:10px;font-size:0.8rem;font-weight:600;background:rgba(255,255,255,0.03)}.membrane-popup-arrow{font-size:1.5rem;color:#64748b}.membrane-popup-actions{display:flex;gap:0.75rem;justify-content:center}.membrane-popup-btn{padding:0.6rem 1.5rem;border-radius:8px;font-size:0.9rem;font-weight:600;cursor:pointer;border:none;transition:transform 0.15s,opacity 0.15s}.membrane-popup-btn:hover{transform:translateY(-1px)}.membrane-popup-cancel{background:rgba(255,255,255,0.06);color:#cbd5e1;border:1px solid rgba(255,255,255,0.12)}.membrane-popup-cancel:hover{border-color:rgba(255,255,255,0.25)}@media(max-width:600px){.membrane-container{border-radius:12px}.membrane-legend{gap:0.6rem;padding:0.5rem}.membrane-legend-item{font-size:0.7rem;gap:0.35rem}.membrane-legend-swatch{width:12px;height:12px}.membrane-reset-btn{top:8px;right:8px;padding:3px 8px;font-size:0.65rem}.membrane-popup{padding:1.25rem 1rem;border-radius:12px;width:94%}.membrane-popup-icon{font-size:2rem;margin-bottom:0.5rem}.membrane-popup-title{font-size:1rem}.membrane-popup-msg{font-size:0.8rem;margin-bottom:1rem}.membrane-popup-visual{flex-direction:column;gap:0.5rem}.membrane-popup-arrow{transform:rotate(90deg)}.membrane-popup-zone{padding:0.4rem 0.75rem;font-size:0.75rem}.membrane-popup-btn{padding:0.5rem 1.25rem;font-size:0.85rem}}'; +document.head.appendChild(s);} +function addResetButton(){var btn=document.createElement('button');btn.className='membrane-reset-btn';btn.textContent='Reset Demo';btn.title='Reset all objects to their default zones'; +btn.onclick=function(){objects=JSON.parse(JSON.stringify(DEMO_OBJECTS));popup=null;dragState=null;hoveredZone=null;selectedObj=null;render();};containerEl.appendChild(btn);} +function addLegend(){var legend=document.createElement('div');legend.className='membrane-legend'; +legend.innerHTML=ZONES.slice().reverse().map(function(z){return'
'+z.icon+' '+z.label+'
';}).join(''); +containerEl.appendChild(legend);} +function init(sel){injectStyles();var wrapper=document.querySelector(sel);if(!wrapper)return; +containerEl=document.createElement('div');containerEl.className='membrane-container'; +canvasEl=document.createElementNS('http://www.w3.org/2000/svg','svg');canvasEl.setAttribute('xmlns','http://www.w3.org/2000/svg'); +containerEl.appendChild(canvasEl);wrapper.appendChild(containerEl);addResetButton();addLegend();initDrag();initTooltip();render(); +var rt;window.addEventListener('resize',function(){clearTimeout(rt);rt=setTimeout(render,100);});} +window.DataMembrane={init:init}; +})();`; }