/* Общие функции: форматирование, карточки, расписание, меню. */
(function () {
const SK = window.SK;
SK.ch = id => SK.channels.find(c => c.id === id);
SK.vid = id => SK.videos.find(v => v.id === id);
SK.ratioClass = r => (r === "1:1" ? "r11" : r === "9:16" ? "r916" : "");
SK.esc = s => String(s).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
SK.dur = s => {
s = Math.round(s);
const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), x = s % 60;
return (h ? h + ":" + String(m).padStart(2, "0") : m) + ":" + String(x).padStart(2, "0");
};
SK.plural = (n, one, few, many) => {
const a = Math.abs(n) % 100, b = a % 10;
if (a > 10 && a < 20) return many;
if (b > 1 && b < 5) return few;
if (b === 1) return one;
return many;
};
SK.views = n => {
const word = SK.plural(n, "просмотр", "просмотра", "просмотров");
if (n >= 1000) return (n / 1000).toFixed(n >= 10000 ? 0 : 1).replace(".", ",") + " тыс. просмотров";
return n + " " + word;
};
SK.subs = n => n.toLocaleString("ru-RU") + " " + SK.plural(n, "подписчик", "подписчика", "подписчиков");
const MONTHS = ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"];
SK.date = iso => {
const d = new Date(iso + "T12:00:00");
const days = Math.round((Date.now() - d) / 864e5);
if (days <= 0) return "сегодня";
if (days === 1) return "вчера";
if (days < 7) return days + " " + SK.plural(days, "день", "дня", "дней") + " назад";
return d.getDate() + " " + MONTHS[d.getMonth()];
};
SK.card = v => {
const c = SK.ch(v.ch);
return `
${SK.dur(v.dur)}${v.ratio !== "16:9" ? `${v.ratio}` : ""}${SK.esc(v.title)}
`;
};
/* ---- эфирная сетка: детерминированная, одинаковая у всех зрителей ---- */
function rng(seed) {
let s = 0;
for (const ch of seed) s = (s * 31 + ch.charCodeAt(0)) >>> 0;
return () => {
s = (s + 0x6d2b79f5) >>> 0;
let t = s;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// окна вещания по каналам, в минутах от полуночи
function windows(chId, dow) {
switch (chId) {
case "tihaya-smena": return dow === 1 ? [[13 * 60, 1440]] : [[0, 1440]];
case "fosfor": return [[0, 5 * 60], [10 * 60, 1440]];
case "erohin": return dow === 0 ? [[10 * 60, 22 * 60]] : [[8 * 60, 12 * 60 + 30], [16 * 60, 23 * 60]];
case "nochnoy-zond": return [[0, 7 * 60], [21 * 60, 1440]];
case "kvadrat": return dow === 4 ? [[10 * 60, 1440]] : [[11 * 60, 15 * 60], [18 * 60, 23 * 60]];
}
return [];
}
SK.dayKey = d => d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate();
SK.schedule = (chId, day) => {
const r = rng(chId + SK.dayKey(day));
const titles = SK.programs[chId];
const lens = [30, 45, 60, 60, 90, 120];
const out = [];
let prev = -1;
for (const [a, b] of windows(chId, day.getDay())) {
let t = a;
while (t < b) {
let len = lens[Math.floor(r() * lens.length)];
if (t + len > b) len = b - t;
let k = Math.floor(r() * titles.length);
if (k === prev) k = (k + 1) % titles.length;
prev = k;
out.push({ start: t, end: t + len, title: titles[k] });
t += len;
}
}
return out;
};
SK.hm = m => String(Math.floor(m / 60) % 24).padStart(2, "0") + ":" + String(m % 60).padStart(2, "0");
SK.nowMin = () => { const d = new Date(); return d.getHours() * 60 + d.getMinutes() + d.getSeconds() / 60; };
SK.onAir = chId => {
const m = SK.nowMin();
return SK.schedule(chId, new Date()).find(p => p.start <= m && p.end > m) || null;
};
/* Шкала времени. opts: { day, from, to, compact } */
SK.timeline = (el, opts) => {
const day = opts.day || new Date();
const from = opts.from ?? 0, to = opts.to ?? 1440;
const isToday = SK.dayKey(day) === SK.dayKey(new Date());
const hourPx = () => parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--hour")) || 168;
const px = m => ((m - from) / 60) * hourPx();
function render() {
const now = SK.nowMin();
let ruler = "";
for (let m = from; m <= to; m += 30) {
ruler += m % 60 === 0
? `${SK.hm(m)}`
: ``;
}
const tracks = SK.channels.map(c => {
const progs = SK.schedule(c.id, day).filter(p => p.end > from && p.start < to);
let html = "", cursor = from;
for (const p of progs) {
if (p.start > cursor) html += clip({ start: cursor, end: p.start, title: "Нет эфира", off: true }, c);
html += clip(p, c);
cursor = p.end;
}
if (cursor < to) html += clip({ start: cursor, end: to, title: "Нет эфира", off: true }, c);
return `