Members area: Circle Method lessons 2-10 + Video 8 gated behind position ownership

The product now has a lid. Server refuses the gated video FILES without a
wallet-verified member session (the Mini App bridge session counts, so
Telegram members see no locks). Lesson 1, every poster, and all join-funnel
and transparency videos stay public: free preview + trust engine. Training
page renders locked cards with a one-signature unlock (same SIWE flow as
Messages) and a members-unlocked badge. Chatbot: new members-area canned
answer, Method answer notes the preview, AI prompt routing rule now answers
prospects directly instead of sending locked links; bot lesson command notes
the unlock path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-25 04:10:57 -05:00
parent 76a9b449c9
commit ca4b5bf34f
5 changed files with 70 additions and 7 deletions
+52
View File
@@ -16,3 +16,55 @@ fetch('/api/public/config').then(function(r){return r.json()}).then(function(c){
// carry the member's saved id onto tools links so promo assets arrive personalized
try{var tid=localStorage.getItem('rmc.toolsId')||localStorage.getItem('ctb.myId');
if(tid&&/^\d+$/.test(tid))document.querySelectorAll('a[href="/tools"]').forEach(function(a){a.href='/tools?id='+tid;});}catch(e){}
// Members area: Circle Method lessons 2-10 + Video 8 are member-only. The
// SERVER refuses those video files without a session — this makes the page
// honest about it: locked cards with a one-signature unlock. Members arriving
// through the Telegram Mini App already carry a session and see no locks.
(async function(){
'use strict';
const gated=[...document.querySelectorAll('video')].filter(v=>{
const s=v.getAttribute('src')||'';
return (/\/v\/rmc-method-m\d+l\d+-/.test(s)&&s.indexOf('rmc-method-m1l1-')<0)||s.indexOf('/training/egift_cards.mp4')>=0;
});
if(!gated.length)return;
let member=null;
try{const r=await fetch('/api/public/msg-me');if(r.ok)member=await r.json();}catch(e){}
if(member&&member.id){
const h=document.getElementById('module-1');
if(h){const b=document.createElement('p');b.className='micro';b.style.cssText='max-width:880px;margin:6px auto 0;color:var(--ok)';b.textContent='✅ Members area unlocked — position #'+member.id;h.after(b);}
return;
}
gated.forEach(v=>{
const poster=v.getAttribute('poster')||'';
const box=document.createElement('div');
box.className='rmc-lock';
box.style.cssText='position:relative;border-radius:12px;overflow:hidden;aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;background:#08131f center/cover no-repeat';
if(poster)box.style.backgroundImage="linear-gradient(rgba(4,10,17,.85),rgba(4,10,17,.85)),url('"+poster+"')";
box.innerHTML='<div style="text-align:center;padding:18px;max-width:460px">'
+'<div style="font-size:32px">🔒</div>'
+'<div style="font-weight:800;font-size:17px;margin:6px 0 4px">Members area</div>'
+'<p class="micro" style="color:var(--muted);margin:0 0 12px">This lesson comes with every team position. Sign in with the wallet that owns your position — one free signature, it can\'t move funds. Linked on Telegram? The ☰ Mini App in @RMCircleBot unlocks everything automatically.</p>'
+'<button class="btn btn-primary btn-sm rmc-unlock">🔓 I\'m a member — connect &amp; unlock</button> '
+'<a class="btn btn-secondary btn-sm" href="/start">Not yet? See how to join</a>'
+'<div class="micro rmc-lock-err" style="color:#ff7b7b;margin-top:8px"></div></div>';
v.replaceWith(box);
});
const UNLOCK_LABEL='🔓 I\'m a member — connect & unlock';
document.querySelectorAll('button.rmc-unlock').forEach(b=>b.addEventListener('click',async ev=>{
const btn=ev.currentTarget,err=btn.closest('.rmc-lock').querySelector('.rmc-lock-err');
btn.disabled=true;btn.textContent='Connecting…';
try{
const eth=await window.RMCWallet.pick();
if(!eth){err.textContent='No wallet in this browser. On a phone, open this page inside your wallet app\'s browser — or use the Telegram Mini App.';btn.disabled=false;btn.textContent=UNLOCK_LABEL;return;}
const accs=await eth.request({method:'eth_requestAccounts'});const account=accs[0];
const ch=await(await fetch('/api/public/msg-challenge',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({address:account})})).json();
if(!ch.message)throw new Error(ch.error||'Could not start sign-in.');
let hex='0x';for(const b2 of new TextEncoder().encode(ch.message))hex+=b2.toString(16).padStart(2,'0');
const sig=await eth.request({method:'personal_sign',params:[hex,account]});
const vr=await(await fetch('/api/public/msg-verify',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({address:account,signature:sig})})).json();
if(!vr.ok)throw new Error(vr.error||'Verification failed — is this the wallet that owns your position?');
location.reload();
}catch(e){err.textContent=e.message||String(e);btn.disabled=false;btn.textContent=UNLOCK_LABEL;}
}));
})();