[html]<!-- ПРИМЕРОЧНАЯ ДЛЯ ПОСТА. Вставить целиком в сообщение на форуме.
Работает внутри песочницы мода html-в-постах: сама с форумом не
разговаривает, а просит об этом страницу форума через мост в solacium.js. -->
<div id="fitRoot">
<div class="fit-head"><b>примерочная</b><span id="fitWho">…</span></div>
<div id="fitBody">загружаю…</div>
</div>
<style>
#fitRoot{font:13px/1.5 Arial,sans-serif;color:#454545;text-align:left;max-width:880px;margin:0 auto}
#fitRoot *{box-sizing:border-box}
#fitRoot .fit-head{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}
#fitRoot .fit-head b{font-size:17px;text-transform:lowercase}
#fitRoot #fitWho{font-size:11px;color:#8b8080;margin-left:auto}
#fitRoot .fit-grid{display:grid;grid-template-columns:250px 1fr;gap:12px;align-items:start}
@media(max-width:700px){#fitRoot .fit-grid{grid-template-columns:1fr}}
#fitRoot .fit-card{background:#f4f4f4;border:1px solid #DFDFDF;border-radius:12px;padding:12px}
#fitRoot .fit-sect{font-size:11px;color:#8b8080;text-transform:lowercase;margin:14px 0 6px;
display:flex;align-items:center;gap:8px}
#fitRoot .fit-sect:first-child{margin-top:0}
#fitRoot .fit-sect::after{content:"";flex:1;height:1px;background:#DFDFDF}
#fitRoot .fit-tools{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px}
#fitRoot .fit-tools button{background:#fff;border:1px solid #DFDFDF;border-radius:20px;color:#C06E74;
height:26px;min-width:26px;padding:0 9px;cursor:pointer;font:700 12px Arial}
#fitRoot .fit-colors{display:flex;gap:5px;margin:6px 0}
#fitRoot .fit-colors button{width:20px;height:20px;border-radius:50%;border:2px solid transparent;cursor:pointer}
#fitRoot .fit-sheet{background:#fff;border:1px solid #DFDFDF;border-radius:8px;padding:14px;
display:flex;justify-content:center}
#fitRoot #fitEd{width:180px;min-height:110px;padding:6px;zoom:1.5;font:10px/130% Arial;color:#454545;
text-align:justify;outline:none;box-shadow:0 0 0 1px rgba(0,0,0,.08);border-radius:4px}
#fitRoot #fitEd:focus{box-shadow:0 0 0 2px #C06E74}
#fitRoot #fitEd a{color:#C06E74;text-decoration:none}
#fitRoot label{display:block;font-size:11px;color:#8b8080;margin:8px 0 4px}
#fitRoot input[type=text]{width:100%;border:1px solid #DFDFDF;border-radius:8px;padding:6px 9px;
font:13px Arial;background:#fff;color:#454545}
#fitRoot .fit-two{display:grid;grid-template-columns:1fr 1fr;gap:8px}
#fitRoot .fit-b{background:#ECDFDD;color:#C06E74;border:0;border-radius:20px;padding:7px 16px;
cursor:pointer;font:600 13px Arial;margin-top:8px;text-transform:lowercase}
#fitRoot .fit-b[disabled]{opacity:.45;cursor:not-allowed}
#fitRoot .fit-note{font-size:11px;color:#8b8080;margin-top:8px;line-height:1.5}
#fitRoot .fit-ok{color:#4a8060}
#fitRoot .fit-bad{color:#b04a52}
#fitRoot .fit-icn{width:32px;height:32px;border-radius:50%;background:#ececec center/cover no-repeat;
margin:-20px auto 0;position:relative;z-index:2;box-shadow:0 1px 4px rgba(0,0,0,.18)}
#fitRoot .fit-ava{max-width:180px;display:block;margin:6px auto 0;border-radius:3px}
#fitRoot .fit-nums{display:flex;gap:8px;justify-content:center;margin-top:8px;font-size:9px;color:#8b8080}
#fitRoot .fit-nums span{display:flex;flex-direction:column;align-items:center;gap:1px}
#fitRoot .fit-nums b{font-size:11px;color:#454545;font-weight:600}
#fitRoot .fit-pv{width:220px;background:#ececec;border:1px solid #DFDFDF;border-radius:12px;
padding:4px 4px 0;margin:22px auto 0}
#fitRoot .fit-hdr{height:55px;border-radius:10px 10px 0 0;background:#c9c2b2;display:grid;place-items:center;
font:600 10px Arial;color:rgba(255,255,255,.9)}
#fitRoot .fit-inner{background:#f4f4f4;border-radius:10px;margin-top:4px;padding:10px;text-align:center}
#fitRoot .fit-name{font:700 15px Arial;text-transform:lowercase}
#fitRoot .fit-lz{width:180px;margin:6px auto 0;font-size:10px;text-align:justify;line-height:130%}
#fitRoot .fit-plate{margin-top:6px;border-radius:0 0 10px 10px;padding:7px 10px 8px;text-align:center;
color:#f5f5f5;line-height:110%;background:#c9c2b2}
#fitRoot .fit-plate b{display:block;font-weight:400;font-size:11px}
#fitRoot .fit-plate i{display:block;font-style:normal;font-size:9px}
#fitRoot .fit-cat{display:grid;grid-template-columns:repeat(auto-fill,minmax(112px,1fr));gap:6px;margin-top:4px}
#fitRoot .fit-item{border:1px solid #DFDFDF;border-radius:8px;padding:4px;background:#fff;cursor:pointer;
display:grid;gap:4px;text-align:left;font:inherit;color:inherit}
#fitRoot .fit-item[aria-pressed="true"]{border-color:#C06E74;box-shadow:0 0 0 2px #f9e7e8}
#fitRoot .fit-item .pic{height:26px;border-radius:5px;background:#ececec center/cover no-repeat}
#fitRoot .fit-item .cap{font-size:10px;color:#8b8080;display:flex;justify-content:space-between;gap:4px}
#fitRoot .fit-group{font-size:10px;color:#8b8080;margin:8px 0 2px;text-transform:lowercase}
#fitRoot .fit-vars{display:flex;gap:6px;align-items:center;margin-top:6px}
#fitRoot .fit-vars button{width:22px;height:22px;border-radius:50%;border:2px solid transparent;cursor:pointer}
#fitRoot .fit-vars button[aria-pressed="true"]{border-color:#454545}
#fitRoot .fit-count{font:11px monospace;color:#8b8080;margin-top:6px}
#fitRoot .fit-count.over{color:#b04a52}
</style>
<script>
(function () {
'use strict';
var BUILD = '06.09 11:32';
/* ---------- мост: мы в песочнице, поэтому просим страницу форума ---------- */
function ask(action, payload) {
return new Promise(function (resolve, reject) {
var id = String(Math.random()).slice(2);
var timer = setTimeout(function () { off(); reject(new Error('форум не ответил')); }, 20000);
function on(e) {
var d = e.data;
if (!d || d.sol !== 'answer' || d.id !== id) return;
clearTimeout(timer); off();
d.ok ? resolve(d.data) : reject(new Error(d.data));
}
function off() { window.removeEventListener('message', on); }
window.addEventListener('message', on);
parent.postMessage({ sol: 'ask', id: id, action: action, payload: payload }, '*');
});
}
var body = document.getElementById('fitBody');
var who = document.getElementById('fitWho');
var state = { lz: '', p1: '', p2: '', hdr: '', icn: '', ank: '', login: '',
touched: false, variant: 'A', avatar: '', posts: '', money: '' };
var CAT = [];
var MAX = 897;
function esc(t) {
return String(t == null ? '' : t).replace(/&/g, '&').replace(/</g, '<')
.replace(/>/g, '>').replace(/"/g, '"');
}
/* ---------- сборка кода лз ---------- */
var ALLOW = { B:'b', STRONG:'b', I:'i', EM:'i', U:'u', S:'s', STRIKE:'s', DEL:'s',
SMALL:'small', BIG:'big', SUB:'sub', SUP:'sup', A:'a', SPAN:'span', FONT:'span' };
var STYLE_OK = ['color','font-size','font-family','font-weight','font-style','text-decoration'];
function clean(node) {
var out = '';
for (var k = 0; k < node.childNodes.length; k++) {
var n = node.childNodes[k];
if (n.nodeType === 3) { out += esc(n.nodeValue); continue; }
if (n.nodeType !== 1) continue;
if (n.tagName === 'BR') { out += '<br>'; continue; }
var inner = clean(n);
if (n.tagName === 'DIV' || n.tagName === 'P') {
out += (out && !/<br>$/.test(out) ? '<br>' : '') + inner; continue;
}
var tag = ALLOW[n.tagName];
if (!tag) { out += inner; continue; }
if (!inner) continue;
if (tag === 'a') {
var href = n.getAttribute('href') || '';
out += /^https?:\/\//i.test(href) ? '<a href="' + esc(href) + '">' + inner + '</a>' : inner;
continue;
}
if (tag === 'span') {
var st = [];
for (var i = 0; i < n.style.length; i++) {
var pr = n.style[i];
if (STYLE_OK.indexOf(pr) !== -1) st.push(pr + ':' + n.style.getPropertyValue(pr));
}
out += st.length ? '<span style="' + st.join(';') + '">' + inner + '</span>' : inner;
continue;
}
out += '<' + tag + '>' + inner + '</' + tag + '>';
}
return out;
}
function collapse(node) {
[].forEach.call(node.querySelectorAll('span'), function (sp) {
while (sp.childNodes.length === 1 && sp.firstChild.nodeType === 1 &&
sp.firstChild.tagName === 'SPAN') {
var kid = sp.firstChild;
for (var i = 0; i < kid.style.length; i++) {
sp.style.setProperty(kid.style[i], kid.style.getPropertyValue(kid.style[i]));
}
while (kid.firstChild) sp.insertBefore(kid.firstChild, kid);
sp.removeChild(kid);
}
});
return node;
}
function lzCode() { return '<div><small>' + state.lz + '</small></div>'; }
/* ---------- разбор того, что уже стоит в профиле ---------- */
function parseProfile(v) {
var f2 = v.fld2 || '';
var m = f2.match(/<a[^>]*class="ank"[^>]*>([\s\S]*?)<\/a>/i);
state.ank = m ? m[1].replace(/<[^>]*>/g, '').trim() : '';
state.lz = f2.replace(/<a[^>]*class="ank"[^>]*>[\s\S]*?<\/a>/i, '')
.replace(/^\s*<div>\s*<small>/i, '').replace(/<\/small>\s*<\/div>\s*$/i, '').trim();
var f5 = v.fld5 || '';
state.p1 = (f5.match(/<b>([\s\S]*?)<\/b>/i) || [, ''])[1].replace(/<[^>]*>/g, '').trim();
state.p2 = (f5.match(/<i>([\s\S]*?)<\/i>/i) || [, ''])[1].replace(/<[^>]*>/g, '').trim();
state.icn = ((v.fld3 || '').match(/class="icn\s+([^"]*)"/i) || [, ''])[1].trim();
state.hdr = ((v.fld4 || '').match(/class="hdr\s+([^"]*)"/i) || [, ''])[1].trim();
}
/* ---------- отрисовка ---------- */
function render() {
body.innerHTML =
'<div class="fit-grid">' +
'<div class="fit-card">' +
'<div class="fit-pv">' +
'<div class="fit-icn" id="fitIcn"></div>' +
'<div class="fit-hdr" id="fitHdr">хедер</div>' +
'<div class="fit-inner">' +
'<div class="fit-name" id="fitName">' + esc(state.login) + '</div>' +
'<img class="fit-ava" id="fitAva" alt="">' +
'<div class="fit-lz" id="fitPv"></div>' +
'<div class="fit-nums"><span>сообщений<b id="fitPosts">—</b></span>' +
'<span>деньги<b id="fitMoney">—</b></span></div>' +
'</div>' +
'<div class="fit-plate" id="fitPlate"></div>' +
'</div>' +
'<div class="fit-count" id="fitCount"></div>' +
'<button class="fit-b" id="fitSend">подать заявку</button>' +
'<div class="fit-note" id="fitMsg"></div>' +
'</div>' +
'<div class="fit-card">' +
'<div class="fit-sect">лз — личное звание</div>' +
'<div class="fit-tools" id="fitTools">' +
'<button data-cmd="bold">B</button><button data-cmd="italic">I</button>' +
'<button data-cmd="underline">U</button><button data-cmd="strikeThrough">S</button>' +
'<button data-wrap="small">small</button><button data-wrap="big">big</button>' +
'<button data-clear="1">убрать формат</button>' +
'</div>' +
'<div class="fit-colors" id="fitColors">' +
['#454545','#C06E74','#8b8080','#7a6a9e','#4a8060','#a4652f'].map(function (c) {
return '<button data-color="' + c + '" style="background:' + c + '"></button>';
}).join('') +
'</div>' +
'<div class="fit-sheet"><div id="fitEd" contenteditable="true" spellcheck="false"></div></div>' +
'<div class="fit-note">ширина листа настоящая — 180px, увеличение только для глаз.</div>' +
'<div class="fit-sect">аватар — только для примерки</div>' +
'<input type="text" id="fitAvaIn" placeholder="https://ссылка на картинку">' +
'<div class="fit-note">в заявку не уходит: аватар вы меняете сами в профиле, ' +
'здесь он нужен, чтобы посмотреть, как всё вместе смотрится.</div>' +
'<div class="fit-sect">плашка — две строки</div>' +
'<div class="fit-two">' +
'<div><label>первая, крупная</label><input type="text" id="fitP1" maxlength="30"></div>' +
'<div><label>вторая, мелкая</label><input type="text" id="fitP2" maxlength="30"></div>' +
'</div>' +
'<div class="fit-sect">хедер</div>' +
'<div id="fitHdrCat">' + catalogHtml('hdr') + '</div>' +
'<div class="fit-vars" id="fitVars"></div>' +
'<div class="fit-sect">иконка</div>' +
'<div id="fitIcnCat">' + catalogHtml('icn') + '</div>' +
'</div>' +
'</div>';
fill();
bind();
refresh();
}
function catalogHtml(type) {
var items = CAT.filter(function (i) { return i.type === type; });
if (!items.length) return '<div class="fit-note">каталог пуст — админ ещё не добавил вещи.</div>';
var out = '', group = null;
items.forEach(function (i) {
if (i.group !== group) { group = i.group; out += '<div class="fit-group">' + esc(group) + '</div>'; }
out += '<div class="fit-cat"><button class="fit-item" data-pick="' + type + '" data-cls="' + esc(i.cls) + '" ' +
'aria-pressed="' + (state[type === 'hdr' ? 'hdr' : 'icn'] === i.cls) + '">' +
'<span class="pic" style="background-image:url(' + esc(i.img) + ')"></span>' +
'<span class="cap"><b>' + esc(i.name) + '</b><i>' + i.price + '</i></span></button></div>';
});
return out;
}
function item(type, cls) {
return CAT.filter(function (i) { return i.type === type && i.cls === cls; })[0] || null;
}
function drawVars() {
var box = document.getElementById('fitVars');
if (!box) return;
var h = item('hdr', state.hdr);
if (!h) { box.innerHTML = ''; return; }
box.innerHTML = '<span class="fit-note" style="margin:0">цвет плашки:</span>' +
['A', 'B'].map(function (v) {
var c = v === 'A' ? h.plateA : h.plateB;
return '<button data-var="' + v + '" style="background:' + esc(c) + '" ' +
'aria-pressed="' + (state.variant === v) + '"></button>';
}).join('');
}
function fill() {
var ed = document.getElementById('fitEd');
if (ed) ed.innerHTML = state.lz;
[['fitP1','p1'], ['fitP2','p2'], ['fitAvaIn','avatar']]
.forEach(function (p) {
var el = document.getElementById(p[0]);
if (el) el.value = state[p[1]];
});
var nm = document.getElementById('fitName');
if (nm) nm.textContent = state.login;
}
function refresh() {
document.getElementById('fitPv').innerHTML = state.lz;
document.getElementById('fitPlate').innerHTML =
'<b>' + esc(state.p1) + '</b>' + (state.p2 ? '<i>' + esc(state.p2) + '</i>' : '');
var hd = item('hdr', state.hdr), box = document.getElementById('fitHdr');
if (hd) {
box.style.background = 'url(' + hd.img + ') center/cover no-repeat';
box.textContent = '';
var c = state.variant === 'B' ? hd.plateB : hd.plateA;
document.getElementById('fitPlate').style.background = c;
} else {
box.style.background = '#c9c2b2';
box.textContent = 'хедер не выбран';
}
var ic = item('icn', state.icn), ib = document.getElementById('fitIcn');
if (ib) ib.style.backgroundImage = ic ? 'url(' + ic.img + ')' : 'none';
var av = document.getElementById('fitAva');
if (av) { if (state.avatar) { av.src = state.avatar; av.style.display = ''; }
else av.style.display = 'none'; }
var pn = document.getElementById('fitPosts'); if (pn) pn.textContent = state.posts || '—';
var mn = document.getElementById('fitMoney'); if (mn) mn.textContent = state.money || '—';
drawVars();
var len = lzCode().length;
var c = document.getElementById('fitCount');
c.className = 'fit-count' + (len > MAX ? ' over' : '');
c.textContent = 'код лз: ' + len + ' из ' + MAX + ' знаков';
document.getElementById('fitSend').disabled = len > MAX || !state.lz;
grow();
}
/* мод задаёт высоту айфрейма сам, но не всегда успевает за нашим ростом */
function grow() {
var h = document.documentElement.scrollHeight;
try { parent.postMessage({ sol: 'height', height: h }, '*'); } catch (e) {}
if (window.frameElement) { try { window.frameElement.style.height = (h + 20) + 'px'; } catch (e) {} }
}
function wrapSel(open, close) {
var sel = window.getSelection();
if (!sel.rangeCount || sel.isCollapsed) return;
var box = document.createElement('div');
box.appendChild(sel.getRangeAt(0).cloneContents());
document.execCommand('insertHTML', false, '<span data-sel="1">' + open + box.innerHTML + close + '</span>');
var wsp = document.getElementById('fitEd').querySelector('span[data-sel]');
if (wsp) {
wsp.removeAttribute('data-sel');
var r = document.createRange();
r.selectNodeContents(wsp);
sel.removeAllRanges(); sel.addRange(r);
}
}
function bind() {
try { document.execCommand('styleWithCSS', false, false); } catch (e) {}
var ed = document.getElementById('fitEd');
ed.addEventListener('input', function () {
state.touched = true;
state.lz = clean(collapse(ed.cloneNode(true)));
refresh();
});
ed.addEventListener('paste', function (e) {
e.preventDefault();
document.execCommand('insertText', false,
(e.clipboardData || window.clipboardData).getData('text/plain'));
});
if (body.dataset.bound) return;
body.dataset.bound = '1';
body.addEventListener('mousedown', function (e) {
if (e.target.closest('#fitTools, #fitColors')) e.preventDefault();
});
body.addEventListener('click', function (e) {
var b = e.target.closest('button'); if (!b) return;
if (b.id === 'fitSend') return send(b);
if (b.dataset.pick) {
state[b.dataset.pick === 'hdr' ? 'hdr' : 'icn'] = b.dataset.cls;
state.touched = true;
document.getElementById('fitHdrCat').innerHTML = catalogHtml('hdr');
document.getElementById('fitIcnCat').innerHTML = catalogHtml('icn');
return refresh();
}
if (b.dataset.var) { state.variant = b.dataset.var; state.touched = true; return refresh(); }
if (b.dataset.cmd) document.execCommand(b.dataset.cmd);
else if (b.dataset.wrap) wrapSel('<' + b.dataset.wrap + '>', '</' + b.dataset.wrap + '>');
else if (b.dataset.clear) { document.execCommand('removeFormat'); document.execCommand('unlink'); }
else if (b.dataset.color) wrapSel('<span style="color:' + b.dataset.color + '">', '</span>');
else return;
state.touched = true;
state.lz = clean(collapse(document.getElementById('fitEd').cloneNode(true)));
refresh();
});
body.addEventListener('input', function (e) {
var id = e.target.id;
if (id === 'fitP1') state.p1 = e.target.value;
else if (id === 'fitP2') state.p2 = e.target.value;
else if (id === 'fitAvaIn') state.avatar = e.target.value.trim();
else if (id === 'fitHdrIn') state.hdr = e.target.value.trim();
else if (id === 'fitIcnIn') state.icn = e.target.value.trim();
else return;
state.touched = true;
refresh();
});
}
async function send(btn) {
var msg = document.getElementById('fitMsg');
btn.disabled = true;
msg.className = 'fit-note';
msg.textContent = 'отправляю заявку…';
try {
var res = await ask('submit', {
header: state.hdr, icon: state.icn, ank: state.ank, variant: state.variant,
plate1: state.p1, plate2: state.p2, lz: lzCode()
});
msg.className = 'fit-note fit-ok';
msg.textContent = 'заявка опубликована, пост ' + (res && res.post ? '№' + res.post : '') +
' в теме ' + (res && res.topic ? res.topic : '') + '.';
} catch (e) {
msg.className = 'fit-note fit-bad';
msg.textContent = 'не отправилось. ' + e.message;
btn.disabled = false;
}
grow();
}
/* ---------- старт: рисуем сразу, профиль подтягиваем следом ---------- */
who.textContent = 'сборка ' + BUILD;
render();
/* сверяем версии по обе стороны моста: расхождение здесь — самая
частая причина странных ошибок при отправке */
ask('catalog').then(function (r) {
CAT = (r && r.items) || [];
var hc = document.getElementById('fitHdrCat'), ic = document.getElementById('fitIcnCat');
if (hc) hc.innerHTML = catalogHtml('hdr');
if (ic) ic.innerHTML = catalogHtml('icn');
refresh();
}).catch(function () {});
ask('ping').then(function (r) {
who.textContent = 'примерочная ' + BUILD + ' · ядро ' + (r && r.build ? r.build : '?');
}).catch(function () {
who.textContent = 'примерочная ' + BUILD + ' · ядро не отвечает';
});
ask('me').then(function (v) {
state.login = v.login || '';
state.posts = v.posts || '';
state.money = v.fld1 || '';
if (!state.touched) {
parseProfile(v);
if (v.avatar) state.avatar = v.avatar;
fill(); refresh();
}
who.textContent = 'вы: ' + state.login + ' · сборка ' + BUILD;
var msg = document.getElementById('fitMsg');
if (msg) msg.textContent = 'нынешнее оформление подтянуто из профиля.';
}).catch(function (e) {
var msg = document.getElementById('fitMsg');
if (msg) {
msg.className = 'fit-note fit-bad';
msg.textContent = 'форум не отвечает (' + e.message + '). ' +
'проверьте, что solacium.js подключён в html-низе — в нём живёт мост.';
}
});
setInterval(grow, 1000);
})();
</script>
[/html]