[html]<!-- включено по умолчанию -->
<div id="nota-lottery" class="nota-lottery" data-used="1, 5, 6, 7, 8, 11, 10, 13, 16, 18, 21, 25, 26, 30, 38, 41, 49"></div>
<script>
(() => {
const ROOT_ID = 'nota-lottery';
const root = document.getElementById(ROOT_ID);
if (!root) return;
// ==== �� Настройки (меняй под себя) ====
const TICKET_COUNT = 75; // общее количество билетов
const COLUMNS = 15; // количество колонок в ряду
const GRID_WIDTH = '970px'; // ЖЁСТКАЯ ширина сетки (пример: '900px', '100%', 'min(100%, 920px)')
// Полные URL изображений (можно указывать с любых хостингов)
const variantImages = [
'https://notafiles.ru/images/lottery/1.gif',
'https://notafiles.ru/images/lottery/1.gif',
'https://notafiles.ru/images/lottery/1.gif',
'https://notafiles.ru/images/lottery/1.gif',
'https://notafiles.ru/images/lottery/1.gif'
];
const usedImage = 'https://notafiles.ru/images/lottery/x.gif';
const T = { gridCls:'nl-grid', ticketCls:'nl-ticket', numCls:'nl-num', usedCls:'used' };
// ==== Стили ====
(function injectStyles(){
if (document.getElementById('nl-core-styles')) return;
const css = `
#${ROOT_ID}.{grid}{
display:grid;
grid-template-columns:repeat(${COLUMNS},minmax(0,1fr));
gap:8px;
width:100%;
max-width:${GRID_WIDTH};
}
#${ROOT_ID} .${T.ticketCls}{
display:flex;
flex-direction:column;
align-items:center;
gap:4px;
}
#${ROOT_ID} .${T.ticketCls} img{
width:100%;
height:auto;
display:block;
user-select:none;
-webkit-user-drag:none;
opacity:0.7;
}
#${ROOT_ID} .${T.numCls}{
display:block;
font-size:12px;
line-height:1;
text-align:center;
}
#${ROOT_ID} .${T.ticketCls}.${T.usedCls} img{
filter:grayscale(100%);
opacity:.5;
}
`.replace('{grid}', T.gridCls);
const st = document.createElement('style');
st.id = 'nl-core-styles';
st.textContent = css;
document.head.appendChild(st);
})();
// ==== Утилиты ====
const parseUsed = (str) => new Set(
(str||'').split(/[,\s]+/).map(s=>s.trim()).filter(Boolean)
.map(x=>parseInt(x,10)).filter(n=>Number.isInteger(n)&&n>=1&&n<=TICKET_COUNT)
);
const serializeUsed = (set) => Array.from(set).sort((a,b)=>a-b).join(', ');
// Детерминированный выбор варианта по номеру
const pickVariantIndex = (n) => {
const len = Math.max(variantImages.length, 1);
let x = (n * 9301 + 49297) % 233280;
return x % len;
};
// ==== Построение ====
root.classList.add(T.gridCls);
const build = () => {
root.innerHTML = '';
const usedSet = parseUsed(root.getAttribute('data-used')||'');
const frag = document.createDocumentFragment();
for (let i = 1; i <= TICKET_COUNT; i++){
const wrap = document.createElement('div');
wrap.className = T.ticketCls;
wrap.dataset.id = String(i);
const img = document.createElement('img');
if (usedSet.has(i)){
img.src = usedImage;
wrap.classList.add(T.usedCls);
} else {
img.src = variantImages[pickVariantIndex(i)];
}
img.alt = `#${i}`;
const num = document.createElement('span');
num.className = T.numCls;
num.textContent = `#${i}`;
wrap.appendChild(img);
wrap.appendChild(num);
frag.appendChild(wrap);
}
root.appendChild(frag);
};
build();
// ==== API ====
const API = {
getUsed: ()=>Array.from(parseUsed(root.getAttribute('data-used')||'')).sort((a,b)=>a-b),
setUsed: (val)=>{
let set;
if (val instanceof Set) set=val;
else if (Array.isArray(val)) set=new Set(val.map(n=>parseInt(n,10)));
else set=parseUsed(String(val||''));
root.setAttribute('data-used', serializeUsed(set));
root.querySelectorAll('.'+T.ticketCls).forEach(div=>{
const id=parseInt(div.dataset.id,10);
const img=div.querySelector('img');
if(!img) return;
if(set.has(id)){ div.classList.add(T.usedCls); img.src = usedImage; }
else{ div.classList.remove(T.usedCls); img.src = variantImages[pickVariantIndex(id)]; }
});
return set;
},
toggle:(id)=>{
const set=new Set(API.getUsed());
id=parseInt(id,10);
if(!Number.isInteger(id)||id<1||id>TICKET_COUNT) return set;
if(set.has(id)) set.delete(id); else set.add(id);
return API.setUsed(set);
},
serialize:()=>serializeUsed(new Set(API.getUsed()))
};
window.NL = API;
})();
</script>[/html]