p='/home/user/sprout/app.js' js=open(p).read() def func(src,name): i=src.find('async function '+name) if i<0: i=src.find('function '+name) b=src.find('{',i); depth=0; k=b while kοΌ‹ Subscribe\':\'\'}'+old_btn assert old_btn in js, "btn not found" js=js.replace(old_btn,new_btn,1) # 2) subscribe wiring after share wiring share_line="$('#shareb',m).onclick=()=>{ const url=location.href.split('#')[0]; navigator.clipboard?.writeText(url).then(()=>toast('Link copied!'),()=>toast(url)); };" wire="\n const subb=$('#subb',m); if(subb){ let fol=await isFollowing(v.ch); const paint=()=>{ subb.textContent=fol?'βœ“ Subscribed':'οΌ‹ Subscribe'; subb.className=`px-3 py-1.5 rounded-full font-semibold ${fol?'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300':'bg-stone-100 dark:bg-stone-800'}`; }; paint(); subb.onclick=async()=>{ if(fol){ await unfollowChannel(v.ch); fol=false; } else { await followChannel(v.ch); fol=true; } paint(); }; }" assert share_line in js, "share line not found" js=js.replace(share_line, share_line+wire,1) # 3) replace renderProfile new_profile='''async function renderProfile(){ app.innerHTML=shell(loadingState()); if(sb && !state.videos.length) state.videos=await fetchVideos(); const mine=state.videos.filter(v=>state.user&&v.ch===state.user.channel); const totalViews=mine.reduce((a,v)=>a+v.views,0); let followers=0; if(sb&&state.user) followers=await fetchFollowerCount(state.user.channel); const eligible=followers>=MON.followers && totalViews>=MON.views; const bal=eligible?totalViews*MON.perView:0; app.innerHTML=shell(`
${av(state.user,'w-20 h-20','text-4xl')}

${state.user.channel}

${fmt(followers)} followers \u00b7 ${mine.length} videos \u00b7 age ${state.user.age}

${fmt(totalViews)}
Total views
${fmt(followers)}
Followers
$${bal.toFixed(2)}
Earnings
${eligible?'\u2713 Monetized \u2014 you earn $1 per 5,000 views.':`\ud83d\udd12 Not monetized yet \u2014 need ${MON.followers} followers & ${fmt(MON.views)} views. `}

Your videos

${mine.length?`
${mine.map(vcard).join('')}
`:`

No videos yet \u2014

`}
`); }''' op=func(js,'renderProfile'); assert op; js=js.replace(op,new_profile,1) # 4) replace renderEarnings + payout oe=func(js,'renderEarnings'); assert oe new_earn='''async function renderEarnings(){ app.innerHTML=shell(loadingState()); if(sb && !state.videos.length) state.videos=await fetchVideos(); const mine=state.videos.filter(v=>state.user&&v.ch===state.user.channel); const views=mine.reduce((a,v)=>a+v.views,0); let followers=0; if(sb&&state.user) followers=await fetchFollowerCount(state.user.channel); const fOk=followers>=MON.followers, vOk=views>=MON.views, eligible=fOk&&vOk; const balance=eligible?views*MON.perView:0, canPay=balance>=1; const fPct=Math.min(100,Math.round(followers/MON.followers*100)), vPct=Math.min(100,Math.round(views/MON.views*100)); app.innerHTML=shell(`

\ud83d\udcb0 Earnings

Become monetized at ${MON.followers} followers + ${fmt(MON.views)} views. Then you earn $1 per 5,000 views. Payouts go to a parent/guardian.

${eligible?`
Available balance \u00b7 \u2713 Monetized
$${balance.toFixed(2)}
` :`
\ud83d\udd12 Monetization progress
Followers${fmt(followers)} / ${MON.followers}
Total views${fmt(views)} / ${fmt(MON.views)}

Keep posting & growing \u2014 unlock both to start earning.

`}

How the money works \ud83d\udcb8

${[['\ud83d\udcfa','Sprout runs kid-safe ads','Pre-screened, non-targeted sponsors on every video \u2014 how the company earns from day one.'],['\ud83e\udd1d','Revenue split','Monetized creators keep 25% of the revenue their videos earn; Sprout keeps 75% to run hosting, safety & the platform.'],['\u2b50','Memberships & gifts','Fans can subscribe monthly or send gifts \u2014 extra income for creators and Sprout.']].map(([i,t,d])=>`
${i}
${t}
${d}
`).join('')}

Real payouts need a payment processor (Stripe) \u2014 added once there's an audience.

`); }''' js=js.replace(oe,new_earn,1) # payout already exists after; update its threshold message (keep as is) open(p,'w').write(js) print("written bytes",len(js))