/* SAN AVELIO — PERSONAL ARCHIVE v7 Standalone replacement for About Studio v4. Keeps DOSSIER v42 intact. Existing SAABOUT1 records and legacy plain text remain readable. */ (function(){ 'use strict'; if(window.__SA_ABOUT_STUDIO_V8__)return; window.__SA_ABOUT_STUDIO_V8__=true; if(!/\/profile\.php$/i.test(location.pathname)||new URLSearchParams(location.search).has('section'))return; var PREFIX='SAABOUT1:', APP=1887506, API=location.origin+'/api.php'; var source=null, wrap=null, feed=null, addButton=null, lastRaw=null, modal=null, busy=false; function el(tag,cls,txt){var n=document.createElement(tag);if(cls)n.className=cls;if(txt!==undefined)n.textContent=txt;return n} function uid(){return (new URLSearchParams(location.search).get('id')||'').replace(/\D/g,'')} function owner(){return !!(source&&source.classList.contains('sa40-edit'))} function raw(){return source?source.textContent:''} function parse(value){ value=String(value||'').trim(); if(value.indexOf(PREFIX)===0){ try{var a=JSON.parse(value.slice(PREFIX.length));if(Array.isArray(a))return a.filter(function(x){return x&&['text','fact','quote','image','divider'].indexOf(x.type)>=0}).slice(0,300)}catch(e){} } if(!value||value==='—'||value==='-'||/нажмите, чтобы заполнить/i.test(value))return []; return [{type:'text',title:'',body:value}]; } function encode(a){return PREFIX+JSON.stringify(a)} function api(method,params){ var $=window.jQuery||window.$; if(!$||!$.ajax)return Promise.reject(Error('Не удалось подключиться к хранилищу')); return new Promise(function(resolve,reject){ $.ajax({url:API,type:method==='storage.get'?'GET':'POST',dataType:'json',cache:false, data:Object.assign({method:method,app_id:APP},params), success:function(j){if(j&&(j.error||j.errors))reject(Error('Сервер отклонил запрос'));else resolve(j)}, error:function(x){reject(Error('Ошибка соединения: '+(x.status||'нет ответа')))} }); }); } function serverRead(){ var k='san_avelio_profile_v1_'+uid(); return api('storage.get',{key:[k],_:Date.now()}).then(function(j){ var v=j&&j.response&&j.response.storage&&j.response.storage.data&&j.response.storage.data[k]; if(!v)throw Error('Профиль пока не опубликован в общем хранилище'); return typeof v==='string'?JSON.parse(v):v; }); } async function persist(items,original){ if(!owner())throw Error('Редактирование доступно только владельцу профиля'); var ticket=String(window.ForumAPITicket||''); if(!ticket)throw Error('Нет токена авторизации'); var k='san_avelio_profile_v1_'+uid(), data=await serverRead(); if(String(data.dossier||'')!==String(original||''))throw Error('Данные изменились в другом окне. Обновите страницу'); data.dossier=encode(items); await api('storage.set',{key:k,value:JSON.stringify(data),token:ticket}); var check=await serverRead(); if(String(check.dossier||'')!==data.dossier)throw Error('Сервер не подтвердил сохранение'); source.textContent=data.dossier; lastRaw=null;render(); } function safeLink(u){if(/^data:image\/(?:jpeg|png|webp);base64,[A-Za-z0-9+/=]+$/.test(String(u||'')))return u;try{var url=new URL(String(u||''));return url.protocol==='https:'?url.href:''}catch(e){return ''}} function formatted(container,text){ // Text-only rendering with line breaks; never insert user HTML. var lines=String(text||'').split(/\n/); lines.forEach(function(line,i){ if(i)container.append(document.createElement('br')); container.append(document.createTextNode(line)); }); } function render(){ if(!source||!feed||modal)return; var value=raw();if(value===lastRaw)return;lastRaw=value; var items=parse(value);feed.replaceChildren(); if(!items.length){ var empty=el('div','sa-pa-empty'); empty.append(el('span','sa-pa-empty-icon','✦'),el('h3','','Здесь пока тихо'),el('p','','Небольшие детали, любимые вещи, случайные мысли — всё, что делает персонажа живым.')); feed.append(empty); }else{ items.forEach(function(item,i){ var card=el('article','sa-pa-entry sa-pa-'+item.type); if(item.type!=='divider'){card.classList.add('sa-pa-collapsed');} var top=el('div','sa-pa-entry-top'); top.append(el('span','sa-pa-index',String(i+1).padStart(2,'0'))); top.append(el('span','sa-pa-type',({text:'Личная заметка',fact:'Деталь',quote:'Цитата',image:'Фотография',divider:'Разделитель'})[item.type]||'Запись')); if(item.type!=='divider'){ var toggle=el('button','sa-pa-toggle','Развернуть');toggle.type='button';toggle.setAttribute('aria-expanded','false'); toggle.onclick=function(){var expanded=card.classList.toggle('sa-pa-collapsed')===false;toggle.textContent=expanded?'Свернуть':'Развернуть';toggle.setAttribute('aria-expanded',String(expanded))};top.append(toggle); } if(owner()){ var edit=el('button','sa-pa-small-edit','Изменить'); edit.type='button';edit.onclick=function(){openComposer(i)};top.append(edit); } card.append(top); if(item.type==='divider'){ card.append(el('div','sa-pa-ornament','✦ · ✦ · ✦')); }else{ if(item.type==='image'){ var url=safeLink(item.url); if(url){var img=el('img','sa-pa-photo');img.src=url;img.alt=item.title||'Фотография персонажа';img.loading='lazy';img.referrerPolicy='no-referrer';card.append(img)} } if(item.title)card.append(el('h3','sa-pa-entry-title',item.title)); if(item.body){var body=el('div','sa-pa-entry-body');formatted(body,item.body);card.append(body)} } feed.append(card); }); } if(addButton)addButton.hidden=!owner(); } function close(){if(modal){modal.remove();modal=null;lastRaw=null;render()}} function openComposer(index){ if(!owner()||modal||busy)return; var original=raw(),items=parse(original).map(function(x){return Object.assign({},x)}); var isNew=index==null, item=isNew?{type:'text',title:'',body:''}:Object.assign({},items[index]); if(!item)return; var overlay=el('div','sa-pa-overlay'),dialog=el('section','sa-pa-dialog'); dialog.setAttribute('role','dialog');dialog.setAttribute('aria-modal','true');dialog.setAttribute('aria-label',isNew?'Добавить запись':'Изменить запись'); var header=el('header','sa-pa-modal-head'); var heading=el('div');heading.append(el('span','sa-pa-kicker','SAN AVELIO / ARCHIVIO PERSONALE'),el('h2','',isNew?'Новая запись':'Запись '+String(index+1).padStart(2,'0'))); var cross=el('button','sa-pa-close','');cross.type='button';cross.setAttribute('aria-label','Закрыть');cross.onclick=close; header.append(heading,cross); var content=el('div','sa-pa-modal-content'); content.append(el('p','sa-pa-hint','Расскажите о персонаже то, чему не нашлось места в анкете. Можно писать свободно — без обязательных пунктов.')); var titleLabel=el('label','sa-pa-label','Заголовок (необязательно)'); var titleInput=el('input','sa-pa-input');titleInput.type='text';titleInput.maxLength=180;titleInput.placeholder='Например, маленькие странности';titleInput.value=item.title||''; titleLabel.append(titleInput); var bodyLabel=el('label','sa-pa-label','Ваша запись'); var bodyInput=el('textarea','sa-pa-textarea');bodyInput.maxLength=12000;bodyInput.placeholder='О чём стоит знать другим? Любимая мелодия, привычка, странная история, случайное воспоминание…';bodyInput.value=item.body||''; bodyLabel.append(bodyInput); var imageLabel=el('label','sa-pa-label','Фотография (необязательно)'); var imageInput=el('input','sa-pa-input');imageInput.type='url';imageInput.placeholder='https://...';imageInput.value=item.url||''; imageLabel.append(imageInput); var fileLabel=el('label','sa-pa-file','Выбрать изображение с компьютера'); var fileInput=el('input');fileInput.type='file';fileInput.accept='image/jpeg,image/png,image/webp';fileInput.style.display='none';fileLabel.append(fileInput); var fileHint=el('p','sa-pa-file-hint','Фото будет уменьшено для хранения вместе с записью. Для больших оригиналов оставлена ссылка.'); var selectedData=''; fileInput.onchange=function(){ var f=fileInput.files&&fileInput.files[0];if(!f)return; if(!/^image\/(jpeg|png|webp)$/.test(f.type)){note.textContent='Поддерживаются JPG, PNG и WebP';return} if(f.size>12*1024*1024){note.textContent='Файл больше 12 МБ';return} note.textContent='Подготавливаем изображение…'; var fr=new FileReader();fr.onerror=function(){note.textContent='Не удалось прочитать файл'}; fr.onload=function(){ var pic=new Image();pic.onerror=function(){note.textContent='Не удалось открыть изображение'}; pic.onload=function(){ var scale=Math.min(1,850/pic.width,850/pic.height),canvas=document.createElement('canvas'); canvas.width=Math.max(1,Math.round(pic.width*scale));canvas.height=Math.max(1,Math.round(pic.height*scale)); canvas.getContext('2d').drawImage(pic,0,0,canvas.width,canvas.height); var out=canvas.toDataURL('image/jpeg',.62); if(out.length>100000){note.textContent='Изображение слишком объёмное для профиля. Используйте ссылку или уменьшите файл.';return} selectedData=out;imageInput.value='';note.textContent='Фото выбрано: '+f.name; };pic.src=fr.result; };fr.readAsDataURL(f); }; content.append(titleLabel,bodyLabel,fileLabel,fileHint,imageLabel); var note=el('div','sa-pa-status',''); var footer=el('footer','sa-pa-modal-foot'); var left=el('div','sa-pa-foot-left'); if(!isNew){ var del=el('button','sa-pa-delete','Удалить запись');del.type='button';left.append(del); del.onclick=function(){ if(!confirm('Удалить эту запись?'))return; commit(function(){items.splice(index,1)}); }; } var cancel=el('button','sa-pa-secondary','Отмена');cancel.type='button';cancel.onclick=close; var save=el('button','sa-pa-primary','Сохранить запись');save.type='button'; footer.append(left,cancel,save); function commit(update){ if(busy)return; busy=true;save.disabled=true;cancel.disabled=true;note.textContent='Сохраняем в профиле…'; update(); persist(items,original).then(function(){busy=false;close()}).catch(function(e){ busy=false;save.disabled=false;cancel.disabled=false;note.textContent='Не сохранено: '+e.message; }); } save.onclick=function(){ var title=titleInput.value.trim(),body=bodyInput.value.trim(),url=selectedData||imageInput.value.trim(); if(url&&!safeLink(url)){note.textContent='Для фотографии нужна прямая HTTPS-ссылка';return} if(!title&&!body&&!url){note.textContent='Напишите хотя бы несколько слов';return} if(isNew&&items.length>=300){note.textContent='Можно сохранить не более 300 записей';return} commit(function(){ var next={type:url?'image':'text',title:title,body:body,url:url}; if(isNew){items.push(next)} else items[index]=next; }); }; dialog.append(header,content,note,footer);overlay.append(dialog); overlay.addEventListener('click',function(e){if(e.target===overlay&&!busy)close()}); document.body.append(overlay);modal=overlay;bodyInput.focus(); } function styles(){ if(document.getElementById('sa-pa-css-v8'))return; var s=el('style');s.id='sa-pa-css-v8';s.textContent=` #sa-dossier-clean .sa-pa-root{max-width:1000px;margin:0 0 26px;color:#473b30} #sa-dossier-clean .sa-pa-header{position:relative;display:flex;align-items:center;justify-content:space-between;gap:20px;padding:18px 8px 21px;background:transparent;border:0;border-bottom:1px solid #b59668;box-shadow:none;overflow:hidden} #sa-dossier-clean .sa-pa-header:after{display:none!important} #sa-dossier-clean .sa-pa-kicker{display:block;font:700 9px/1.4 Arial,sans-serif;letter-spacing:2.4px;color:#a4763e} #sa-dossier-clean .sa-pa-heading{display:block;margin:7px 0 0;font:normal 29px/1.15 Georgia,serif;color:#664526} #sa-dossier-clean .sa-pa-sub{display:block;margin:9px 0 0;font:12px/1.5 Georgia,serif;color:#80684d} #sa-dossier-clean .sa-pa-add{position:relative;z-index:1;flex-shrink:0;cursor:pointer;padding:13px 20px;border:1px solid #d1a363;background:linear-gradient(#e9c489,#bd8948);color:#152b2b;font:700 10px Arial,sans-serif;letter-spacing:1.2px;box-shadow:0 4px 13px #0004;transition:filter .2s,transform .2s} #sa-dossier-clean .sa-pa-add:hover{filter:brightness(1.09);transform:translateY(-2px)} #sa-dossier-clean .sa-pa-feed{display:grid;gap:16px;padding:20px 0} #sa-dossier-clean .sa-pa-entry{position:relative;padding:24px 29px 27px;border:1px solid #c6aa7e;background:linear-gradient(145deg,#faf1df,#eee0c6);box-shadow:0 6px 22px #61452114;overflow-wrap:anywhere} #sa-dossier-clean .sa-pa-entry:before{content:"";position:absolute;top:0;left:0;width:72px;height:2px;background:#ad7b39} #sa-dossier-clean .sa-pa-entry-top{display:flex;align-items:center;gap:11px;margin-bottom:17px;padding-bottom:11px;border-bottom:1px solid #a88a5d45} #sa-dossier-clean .sa-pa-index{font:normal 21px Georgia,serif;color:#986c34} #sa-dossier-clean .sa-pa-type{font:700 9px Arial,sans-serif;letter-spacing:1.6px;text-transform:uppercase;color:#957147} #sa-dossier-clean .sa-pa-small-edit{margin-left:auto;cursor:pointer;border:0;border-bottom:1px solid #a78153;background:transparent;padding:5px;color:#7b562f;font:10px Georgia,serif} #sa-dossier-clean .sa-pa-toggle{margin-left:auto;cursor:pointer;border:0;background:transparent;color:#78572f;font:11px Georgia,serif;text-decoration:underline;text-underline-offset:3px} #sa-dossier-clean .sa-pa-toggle+.sa-pa-small-edit{margin-left:7px} #sa-dossier-clean .sa-pa-collapsed .sa-pa-entry-body{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden} #sa-dossier-clean .sa-pa-collapsed .sa-pa-photo{max-height:130px;max-width:200px;object-fit:cover} #sa-dossier-clean .sa-pa-entry-title{font:normal 23px/1.25 Georgia,serif;color:#684523;margin:0 0 12px} #sa-dossier-clean .sa-pa-entry-body{font:14px/1.85 Georgia,serif;color:#4b4035;white-space:normal} #sa-dossier-clean .sa-pa-quote .sa-pa-entry-body{font-style:italic;font-size:17px} #sa-dossier-clean .sa-pa-photo{display:block;max-width:100%;max-height:460px;object-fit:contain;margin:0 0 18px;border:5px solid #fff8e9;box-shadow:0 4px 16px #39271624} #sa-dossier-clean .sa-pa-divider{text-align:center;background:transparent;border:0;box-shadow:none;padding:4px} #sa-dossier-clean .sa-pa-ornament{font:19px Georgia,serif;color:#a77d45;letter-spacing:9px} #sa-dossier-clean .sa-pa-empty{text-align:center;padding:46px 18px;background:#f6ebd5;border:1px dashed #b99a6b} #sa-dossier-clean .sa-pa-empty-icon{font:29px Georgia,serif;color:#b58c4d} #sa-dossier-clean .sa-pa-empty h3{font:normal 24px Georgia,serif;color:#6f4c2a;margin:12px 0} #sa-dossier-clean .sa-pa-empty p{font:13px/1.6 Georgia,serif;color:#77634e;margin:0} .sa-pa-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;box-sizing:border-box;padding:22px;background:rgba(2,16,20,.83);backdrop-filter:blur(7px)} .sa-pa-dialog{box-sizing:border-box;width:min(780px,100%);max-height:min(91vh,900px);display:flex;flex-direction:column;border:1px solid #c69b5c;background:#f2e4c9;box-shadow:0 30px 90px #000a,0 0 0 7px #0c2529;font-family:Georgia,serif;color:#473a2c;overflow:hidden} .sa-pa-modal-head{flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:24px 30px;background:linear-gradient(110deg,#092126,#183a3d);border-bottom:2px solid #b68a4a} .sa-pa-modal-head h2{margin:8px 0 0;font:normal 31px Georgia,serif;color:#f4e4c7} .sa-pa-close{position:relative;cursor:pointer;border:1px solid #ae8d5a;background:transparent;color:#edcb8d;width:35px;height:35px;font:24px Georgia,serif} .sa-pa-close:before,.sa-pa-close:after{content:"";position:absolute;left:8px;top:16px;width:18px;height:1px;background:#edcb8d;transform:rotate(45deg)} .sa-pa-close:after{transform:rotate(-45deg)} .sa-pa-modal-content{padding:25px 31px 15px;overflow:auto} .sa-pa-hint{margin:0 0 20px;font:13px/1.6 Georgia,serif;color:#79654e} .sa-pa-label{display:block;margin:0 0 17px;font:700 9px Arial,sans-serif;letter-spacing:1.2px;color:#896136;text-transform:uppercase} .sa-pa-input,.sa-pa-textarea{display:block;box-sizing:border-box;width:100%;margin-top:8px;padding:13px 15px;border:1px solid #c7a77a;border-radius:0;outline:0;background:#fffaf0;color:#403529;font:15px/1.65 Georgia,serif;box-shadow:inset 0 1px 3px #8a623016;transition:border-color .2s,box-shadow .2s} .sa-pa-input:focus,.sa-pa-textarea:focus{border-color:#a46d2d;box-shadow:0 0 0 3px #bd92452b} .sa-pa-textarea{min-height:190px;resize:vertical} .sa-pa-file{display:inline-block;padding:11px 16px;margin-bottom:6px;cursor:pointer;border:1px solid #ae8754;color:#674525;background:#f7ebd5;font:700 11px Arial,sans-serif;letter-spacing:.5px} .sa-pa-file-hint{font:11px/1.5 Georgia,serif;color:#887259;margin:0 0 18px} .sa-pa-status{padding:0 31px;min-height:20px;font:12px Georgia,serif;color:#9c4439} .sa-pa-modal-foot{display:flex;align-items:center;gap:10px;flex-shrink:0;padding:17px 30px;background:#e6d4b4;border-top:1px solid #c7aa7c} .sa-pa-foot-left{margin-right:auto} .sa-pa-primary,.sa-pa-secondary,.sa-pa-delete{cursor:pointer;padding:11px 16px;border:1px solid #9b7749;font:700 10px Arial,sans-serif;letter-spacing:.7px} .sa-pa-primary{background:#13353a;border-color:#13353a;color:#f4d6a3} .sa-pa-secondary{background:transparent;color:#6b4a2b} .sa-pa-delete{background:transparent;color:#984a3e;border-color:#bc8174} .sa-pa-primary:disabled,.sa-pa-secondary:disabled{opacity:.5;cursor:wait} @media(max-width:650px){#sa-dossier-clean .sa-pa-header{flex-direction:column;align-items:stretch;padding:18px}#sa-dossier-clean .sa-pa-heading{font-size:23px}.sa-pa-dialog{max-height:95vh}.sa-pa-modal-head{padding:18px}.sa-pa-modal-head h2{font-size:24px}.sa-pa-close:before,.sa-pa-close:after{content:"";position:absolute;left:8px;top:16px;width:18px;height:1px;background:#edcb8d;transform:rotate(45deg)} .sa-pa-close:after{transform:rotate(-45deg)} .sa-pa-modal-content{padding:18px}.sa-pa-modal-foot{padding:14px;flex-wrap:wrap}.sa-pa-foot-left{width:100%}} `; document.head.append(s); } function install(){ var root=document.getElementById('sa-dossier-clean'); var panel=root&&root.querySelector('.panel[data-p="bio"]'); if(!panel)return; var found=panel.querySelector('.sa41-section-text')||panel.querySelector('.sa-section-text'); // Guest DOSSIER may render the dossier as an unclassed text node. if(!found){ var all=panel.querySelectorAll('div,p,span'); for(var j=0;j