User:Docmoates/Social: Difference between revisions

From XMethod Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
<html>
<div id="social-app"></div>
<div id="sf-app">
<style>
<style>
#sf-app{max-width:680px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#f0f2f5;min-height:100vh;padding:20px}
#social-app{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#fafafa;min-height:100vh;padding-bottom:40px}
#sf-app *{box-sizing:border-box}
#social-app *{box-sizing:border-box;margin:0;padding:0}
.sf-card{background:#fff;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px}
.sa-profile-header{background:#fff;border-bottom:1px solid #dbdbdb}
.sf-avatar{width:44px;height:44px;border-radius:50%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:600;font-size:16px;cursor:pointer;overflow:hidden;flex-shrink:0}
.sa-cover{height:200px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);position:relative}
.sf-avatar img{width:100%;height:100%;object-fit:cover}
.sa-cover img{width:100%;height:100%;object-fit:cover}
.sf-avatar-sm{width:32px;height:32px;font-size:12px}
.sa-cover-edit{position:absolute;bottom:12px;right:12px;background:rgba(0,0,0,.6);color:#fff;border:none;padding:8px 16px;border-radius:8px;cursor:pointer;font-size:13px}
.sf-btn{background:#1877f2;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-weight:600;cursor:pointer}
.sa-profile-info{max-width:935px;margin:0 auto;padding:0 20px;display:flex;gap:30px;margin-top:-50px;position:relative;z-index:10}
.sf-btn:disabled{opacity:.5}
.sa-profile-pic{width:150px;height:150px;border-radius:50%;border:4px solid #fff;background:#fff;overflow:hidden;cursor:pointer;flex-shrink:0;box-shadow:0 2px 10px rgba(0,0,0,.1)}
.sf-btn-light{background:#e4e6eb;color:#050505}
.sa-profile-pic img{width:100%;height:100%;object-fit:cover}
.sf-modal{display:none;position:fixed;inset:0;background:rgba(255,255,255,.85);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:1000;align-items:center;justify-content:center}
.sa-profile-pic-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:48px;font-weight:600}
.sf-modal.open{display:flex}
.sa-profile-details{flex:1;padding-top:60px}
.sf-modal-box{background:#fff;border-radius:12px;padding:20px;width:90%;max-width:420px;box-shadow:0 8px 32px rgba(0,0,0,.15)}
.sa-profile-top{display:flex;align-items:center;gap:20px;margin-bottom:16px;flex-wrap:wrap}
.sf-modal-title{font-size:18px;font-weight:600;margin-bottom:16px}
.sa-username{font-size:28px;font-weight:300}
.sf-tabs{display:flex;border-bottom:2px solid #e4e6eb;margin-bottom:16px}
.sa-edit-btn{background:#0095f6;color:#fff;border:none;padding:8px 24px;border-radius:8px;font-weight:600;cursor:pointer;font-size:14px}
.sf-tab{flex:1;padding:10px;text-align:center;cursor:pointer;font-weight:600;color:#65676b;border-bottom:2px solid transparent;margin-bottom:-2px}
.sa-edit-btn:hover{background:#1877f2}
.sf-tab.active{color:#1877f2;border-color:#1877f2}
.sa-stats{display:flex;gap:32px;margin-bottom:16px}
.sf-tab-panel{display:none}
.sa-stat{text-align:center}
.sf-tab-panel.active{display:block}
.sa-stat-num{font-weight:600;font-size:18px}
.sf-upload-area{border:2px dashed #ccd0d5;border-radius:8px;padding:32px;text-align:center;cursor:pointer;transition:.2s}
.sa-stat-label{color:#8e8e8e;font-size:14px}
.sf-upload-area:hover{border-color:#1877f2;background:#f0f7ff}
.sa-bio{max-width:400px;line-height:1.5}
.sf-upload-area.drag{border-color:#1877f2;background:#e7f3ff}
.sa-bio-name{font-weight:600}
.sf-input{width:100%;padding:10px 12px;border:1px solid #ccd0d5;border-radius:8px;font-size:14px;margin-bottom:12px}
.sa-main{max-width:935px;margin:0 auto;padding:20px}
.sf-preview{max-width:100%;max-height:120px;border-radius:8px;margin-top:12px}
.sa-stories{display:flex;gap:16px;padding:16px 0;overflow-x:auto;margin-bottom:20px}
.sf-progress{height:6px;background:#e4e6eb;border-radius:3px;margin-top:12px;overflow:hidden}
.sa-story{display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer}
.sf-progress-bar{height:100%;background:#1877f2;width:0;transition:width .3s}
.sa-story-ring{width:66px;height:66px;border-radius:50%;padding:3px;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)}
.sf-composer{padding:16px}
.sa-story-ring.add{background:#dbdbdb}
.sf-composer-top{display:flex;gap:12px}
.sa-story-ring.seen{background:#dbdbdb}
.sf-composer-input{flex:1;border:none;background:#f0f2f5;border-radius:20px;padding:12px 16px;font-size:15px;resize:none;min-height:44px;outline:none}
.sa-story-inner{width:100%;height:100%;border-radius:50%;border:2px solid #fff;overflow:hidden;background:#fafafa;display:flex;align-items:center;justify-content:center}
.sf-composer-actions{display:flex;justify-content:space-between;align-items:center;margin-top:12px;padding-top:12px;border-top:1px solid #e4e6eb}
.sa-story-inner img{width:100%;height:100%;object-fit:cover}
.sf-composer-btns{display:flex;gap:4px}
.sa-story-add{font-size:24px;color:#0095f6}
.sf-icon-btn{width:36px;height:36px;border:none;background:#f0f2f5;border-radius:8px;font-size:18px;cursor:pointer}
.sa-story-name{font-size:12px;color:#262626;max-width:66px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sf-icon-btn:hover{background:#e4e6eb}
.sa-composer{background:#fff;border:1px solid #dbdbdb;border-radius:12px;margin-bottom:20px}
.sf-stories{display:flex;gap:12px;padding:16px;overflow-x:auto}
.sa-composer-top{display:flex;gap:12px;padding:16px}
.sf-story{display:flex;flex-direction:column;align-items:center;gap:4px}
.sa-composer-avatar{width:44px;height:44px;border-radius:50%;overflow:hidden;flex-shrink:0}
.sf-story-ring{width:60px;height:60px;border-radius:50%;padding:2px;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)}
.sa-composer-avatar img{width:100%;height:100%;object-fit:cover}
.sf-story-ring.add{background:#e4e6eb}
.sa-composer-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:16px;font-weight:600}
.sf-story-inner{width:100%;height:100%;border-radius:50%;border:2px solid #fff;overflow:hidden;display:flex;align-items:center;justify-content:center;background:#f0f2f5;cursor:pointer}
.sa-composer-input{flex:1;border:none;resize:none;font-size:16px;line-height:1.5;min-height:60px;outline:none;font-family:inherit}
.sf-story-inner img{width:100%;height:100%;object-fit:cover}
.sa-composer-input::placeholder{color:#8e8e8e}
.sf-story-name{font-size:11px;color:#65676b;max-width:64px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sa-composer-preview{padding:0 16px 16px;display:none}
.sf-post-header{display:flex;gap:10px;padding:12px 16px}
.sa-composer-preview.show{display:block}
.sf-post-info{flex:1}
.sa-composer-preview-inner{position:relative;display:inline-block}
.sf-post-name{font-weight:600;font-size:14px}
.sa-composer-preview img,.sa-composer-preview video{max-width:100%;max-height:300px;border-radius:8px}
.sf-post-meta{font-size:12px;color:#65676b}
.sa-composer-preview-remove{position:absolute;top:8px;right:8px;width:28px;height:28px;border-radius:50%;background:rgba(0,0,0,.7);color:#fff;border:none;cursor:pointer;font-size:16px}
.sf-post-content{padding:0 16px 12px;font-size:15px;line-height:1.4;white-space:pre-wrap}
.sa-composer-location{padding:0 16px 12px;font-size:14px;color:#8e8e8e;display:none}
.sf-post-media img,.sf-post-media video,.sf-post-media iframe{width:100%;max-height:500px;object-fit:cover}
.sa-composer-location.show{display:flex;align-items:center;gap:8px}
.sf-post-location{padding:4px 16px 8px;font-size:12px;color:#65676b}
.sa-composer-location button{background:none;border:none;cursor:pointer;font-size:14px}
.sf-post-stats{display:flex;justify-content:space-between;padding:8px 16px;font-size:13px;color:#65676b;border-bottom:1px solid #e4e6eb}
.sa-composer-actions{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-top:1px solid #efefef}
.sf-post-actions{display:flex;padding:4px 8px}
.sa-composer-buttons{display:flex;gap:4px}
.sf-post-action{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:8px;border:none;background:none;font-size:14px;font-weight:600;color:#65676b;cursor:pointer;border-radius:4px}
.sa-icon-btn{width:40px;height:40px;border:none;background:none;cursor:pointer;font-size:20px;border-radius:50%;transition:background .2s}
.sf-post-action:hover{background:#f0f2f5}
.sa-icon-btn:hover{background:#f0f0f0}
.sf-post-action.liked{color:#1877f2}
.sa-post-btn{background:#0095f6;color:#fff;border:none;padding:10px 24px;border-radius:8px;font-weight:600;cursor:pointer;font-size:14px}
.sf-comments{padding:8px 16px 12px;background:#f7f8fa}
.sa-post-btn:disabled{opacity:.3;cursor:default}
.sf-comment{display:flex;gap:8px;margin-bottom:8px}
.sa-post-btn:not(:disabled):hover{background:#1877f2}
.sf-comment-bubble{background:#fff;border-radius:16px;padding:8px 12px}
.sa-feed{display:flex;flex-direction:column;gap:20px}
.sf-comment-author{font-weight:600;font-size:12px}
.sa-post{background:#fff;border:1px solid #dbdbdb;border-radius:12px;overflow:hidden}
.sf-comment-text{font-size:13px}
.sa-post-header{display:flex;align-items:center;gap:12px;padding:14px 16px}
.sf-comment-form{display:flex;gap:8px;margin-top:8px}
.sa-post-avatar{width:42px;height:42px;border-radius:50%;overflow:hidden}
.sf-comment-input{flex:1;border:none;background:#fff;border-radius:18px;padding:8px 12px;font-size:13px;outline:none}
.sa-post-avatar img{width:100%;height:100%;object-fit:cover}
.sf-empty{text-align:center;padding:48px 20px;color:#65676b}
.sa-post-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;font-weight:600}
.sf-empty-icon{font-size:48px;margin-bottom:12px}
.sa-post-user{flex:1}
.sf-viewer{display:none;position:fixed;inset:0;background:#000;z-index:2000;align-items:center;justify-content:center}
.sa-post-username{font-weight:600;font-size:14px;color:#262626}
.sf-viewer.open{display:flex}
.sa-post-meta{font-size:12px;color:#8e8e8e}
.sf-viewer img{max-width:100%;max-height:100%;object-fit:contain}
.sa-post-menu{background:none;border:none;cursor:pointer;font-size:20px;color:#262626}
.sf-viewer-close{position:absolute;top:16px;right:16px;width:40px;height:40px;border-radius:50%;border:none;background:rgba(255,255,255,.2);color:#fff;font-size:20px;cursor:pointer}
.sa-post-content{padding:0 16px 12px;font-size:15px;line-height:1.5;white-space:pre-wrap}
.sf-viewer-progress{position:absolute;top:8px;left:8px;right:8px;height:3px;background:rgba(255,255,255,.3);border-radius:2px}
.sa-post-location{padding:0 16px 8px;font-size:13px;color:#8e8e8e}
.sf-viewer-progress span{display:block;height:100%;background:#fff;border-radius:2px;animation:progress 5s linear}
.sa-post-media{background:#000}
@keyframes progress{from{width:0}to{width:100%}}
.sa-post-media img{width:100%;max-height:600px;object-fit:contain}
.sf-emoji-picker{position:absolute;bottom:44px;left:0;background:#fff;border-radius:12px;box-shadow:0 4px 16px rgba(0,0,0,.15);padding:12px;width:280px;z-index:100;display:none}
.sa-post-media video{width:100%;max-height:600px}
.sf-emoji-picker.open{display:block}
.sa-post-media iframe{width:100%;height:400px;border:none}
.sf-emoji-cats{display:flex;gap:2px;margin-bottom:8px;border-bottom:1px solid #e4e6eb;padding-bottom:8px}
.sa-post-actions{display:flex;gap:16px;padding:12px 16px}
.sf-emoji-cat{border:none;background:none;font-size:16px;padding:4px 8px;cursor:pointer;border-radius:4px;opacity:.5}
.sa-post-action{background:none;border:none;cursor:pointer;font-size:24px;padding:0;transition:transform .2s}
.sf-emoji-cat:hover,.sf-emoji-cat.active{opacity:1;background:#f0f2f5}
.sa-post-action:hover{transform:scale(1.1)}
.sf-emoji-list{display:grid;grid-template-columns:repeat(8,1fr);gap:2px;max-height:180px;overflow-y:auto}
.sa-post-action.liked{color:#ed4956}
.sf-emoji-list button{border:none;background:none;font-size:20px;padding:4px;cursor:pointer;border-radius:4px}
.sa-post-likes{padding:0 16px 8px;font-weight:600;font-size:14px}
.sf-emoji-list button:hover{background:#f0f2f5}
.sa-post-caption{padding:0 16px 8px;font-size:14px;line-height:1.4}
.sf-pending{display:none;padding:8px 16px;margin-bottom:8px;background:#fff;border-radius:8px}
.sa-post-caption strong{font-weight:600}
.sf-pending.show{display:flex;align-items:center;gap:8px}
.sa-post-comments-count{padding:0 16px 8px;font-size:14px;color:#8e8e8e;cursor:pointer}
.sf-pending img,.sf-pending video{max-height:80px;border-radius:6px}
.sa-post-time{padding:0 16px 12px;font-size:10px;color:#8e8e8e;text-transform:uppercase}
.sf-del{margin-left:auto;border:none;background:none;color:#65676b;cursor:pointer}
.sa-post-comment-form{display:flex;gap:12px;padding:12px 16px;border-top:1px solid #efefef}
.sa-post-comment-input{flex:1;border:none;font-size:14px;outline:none}
.sa-post-comment-input::placeholder{color:#8e8e8e}
.sa-post-comment-submit{background:none;border:none;color:#0095f6;font-weight:600;cursor:pointer;font-size:14px}
.sa-post-comment-submit:disabled{opacity:.3;cursor:default}
.sa-comments-section{display:none;padding:0 16px 12px;max-height:300px;overflow-y:auto}
.sa-comments-section.show{display:block}
.sa-comment{display:flex;gap:12px;margin-bottom:12px}
.sa-comment-avatar{width:32px;height:32px;border-radius:50%;overflow:hidden;flex-shrink:0}
.sa-comment-avatar img{width:100%;height:100%;object-fit:cover}
.sa-comment-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:600}
.sa-comment-body{flex:1}
.sa-comment-text{font-size:14px;line-height:1.4}
.sa-comment-text strong{font-weight:600}
.sa-comment-time{font-size:12px;color:#8e8e8e;margin-top:4px}
.sa-empty{text-align:center;padding:60px 20px;background:#fff;border:1px solid #dbdbdb;border-radius:12px}
.sa-empty-icon{font-size:64px;margin-bottom:16px}
.sa-empty-title{font-size:24px;font-weight:300;margin-bottom:8px}
.sa-empty-text{color:#8e8e8e}
.sa-modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.65);z-index:1000;align-items:center;justify-content:center;padding:20px}
.sa-modal.open{display:flex}
.sa-modal-content{background:#fff;border-radius:12px;width:100%;max-width:500px;max-height:90vh;overflow:auto;animation:modalIn .2s}
@keyframes modalIn{from{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}
.sa-modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px;border-bottom:1px solid #dbdbdb}
.sa-modal-title{font-weight:600;font-size:16px}
.sa-modal-close{background:none;border:none;font-size:24px;cursor:pointer;padding:0;line-height:1}
.sa-modal-body{padding:20px}
.sa-tabs{display:flex;border-bottom:1px solid #dbdbdb}
.sa-tab{flex:1;padding:16px;text-align:center;cursor:pointer;font-weight:600;color:#8e8e8e;border-bottom:1px solid transparent;margin-bottom:-1px;transition:all .2s}
.sa-tab:hover{color:#262626}
.sa-tab.active{color:#262626;border-bottom-color:#262626}
.sa-tab-panel{display:none;padding:20px}
.sa-tab-panel.active{display:block}
.sa-upload-zone{border:2px dashed #dbdbdb;border-radius:8px;padding:40px;text-align:center;cursor:pointer;transition:all .2s}
.sa-upload-zone:hover{border-color:#0095f6;background:#f0f7ff}
.sa-upload-zone.dragover{border-color:#0095f6;background:#e3f2fd}
.sa-upload-zone input{display:none}
.sa-upload-icon{font-size:48px;margin-bottom:12px}
.sa-upload-text{color:#8e8e8e}
.sa-upload-text span{color:#0095f6;font-weight:600}
.sa-input{width:100%;padding:14px;border:1px solid #dbdbdb;border-radius:8px;font-size:14px;margin-bottom:12px;outline:none;transition:border-color .2s}
.sa-input:focus{border-color:#262626}
.sa-textarea{width:100%;padding:14px;border:1px solid #dbdbdb;border-radius:8px;font-size:14px;margin-bottom:12px;outline:none;resize:vertical;min-height:80px;font-family:inherit}
.sa-textarea:focus{border-color:#262626}
.sa-preview-img{width:100%;max-height:200px;object-fit:contain;border-radius:8px;margin-top:12px;display:none}
.sa-preview-img.show{display:block}
.sa-progress{height:4px;background:#dbdbdb;border-radius:2px;margin-top:12px;overflow:hidden;display:none}
.sa-progress.show{display:block}
.sa-progress-bar{height:100%;background:linear-gradient(90deg,#0095f6,#00d4ff);width:0;transition:width .3s}
.sa-btn-row{display:flex;gap:12px;margin-top:20px}
.sa-btn{flex:1;padding:14px;border:none;border-radius:8px;font-weight:600;font-size:14px;cursor:pointer;transition:all .2s}
.sa-btn-primary{background:#0095f6;color:#fff}
.sa-btn-primary:hover{background:#1877f2}
.sa-btn-secondary{background:#efefef;color:#262626}
.sa-btn-secondary:hover{background:#dbdbdb}
.sa-emoji-picker{position:absolute;bottom:50px;left:0;background:#fff;border-radius:12px;box-shadow:0 0 20px rgba(0,0,0,.15);width:320px;z-index:100;display:none}
.sa-emoji-picker.open{display:block}
.sa-emoji-header{display:flex;gap:4px;padding:12px;border-bottom:1px solid #efefef;overflow-x:auto}
.sa-emoji-cat{background:none;border:none;font-size:20px;padding:8px;cursor:pointer;border-radius:8px;opacity:.5;transition:all .2s}
.sa-emoji-cat:hover,.sa-emoji-cat.active{opacity:1;background:#efefef}
.sa-emoji-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:4px;padding:12px;max-height:200px;overflow-y:auto}
.sa-emoji-btn{background:none;border:none;font-size:24px;padding:4px;cursor:pointer;border-radius:4px;transition:background .2s}
.sa-emoji-btn:hover{background:#efefef}
.sa-story-viewer{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:#1a1a1a;z-index:2000;align-items:center;justify-content:center}
.sa-story-viewer.open{display:flex}
.sa-story-viewer-content{position:relative;max-width:400px;width:100%}
.sa-story-viewer-progress{position:absolute;top:16px;left:16px;right:16px;height:2px;background:rgba(255,255,255,.3);border-radius:1px}
.sa-story-viewer-progress-bar{height:100%;background:#fff;border-radius:1px;width:0;animation:storyProgress 5s linear forwards}
@keyframes storyProgress{from{width:0}to{width:100%}}
.sa-story-viewer-header{position:absolute;top:24px;left:16px;right:16px;display:flex;align-items:center;gap:12px;color:#fff}
.sa-story-viewer-avatar{width:32px;height:32px;border-radius:50%;overflow:hidden;border:2px solid #fff}
.sa-story-viewer-avatar img{width:100%;height:100%;object-fit:cover}
.sa-story-viewer-name{font-weight:600;font-size:14px}
.sa-story-viewer-close{position:absolute;top:16px;right:16px;background:none;border:none;color:#fff;font-size:28px;cursor:pointer;z-index:10}
.sa-story-viewer img{max-width:100%;max-height:80vh;border-radius:8px}
@media(max-width:768px){
.sa-profile-info{flex-direction:column;align-items:center;text-align:center;margin-top:-75px}
.sa-profile-pic{width:120px;height:120px}
.sa-profile-details{padding-top:16px}
.sa-stats{justify-content:center}
.sa-bio{margin:0 auto}
.sa-cover{height:150px}
}
</style>
</style>
<script>
(function(){
'use strict';
function waitForMw(callback,maxWait){
var start=Date.now();
maxWait=maxWait||10000;
(function check(){
if(window.jQuery&&window.mw&&mw.loader&&mw.config){
callback(window.jQuery,window.mw);
}else if(Date.now()-start<maxWait){
setTimeout(check,100);
}else{
console.error('MediaWiki not loaded');
document.getElementById('social-app').innerHTML='<div style="padding:40px;text-align:center">Error: MediaWiki not loaded. Please refresh.</div>';
}
})();
}


<div class="sf-card sf-stories" id="stories"></div>
waitForMw(function($,mw){
mw.loader.using(['mediawiki.api','mediawiki.util']).then(function(){
initApp($,mw);
}).catch(function(e){
console.error('Module load error:',e);
document.getElementById('social-app').innerHTML='<div style="padding:40px;text-align:center">Error loading modules. Please refresh.</div>';
});
});


<div class="sf-card sf-composer">
function initApp($,mw){
<div class="sf-composer-top">
var api=new mw.Api();
<div class="sf-avatar" id="my-avatar"></div>
var apiUrl=mw.util.wikiScript('api');
<textarea class="sf-composer-input" id="post-text" placeholder="What's on your mind?"></textarea>
var currentUser=mw.config.get('wgUserName')||'Guest';
</div>
var isLoggedIn=currentUser!=='Guest'&&currentUser!==null;
<div class="sf-pending" id="pending-media"></div>
<div class="sf-pending" id="pending-location"></div>
<div class="sf-composer-actions">
<div class="sf-composer-btns" style="position:relative">
<button class="sf-icon-btn" id="btn-photo" title="Photo">🖼️</button>
<button class="sf-icon-btn" id="btn-emoji" title="Emoji">😊</button>
<button class="sf-icon-btn" id="btn-video" title="Video">🎬</button>
<button class="sf-icon-btn" id="btn-location" title="Location">📍</button>
<div class="sf-emoji-picker" id="emoji-picker"></div>
</div>
<button class="sf-btn" id="btn-post">Post</button>
</div>
</div>


<div id="feed"></div>
var userData={
photo:localStorage.getItem('sf_photo_'+currentUser)||'',
cover:localStorage.getItem('sf_cover_'+currentUser)||'',
bio:localStorage.getItem('sf_bio_'+currentUser)||'',
displayName:localStorage.getItem('sf_name_'+currentUser)||currentUser
};


<div class="sf-modal" id="modal-profile">
var pendingPost={image:'',video:'',location:''};
<div class="sf-modal-box">
var uploadFiles={};
<div class="sf-modal-title">Profile Photo</div>
 
<div class="sf-tabs"><div class="sf-tab active" data-t="p-upload">Upload</div><div class="sf-tab" data-t="p-url">URL</div></div>
var emojis={
<div class="sf-tab-panel active" id="p-upload">
'😀':['😀','😃','😄','😁','😆','😅','🤣','😂','🙂','😉','😊','😇','🥰','😍','🤩','😘','😗','😚','😋','😛','😜','🤪','😝','🤑','🤗','🤭','🤫','🤔','🤐','🤨','😐','😑','😶','😏','😒','🙄','😬','🤥','😌','😔','😪','🤤','😴','😷','🤒','🤕','🤢','🤮','🤧','🥵','🥶','🥴','😵','🤯','🤠','🥳','🥸','😎','🤓','🧐','😕','😟','🙁','☹️','😮','😯','😲','😳','🥺','😦','😧','😨','😰','😥','😢','😭','😱','😖','😣','😞','😓','😩','😫','🥱','😤','😡','😠','🤬','😈','👿','💀','☠️','💩','🤡','👹','👺','👻','👽','👾','🤖'],
<div class="sf-upload-area" id="profile-drop"><div style="font-size:32px;margin-bottom:8px">📸</div><div style="color:#65676b">Click or drag image</div><input type="file" accept="image/*" style="display:none"></div>
'👋':['👋','🤚','🖐️','✋','🖖','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','🖕','👇','☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍️','💅','🤳','💪','🦾','🦿','🦵','🦶','👂','🦻','👃','🧠','🫀','🫁','🦷','🦴','👀','👁️','👅','👄','💋','🩸'],
<div class="sf-progress" style="display:none"><div class="sf-progress-bar"></div></div>
'❤️':['❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝','💟','☮️','✝️','☪️','🕉️','☸️','✡️','🔯','🕎','☯️','☦️','🛐','⛎','♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓'],
<img class="sf-preview" style="display:none">
'🐶':['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐻‍❄️','🐨','🐯','🦁','🐮','🐷','🐽','🐸','🐵','🙈','🙉','🙊','🐒','🐔','🐧','🐦','🐤','🐣','🐥','🦆','🦅','🦉','🦇','🐺','🐗','🐴','🦄','🐝','🪱','🐛','🦋','🐌','🐞','🐜','🪰','🪲','🪳','🦟','🦗','🕷️','🦂','🐢','🐍','🦎','🦖','🦕','🐙','🦑','🦐','🦞','🦀','🐡','🐠','🐟','🐬','🐳','🐋','🦈','🐊','🐅','🐆','🦓','🦍','🦧','🦣','🐘','🦛','🦏','🐪','🐫','🦒','🦘','🦬','🐃','🐂','🐄','🐎','🐖','🐏','🐑','🦙','🐐','🦌','🐕','🐩','🦮','🐕‍🦺','🐈','🐈‍⬛','🪶','🐓','🦃','🦤','🦚','🦜','🦢','🦩','🕊️','🐇','🦝','🦨','🦡','🦫','🦦','🦥','🐁','🐀','🐿️','🦔'],
</div>
'🍎':['🍎','🍐','🍊','🍋','🍌','🍉','🍇','🍓','🫐','🍈','🍒','🍑','🥭','🍍','🥥','🥝','🍅','🍆','🥑','🥦','🥬','🥒','🌶️','🫑','🌽','🥕','🫒','🧄','🧅','🥔','🍠','🥐','🥯','🍞','🥖','🥨','🧀','🥚','🍳','🧈','🥞','🧇','🥓','🥩','🍗','🍖','🦴','🌭','🍔','🍟','🍕','🫓','🥪','🥙','🧆','🌮','🌯','🫔','🥗','🥘','🫕','🥫','🍝','🍜','🍲','🍛','🍣','🍱','🥟','🦪','🍤','🍙','🍚','🍘','🍥','🥠','🥮','🍢','🍡','🍧','🍨','🍦','🥧','🧁','🍰','🎂','🍮','🍭','🍬','🍫','🍿','🍩','🍪','🌰','🥜','🍯','🥛','🍼','🫖','☕','🍵','🧃','🥤','🧋','🍶','🍺','🍻','🥂','🍷','🥃','🍸','🍹','🧉','🍾','🧊','🥄','🍴','🍽️','🥣','🥡','🥢','🧂'],
<div class="sf-tab-panel" id="p-url">
'⚽':['⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🪀','🏓','🏸','🏒','🏑','🥍','🏏','🪃','🥅','⛳','🪁','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛼','🛷','⛸️','🥌','🎿','⛷️','🏂','🪂','🏋️','🤼','🤸','⛹️','🤺','🤾','🏌️','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚵','🚴','🏆','🥇','🥈','🥉','🏅','🎖️','🏵️','🎗️','🎫','🎟️','🎪','🤹','🎭','🩰','🎨','🎬','🎤','🎧','🎼','🎹','🥁','🪘','🎷','🎺','🪗','🎸','🪕','🎻','🪈','🎲','♟️','🎯','🎳','🎮','🎰','🧩']
<input class="sf-input" placeholder="Paste image URL..." id="profile-url">
};
<img class="sf-preview" style="display:none">
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-profile')">Cancel</button>
<button class="sf-btn" id="profile-save">Save</button>
</div>
</div>
</div>


<div class="sf-modal" id="modal-photo">
function getInitials(name){
<div class="sf-modal-box">
return(name||'?').split(/[\s_]+/).map(function(w){return(w[0]||'').toUpperCase();}).join('').substring(0,2)||'?';
<div class="sf-modal-title">Add Photo</div>
}
<div class="sf-tabs"><div class="sf-tab active" data-t="i-upload">Upload</div><div class="sf-tab" data-t="i-url">URL</div></div>
<div class="sf-tab-panel active" id="i-upload">
<div class="sf-upload-area" id="photo-drop"><div style="font-size:32px;margin-bottom:8px">📤</div><div style="color:#65676b">Click or drag image</div><input type="file" accept="image/*" style="display:none"></div>
<div class="sf-progress" style="display:none"><div class="sf-progress-bar"></div></div>
<img class="sf-preview" style="display:none">
</div>
<div class="sf-tab-panel" id="i-url">
<input class="sf-input" placeholder="Paste image URL..." id="photo-url">
<img class="sf-preview" style="display:none">
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-photo')">Cancel</button>
<button class="sf-btn" id="photo-add">Add</button>
</div>
</div>
</div>


<div class="sf-modal" id="modal-video">
function escapeHtml(text){
<div class="sf-modal-box">
var div=document.createElement('div');
<div class="sf-modal-title">Add Video</div>
div.textContent=text;
<div class="sf-tabs"><div class="sf-tab active" data-t="v-upload">Upload</div><div class="sf-tab" data-t="v-url">URL</div></div>
return div.innerHTML;
<div class="sf-tab-panel active" id="v-upload">
}
<div class="sf-upload-area" id="video-drop"><div style="font-size:32px;margin-bottom:8px">🎬</div><div style="color:#65676b">Click or drag video</div><input type="file" accept="video/*" style="display:none"></div>
<div class="sf-progress" style="display:none"><div class="sf-progress-bar"></div></div>
<video class="sf-preview" controls style="display:none"></video>
</div>
<div class="sf-tab-panel" id="v-url">
<input class="sf-input" placeholder="YouTube, Vimeo, or direct URL..." id="video-url">
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-video')">Cancel</button>
<button class="sf-btn" id="video-add">Add</button>
</div>
</div>
</div>


<div class="sf-modal" id="modal-story">
function timeAgo(dateStr){
<div class="sf-modal-box">
var seconds=Math.floor((Date.now()-new Date(dateStr))/1000);
<div class="sf-modal-title">Add Story</div>
if(seconds<60)return'Just now';
<div class="sf-tabs"><div class="sf-tab active" data-t="s-upload">Upload</div><div class="sf-tab" data-t="s-url">URL</div></div>
var minutes=Math.floor(seconds/60);
<div class="sf-tab-panel active" id="s-upload">
if(minutes<60)return minutes+'m ago';
<div class="sf-upload-area" id="story-drop"><div style="font-size:32px;margin-bottom:8px">📷</div><div style="color:#65676b">Click or drag image</div><input type="file" accept="image/*" style="display:none"></div>
var hours=Math.floor(minutes/60);
<div class="sf-progress" style="display:none"><div class="sf-progress-bar"></div></div>
if(hours<24)return hours+'h ago';
<img class="sf-preview" style="display:none">
var days=Math.floor(hours/24);
</div>
if(days<7)return days+'d ago';
<div class="sf-tab-panel" id="s-url">
var weeks=Math.floor(days/7);
<input class="sf-input" placeholder="Paste image URL..." id="story-url">
return weeks+'w ago';
</div>
}
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-story')">Cancel</button>
<button class="sf-btn" id="story-add">Add Story</button>
</div>
</div>
</div>


<div class="sf-modal" id="modal-location">
function renderAvatar(pic,name,size){
<div class="sf-modal-box">
if(pic)return'<img src="'+escapeHtml(pic)+'" alt="">';
<div class="sf-modal-title">Add Location</div>
var cls=size==='small'?'sa-comment-avatar-placeholder':size==='post'?'sa-post-avatar-placeholder':size==='composer'?'sa-composer-avatar-placeholder':'sa-profile-pic-placeholder';
<button class="sf-btn" id="detect-loc" style="width:100%;margin-bottom:12px">📍 Use Current Location</button>
return'<div class="'+cls+'">'+getInitials(name)+'</div>';
<div style="text-align:center;color:#65676b;margin-bottom:12px">— or —</div>
}
<input class="sf-input" placeholder="Enter location..." id="loc-input">
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-location')">Cancel</button>
<button class="sf-btn" id="loc-add">Add</button>
</div>
</div>
</div>


<div class="sf-viewer" id="story-viewer">
function openModal(id){$('#'+id).addClass('open');}
<div class="sf-viewer-progress"><span></span></div>
function closeModal(id){$('#'+id).removeClass('open');}
<button class="sf-viewer-close">✕</button>
<img src="">
</div>


<script>
function uploadFile(file,prefix,onProgress){
(function(){
return api.getToken('csrf').then(function(token){
var $=window.jQuery,mw=window.mw;
var ext=file.name.split('.').pop()||'jpg';
if(!$||!mw){console.error('jQuery or mw not found');return;}
var filename=prefix+'_'+currentUser+'_'+Date.now()+'.'+ext;
var formData=new FormData();
formData.append('action','upload');
formData.append('filename',filename);
formData.append('file',file);
formData.append('token',token);
formData.append('format','json');
formData.append('ignorewarnings','1');
return $.ajax({
url:apiUrl,
type:'POST',
data:formData,
processData:false,
contentType:false,
xhr:function(){
var xhr=$.ajaxSettings.xhr();
if(xhr.upload&&onProgress){
xhr.upload.addEventListener('progress',function(e){
if(e.lengthComputable)onProgress(Math.round(e.loaded/e.total*100));
});
}
return xhr;
}
});
}).then(function(response){
if(response.upload&&response.upload.imageinfo){
return response.upload.imageinfo.url;
}
throw new Error(response.error?response.error.info:'Upload failed');
});
}
 
function buildApp(){
var html='\
<div class="sa-profile-header">\
<div class="sa-cover" id="cover-area">'+(userData.cover?'<img src="'+escapeHtml(userData.cover)+'" alt="">':'')+'<button class="sa-cover-edit" id="edit-cover-btn">📷 Edit Cover</button></div>\
<div class="sa-profile-info">\
<div class="sa-profile-pic" id="profile-pic">'+renderAvatar(userData.photo,currentUser,'profile')+'</div>\
<div class="sa-profile-details">\
<div class="sa-profile-top">\
<h1 class="sa-username" id="display-name">'+escapeHtml(userData.displayName)+'</h1>\
<button class="sa-edit-btn" id="edit-profile-btn">Edit Profile</button>\
</div>\
<div class="sa-stats">\
<div class="sa-stat"><div class="sa-stat-num" id="post-count">0</div><div class="sa-stat-label">posts</div></div>\
<div class="sa-stat"><div class="sa-stat-num">0</div><div class="sa-stat-label">followers</div></div>\
<div class="sa-stat"><div class="sa-stat-num">0</div><div class="sa-stat-label">following</div></div>\
</div>\
<div class="sa-bio" id="bio-display">'+(userData.bio?'<div class="sa-bio-name">'+escapeHtml(userData.displayName)+'</div>'+escapeHtml(userData.bio).replace(/\n/g,'<br>'):'Click Edit Profile to add a bio')+'</div>\
</div>\
</div>\
</div>\
<div class="sa-main">\
<div class="sa-stories" id="stories-container"></div>\
<div class="sa-composer">\
<div class="sa-composer-top">\
<div class="sa-composer-avatar" id="composer-avatar">'+renderAvatar(userData.photo,currentUser,'composer')+'</div>\
<textarea class="sa-composer-input" id="post-input" placeholder="What\'s on your mind, '+escapeHtml(currentUser.split(/[\s_]/)[0])+'?"></textarea>\
</div>\
<div class="sa-composer-preview" id="post-preview"><div class="sa-composer-preview-inner"><button class="sa-composer-preview-remove" id="remove-media">×</button></div></div>\
<div class="sa-composer-location" id="post-location">📍 <span></span><button id="remove-location">×</button></div>\
<div class="sa-composer-actions">\
<div class="sa-composer-buttons" style="position:relative">\
<button class="sa-icon-btn" id="btn-add-photo" title="Photo">🖼️</button>\
<button class="sa-icon-btn" id="btn-add-video" title="Video">🎬</button>\
<button class="sa-icon-btn" id="btn-add-location" title="Location">📍</button>\
<button class="sa-icon-btn" id="btn-add-emoji" title="Emoji">😊</button>\
<div class="sa-emoji-picker" id="emoji-picker"></div>\
</div>\
<button class="sa-post-btn" id="btn-submit-post" disabled>Post</button>\
</div>\
</div>\
<div class="sa-feed" id="feed-container"></div>\
</div>';
 
// Modals
html+='\
<div class="sa-modal" id="modal-edit-profile">\
<div class="sa-modal-content">\
<div class="sa-modal-header"><span class="sa-modal-title">Edit Profile</span><button class="sa-modal-close" data-close="modal-edit-profile">×</button></div>\
<div class="sa-modal-body">\
<label style="font-weight:600;display:block;margin-bottom:8px">Display Name</label>\
<input class="sa-input" id="input-display-name" value="'+escapeHtml(userData.displayName)+'">\
<label style="font-weight:600;display:block;margin-bottom:8px">Bio</label>\
<textarea class="sa-textarea" id="input-bio" placeholder="Tell people about yourself...">'+escapeHtml(userData.bio)+'</textarea>\
<div class="sa-btn-row">\
<button class="sa-btn sa-btn-secondary" data-close="modal-edit-profile">Cancel</button>\
<button class="sa-btn sa-btn-primary" id="save-profile">Save</button>\
</div>\
</div>\
</div>\
</div>';
 
html+='\
<div class="sa-modal" id="modal-profile-pic">\
<div class="sa-modal-content">\
<div class="sa-modal-header"><span class="sa-modal-title">Profile Picture</span><button class="sa-modal-close" data-close="modal-profile-pic">×</button></div>\
<div class="sa-tabs"><div class="sa-tab active" data-panel="panel-pic-upload">Upload</div><div class="sa-tab" data-panel="panel-pic-url">URL</div></div>\
<div class="sa-tab-panel active" id="panel-pic-upload">\
<div class="sa-upload-zone" id="zone-profile-pic"><input type="file" accept="image/*"><div class="sa-upload-icon">📷</div><div class="sa-upload-text">Drag photo here or <span>browse</span></div></div>\
<div class="sa-progress" id="progress-profile-pic"><div class="sa-progress-bar"></div></div>\
<img class="sa-preview-img" id="preview-profile-pic">\
</div>\
<div class="sa-tab-panel" id="panel-pic-url">\
<input class="sa-input" id="input-pic-url" placeholder="Paste image URL...">\
<img class="sa-preview-img" id="preview-pic-url">\
</div>\
<div class="sa-btn-row" style="padding:0 20px 20px">\
<button class="sa-btn sa-btn-secondary" data-close="modal-profile-pic">Cancel</button>\
<button class="sa-btn sa-btn-primary" id="save-profile-pic">Save</button>\
</div>\
</div>\
</div>';
 
html+='\
<div class="sa-modal" id="modal-cover">\
<div class="sa-modal-content">\
<div class="sa-modal-header"><span class="sa-modal-title">Cover Photo</span><button class="sa-modal-close" data-close="modal-cover">×</button></div>\
<div class="sa-tabs"><div class="sa-tab active" data-panel="panel-cover-upload">Upload</div><div class="sa-tab" data-panel="panel-cover-url">URL</div></div>\
<div class="sa-tab-panel active" id="panel-cover-upload">\
<div class="sa-upload-zone" id="zone-cover"><input type="file" accept="image/*"><div class="sa-upload-icon">🖼️</div><div class="sa-upload-text">Drag photo here or <span>browse</span></div></div>\
<div class="sa-progress" id="progress-cover"><div class="sa-progress-bar"></div></div>\
<img class="sa-preview-img" id="preview-cover">\
</div>\
<div class="sa-tab-panel" id="panel-cover-url">\
<input class="sa-input" id="input-cover-url" placeholder="Paste image URL...">\
<img class="sa-preview-img" id="preview-cover-url">\
</div>\
<div class="sa-btn-row" style="padding:0 20px 20px">\
<button class="sa-btn sa-btn-secondary" data-close="modal-cover">Cancel</button>\
<button class="sa-btn sa-btn-primary" id="save-cover">Save</button>\
</div>\
</div>\
</div>';
 
html+='\
<div class="sa-modal" id="modal-photo">\
<div class="sa-modal-content">\
<div class="sa-modal-header"><span class="sa-modal-title">Add Photo</span><button class="sa-modal-close" data-close="modal-photo">×</button></div>\
<div class="sa-tabs"><div class="sa-tab active" data-panel="panel-photo-upload">Upload</div><div class="sa-tab" data-panel="panel-photo-url">URL</div></div>\
<div class="sa-tab-panel active" id="panel-photo-upload">\
<div class="sa-upload-zone" id="zone-photo"><input type="file" accept="image/*"><div class="sa-upload-icon">📤</div><div class="sa-upload-text">Drag photo here or <span>browse</span></div></div>\
<div class="sa-progress" id="progress-photo"><div class="sa-progress-bar"></div></div>\
<img class="sa-preview-img" id="preview-photo">\
</div>\
<div class="sa-tab-panel" id="panel-photo-url">\
<input class="sa-input" id="input-photo-url" placeholder="Paste image URL...">\
<img class="sa-preview-img" id="preview-photo-url">\
</div>\
<div class="sa-btn-row" style="padding:0 20px 20px">\
<button class="sa-btn sa-btn-secondary" data-close="modal-photo">Cancel</button>\
<button class="sa-btn sa-btn-primary" id="add-photo">Add Photo</button>\
</div>\
</div>\
</div>';
 
html+='\
<div class="sa-modal" id="modal-video">\
<div class="sa-modal-content">\
<div class="sa-modal-header"><span class="sa-modal-title">Add Video</span><button class="sa-modal-close" data-close="modal-video">×</button></div>\
<div class="sa-tabs"><div class="sa-tab active" data-panel="panel-video-upload">Upload</div><div class="sa-tab" data-panel="panel-video-url">URL</div></div>\
<div class="sa-tab-panel active" id="panel-video-upload">\
<div class="sa-upload-zone" id="zone-video"><input type="file" accept="video/*"><div class="sa-upload-icon">🎬</div><div class="sa-upload-text">Drag video here or <span>browse</span></div></div>\
<div class="sa-progress" id="progress-video"><div class="sa-progress-bar"></div></div>\
<video class="sa-preview-img" id="preview-video" controls style="display:none"></video>\
</div>\
<div class="sa-tab-panel" id="panel-video-url">\
<input class="sa-input" id="input-video-url" placeholder="YouTube, Vimeo, or direct video URL...">\
<p style="color:#8e8e8e;font-size:12px">Supports YouTube, Vimeo, and direct video links</p>\
</div>\
<div class="sa-btn-row" style="padding:0 20px 20px">\
<button class="sa-btn sa-btn-secondary" data-close="modal-video">Cancel</button>\
<button class="sa-btn sa-btn-primary" id="add-video">Add Video</button>\
</div>\
</div>\
</div>';


$.when(mw.loader.using(['mediawiki.api','mediawiki.util']),$.ready).then(function(){
html+='\
var api=new mw.Api();
<div class="sa-modal" id="modal-location">\
var apiUrl=mw.util.wikiScript('api');
<div class="sa-modal-content">\
var user=mw.config.get('wgUserName')||'Guest';
<div class="sa-modal-header"><span class="sa-modal-title">Add Location</span><button class="sa-modal-close" data-close="modal-location">×</button></div>\
var photo=localStorage.getItem('sfphoto_'+user)||'';
<div class="sa-modal-body">\
var pending={img:'',video:'',loc:''};
<button class="sa-btn sa-btn-primary" id="detect-location" style="width:100%;margin-bottom:16px">📍 Use Current Location</button>\
var uploads={profile:null,photo:null,video:null,story:null};
<div style="text-align:center;color:#8e8e8e;margin-bottom:16px">— or —</div>\
<input class="sa-input" id="input-location" placeholder="Enter location manually...">\
<div class="sa-btn-row">\
<button class="sa-btn sa-btn-secondary" data-close="modal-location">Cancel</button>\
<button class="sa-btn sa-btn-primary" id="add-location">Add</button>\
</div>\
</div>\
</div>\
</div>';


var emojis={
html+='\
'😀':['😀','😃','😄','😁','😅','😂','🤣','😊','😇','🙂','😉','😍','🥰','😘','😋','😛','😜','🤪','😎','🤩','🥳','😏','😒','😞','😔','😟','😕','🙁','😣','😖','😫','😩','🥺','😢','😭','😤','😠','😡','🤬','😈','👿','💀','💩','🤡','👻','👽','🤖','😺','😸','😹','😻','😼','😽','🙀','😿','😾'],
<div class="sa-modal" id="modal-story">\
'👋':['👋','🤚','🖐','✋','🖖','👌','🤌','🤏','✌','🤞','🤟','🤘','🤙','👈','👉','👆','🖕','👇','☝','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍','💪','🦾','🦿','🦵','🦶','👂','🦻','👃','👀','👁','👅','👄','💋','🧠','🫀','🫁','🦷','🦴'],
<div class="sa-modal-content">\
'🐶':['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐨','🐯','🦁','🐮','🐷','🐸','🐵','🐔','🐧','🐦','🐤','🦆','🦅','🦉','🦇','🐺','🐗','🐴','🦄','🐝','🐛','🦋','🐌','🐞','🐜','🦟','🐢','🐍','🦎','🦂','🦀','🦑','🐙','🦐','🐠','🐟','🐬','🐳','🦈','🐊','🐅','🐆','🦓','🦍','🐘','🦛','🦏','🐪','🦒','🦘','🐃','🐂','🐄','🐎','🐖','🐏','🐑','🦙','🐐','🦌','🐕','🐩','🦮','🐈','🐓','🦃','🦚','🦜','🦢','🦩','🐇','🦝','🦨','🦡','🦫','🦦','🦥','🐁','🐀','🐿','🦔'],
<div class="sa-modal-header"><span class="sa-modal-title">Add to Story</span><button class="sa-modal-close" data-close="modal-story">×</button></div>\
'🍎':['🍎','🍊','🍋','🍌','🍉','🍇','🍓','🫐','🍈','🍒','🍑','🥭','🍍','🥥','🥝','🍅','🥑','🥦','🥬','🥒','🌶','🫑','🌽','🥕','🧄','🧅','🥔','🍠','🥐','🥯','🍞','🥖','🥨','🧀','🥚','🍳','🧈','🥞','🧇','🥓','🥩','🍗','🍖','🌭','🍔','🍟','🍕','🥪','🥙','🧆','🌮','🌯','🥗','🥘','🥫','🍝','🍜','🍲','🍛','🍣','🍱','🥟','🍤','🍙','🍚','🍘','🍥','🥠','🍢','🍡','🍧','🍨','🍦','🥧','🧁','🍰','🎂','🍮','🍭','🍬','🍫','🍿','🍩','🍪','🌰','🥜','🍯','🥛','🍼','☕','🍵','🧃','🥤','🧋','🍶','🍺','🍻','🥂','🍷','🥃','🍸','🍹','🧉','🍾','🧊'],
<div class="sa-tabs"><div class="sa-tab active" data-panel="panel-story-upload">Upload</div><div class="sa-tab" data-panel="panel-story-url">URL</div></div>\
'⚽':['⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🪀','🏓','🏸','🏒','🏑','🥍','🏏','🪃','🥅','⛳','🪁','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛼','🛷','⛸','🥌','🎿','⛷','🏂','🪂','🏋','🤼','🤸','⛹','🤺','🤾','🏌','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚵','🚴','🏆','🥇','🥈','🥉','🏅','🎖','🏵','🎗','🎫','🎟','🎪','🎭','🎨','🎬','🎤','🎧','🎼','🎹','🥁','🪘','🎷','🎺','🪗','🎸','🪕','🎻','🎲','♟','🎯','🎳','🎮','🎰','🧩'],
<div class="sa-tab-panel active" id="panel-story-upload">\
'❤️':['❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝','💟','☮️','✝️','☪️','🕉️','☸️','✡️','🔯','🕎','☯️','☦️','🛐','⛎','♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓','🆔','⚛️','🉑','☢️','☣️','📴','📳','🈶','🈚','🈸','🈺','🈷️','✴️','🆚','💮','🉐','㊙️','㊗️','🈴','🈵','🈹','🈲','🅰️','🅱️','🆎','🆑','🅾️','🆘','❌','⭕','🛑','⛔','📛','🚫','💯','💢','♨️','🚷','🚯','🚳','🚱','🔞','📵','🚭','❗','❕','❓','❔','‼️','⁉️','🔅','🔆','〽️','⚠️','🚸','🔱','⚜️','🔰','♻️','✅','🈯','💹','❇️','✳️','❎','🌐','💠']
<div class="sa-upload-zone" id="zone-story"><input type="file" accept="image/*"><div class="sa-upload-icon">📷</div><div class="sa-upload-text">Drag photo here or <span>browse</span></div></div>\
};
<div class="sa-progress" id="progress-story"><div class="sa-progress-bar"></div></div>\
<img class="sa-preview-img" id="preview-story">\
</div>\
<div class="sa-tab-panel" id="panel-story-url">\
<input class="sa-input" id="input-story-url" placeholder="Paste image URL...">\
<img class="sa-preview-img" id="preview-story-url">\
</div>\
<div class="sa-btn-row" style="padding:0 20px 20px">\
<button class="sa-btn sa-btn-secondary" data-close="modal-story">Cancel</button>\
<button class="sa-btn sa-btn-primary" id="add-story">Share to Story</button>\
</div>\
</div>\
</div>';


function initials(n){return(n||'?').split(/[\s_]+/).map(function(w){return w[0]||'';}).join('').substring(0,2).toUpperCase()||'?';}
html+='\
function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML;}
<div class="sa-story-viewer" id="story-viewer">\
function timeAgo(d){var s=Math.floor((Date.now()-new Date(d))/1e3);if(s<60)return'now';if(s<3600)return Math.floor(s/60)+'m';if(s<86400)return Math.floor(s/3600)+'h';return Math.floor(s/86400)+'d';}
<div class="sa-story-viewer-content">\
<div class="sa-story-viewer-progress"><div class="sa-story-viewer-progress-bar"></div></div>\
<button class="sa-story-viewer-close" id="close-story-viewer">×</button>\
<div class="sa-story-viewer-header">\
<div class="sa-story-viewer-avatar" id="story-viewer-avatar"></div>\
<div class="sa-story-viewer-name" id="story-viewer-name"></div>\
</div>\
<img id="story-viewer-image" src="">\
</div>\
</div>';


function renderAvatar(el,name,pic){
$('#social-app').html(html);
el=$(el);
if(pic)el.html('<img src="'+pic+'">');
else el.text(initials(name));
}
}


function openModal(id){$('#'+id).addClass('open');}
function setupEventListeners(){
window.closeModal=function(id){$('#'+id).removeClass('open');}
var $app=$('#social-app');
 
// Modal close buttons
$app.on('click','[data-close]',function(){
closeModal($(this).data('close'));
});
 
// Modal backdrop click
$app.on('click','.sa-modal',function(e){
if($(e.target).hasClass('sa-modal'))closeModal(this.id);
});


function setupTabs(container){
// Tabs
$(container).on('click','.sf-tab',function(){
$app.on('click','.sa-tab',function(){
var t=$(this),panel=$('#'+t.data('t'));
var $tab=$(this),$panel=$('#'+$tab.data('panel'));
t.siblings().removeClass('active');t.addClass('active');
$tab.addClass('active').siblings().removeClass('active');
panel.siblings('.sf-tab-panel').removeClass('active');panel.addClass('active');
$panel.addClass('active').siblings('.sa-tab-panel').removeClass('active');
});
});
}


function setupUpload(dropId,type,onFile){
// Upload zones
var drop=$('#'+dropId),input=drop.find('input'),progress=drop.siblings('.sf-progress'),bar=progress.find('.sf-progress-bar'),preview=drop.siblings('.sf-preview');
function setupUploadZone(zoneId,previewId,progressId,fileKey){
drop.on('click',function(){input.click();});
var $zone=$('#'+zoneId),$input=$zone.find('input'),$preview=$('#'+previewId),$progress=$('#'+progressId);
drop.on('dragover',function(e){e.preventDefault();drop.addClass('drag');});
$zone.on('click',function(){$input.click();});
drop.on('dragleave drop',function(){drop.removeClass('drag');});
$zone.on('dragover',function(e){e.preventDefault();$(this).addClass('dragover');});
drop.on('drop',function(e){e.preventDefault();if(e.originalEvent.dataTransfer.files[0])handleFile(e.originalEvent.dataTransfer.files[0]);});
$zone.on('dragleave drop',function(){$(this).removeClass('dragover');});
input.on('change',function(){if(this.files[0])handleFile(this.files[0]);});
$zone.on('drop',function(e){
function handleFile(f){
e.preventDefault();
uploads[type]=f;
var files=e.originalEvent.dataTransfer.files;
if(f.type.startsWith('image/')){
if(files.length)handleFile(files[0]);
var r=new FileReader();
});
r.onload=function(e){preview.attr('src',e.target.result).show();};
$input.on('change',function(){if(this.files.length)handleFile(this.files[0]);});
r.readAsDataURL(f);
function handleFile(file){
}else if(f.type.startsWith('video/')){
uploadFiles[fileKey]=file;
preview.attr('src',URL.createObjectURL(f)).show();
if(file.type.startsWith('image/')){
var reader=new FileReader();
reader.onload=function(e){$preview.attr('src',e.target.result).addClass('show');};
reader.readAsDataURL(file);
}else if(file.type.startsWith('video/')){
$preview.attr('src',URL.createObjectURL(file)).addClass('show').show();
}
}
if(onFile)onFile(f);
}
}
return{
return{
reset:function(){uploads[type]=null;preview.hide();progress.hide();bar.css('width',0);input.val('');},
reset:function(){uploadFiles[fileKey]=null;$preview.removeClass('show').hide().attr('src','');$progress.removeClass('show').find('.sa-progress-bar').css('width',0);$input.val('');},
progress:function(p){progress.show();bar.css('width',p+'%');},
setProgress:function(pct){$progress.addClass('show').find('.sa-progress-bar').css('width',pct+'%');}
done:function(){bar.css('width','100%');}
};
};
}
}


function uploadToWiki(file,prefix,onProgress){
var uploadProfilePic=setupUploadZone('zone-profile-pic','preview-profile-pic','progress-profile-pic','profilePic');
return api.getToken('csrf').then(function(token){
var uploadCover=setupUploadZone('zone-cover','preview-cover','progress-cover','cover');
var fname=prefix+'_'+user+'_'+Date.now()+'.'+(file.name.split('.').pop()||'jpg');
var uploadPhoto=setupUploadZone('zone-photo','preview-photo','progress-photo','photo');
var fd=new FormData();
var uploadVideo=setupUploadZone('zone-video','preview-video','progress-video','video');
fd.append('action','upload');fd.append('filename',fname);fd.append('file',file);
var uploadStory=setupUploadZone('zone-story','preview-story','progress-story','story');
fd.append('token',token);fd.append('format','json');fd.append('ignorewarnings','1');
 
return $.ajax({
// URL preview inputs
url:apiUrl,type:'POST',data:fd,processData:false,contentType:false,
$('#input-pic-url').on('input',function(){var v=$(this).val();$('#preview-pic-url').attr('src',v).toggleClass('show',!!v);});
xhr:function(){
$('#input-cover-url').on('input',function(){var v=$(this).val();$('#preview-cover-url').attr('src',v).toggleClass('show',!!v);});
var x=$.ajaxSettings.xhr();
$('#input-photo-url').on('input',function(){var v=$(this).val();$('#preview-photo-url').attr('src',v).toggleClass('show',!!v);});
if(x.upload)x.upload.onprogress=function(e){if(e.lengthComputable&&onProgress)onProgress(Math.round(e.loaded/e.total*100));};
$('#input-story-url').on('input',function(){var v=$(this).val();$('#preview-story-url').attr('src',v).toggleClass('show',!!v);});
return x;
 
// Edit profile button
$('#edit-profile-btn').on('click',function(){openModal('modal-edit-profile');});
$('#save-profile').on('click',function(){
userData.displayName=$('#input-display-name').val().trim()||currentUser;
userData.bio=$('#input-bio').val().trim();
localStorage.setItem('sf_name_'+currentUser,userData.displayName);
localStorage.setItem('sf_bio_'+currentUser,userData.bio);
$('#display-name').text(userData.displayName);
$('#bio-display').html(userData.bio?'<div class="sa-bio-name">'+escapeHtml(userData.displayName)+'</div>'+escapeHtml(userData.bio).replace(/\n/g,'<br>'):'Click Edit Profile to add a bio');
closeModal('modal-edit-profile');
});
 
// Profile picture
$('#profile-pic').on('click',function(){uploadProfilePic.reset();$('#input-pic-url').val('');$('#preview-pic-url').removeClass('show');openModal('modal-profile-pic');});
$('#save-profile-pic').on('click',function(){
var activePanel=$('#modal-profile-pic .sa-tab.active').data('panel');
if(activePanel==='panel-pic-url'){
var url=$('#input-pic-url').val().trim();
if(url){saveProfilePic(url);}else{alert('Please enter a URL');}
}else if(uploadFiles.profilePic){
uploadFile(uploadFiles.profilePic,'ProfilePic',uploadProfilePic.setProgress).then(function(url){
saveProfilePic(url);
}).catch(function(e){alert('Upload error: '+e.message);});
}else{alert('Please select an image');}
});
function saveProfilePic(url){
userData.photo=url;
localStorage.setItem('sf_photo_'+currentUser,url);
$('#profile-pic').html(renderAvatar(url,currentUser,'profile'));
$('#composer-avatar').html(renderAvatar(url,currentUser,'composer'));
closeModal('modal-profile-pic');
uploadProfilePic.reset();
}
 
// Cover photo
$('#edit-cover-btn').on('click',function(){uploadCover.reset();$('#input-cover-url').val('');$('#preview-cover-url').removeClass('show');openModal('modal-cover');});
$('#save-cover').on('click',function(){
var activePanel=$('#modal-cover .sa-tab.active').data('panel');
if(activePanel==='panel-cover-url'){
var url=$('#input-cover-url').val().trim();
if(url){saveCover(url);}else{alert('Please enter a URL');}
}else if(uploadFiles.cover){
uploadFile(uploadFiles.cover,'Cover',uploadCover.setProgress).then(function(url){
saveCover(url);
}).catch(function(e){alert('Upload error: '+e.message);});
}else{alert('Please select an image');}
});
function saveCover(url){
userData.cover=url;
localStorage.setItem('sf_cover_'+currentUser,url);
$('#cover-area').html('<img src="'+url+'" alt=""><button class="sa-cover-edit" id="edit-cover-btn">📷 Edit Cover</button>');
closeModal('modal-cover');
uploadCover.reset();
}
}
// Photo for post
$('#btn-add-photo').on('click',function(){uploadPhoto.reset();$('#input-photo-url').val('');$('#preview-photo-url').removeClass('show');openModal('modal-photo');});
$('#add-photo').on('click',function(){
var activePanel=$('#modal-photo .sa-tab.active').data('panel');
if(activePanel==='panel-photo-url'){
var url=$('#input-photo-url').val().trim();
if(url){setPendingMedia('image',url);closeModal('modal-photo');}else{alert('Please enter a URL');}
}else if(uploadFiles.photo){
uploadFile(uploadFiles.photo,'Post',uploadPhoto.setProgress).then(function(url){
setPendingMedia('image',url);
closeModal('modal-photo');
uploadPhoto.reset();
}).catch(function(e){alert('Upload error: '+e.message);});
}else{alert('Please select an image');}
});
});
}).then(function(r){
 
if(r.upload&&r.upload.imageinfo)return r.upload.imageinfo.url;
// Video for post
throw new Error(r.error?r.error.info:'Upload failed');
$('#btn-add-video').on('click',function(){uploadVideo.reset();$('#input-video-url').val('');openModal('modal-video');});
$('#add-video').on('click',function(){
var activePanel=$('#modal-video .sa-tab.active').data('panel');
if(activePanel==='panel-video-url'){
var url=$('#input-video-url').val().trim();
if(url){setPendingMedia('video',url);closeModal('modal-video');}else{alert('Please enter a URL');}
}else if(uploadFiles.video){
uploadFile(uploadFiles.video,'Video',uploadVideo.setProgress).then(function(url){
setPendingMedia('video',url);
closeModal('modal-video');
uploadVideo.reset();
}).catch(function(e){alert('Upload error: '+e.message);});
}else{alert('Please select a video');}
});
});
function setPendingMedia(type,url){
pendingPost.image='';pendingPost.video='';
pendingPost[type]=url;
var $preview=$('#post-preview'),$inner=$preview.find('.sa-composer-preview-inner');
if(type==='image'){
$inner.find('img,video').remove();
$inner.append('<img src="'+url+'" alt="">');
}else{
$inner.find('img,video').remove();
if(url.match(/youtube|youtu\.be/)){
$inner.append('<img src="https://img.youtube.com/vi/'+(url.match(/(?:v=|youtu\.be\/)([^&]+)/)||[])[1]+'/hqdefault.jpg" alt="">');
}else{
$inner.append('<video src="'+url+'" style="max-height:200px"></video>');
}
}
$preview.addClass('show');
updatePostButton();
}
}


// Init avatar
$('#remove-media').on('click',function(){
renderAvatar('#my-avatar',user,photo);
pendingPost.image='';pendingPost.video='';
$('#my-avatar').on('click',function(){
$('#post-preview').removeClass('show').find('img,video').remove();
uploads.profile=null;
updatePostButton();
$('#p-upload .sf-preview').hide();$('#p-url .sf-preview').hide();
$('#profile-url').val(photo);
if(photo)$('#p-url .sf-preview').attr('src',photo).show();
openModal('modal-profile');
});
});


// Profile modal
// Location
setupTabs('#modal-profile');
$('#btn-add-location').on('click',function(){$('#input-location').val('');openModal('modal-location');});
var profileUp=setupUpload('profile-drop','profile');
$('#detect-location').on('click',function(){
$('#profile-url').on('input',function(){
var $btn=$(this);
var u=$(this).val().trim();
$btn.text('Detecting...').prop('disabled',true);
$('#p-url .sf-preview').attr('src',u).toggle(!!u);
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(function(pos){
$.getJSON('https://nominatim.openstreetmap.org/reverse?format=json&lat='+pos.coords.latitude+'&lon='+pos.coords.longitude).done(function(data){
var loc=(data.display_name||'').split(',').slice(0,3).join(',')||pos.coords.latitude+','+pos.coords.longitude;
$('#input-location').val(loc);
$btn.text('📍 Use Current Location').prop('disabled',false);
}).fail(function(){
$('#input-location').val(pos.coords.latitude+','+pos.coords.longitude);
$btn.text('📍 Use Current Location').prop('disabled',false);
});
});
$('#profile-save').on('click',function(){
},function(){
var activeTab=$('#modal-profile .sf-tab.active').data('t');
alert('Could not get location');
if(activeTab==='p-url'){
$btn.text('📍 Use Current Location').prop('disabled',false);
photo=$('#profile-url').val().trim();
});
localStorage.setItem('sfphoto_'+user,photo);
}else{
renderAvatar('#my-avatar',user,photo);
alert('Geolocation not supported');
closeModal('modal-profile');
$btn.text('📍 Use Current Location').prop('disabled',false);
}else if(uploads.profile){
}
uploadToWiki(uploads.profile,'Profile',profileUp.progress).then(function(url){
});
profileUp.done();
$('#add-location').on('click',function(){
photo=url;localStorage.setItem('sfphoto_'+user,photo);
var loc=$('#input-location').val().trim();
renderAvatar('#my-avatar',user,photo);
if(loc){
setTimeout(function(){closeModal('modal-profile');profileUp.reset();},500);
pendingPost.location=loc;
}).catch(function(e){alert('Upload error: '+e);});
$('#post-location').addClass('show').find('span').text(loc);
}else{alert('Select an image');}
closeModal('modal-location');
}else{alert('Please enter a location');}
});
$('#remove-location').on('click',function(){
pendingPost.location='';
$('#post-location').removeClass('show');
});
});


// Photo modal
// Emoji picker
setupTabs('#modal-photo');
var $picker=$('#emoji-picker');
var photoUp=setupUpload('photo-drop','photo');
var emojiCats=Object.keys(emojis);
$('#photo-url').on('input',function(){$('#i-url .sf-preview').attr('src',$(this).val()).toggle(!!$(this).val());});
$picker.html('<div class="sa-emoji-header">'+emojiCats.map(function(c,i){return'<button class="sa-emoji-cat'+(i===0?' active':'')+'" data-cat="'+c+'">'+c+'</button>';}).join('')+'</div><div class="sa-emoji-grid"></div>');
$('#photo-add').on('click',function(){
function showEmojis(cat){$picker.find('.sa-emoji-grid').html(emojis[cat].map(function(e){return'<button class="sa-emoji-btn">'+e+'</button>';}).join(''));}
var activeTab=$('#modal-photo .sf-tab.active').data('t');
showEmojis(emojiCats[0]);
if(activeTab==='i-url'){
$picker.on('click','.sa-emoji-cat',function(){$(this).addClass('active').siblings().removeClass('active');showEmojis($(this).data('cat'));});
var u=$('#photo-url').val().trim();
$picker.on('click','.sa-emoji-btn',function(){
if(u){setPendingMedia('img',u);closeModal('modal-photo');}
var emoji=$(this).text(),$input=$('#post-input')[0];
else alert('Enter URL');
var start=$input.selectionStart,val=$input.value;
}else if(uploads.photo){
$input.value=val.slice(0,start)+emoji+val.slice($input.selectionEnd);
uploadToWiki(uploads.photo,'SocialPhoto',photoUp.progress).then(function(url){
$input.selectionStart=$input.selectionEnd=start+emoji.length;
photoUp.done();setPendingMedia('img',url);
$input.focus();
setTimeout(function(){closeModal('modal-photo');photoUp.reset();},500);
updatePostButton();
}).catch(function(e){alert('Upload error: '+e);});
}else alert('Select an image');
});
});
$('#btn-add-emoji').on('click',function(e){e.stopPropagation();$picker.toggleClass('open');});
$(document).on('click',function(e){if(!$(e.target).closest('#emoji-picker,#btn-add-emoji').length)$picker.removeClass('open');});


// Video modal
// Post input
setupTabs('#modal-video');
$('#post-input').on('input',updatePostButton);
var videoUp=setupUpload('video-drop','video');
function updatePostButton(){
$('#video-add').on('click',function(){
var hasContent=$('#post-input').val().trim()||pendingPost.image||pendingPost.video;
var activeTab=$('#modal-video .sf-tab.active').data('t');
$('#btn-submit-post').prop('disabled',!hasContent);
if(activeTab==='v-url'){
}
var u=$('#video-url').val().trim();
 
if(u){setPendingMedia('video',u);closeModal('modal-video');}
// Submit post
else alert('Enter URL');
$('#btn-submit-post').on('click',function(){
}else if(uploads.video){
var text=$('#post-input').val().trim();
uploadToWiki(uploads.video,'SocialVideo',videoUp.progress).then(function(url){
if(!text&&!pendingPost.image&&!pendingPost.video){alert('Please add some content');return;}
videoUp.done();setPendingMedia('video',url);
var $btn=$(this);
setTimeout(function(){closeModal('modal-video');videoUp.reset();},500);
$btn.prop('disabled',true).text('Posting...');
}).catch(function(e){alert('Upload error: '+e);});
var params={action:'socialfeed',sfaction:'createpost',content:text||'(media)'};
}else alert('Select a video');
if(pendingPost.image)params.image_url=pendingPost.image;
if(pendingPost.video)params.video_url=pendingPost.video;
if(pendingPost.location)params.location=pendingPost.location;
api.postWithToken('csrf',params).then(function(){
$('#post-input').val('');
pendingPost={image:'',video:'',location:''};
$('#post-preview').removeClass('show').find('img,video').remove();
$('#post-location').removeClass('show');
$btn.prop('disabled',true).text('Post');
loadPosts();
}).catch(function(e){
alert('Error posting: '+(e.message||e));
$btn.prop('disabled',false).text('Post');
});
});
});


// Story modal
// Story
setupTabs('#modal-story');
$app.on('click','#add-story-btn',function(){uploadStory.reset();$('#input-story-url').val('');$('#preview-story-url').removeClass('show');openModal('modal-story');});
var storyUp=setupUpload('story-drop','story');
$('#add-story').on('click',function(){
$('#story-url').on('input',function(){$('#s-url .sf-preview').attr('src',$(this).val()).toggle(!!$(this).val());});
var activePanel=$('#modal-story .sa-tab.active').data('panel');
$('#story-add').on('click',function(){
var activeTab=$('#modal-story .sf-tab.active').data('t');
function postStory(url){
function postStory(url){
api.postWithToken('csrf',{action:'socialfeed',sfaction:'createstory',image_url:url}).then(function(){
api.postWithToken('csrf',{action:'socialfeed',sfaction:'createstory',image_url:url}).then(function(){
closeModal('modal-story');storyUp.reset();$('#story-url').val('');loadStories();
closeModal('modal-story');
}).catch(function(e){alert('Error: '+e);});
uploadStory.reset();
loadStories();
}).catch(function(e){alert('Error: '+e.message);});
}
}
if(activeTab==='s-url'){
if(activePanel==='panel-story-url'){
var u=$('#story-url').val().trim();
var url=$('#input-story-url').val().trim();
if(u)postStory(u);else alert('Enter URL');
if(url){postStory(url);}else{alert('Please enter a URL');}
}else if(uploads.story){
}else if(uploadFiles.story){
uploadToWiki(uploads.story,'Story',storyUp.progress).then(function(url){
uploadFile(uploadFiles.story,'Story',uploadStory.setProgress).then(function(url){
storyUp.done();setTimeout(function(){postStory(url);},500);
postStory(url);
}).catch(function(e){alert('Upload error: '+e);});
}).catch(function(e){alert('Upload error: '+e.message);});
}else alert('Select an image');
}else{alert('Please select an image');}
});
});


// Location modal
// Story viewer
$('#detect-loc').on('click',function(){
var storyTimeout;
var btn=$(this);btn.text('Detecting...');
$app.on('click','.sa-story[data-story-url]',function(){
if(navigator.geolocation){
var url=$(this).data('story-url'),name=$(this).data('story-user'),pic=localStorage.getItem('sf_photo_'+name)||'';
navigator.geolocation.getCurrentPosition(function(p){
$('#story-viewer-image').attr('src',url);
$.getJSON('https://nominatim.openstreetmap.org/reverse?format=json&lat='+p.coords.latitude+'&lon='+p.coords.longitude,function(d){
$('#story-viewer-name').text(name);
$('#loc-input').val((d.display_name||'').split(',').slice(0,3).join(',')||p.coords.latitude+','+p.coords.longitude);
$('#story-viewer-avatar').html(renderAvatar(pic,name,'small'));
btn.text('📍 Use Current Location');
$('#story-viewer').addClass('open');
}).fail(function(){$('#loc-input').val(p.coords.latitude+','+p.coords.longitude);btn.text('📍 Use Current Location');});
$('.sa-story-viewer-progress-bar').css('animation','none');
},function(){alert('Location unavailable');btn.text('📍 Use Current Location');});
setTimeout(function(){$('.sa-story-viewer-progress-bar').css('animation','storyProgress 5s linear forwards');},10);
}else alert('Geolocation not supported');
clearTimeout(storyTimeout);
storyTimeout=setTimeout(function(){$('#story-viewer').removeClass('open');},5000);
});
});
$('#loc-add').on('click',function(){
$('#close-story-viewer,#story-viewer').on('click',function(e){
var loc=$('#loc-input').val().trim();
if(e.target.id==='close-story-viewer'||e.target.id==='story-viewer'){
if(loc){pending.loc=loc;$('#pending-location').addClass('show').html('📍 '+esc(loc)+'<button class="sf-del">✕</button>');closeModal('modal-location');}
$('#story-viewer').removeClass('open');
else alert('Enter location');
clearTimeout(storyTimeout);
}
});
});
$('#pending-location').on('click','.sf-del',function(){pending.loc='';$('#pending-location').removeClass('show').empty();});


// Buttons
// Feed interactions
$('#btn-photo').on('click',function(){$('#photo-url').val('');$('#i-url .sf-preview').hide();photoUp.reset();openModal('modal-photo');});
$app.on('click','.sa-post-action[data-like]',function(){
$('#btn-video').on('click',function(){$('#video-url').val('');videoUp.reset();openModal('modal-video');});
var postId=$(this).data('like');
$('#btn-location').on('click',function(){$('#loc-input').val('');openModal('modal-location');});
api.postWithToken('csrf',{action:'socialfeed',sfaction:'react',post_id:postId,reaction_type:'like'}).then(loadPosts);
});


// Emoji picker
$app.on('click','.sa-post-menu[data-delete]',function(){
var picker=$('#emoji-picker'),cats=Object.keys(emojis);
if(confirm('Delete this post?')){
picker.html('<div class="sf-emoji-cats">'+cats.map(function(c,i){return'<button class="sf-emoji-cat'+(i===0?' active':'')+'" data-c="'+c+'">'+c+'</button>';}).join('')+'</div><div class="sf-emoji-list"></div>');
api.postWithToken('csrf',{action:'socialfeed',sfaction:'deletepost',post_id:$(this).data('delete')}).then(loadPosts);
function showEmojis(cat){picker.find('.sf-emoji-list').html(emojis[cat].map(function(e){return'<button>'+e+'</button>';}).join(''));}
}
showEmojis(cats[0]);
});
picker.on('click','.sf-emoji-cat',function(){picker.find('.sf-emoji-cat').removeClass('active');$(this).addClass('active');showEmojis($(this).data('c'));});
picker.on('click','.sf-emoji-list button',function(){var ta=$('#post-text')[0],e=$(this).text(),s=ta.selectionStart,v=ta.value;ta.value=v.slice(0,s)+e+v.slice(ta.selectionEnd);ta.selectionStart=ta.selectionEnd=s+e.length;ta.focus();});
$('#btn-emoji').on('click',function(e){e.stopPropagation();picker.toggleClass('open');});
$(document).on('click',function(e){if(!$(e.target).closest('#emoji-picker,#btn-emoji').length)picker.removeClass('open');});


// Pending media
$app.on('click','.sa-post-comments-count',function(){
function setPendingMedia(type,url){
var postId=$(this).data('post');
pending.img=pending.video='';
var $section=$('#comments-'+postId);
pending[type==='img'?'img':'video']=url;
if($section.hasClass('show')){
var html=type==='img'?'<img src="'+url+'">':'<video src="'+url+'" style="max-height:80px"></video>';
$section.removeClass('show');
$('#pending-media').addClass('show').html(html+'<button class="sf-del">✕</button>');
}else{
$section.addClass('show');
loadComments(postId);
}
}
$('#pending-media').on('click','.sf-del',function(){pending.img=pending.video='';$('#pending-media').removeClass('show').empty();});
});


// Post
$app.on('keypress','.sa-post-comment-input',function(e){
$('#btn-post').on('click',function(){
if(e.which===13){
var txt=$('#post-text').val().trim();
var $input=$(this),text=$input.val().trim(),postId=$input.data('post');
if(!txt&&!pending.img&&!pending.video){alert('Write something or add media');return;}
if(text){
var btn=$(this);btn.prop('disabled',true).text('Posting...');
$input.val('').prop('disabled',true);
var params={action:'socialfeed',sfaction:'createpost',content:txt||'(media)'};
api.postWithToken('csrf',{action:'socialfeed',sfaction:'comment',post_id:postId,content:text}).then(function(){
if(pending.img)params.image_url=pending.img;
$input.prop('disabled',false);
if(pending.video)params.video_url=pending.video;
loadComments(postId);
if(pending.loc)params.location=pending.loc;
api.postWithToken('csrf',params).then(function(){
$('#post-text').val('');pending={img:'',video:'',loc:''};
$('#pending-media,#pending-location').removeClass('show').empty();
btn.prop('disabled',false).text('Post');
loadPosts();
loadPosts();
}).catch(function(e){alert('Error: '+e);btn.prop('disabled',false).text('Post');});
}).catch(function(){$input.prop('disabled',false);});
}
}
});
});
}


// Load posts
function loadPosts(){
function loadPosts(){
api.get({action:'socialfeed',sfaction:'getposts',limit:20}).then(function(r){
api.get({action:'socialfeed',sfaction:'getposts',limit:20}).then(function(response){
var posts=(r.socialfeed&&r.socialfeed.posts)||[];
var posts=(response.socialfeed&&response.socialfeed.posts)||[];
if(!posts.length){$('#feed').html('<div class="sf-card sf-empty"><div class="sf-empty-icon">📝</div><div>No posts yet</div></div>');return;}
$('#post-count').text(posts.length);
var html=posts.map(function(p){
if(!posts.length){
var pic=localStorage.getItem('sfphoto_'+p.username)||'';
$('#feed-container').html('<div class="sa-empty"><div class="sa-empty-icon">📷</div><div class="sa-empty-title">Share Photos</div><div class="sa-empty-text">When you share photos, they will appear on your profile.</div></div>');
var av=pic?'<img src="'+pic+'">':initials(p.username);
return;
var total=0;for(var k in p.reaction_counts)total+=p.reaction_counts[k];
}
var html=posts.map(function(post){
var userPic=localStorage.getItem('sf_photo_'+post.username)||'';
var totalReactions=0;
for(var k in post.reaction_counts)totalReactions+=post.reaction_counts[k];
var media='';
var media='';
if(p.image_url)media='<div class="sf-post-media"><img src="'+p.image_url+'"></div>';
if(post.image_url){
if(p.video_url){
media='<div class="sa-post-media"><img src="'+escapeHtml(post.image_url)+'" alt=""></div>';
if(p.video_url.match(/youtube|youtu\.be/)){
var id=(p.video_url.match(/(?:v=|youtu\.be\/)([^&]+)/)||[])[1];
if(id)media='<div class="sf-post-media"><iframe src="https://www.youtube.com/embed/'+id+'" frameborder="0" allowfullscreen style="height:315px"></iframe></div>';
}else media='<div class="sf-post-media"><video src="'+p.video_url+'" controls></video></div>';
}
}
var loc=p.location?'<div class="sf-post-location">📍 '+esc(p.location)+'</div>':'';
if(post.video_url){
var del=p.username===user?'<button class="sf-del" data-del="'+p.id+'" style="font-size:16px">🗑️</button>':'';
if(post.video_url.match(/youtube|youtu\.be/)){
return'<div class="sf-card"><div class="sf-post-header"><div class="sf-avatar sf-avatar-sm">'+av+'</div><div class="sf-post-info"><div class="sf-post-name">'+esc(p.username)+'</div><div class="sf-post-meta">'+timeAgo(p.created)+' · 🌐</div></div>'+del+'</div><div class="sf-post-content">'+esc(p.content)+'</div>'+loc+media+'<div class="sf-post-stats"><div>'+(total?'👍 '+total:'')+'</div><div>'+p.comments+' comments</div></div><div class="sf-post-actions"><button class="sf-post-action'+(p.user_reaction==='like'?' liked':'')+'" data-like="'+p.id+'">👍 Like</button><button class="sf-post-action" data-cmt="'+p.id+'">💬 Comment</button><button class="sf-post-action">↗️ Share</button></div><div class="sf-comments" id="cmt-'+p.id+'" style="display:none"></div></div>';
var videoId=(post.video_url.match(/(?:v=|youtu\.be\/)([^&]+)/)||[])[1];
if(videoId)media='<div class="sa-post-media"><iframe src="https://www.youtube.com/embed/'+videoId+'" allowfullscreen></iframe></div>';
}else if(post.video_url.match(/vimeo/)){
var vimeoId=(post.video_url.match(/vimeo\.com\/(\d+)/)||[])[1];
if(vimeoId)media='<div class="sa-post-media"><iframe src="https://player.vimeo.com/video/'+vimeoId+'" allowfullscreen></iframe></div>';
}else{
media='<div class="sa-post-media"><video src="'+escapeHtml(post.video_url)+'" controls></video></div>';
}
}
var location=post.location?'<div class="sa-post-location">📍 '+escapeHtml(post.location)+'</div>':'';
var deleteBtn=post.username===currentUser?'<button class="sa-post-menu" data-delete="'+post.id+'">🗑️</button>':'';
var likedClass=post.user_reaction==='like'?' liked':'';
return'\
<div class="sa-post">\
<div class="sa-post-header">\
<div class="sa-post-avatar">'+renderAvatar(userPic,post.username,'post')+'</div>\
<div class="sa-post-user">\
<div class="sa-post-username">'+escapeHtml(post.username)+'</div>\
<div class="sa-post-meta">'+timeAgo(post.created)+'</div>\
</div>\
'+deleteBtn+'\
</div>\
'+(post.content&&post.content!=='(media)'?'<div class="sa-post-content">'+escapeHtml(post.content)+'</div>':'')+'\
'+location+media+'\
<div class="sa-post-actions">\
<button class="sa-post-action'+likedClass+'" data-like="'+post.id+'">'+(post.user_reaction==='like'?'❤️':'🤍')+'</button>\
<button class="sa-post-action">💬</button>\
<button class="sa-post-action">📤</button>\
<button class="sa-post-action" style="margin-left:auto">🔖</button>\
</div>\
'+(totalReactions?'<div class="sa-post-likes">'+totalReactions+' like'+(totalReactions>1?'s':'')+'</div>':'')+'\
'+(post.content&&post.content!=='(media)'?'':'')+(post.comments?'<div class="sa-post-comments-count" data-post="'+post.id+'">View all '+post.comments+' comment'+(post.comments>1?'s':'')+'</div>':'')+'\
<div class="sa-comments-section" id="comments-'+post.id+'"></div>\
<div class="sa-post-time">'+timeAgo(post.created)+'</div>\
<div class="sa-post-comment-form">\
<input class="sa-post-comment-input" data-post="'+post.id+'" placeholder="Add a comment...">\
<button class="sa-post-comment-submit">Post</button>\
</div>\
</div>';
}).join('');
}).join('');
$('#feed').html(html);
$('#feed-container').html(html);
}).catch(function(){$('#feed').html('<div class="sf-card sf-empty"><div class="sf-empty-icon">😕</div><div>Error loading posts</div></div>');});
}).catch(function(e){
console.error('Error loading posts:',e);
$('#feed-container').html('<div class="sa-empty"><div class="sa-empty-icon">😕</div><div class="sa-empty-title">Error Loading</div><div class="sa-empty-text">Could not load posts. Please refresh.</div></div>');
});
}
}


$('#feed').on('click','[data-del]',function(){
function loadComments(postId){
if(confirm('Delete this post?'))api.postWithToken('csrf',{action:'socialfeed',sfaction:'deletepost',post_id:$(this).data('del')}).then(loadPosts);
api.get({action:'socialfeed',sfaction:'getcomments',post_id:postId}).then(function(response){
});
var comments=(response.socialfeed&&response.socialfeed.comments)||[];
$('#feed').on('click','[data-like]',function(){
var html=comments.map(function(comment){
api.postWithToken('csrf',{action:'socialfeed',sfaction:'react',post_id:$(this).data('like'),reaction_type:'like'}).then(loadPosts);
var userPic=localStorage.getItem('sf_photo_'+comment.username)||'';
});
return'\
$('#feed').on('click','[data-cmt]',function(){
<div class="sa-comment">\
var id=$(this).data('cmt'),box=$('#cmt-'+id);
<div class="sa-comment-avatar">'+renderAvatar(userPic,comment.username,'small')+'</div>\
if(box.is(':visible')){box.hide();}else{box.show();loadComments(id);}
<div class="sa-comment-body">\
});
<div class="sa-comment-text"><strong>'+escapeHtml(comment.username)+'</strong> '+escapeHtml(comment.content)+'</div>\
 
<div class="sa-comment-time">'+timeAgo(comment.created)+'</div>\
function loadComments(id){
</div>\
api.get({action:'socialfeed',sfaction:'getcomments',post_id:id}).then(function(r){
</div>';
var cmts=(r.socialfeed&&r.socialfeed.comments)||[];
var myPic=photo?'<img src="'+photo+'">':initials(user);
var html=cmts.map(function(c){
var pic=localStorage.getItem('sfphoto_'+c.username)||'';
var av=pic?'<img src="'+pic+'">':initials(c.username);
return'<div class="sf-comment"><div class="sf-avatar sf-avatar-sm">'+av+'</div><div class="sf-comment-bubble"><div class="sf-comment-author">'+esc(c.username)+'</div><div class="sf-comment-text">'+esc(c.content)+'</div></div></div>';
}).join('');
}).join('');
html+='<div class="sf-comment-form"><div class="sf-avatar sf-avatar-sm">'+myPic+'</div><input class="sf-comment-input" data-pid="'+id+'" placeholder="Write a comment..."></div>';
$('#comments-'+postId).html(html||'<div style="color:#8e8e8e;font-size:14px">No comments yet</div>');
$('#cmt-'+id).html(html);
});
});
}
}


$('#feed').on('keypress','.sf-comment-input',function(e){
if(e.which===13){
var input=$(this),txt=input.val().trim(),pid=input.data('pid');
if(txt){
input.val('');
api.postWithToken('csrf',{action:'socialfeed',sfaction:'comment',post_id:pid,content:txt}).then(function(){loadComments(pid);loadPosts();});
}
}
});
// Stories
function loadStories(){
function loadStories(){
api.get({action:'socialfeed',sfaction:'getstories'}).then(function(r){
api.get({action:'socialfeed',sfaction:'getstories'}).then(function(response){
var stories=(r.socialfeed&&r.socialfeed.stories)||[];
var stories=(response.socialfeed&&response.socialfeed.stories)||[];
var html='<div class="sf-story"><div class="sf-story-ring add"><div class="sf-story-inner" id="add-story">+</div></div><div class="sf-story-name">Add</div></div>';
var html='<div class="sa-story"><div class="sa-story-ring add"><div class="sa-story-inner" id="add-story-btn">+</div></div><div class="sa-story-name">Your story</div></div>';
html+=stories.map(function(s){
html+=stories.map(function(story){
var pic=localStorage.getItem('sfphoto_'+s.username)||'';
var userPic=localStorage.getItem('sf_photo_'+story.username)||'';
var av=pic?'<img src="'+pic+'">':initials(s.username);
return'\
return'<div class="sf-story"><div class="sf-story-ring"><div class="sf-story-inner" data-simg="'+s.image_url+'">'+av+'</div></div><div class="sf-story-name">'+esc(s.username)+'</div></div>';
<div class="sa-story" data-story-url="'+escapeHtml(story.image_url)+'" data-story-user="'+escapeHtml(story.username)+'">\
<div class="sa-story-ring"><div class="sa-story-inner">'+renderAvatar(userPic,story.username,'small')+'</div></div>\
<div class="sa-story-name">'+escapeHtml(story.username)+'</div>\
</div>';
}).join('');
}).join('');
$('#stories').html(html);
$('#stories-container').html(html);
}).catch(function(){});
}).catch(function(){});
}
}


$('#stories').on('click','#add-story',function(){
// Initialize
$('#story-url').val('');$('#s-url .sf-preview').hide();storyUp.reset();openModal('modal-story');
buildApp();
});
setupEventListeners();
$('#stories').on('click','[data-simg]',function(){
var viewer=$('#story-viewer'),img=$(this).data('simg');
viewer.find('img').attr('src',img);
viewer.addClass('open');
viewer.find('.sf-viewer-progress span').css('animation','none');
setTimeout(function(){viewer.find('.sf-viewer-progress span').css('animation','progress 5s linear');},10);
setTimeout(function(){viewer.removeClass('open');},5000);
});
$('#story-viewer').on('click',function(e){if($(e.target).is('.sf-viewer,.sf-viewer-close'))$(this).removeClass('open');});
 
// Init
loadPosts();
loadPosts();
loadStories();
loadStories();
});
}
})();
})();
</script>
</script>
</div>
</html>

Revision as of 03:32, 3 February 2026

<style>

  1. social-app{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#fafafa;min-height:100vh;padding-bottom:40px}
  2. social-app *{box-sizing:border-box;margin:0;padding:0}

.sa-profile-header{background:#fff;border-bottom:1px solid #dbdbdb} .sa-cover{height:200px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);position:relative} .sa-cover img{width:100%;height:100%;object-fit:cover} .sa-cover-edit{position:absolute;bottom:12px;right:12px;background:rgba(0,0,0,.6);color:#fff;border:none;padding:8px 16px;border-radius:8px;cursor:pointer;font-size:13px} .sa-profile-info{max-width:935px;margin:0 auto;padding:0 20px;display:flex;gap:30px;margin-top:-50px;position:relative;z-index:10} .sa-profile-pic{width:150px;height:150px;border-radius:50%;border:4px solid #fff;background:#fff;overflow:hidden;cursor:pointer;flex-shrink:0;box-shadow:0 2px 10px rgba(0,0,0,.1)} .sa-profile-pic img{width:100%;height:100%;object-fit:cover} .sa-profile-pic-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:48px;font-weight:600} .sa-profile-details{flex:1;padding-top:60px} .sa-profile-top{display:flex;align-items:center;gap:20px;margin-bottom:16px;flex-wrap:wrap} .sa-username{font-size:28px;font-weight:300} .sa-edit-btn{background:#0095f6;color:#fff;border:none;padding:8px 24px;border-radius:8px;font-weight:600;cursor:pointer;font-size:14px} .sa-edit-btn:hover{background:#1877f2} .sa-stats{display:flex;gap:32px;margin-bottom:16px} .sa-stat{text-align:center} .sa-stat-num{font-weight:600;font-size:18px} .sa-stat-label{color:#8e8e8e;font-size:14px} .sa-bio{max-width:400px;line-height:1.5} .sa-bio-name{font-weight:600} .sa-main{max-width:935px;margin:0 auto;padding:20px} .sa-stories{display:flex;gap:16px;padding:16px 0;overflow-x:auto;margin-bottom:20px} .sa-story{display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer} .sa-story-ring{width:66px;height:66px;border-radius:50%;padding:3px;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)} .sa-story-ring.add{background:#dbdbdb} .sa-story-ring.seen{background:#dbdbdb} .sa-story-inner{width:100%;height:100%;border-radius:50%;border:2px solid #fff;overflow:hidden;background:#fafafa;display:flex;align-items:center;justify-content:center} .sa-story-inner img{width:100%;height:100%;object-fit:cover} .sa-story-add{font-size:24px;color:#0095f6} .sa-story-name{font-size:12px;color:#262626;max-width:66px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .sa-composer{background:#fff;border:1px solid #dbdbdb;border-radius:12px;margin-bottom:20px} .sa-composer-top{display:flex;gap:12px;padding:16px} .sa-composer-avatar{width:44px;height:44px;border-radius:50%;overflow:hidden;flex-shrink:0} .sa-composer-avatar img{width:100%;height:100%;object-fit:cover} .sa-composer-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:16px;font-weight:600} .sa-composer-input{flex:1;border:none;resize:none;font-size:16px;line-height:1.5;min-height:60px;outline:none;font-family:inherit} .sa-composer-input::placeholder{color:#8e8e8e} .sa-composer-preview{padding:0 16px 16px;display:none} .sa-composer-preview.show{display:block} .sa-composer-preview-inner{position:relative;display:inline-block} .sa-composer-preview img,.sa-composer-preview video{max-width:100%;max-height:300px;border-radius:8px} .sa-composer-preview-remove{position:absolute;top:8px;right:8px;width:28px;height:28px;border-radius:50%;background:rgba(0,0,0,.7);color:#fff;border:none;cursor:pointer;font-size:16px} .sa-composer-location{padding:0 16px 12px;font-size:14px;color:#8e8e8e;display:none} .sa-composer-location.show{display:flex;align-items:center;gap:8px} .sa-composer-location button{background:none;border:none;cursor:pointer;font-size:14px} .sa-composer-actions{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-top:1px solid #efefef} .sa-composer-buttons{display:flex;gap:4px} .sa-icon-btn{width:40px;height:40px;border:none;background:none;cursor:pointer;font-size:20px;border-radius:50%;transition:background .2s} .sa-icon-btn:hover{background:#f0f0f0} .sa-post-btn{background:#0095f6;color:#fff;border:none;padding:10px 24px;border-radius:8px;font-weight:600;cursor:pointer;font-size:14px} .sa-post-btn:disabled{opacity:.3;cursor:default} .sa-post-btn:not(:disabled):hover{background:#1877f2} .sa-feed{display:flex;flex-direction:column;gap:20px} .sa-post{background:#fff;border:1px solid #dbdbdb;border-radius:12px;overflow:hidden} .sa-post-header{display:flex;align-items:center;gap:12px;padding:14px 16px} .sa-post-avatar{width:42px;height:42px;border-radius:50%;overflow:hidden} .sa-post-avatar img{width:100%;height:100%;object-fit:cover} .sa-post-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;font-weight:600} .sa-post-user{flex:1} .sa-post-username{font-weight:600;font-size:14px;color:#262626} .sa-post-meta{font-size:12px;color:#8e8e8e} .sa-post-menu{background:none;border:none;cursor:pointer;font-size:20px;color:#262626} .sa-post-content{padding:0 16px 12px;font-size:15px;line-height:1.5;white-space:pre-wrap} .sa-post-location{padding:0 16px 8px;font-size:13px;color:#8e8e8e} .sa-post-media{background:#000} .sa-post-media img{width:100%;max-height:600px;object-fit:contain} .sa-post-media video{width:100%;max-height:600px} .sa-post-media iframe{width:100%;height:400px;border:none} .sa-post-actions{display:flex;gap:16px;padding:12px 16px} .sa-post-action{background:none;border:none;cursor:pointer;font-size:24px;padding:0;transition:transform .2s} .sa-post-action:hover{transform:scale(1.1)} .sa-post-action.liked{color:#ed4956} .sa-post-likes{padding:0 16px 8px;font-weight:600;font-size:14px} .sa-post-caption{padding:0 16px 8px;font-size:14px;line-height:1.4} .sa-post-caption strong{font-weight:600} .sa-post-comments-count{padding:0 16px 8px;font-size:14px;color:#8e8e8e;cursor:pointer} .sa-post-time{padding:0 16px 12px;font-size:10px;color:#8e8e8e;text-transform:uppercase} .sa-post-comment-form{display:flex;gap:12px;padding:12px 16px;border-top:1px solid #efefef} .sa-post-comment-input{flex:1;border:none;font-size:14px;outline:none} .sa-post-comment-input::placeholder{color:#8e8e8e} .sa-post-comment-submit{background:none;border:none;color:#0095f6;font-weight:600;cursor:pointer;font-size:14px} .sa-post-comment-submit:disabled{opacity:.3;cursor:default} .sa-comments-section{display:none;padding:0 16px 12px;max-height:300px;overflow-y:auto} .sa-comments-section.show{display:block} .sa-comment{display:flex;gap:12px;margin-bottom:12px} .sa-comment-avatar{width:32px;height:32px;border-radius:50%;overflow:hidden;flex-shrink:0} .sa-comment-avatar img{width:100%;height:100%;object-fit:cover} .sa-comment-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:600} .sa-comment-body{flex:1} .sa-comment-text{font-size:14px;line-height:1.4} .sa-comment-text strong{font-weight:600} .sa-comment-time{font-size:12px;color:#8e8e8e;margin-top:4px} .sa-empty{text-align:center;padding:60px 20px;background:#fff;border:1px solid #dbdbdb;border-radius:12px} .sa-empty-icon{font-size:64px;margin-bottom:16px} .sa-empty-title{font-size:24px;font-weight:300;margin-bottom:8px} .sa-empty-text{color:#8e8e8e} .sa-modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.65);z-index:1000;align-items:center;justify-content:center;padding:20px} .sa-modal.open{display:flex} .sa-modal-content{background:#fff;border-radius:12px;width:100%;max-width:500px;max-height:90vh;overflow:auto;animation:modalIn .2s} @keyframes modalIn{from{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}} .sa-modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px;border-bottom:1px solid #dbdbdb} .sa-modal-title{font-weight:600;font-size:16px} .sa-modal-close{background:none;border:none;font-size:24px;cursor:pointer;padding:0;line-height:1} .sa-modal-body{padding:20px} .sa-tabs{display:flex;border-bottom:1px solid #dbdbdb} .sa-tab{flex:1;padding:16px;text-align:center;cursor:pointer;font-weight:600;color:#8e8e8e;border-bottom:1px solid transparent;margin-bottom:-1px;transition:all .2s} .sa-tab:hover{color:#262626} .sa-tab.active{color:#262626;border-bottom-color:#262626} .sa-tab-panel{display:none;padding:20px} .sa-tab-panel.active{display:block} .sa-upload-zone{border:2px dashed #dbdbdb;border-radius:8px;padding:40px;text-align:center;cursor:pointer;transition:all .2s} .sa-upload-zone:hover{border-color:#0095f6;background:#f0f7ff} .sa-upload-zone.dragover{border-color:#0095f6;background:#e3f2fd} .sa-upload-zone input{display:none} .sa-upload-icon{font-size:48px;margin-bottom:12px} .sa-upload-text{color:#8e8e8e} .sa-upload-text span{color:#0095f6;font-weight:600} .sa-input{width:100%;padding:14px;border:1px solid #dbdbdb;border-radius:8px;font-size:14px;margin-bottom:12px;outline:none;transition:border-color .2s} .sa-input:focus{border-color:#262626} .sa-textarea{width:100%;padding:14px;border:1px solid #dbdbdb;border-radius:8px;font-size:14px;margin-bottom:12px;outline:none;resize:vertical;min-height:80px;font-family:inherit} .sa-textarea:focus{border-color:#262626} .sa-preview-img{width:100%;max-height:200px;object-fit:contain;border-radius:8px;margin-top:12px;display:none} .sa-preview-img.show{display:block} .sa-progress{height:4px;background:#dbdbdb;border-radius:2px;margin-top:12px;overflow:hidden;display:none} .sa-progress.show{display:block} .sa-progress-bar{height:100%;background:linear-gradient(90deg,#0095f6,#00d4ff);width:0;transition:width .3s} .sa-btn-row{display:flex;gap:12px;margin-top:20px} .sa-btn{flex:1;padding:14px;border:none;border-radius:8px;font-weight:600;font-size:14px;cursor:pointer;transition:all .2s} .sa-btn-primary{background:#0095f6;color:#fff} .sa-btn-primary:hover{background:#1877f2} .sa-btn-secondary{background:#efefef;color:#262626} .sa-btn-secondary:hover{background:#dbdbdb} .sa-emoji-picker{position:absolute;bottom:50px;left:0;background:#fff;border-radius:12px;box-shadow:0 0 20px rgba(0,0,0,.15);width:320px;z-index:100;display:none} .sa-emoji-picker.open{display:block} .sa-emoji-header{display:flex;gap:4px;padding:12px;border-bottom:1px solid #efefef;overflow-x:auto} .sa-emoji-cat{background:none;border:none;font-size:20px;padding:8px;cursor:pointer;border-radius:8px;opacity:.5;transition:all .2s} .sa-emoji-cat:hover,.sa-emoji-cat.active{opacity:1;background:#efefef} .sa-emoji-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:4px;padding:12px;max-height:200px;overflow-y:auto} .sa-emoji-btn{background:none;border:none;font-size:24px;padding:4px;cursor:pointer;border-radius:4px;transition:background .2s} .sa-emoji-btn:hover{background:#efefef} .sa-story-viewer{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:#1a1a1a;z-index:2000;align-items:center;justify-content:center} .sa-story-viewer.open{display:flex} .sa-story-viewer-content{position:relative;max-width:400px;width:100%} .sa-story-viewer-progress{position:absolute;top:16px;left:16px;right:16px;height:2px;background:rgba(255,255,255,.3);border-radius:1px} .sa-story-viewer-progress-bar{height:100%;background:#fff;border-radius:1px;width:0;animation:storyProgress 5s linear forwards} @keyframes storyProgress{from{width:0}to{width:100%}} .sa-story-viewer-header{position:absolute;top:24px;left:16px;right:16px;display:flex;align-items:center;gap:12px;color:#fff} .sa-story-viewer-avatar{width:32px;height:32px;border-radius:50%;overflow:hidden;border:2px solid #fff} .sa-story-viewer-avatar img{width:100%;height:100%;object-fit:cover} .sa-story-viewer-name{font-weight:600;font-size:14px} .sa-story-viewer-close{position:absolute;top:16px;right:16px;background:none;border:none;color:#fff;font-size:28px;cursor:pointer;z-index:10} .sa-story-viewer img{max-width:100%;max-height:80vh;border-radius:8px} @media(max-width:768px){ .sa-profile-info{flex-direction:column;align-items:center;text-align:center;margin-top:-75px} .sa-profile-pic{width:120px;height:120px} .sa-profile-details{padding-top:16px} .sa-stats{justify-content:center} .sa-bio{margin:0 auto} .sa-cover{height:150px} } </style> <script> (function(){ 'use strict';

function waitForMw(callback,maxWait){ var start=Date.now(); maxWait=maxWait||10000; (function check(){ if(window.jQuery&&window.mw&&mw.loader&&mw.config){ callback(window.jQuery,window.mw); }else if(Date.now()-start<maxWait){ setTimeout(check,100); }else{ console.error('MediaWiki not loaded');

document.getElementById('social-app').innerHTML='

Error: MediaWiki not loaded. Please refresh.

';

} })(); }

waitForMw(function($,mw){ mw.loader.using(['mediawiki.api','mediawiki.util']).then(function(){ initApp($,mw); }).catch(function(e){ console.error('Module load error:',e);

document.getElementById('social-app').innerHTML='

Error loading modules. Please refresh.

';

}); });

function initApp($,mw){ var api=new mw.Api(); var apiUrl=mw.util.wikiScript('api'); var currentUser=mw.config.get('wgUserName')||'Guest'; var isLoggedIn=currentUser!=='Guest'&&currentUser!==null;

var userData={ photo:localStorage.getItem('sf_photo_'+currentUser)||, cover:localStorage.getItem('sf_cover_'+currentUser)||, bio:localStorage.getItem('sf_bio_'+currentUser)||, displayName:localStorage.getItem('sf_name_'+currentUser)||currentUser };

var pendingPost={image:,video:,location:}; var uploadFiles={};

var emojis={ '😀':['😀','😃','😄','😁','😆','😅','🤣','😂','🙂','😉','😊','😇','🥰','😍','🤩','😘','😗','😚','😋','😛','😜','🤪','😝','🤑','🤗','🤭','🤫','🤔','🤐','🤨','😐','😑','😶','😏','😒','🙄','😬','🤥','😌','😔','😪','🤤','😴','😷','🤒','🤕','🤢','🤮','🤧','🥵','🥶','🥴','😵','🤯','🤠','🥳','🥸','😎','🤓','🧐','😕','😟','🙁','☹️','😮','😯','😲','😳','🥺','😦','😧','😨','😰','😥','😢','😭','😱','😖','😣','😞','😓','😩','😫','🥱','😤','😡','😠','🤬','😈','👿','💀','☠️','💩','🤡','👹','👺','👻','👽','👾','🤖'], '👋':['👋','🤚','🖐️','✋','🖖','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','🖕','👇','☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍️','💅','🤳','💪','🦾','🦿','🦵','🦶','👂','🦻','👃','🧠','🫀','🫁','🦷','🦴','👀','👁️','👅','👄','💋','🩸'], '❤️':['❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝','💟','☮️','✝️','☪️','🕉️','☸️','✡️','🔯','🕎','☯️','☦️','🛐','⛎','♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓'], '🐶':['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐻‍❄️','🐨','🐯','🦁','🐮','🐷','🐽','🐸','🐵','🙈','🙉','🙊','🐒','🐔','🐧','🐦','🐤','🐣','🐥','🦆','🦅','🦉','🦇','🐺','🐗','🐴','🦄','🐝','🪱','🐛','🦋','🐌','🐞','🐜','🪰','🪲','🪳','🦟','🦗','🕷️','🦂','🐢','🐍','🦎','🦖','🦕','🐙','🦑','🦐','🦞','🦀','🐡','🐠','🐟','🐬','🐳','🐋','🦈','🐊','🐅','🐆','🦓','🦍','🦧','🦣','🐘','🦛','🦏','🐪','🐫','🦒','🦘','🦬','🐃','🐂','🐄','🐎','🐖','🐏','🐑','🦙','🐐','🦌','🐕','🐩','🦮','🐕‍🦺','🐈','🐈‍⬛','🪶','🐓','🦃','🦤','🦚','🦜','🦢','🦩','🕊️','🐇','🦝','🦨','🦡','🦫','🦦','🦥','🐁','🐀','🐿️','🦔'], '🍎':['🍎','🍐','🍊','🍋','🍌','🍉','🍇','🍓','🫐','🍈','🍒','🍑','🥭','🍍','🥥','🥝','🍅','🍆','🥑','🥦','🥬','🥒','🌶️','🫑','🌽','🥕','🫒','🧄','🧅','🥔','🍠','🥐','🥯','🍞','🥖','🥨','🧀','🥚','🍳','🧈','🥞','🧇','🥓','🥩','🍗','🍖','🦴','🌭','🍔','🍟','🍕','🫓','🥪','🥙','🧆','🌮','🌯','🫔','🥗','🥘','🫕','🥫','🍝','🍜','🍲','🍛','🍣','🍱','🥟','🦪','🍤','🍙','🍚','🍘','🍥','🥠','🥮','🍢','🍡','🍧','🍨','🍦','🥧','🧁','🍰','🎂','🍮','🍭','🍬','🍫','🍿','🍩','🍪','🌰','🥜','🍯','🥛','🍼','🫖','☕','🍵','🧃','🥤','🧋','🍶','🍺','🍻','🥂','🍷','🥃','🍸','🍹','🧉','🍾','🧊','🥄','🍴','🍽️','🥣','🥡','🥢','🧂'], '⚽':['⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🪀','🏓','🏸','🏒','🏑','🥍','🏏','🪃','🥅','⛳','🪁','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛼','🛷','⛸️','🥌','🎿','⛷️','🏂','🪂','🏋️','🤼','🤸','⛹️','🤺','🤾','🏌️','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚵','🚴','🏆','🥇','🥈','🥉','🏅','🎖️','🏵️','🎗️','🎫','🎟️','🎪','🤹','🎭','🩰','🎨','🎬','🎤','🎧','🎼','🎹','🥁','🪘','🎷','🎺','🪗','🎸','🪕','🎻','🪈','🎲','♟️','🎯','🎳','🎮','🎰','🧩'] };

function getInitials(name){ return(name||'?').split(/[\s_]+/).map(function(w){return(w[0]||).toUpperCase();}).join().substring(0,2)||'?'; }

function escapeHtml(text){ var div=document.createElement('div'); div.textContent=text; return div.innerHTML; }

function timeAgo(dateStr){ var seconds=Math.floor((Date.now()-new Date(dateStr))/1000); if(seconds<60)return'Just now'; var minutes=Math.floor(seconds/60); if(minutes<60)return minutes+'m ago'; var hours=Math.floor(minutes/60); if(hours<24)return hours+'h ago'; var days=Math.floor(hours/24); if(days<7)return days+'d ago'; var weeks=Math.floor(days/7); return weeks+'w ago'; }

function renderAvatar(pic,name,size){ if(pic)return'<img src="'+escapeHtml(pic)+'" alt="">'; var cls=size==='small'?'sa-comment-avatar-placeholder':size==='post'?'sa-post-avatar-placeholder':size==='composer'?'sa-composer-avatar-placeholder':'sa-profile-pic-placeholder';

return'

'+getInitials(name)+'

';

}

function openModal(id){$('#'+id).addClass('open');} function closeModal(id){$('#'+id).removeClass('open');}

function uploadFile(file,prefix,onProgress){ return api.getToken('csrf').then(function(token){ var ext=file.name.split('.').pop()||'jpg'; var filename=prefix+'_'+currentUser+'_'+Date.now()+'.'+ext; var formData=new FormData(); formData.append('action','upload'); formData.append('filename',filename); formData.append('file',file); formData.append('token',token); formData.append('format','json'); formData.append('ignorewarnings','1'); return $.ajax({ url:apiUrl, type:'POST', data:formData, processData:false, contentType:false, xhr:function(){ var xhr=$.ajaxSettings.xhr(); if(xhr.upload&&onProgress){ xhr.upload.addEventListener('progress',function(e){ if(e.lengthComputable)onProgress(Math.round(e.loaded/e.total*100)); }); } return xhr; } }); }).then(function(response){ if(response.upload&&response.upload.imageinfo){ return response.upload.imageinfo.url; } throw new Error(response.error?response.error.info:'Upload failed'); }); }

function buildApp(){ var html='\

\
'+(userData.cover?'<img src="'+escapeHtml(userData.cover)+'" alt="">':)+'<button class="sa-cover-edit" id="edit-cover-btn">📷 Edit Cover</button>
\
\
'+renderAvatar(userData.photo,currentUser,'profile')+'
\
\
\

'+escapeHtml(userData.displayName)+'

\

<button class="sa-edit-btn" id="edit-profile-btn">Edit Profile</button>\

\
\
0
posts
\
0
followers
\
0
following
\
\
'+(userData.bio?'
'+escapeHtml(userData.displayName)+'
'+escapeHtml(userData.bio).replace(/\n/g,'
'):'Click Edit Profile to add a bio')+'
\
\
\

\

\
\
\
\
'+renderAvatar(userData.photo,currentUser,'composer')+'
\

<textarea class="sa-composer-input" id="post-input" placeholder="What\'s on your mind, '+escapeHtml(currentUser.split(/[\s_]/)[0])+'?"></textarea>\

\
<button class="sa-composer-preview-remove" id="remove-media">×</button>
\
📍 <button id="remove-location">×</button>
\
\
\

<button class="sa-icon-btn" id="btn-add-photo" title="Photo">🖼️</button>\ <button class="sa-icon-btn" id="btn-add-video" title="Video">🎬</button>\ <button class="sa-icon-btn" id="btn-add-location" title="Location">📍</button>\ <button class="sa-icon-btn" id="btn-add-emoji" title="Emoji">😊</button>\

\
\

<button class="sa-post-btn" id="btn-submit-post" disabled>Post</button>\

\
\
\

';

// Modals html+='\

';

html+='\

';

html+='\

';

html+='\

';

html+='\

';

html+='\

';

html+='\

\
\
Add to Story<button class="sa-modal-close" data-close="modal-story">×</button>
\
Upload
URL
\
\
<input type="file" accept="image/*">
📷
Drag photo here or browse
\
\

<img class="sa-preview-img" id="preview-story">\

\
\

<input class="sa-input" id="input-story-url" placeholder="Paste image URL...">\ <img class="sa-preview-img" id="preview-story-url">\

\
\

<button class="sa-btn sa-btn-secondary" data-close="modal-story">Cancel</button>\ <button class="sa-btn sa-btn-primary" id="add-story">Share to Story</button>\

\
\

';

html+='\

\
\
\

<button class="sa-story-viewer-close" id="close-story-viewer">×</button>\

\
\
\
\

<img id="story-viewer-image" src="">\

\

';

$('#social-app').html(html); }

function setupEventListeners(){ var $app=$('#social-app');

// Modal close buttons $app.on('click','[data-close]',function(){ closeModal($(this).data('close')); });

// Modal backdrop click $app.on('click','.sa-modal',function(e){ if($(e.target).hasClass('sa-modal'))closeModal(this.id); });

// Tabs $app.on('click','.sa-tab',function(){ var $tab=$(this),$panel=$('#'+$tab.data('panel')); $tab.addClass('active').siblings().removeClass('active'); $panel.addClass('active').siblings('.sa-tab-panel').removeClass('active'); });

// Upload zones function setupUploadZone(zoneId,previewId,progressId,fileKey){ var $zone=$('#'+zoneId),$input=$zone.find('input'),$preview=$('#'+previewId),$progress=$('#'+progressId); $zone.on('click',function(){$input.click();}); $zone.on('dragover',function(e){e.preventDefault();$(this).addClass('dragover');}); $zone.on('dragleave drop',function(){$(this).removeClass('dragover');}); $zone.on('drop',function(e){ e.preventDefault(); var files=e.originalEvent.dataTransfer.files; if(files.length)handleFile(files[0]); }); $input.on('change',function(){if(this.files.length)handleFile(this.files[0]);}); function handleFile(file){ uploadFiles[fileKey]=file; if(file.type.startsWith('image/')){ var reader=new FileReader(); reader.onload=function(e){$preview.attr('src',e.target.result).addClass('show');}; reader.readAsDataURL(file); }else if(file.type.startsWith('video/')){ $preview.attr('src',URL.createObjectURL(file)).addClass('show').show(); } } return{ reset:function(){uploadFiles[fileKey]=null;$preview.removeClass('show').hide().attr('src',);$progress.removeClass('show').find('.sa-progress-bar').css('width',0);$input.val();}, setProgress:function(pct){$progress.addClass('show').find('.sa-progress-bar').css('width',pct+'%');} }; }

var uploadProfilePic=setupUploadZone('zone-profile-pic','preview-profile-pic','progress-profile-pic','profilePic'); var uploadCover=setupUploadZone('zone-cover','preview-cover','progress-cover','cover'); var uploadPhoto=setupUploadZone('zone-photo','preview-photo','progress-photo','photo'); var uploadVideo=setupUploadZone('zone-video','preview-video','progress-video','video'); var uploadStory=setupUploadZone('zone-story','preview-story','progress-story','story');

// URL preview inputs $('#input-pic-url').on('input',function(){var v=$(this).val();$('#preview-pic-url').attr('src',v).toggleClass('show',!!v);}); $('#input-cover-url').on('input',function(){var v=$(this).val();$('#preview-cover-url').attr('src',v).toggleClass('show',!!v);}); $('#input-photo-url').on('input',function(){var v=$(this).val();$('#preview-photo-url').attr('src',v).toggleClass('show',!!v);}); $('#input-story-url').on('input',function(){var v=$(this).val();$('#preview-story-url').attr('src',v).toggleClass('show',!!v);});

// Edit profile button $('#edit-profile-btn').on('click',function(){openModal('modal-edit-profile');}); $('#save-profile').on('click',function(){ userData.displayName=$('#input-display-name').val().trim()||currentUser; userData.bio=$('#input-bio').val().trim(); localStorage.setItem('sf_name_'+currentUser,userData.displayName); localStorage.setItem('sf_bio_'+currentUser,userData.bio); $('#display-name').text(userData.displayName);

$('#bio-display').html(userData.bio?'

'+escapeHtml(userData.displayName)+'

'+escapeHtml(userData.bio).replace(/\n/g,'
'):'Click Edit Profile to add a bio');

closeModal('modal-edit-profile'); });

// Profile picture $('#profile-pic').on('click',function(){uploadProfilePic.reset();$('#input-pic-url').val();$('#preview-pic-url').removeClass('show');openModal('modal-profile-pic');}); $('#save-profile-pic').on('click',function(){ var activePanel=$('#modal-profile-pic .sa-tab.active').data('panel'); if(activePanel==='panel-pic-url'){ var url=$('#input-pic-url').val().trim(); if(url){saveProfilePic(url);}else{alert('Please enter a URL');} }else if(uploadFiles.profilePic){ uploadFile(uploadFiles.profilePic,'ProfilePic',uploadProfilePic.setProgress).then(function(url){ saveProfilePic(url); }).catch(function(e){alert('Upload error: '+e.message);}); }else{alert('Please select an image');} }); function saveProfilePic(url){ userData.photo=url; localStorage.setItem('sf_photo_'+currentUser,url); $('#profile-pic').html(renderAvatar(url,currentUser,'profile')); $('#composer-avatar').html(renderAvatar(url,currentUser,'composer')); closeModal('modal-profile-pic'); uploadProfilePic.reset(); }

// Cover photo $('#edit-cover-btn').on('click',function(){uploadCover.reset();$('#input-cover-url').val();$('#preview-cover-url').removeClass('show');openModal('modal-cover');}); $('#save-cover').on('click',function(){ var activePanel=$('#modal-cover .sa-tab.active').data('panel'); if(activePanel==='panel-cover-url'){ var url=$('#input-cover-url').val().trim(); if(url){saveCover(url);}else{alert('Please enter a URL');} }else if(uploadFiles.cover){ uploadFile(uploadFiles.cover,'Cover',uploadCover.setProgress).then(function(url){ saveCover(url); }).catch(function(e){alert('Upload error: '+e.message);}); }else{alert('Please select an image');} }); function saveCover(url){ userData.cover=url; localStorage.setItem('sf_cover_'+currentUser,url); $('#cover-area').html('<img src="'+url+'" alt=""><button class="sa-cover-edit" id="edit-cover-btn">📷 Edit Cover</button>'); closeModal('modal-cover'); uploadCover.reset(); }

// Photo for post $('#btn-add-photo').on('click',function(){uploadPhoto.reset();$('#input-photo-url').val();$('#preview-photo-url').removeClass('show');openModal('modal-photo');}); $('#add-photo').on('click',function(){ var activePanel=$('#modal-photo .sa-tab.active').data('panel'); if(activePanel==='panel-photo-url'){ var url=$('#input-photo-url').val().trim(); if(url){setPendingMedia('image',url);closeModal('modal-photo');}else{alert('Please enter a URL');} }else if(uploadFiles.photo){ uploadFile(uploadFiles.photo,'Post',uploadPhoto.setProgress).then(function(url){ setPendingMedia('image',url); closeModal('modal-photo'); uploadPhoto.reset(); }).catch(function(e){alert('Upload error: '+e.message);}); }else{alert('Please select an image');} });

// Video for post $('#btn-add-video').on('click',function(){uploadVideo.reset();$('#input-video-url').val();openModal('modal-video');}); $('#add-video').on('click',function(){ var activePanel=$('#modal-video .sa-tab.active').data('panel'); if(activePanel==='panel-video-url'){ var url=$('#input-video-url').val().trim(); if(url){setPendingMedia('video',url);closeModal('modal-video');}else{alert('Please enter a URL');} }else if(uploadFiles.video){ uploadFile(uploadFiles.video,'Video',uploadVideo.setProgress).then(function(url){ setPendingMedia('video',url); closeModal('modal-video'); uploadVideo.reset(); }).catch(function(e){alert('Upload error: '+e.message);}); }else{alert('Please select a video');} });

function setPendingMedia(type,url){ pendingPost.image=;pendingPost.video=; pendingPost[type]=url; var $preview=$('#post-preview'),$inner=$preview.find('.sa-composer-preview-inner'); if(type==='image'){ $inner.find('img,video').remove(); $inner.append('<img src="'+url+'" alt="">'); }else{ $inner.find('img,video').remove(); if(url.match(/youtube|youtu\.be/)){ $inner.append('<img src="https://img.youtube.com/vi/'+(url.match(/(?:v=%7Cyoutu\.be\/)([^&]+)/)||[])[1]+'/hqdefault.jpg" alt="">'); }else{ $inner.append('<video src="'+url+'" style="max-height:200px"></video>'); } } $preview.addClass('show'); updatePostButton(); }

$('#remove-media').on('click',function(){ pendingPost.image=;pendingPost.video=; $('#post-preview').removeClass('show').find('img,video').remove(); updatePostButton(); });

// Location $('#btn-add-location').on('click',function(){$('#input-location').val();openModal('modal-location');}); $('#detect-location').on('click',function(){ var $btn=$(this); $btn.text('Detecting...').prop('disabled',true); if(navigator.geolocation){ navigator.geolocation.getCurrentPosition(function(pos){ $.getJSON('https://nominatim.openstreetmap.org/reverse?format=json&lat='+pos.coords.latitude+'&lon='+pos.coords.longitude).done(function(data){ var loc=(data.display_name||).split(',').slice(0,3).join(',')||pos.coords.latitude+','+pos.coords.longitude; $('#input-location').val(loc); $btn.text('📍 Use Current Location').prop('disabled',false); }).fail(function(){ $('#input-location').val(pos.coords.latitude+','+pos.coords.longitude); $btn.text('📍 Use Current Location').prop('disabled',false); }); },function(){ alert('Could not get location'); $btn.text('📍 Use Current Location').prop('disabled',false); }); }else{ alert('Geolocation not supported'); $btn.text('📍 Use Current Location').prop('disabled',false); } }); $('#add-location').on('click',function(){ var loc=$('#input-location').val().trim(); if(loc){ pendingPost.location=loc; $('#post-location').addClass('show').find('span').text(loc); closeModal('modal-location'); }else{alert('Please enter a location');} }); $('#remove-location').on('click',function(){ pendingPost.location=; $('#post-location').removeClass('show'); });

// Emoji picker var $picker=$('#emoji-picker'); var emojiCats=Object.keys(emojis);

$picker.html('

'+emojiCats.map(function(c,i){return'<button class="sa-emoji-cat'+(i===0?' active':)+'" data-cat="'+c+'">'+c+'</button>';}).join()+'

');

function showEmojis(cat){$picker.find('.sa-emoji-grid').html(emojis[cat].map(function(e){return'<button class="sa-emoji-btn">'+e+'</button>';}).join());} showEmojis(emojiCats[0]); $picker.on('click','.sa-emoji-cat',function(){$(this).addClass('active').siblings().removeClass('active');showEmojis($(this).data('cat'));}); $picker.on('click','.sa-emoji-btn',function(){ var emoji=$(this).text(),$input=$('#post-input')[0]; var start=$input.selectionStart,val=$input.value; $input.value=val.slice(0,start)+emoji+val.slice($input.selectionEnd); $input.selectionStart=$input.selectionEnd=start+emoji.length; $input.focus(); updatePostButton(); }); $('#btn-add-emoji').on('click',function(e){e.stopPropagation();$picker.toggleClass('open');}); $(document).on('click',function(e){if(!$(e.target).closest('#emoji-picker,#btn-add-emoji').length)$picker.removeClass('open');});

// Post input $('#post-input').on('input',updatePostButton); function updatePostButton(){ var hasContent=$('#post-input').val().trim()||pendingPost.image||pendingPost.video; $('#btn-submit-post').prop('disabled',!hasContent); }

// Submit post $('#btn-submit-post').on('click',function(){ var text=$('#post-input').val().trim(); if(!text&&!pendingPost.image&&!pendingPost.video){alert('Please add some content');return;} var $btn=$(this); $btn.prop('disabled',true).text('Posting...'); var params={action:'socialfeed',sfaction:'createpost',content:text||'(media)'}; if(pendingPost.image)params.image_url=pendingPost.image; if(pendingPost.video)params.video_url=pendingPost.video; if(pendingPost.location)params.location=pendingPost.location; api.postWithToken('csrf',params).then(function(){ $('#post-input').val(); pendingPost={image:,video:,location:}; $('#post-preview').removeClass('show').find('img,video').remove(); $('#post-location').removeClass('show'); $btn.prop('disabled',true).text('Post'); loadPosts(); }).catch(function(e){ alert('Error posting: '+(e.message||e)); $btn.prop('disabled',false).text('Post'); }); });

// Story $app.on('click','#add-story-btn',function(){uploadStory.reset();$('#input-story-url').val();$('#preview-story-url').removeClass('show');openModal('modal-story');}); $('#add-story').on('click',function(){ var activePanel=$('#modal-story .sa-tab.active').data('panel'); function postStory(url){ api.postWithToken('csrf',{action:'socialfeed',sfaction:'createstory',image_url:url}).then(function(){ closeModal('modal-story'); uploadStory.reset(); loadStories(); }).catch(function(e){alert('Error: '+e.message);}); } if(activePanel==='panel-story-url'){ var url=$('#input-story-url').val().trim(); if(url){postStory(url);}else{alert('Please enter a URL');} }else if(uploadFiles.story){ uploadFile(uploadFiles.story,'Story',uploadStory.setProgress).then(function(url){ postStory(url); }).catch(function(e){alert('Upload error: '+e.message);}); }else{alert('Please select an image');} });

// Story viewer var storyTimeout; $app.on('click','.sa-story[data-story-url]',function(){ var url=$(this).data('story-url'),name=$(this).data('story-user'),pic=localStorage.getItem('sf_photo_'+name)||; $('#story-viewer-image').attr('src',url); $('#story-viewer-name').text(name); $('#story-viewer-avatar').html(renderAvatar(pic,name,'small')); $('#story-viewer').addClass('open'); $('.sa-story-viewer-progress-bar').css('animation','none'); setTimeout(function(){$('.sa-story-viewer-progress-bar').css('animation','storyProgress 5s linear forwards');},10); clearTimeout(storyTimeout); storyTimeout=setTimeout(function(){$('#story-viewer').removeClass('open');},5000); }); $('#close-story-viewer,#story-viewer').on('click',function(e){ if(e.target.id==='close-story-viewer'||e.target.id==='story-viewer'){ $('#story-viewer').removeClass('open'); clearTimeout(storyTimeout); } });

// Feed interactions $app.on('click','.sa-post-action[data-like]',function(){ var postId=$(this).data('like'); api.postWithToken('csrf',{action:'socialfeed',sfaction:'react',post_id:postId,reaction_type:'like'}).then(loadPosts); });

$app.on('click','.sa-post-menu[data-delete]',function(){ if(confirm('Delete this post?')){ api.postWithToken('csrf',{action:'socialfeed',sfaction:'deletepost',post_id:$(this).data('delete')}).then(loadPosts); } });

$app.on('click','.sa-post-comments-count',function(){ var postId=$(this).data('post'); var $section=$('#comments-'+postId); if($section.hasClass('show')){ $section.removeClass('show'); }else{ $section.addClass('show'); loadComments(postId); } });

$app.on('keypress','.sa-post-comment-input',function(e){ if(e.which===13){ var $input=$(this),text=$input.val().trim(),postId=$input.data('post'); if(text){ $input.val().prop('disabled',true); api.postWithToken('csrf',{action:'socialfeed',sfaction:'comment',post_id:postId,content:text}).then(function(){ $input.prop('disabled',false); loadComments(postId); loadPosts(); }).catch(function(){$input.prop('disabled',false);}); } } }); }

function loadPosts(){ api.get({action:'socialfeed',sfaction:'getposts',limit:20}).then(function(response){ var posts=(response.socialfeed&&response.socialfeed.posts)||[]; $('#post-count').text(posts.length); if(!posts.length){

$('#feed-container').html('

📷
Share Photos
When you share photos, they will appear on your profile.

');

return; } var html=posts.map(function(post){ var userPic=localStorage.getItem('sf_photo_'+post.username)||; var totalReactions=0; for(var k in post.reaction_counts)totalReactions+=post.reaction_counts[k]; var media=; if(post.image_url){

media='

<img src="'+escapeHtml(post.image_url)+'" alt="">

';

} if(post.video_url){ if(post.video_url.match(/youtube|youtu\.be/)){ var videoId=(post.video_url.match(/(?:v=|youtu\.be\/)([^&]+)/)||[])[1];

if(videoId)media='

<iframe src="https://www.youtube.com/embed/'+videoId+'" allowfullscreen></iframe>

';

}else if(post.video_url.match(/vimeo/)){ var vimeoId=(post.video_url.match(/vimeo\.com\/(\d+)/)||[])[1];

if(vimeoId)media='

<iframe src="https://player.vimeo.com/video/'+vimeoId+'" allowfullscreen></iframe>

';

}else{

media='

<video src="'+escapeHtml(post.video_url)+'" controls></video>

';

} }

var location=post.location?'

📍 '+escapeHtml(post.location)+'

':;

var deleteBtn=post.username===currentUser?'<button class="sa-post-menu" data-delete="'+post.id+'">🗑️</button>':; var likedClass=post.user_reaction==='like'?' liked':; return'\

\
\
'+renderAvatar(userPic,post.username,'post')+'
\
\
'+escapeHtml(post.username)+'
\ \
\

'+deleteBtn+'\

\ '+(post.content&&post.content!=='(media)'?'
'+escapeHtml(post.content)+'
':)+'\

'+location+media+'\

\

<button class="sa-post-action'+likedClass+'" data-like="'+post.id+'">'+(post.user_reaction==='like'?'❤️':'🤍')+'</button>\ <button class="sa-post-action">💬</button>\ <button class="sa-post-action">📤</button>\ <button class="sa-post-action" style="margin-left:auto">🔖</button>\

\ '+(totalReactions?'
'+totalReactions+' like'+(totalReactions>1?'s':)+'
':)+'\ '+(post.content&&post.content!=='(media)'?:)+(post.comments?'
View all '+post.comments+' comment'+(post.comments>1?'s':)+'
':)+'\
\
'+timeAgo(post.created)+'
\
\

<input class="sa-post-comment-input" data-post="'+post.id+'" placeholder="Add a comment...">\ <button class="sa-post-comment-submit">Post</button>\

\

';

}).join(); $('#feed-container').html(html); }).catch(function(e){ console.error('Error loading posts:',e);

$('#feed-container').html('

😕
Error Loading
Could not load posts. Please refresh.

');

}); }

function loadComments(postId){ api.get({action:'socialfeed',sfaction:'getcomments',post_id:postId}).then(function(response){ var comments=(response.socialfeed&&response.socialfeed.comments)||[]; var html=comments.map(function(comment){ var userPic=localStorage.getItem('sf_photo_'+comment.username)||; return'\

\
'+renderAvatar(userPic,comment.username,'small')+'
\
\
'+escapeHtml(comment.username)+' '+escapeHtml(comment.content)+'
\
'+timeAgo(comment.created)+'
\
\

';

}).join();

$('#comments-'+postId).html(html||'

No comments yet

');

}); }

function loadStories(){ api.get({action:'socialfeed',sfaction:'getstories'}).then(function(response){ var stories=(response.socialfeed&&response.socialfeed.stories)||[];

var html='

+
Your story

';

html+=stories.map(function(story){ var userPic=localStorage.getItem('sf_photo_'+story.username)||; return'\

\
'+renderAvatar(userPic,story.username,'small')+'
\
'+escapeHtml(story.username)+'
\

';

}).join(); $('#stories-container').html(html); }).catch(function(){}); }

// Initialize buildApp(); setupEventListeners(); loadPosts(); loadStories(); } })(); </script>