interactive intro to open social

feat: add ownership visualization and silo comparison

- show stats (record types, apps) when clicking identity
- contrast walled gardens vs atproto ownership
- update info modal to emphasize ownership
- add visual styling for ownership boxes

Changed files
+114 -22
src
static
+72 -4
src/templates.rs
··· 878 .onboarding-progress span.done {{ 879 background: var(--text-light); 880 }} 881 </style> 882 </head> 883 <body> ··· 886 887 <div class="overlay" id="overlay"></div> 888 <div class="info-modal" id="infoModal"> 889 - <h2>@me - your at protocol identity</h2> 890 - <p>in decentralized social networks, you own your identity and your data lives in your personal data server (pds).</p> 891 - <p>third-party applications create records in your repository using different lexicons (data schemas). for example, bluesky creates posts, white wind stores blog entries, tangled.org hosts code repositories, and frontpage aggregates links - all in the same place.</p> 892 - <p>this visualization shows your identity at the center, surrounded by the third-party apps that have created data for you. click an app to see what types of records it stores, then click a record type to see the actual data.</p> 893 <button id="closeInfo">got it</button> 894 <button id="restartTour" onclick="window.restartOnboarding()" style="margin-left: 0.5rem; background: var(--surface-hover);">restart tour</button> 895 </div>
··· 878 .onboarding-progress span.done {{ 879 background: var(--text-light); 880 }} 881 + 882 + .stats-box {{ 883 + display: flex; 884 + gap: 1.5rem; 885 + margin: 1.5rem 0; 886 + padding: 1rem; 887 + background: var(--bg); 888 + border-radius: 4px; 889 + border: 1px solid var(--border); 890 + }} 891 + 892 + .stat {{ 893 + flex: 1; 894 + text-align: center; 895 + }} 896 + 897 + .stat-value {{ 898 + font-size: 1.8rem; 899 + font-weight: 600; 900 + color: var(--text); 901 + margin-bottom: 0.25rem; 902 + }} 903 + 904 + .stat-label {{ 905 + font-size: 0.65rem; 906 + color: var(--text-light); 907 + text-transform: uppercase; 908 + letter-spacing: 0.05em; 909 + }} 910 + 911 + .ownership-box {{ 912 + margin: 1rem 0; 913 + padding: 1rem; 914 + background: var(--bg); 915 + border-radius: 4px; 916 + border: 1px solid var(--border); 917 + }} 918 + 919 + .ownership-box.yours {{ 920 + background: rgba(76, 175, 80, 0.05); 921 + border-color: rgba(76, 175, 80, 0.3); 922 + }} 923 + 924 + @media (prefers-color-scheme: dark) {{ 925 + .ownership-box.yours {{ 926 + background: rgba(76, 175, 80, 0.08); 927 + border-color: rgba(76, 175, 80, 0.4); 928 + }} 929 + }} 930 + 931 + .ownership-header {{ 932 + font-size: 0.7rem; 933 + font-weight: 600; 934 + color: var(--text); 935 + margin-bottom: 0.5rem; 936 + text-transform: uppercase; 937 + letter-spacing: 0.05em; 938 + }} 939 + 940 + .ownership-text {{ 941 + font-size: 0.7rem; 942 + color: var(--text-lighter); 943 + line-height: 1.5; 944 + }} 945 + 946 + .ownership-text strong {{ 947 + color: var(--text); 948 + }} 949 </style> 950 </head> 951 <body> ··· 954 955 <div class="overlay" id="overlay"></div> 956 <div class="info-modal" id="infoModal"> 957 + <h2>@me - your repository</h2> 958 + <p>on instagram, facebook, or twitter: the platform owns your content. if they ban you, it's all gone. you can't move it, export it, or control who accesses it.</p> 959 + <p>on atproto: you own everything. your data lives in your personal server. apps like bluesky, whitewind, and frontpage just write to YOUR repository. you can switch apps, move servers, or revoke access anytime.</p> 960 + <p>click your @ in the center to see what you've built. click any app to see what it's stored in your space.</p> 961 <button id="closeInfo">got it</button> 962 <button id="restartTour" onclick="window.restartOnboarding()" style="margin-left: 0.5rem; background: var(--surface-hover);">restart tour</button> 963 </div>
+42 -18
static/app.js
··· 93 // User may not have an avatar set 94 }); 95 96 // Add identity click handler to show PDS info 97 document.querySelector('.identity').addEventListener('click', () => { 98 const detail = document.getElementById('detail'); 99 const pdsHost = pds.replace('https://', '').replace('http://', ''); 100 detail.innerHTML = ` 101 <button class="detail-close" id="detailClose">×</button> 102 - <h3>your identity</h3> 103 - <div class="subtitle">decentralized identifier & storage</div> 104 - <div class="tree-item"> 105 - <div class="tree-item-header"> 106 - <span style="color: var(--text-light);">did</span> 107 - <span style="font-size: 0.6rem; color: var(--text);">${did}</span> 108 </div> 109 - </div> 110 - <div class="tree-item"> 111 - <div class="tree-item-header"> 112 - <span style="color: var(--text-light);">handle</span> 113 - <span style="font-size: 0.6rem; color: var(--text);">@${handle}</span> 114 </div> 115 </div> 116 - <div class="tree-item"> 117 - <div class="tree-item-header"> 118 - <span style="color: var(--text-light);">personal data server</span> 119 - <span style="font-size: 0.6rem; color: var(--text);">${pds}</span> 120 - </div> 121 </div> 122 - <div style="margin-top: 1rem; padding: 0.6rem; background: var(--bg); border-radius: 4px; font-size: 0.65rem; line-height: 1.5; color: var(--text-lighter);"> 123 - your data lives at <strong style="color: var(--text);">${pdsHost}</strong>. apps like bluesky write to and read from this server. you control @<strong style="color: var(--text);">${handle}</strong> and can move it to a different server anytime. 124 </div> 125 `; 126 detail.classList.add('visible'); ··· 138 .then(r => r.json()) 139 .then(repo => { 140 const collections = repo.collections || []; 141 142 // Group by app namespace (first two parts of lexicon) 143 const apps = {};
··· 93 // User may not have an avatar set 94 }); 95 96 + // Store collections for later use 97 + let allCollections = []; 98 + 99 // Add identity click handler to show PDS info 100 document.querySelector('.identity').addEventListener('click', () => { 101 const detail = document.getElementById('detail'); 102 const pdsHost = pds.replace('https://', '').replace('http://', ''); 103 + 104 + // Count total apps 105 + const appCount = Object.keys(apps).length; 106 + 107 detail.innerHTML = ` 108 <button class="detail-close" id="detailClose">×</button> 109 + <h3>your repository</h3> 110 + <div class="subtitle">what you've built</div> 111 + 112 + <div class="stats-box"> 113 + <div class="stat"> 114 + <div class="stat-value">${allCollections.length}</div> 115 + <div class="stat-label">record types</div> 116 </div> 117 + <div class="stat"> 118 + <div class="stat-value">${appCount}</div> 119 + <div class="stat-label">apps</div> 120 </div> 121 </div> 122 + 123 + <div class="ownership-box"> 124 + <div class="ownership-header">on walled gardens</div> 125 + <div class="ownership-text">platform owns your content. account ban = everything gone. no export, no control.</div> 126 </div> 127 + 128 + <div class="ownership-box yours"> 129 + <div class="ownership-header">on atproto</div> 130 + <div class="ownership-text">you own it. lives at <strong>${pdsHost}</strong>. move servers, switch apps, export anytime. can't be taken away.</div> 131 + </div> 132 + 133 + <div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border);"> 134 + <div style="font-size: 0.65rem; color: var(--text-light); margin-bottom: 0.5rem;">technical details</div> 135 + <div class="tree-item"> 136 + <div class="tree-item-header"> 137 + <span style="color: var(--text-light);">did</span> 138 + <span style="font-size: 0.55rem; color: var(--text);">${did}</span> 139 + </div> 140 + </div> 141 + <div class="tree-item"> 142 + <div class="tree-item-header"> 143 + <span style="color: var(--text-light);">handle</span> 144 + <span style="font-size: 0.6rem; color: var(--text);">@${handle}</span> 145 + </div> 146 + </div> 147 </div> 148 `; 149 detail.classList.add('visible'); ··· 161 .then(r => r.json()) 162 .then(repo => { 163 const collections = repo.collections || []; 164 + allCollections = collections; 165 166 // Group by app namespace (first two parts of lexicon) 167 const apps = {};