mirror of
https://github.com/EDeev/deev.space.git
synced 2026-10-08 04:59:31 +03:00
Перенесено всё, что накопилось на сервере с декабря 2025: - анонимный посетитель по подписанной cookie (main/middleware.py), лайки и дизлайки без регистрации с антинакруткой, уникальные просмотры статей; - карточка-паспорт, стек в одну строку, блок «В цифрах»; - миграции 0005–0008, команды load_initial_data и populate_demo, robots.txt; - обновлённые шаблоны, стили и скрипты. Безопасность и порядок: - все секреты только из окружения, значения по умолчанию пустые; - DEBUG по умолчанию выключен, без DJANGO_SECRET_KEY запуск в бою невозможен; - ALLOWED_HOSTS, путь к базе и SSL-редирект настраиваются переменными; - убрана статика админки Django (результат collectstatic) и media из git; - добавлены .gitignore и .gitattributes (переводы строк — LF).
54 lines
2.3 KiB
JavaScript
54 lines
2.3 KiB
JavaScript
/* Стек в одну строку: что не влезает по ширине — прячется за «+N». */
|
|
(function () {
|
|
function fit(box) {
|
|
const tags = Array.from(box.querySelectorAll('.skill-tag'));
|
|
const btn = box.querySelector('.skills-more-btn');
|
|
tags.forEach(t => t.classList.remove('is-off'));
|
|
if (box.classList.contains('is-open')) {
|
|
btn.hidden = false;
|
|
btn.textContent = 'свернуть';
|
|
return;
|
|
}
|
|
btn.hidden = true;
|
|
const gap = parseFloat(getComputedStyle(box).columnGap) || 0;
|
|
const avail = box.clientWidth;
|
|
const widths = tags.map(t => t.getBoundingClientRect().width);
|
|
const total = widths.reduce((a, w) => a + w, 0) + gap * Math.max(tags.length - 1, 0);
|
|
if (total <= avail + 0.5) return;
|
|
|
|
btn.hidden = false;
|
|
let used = 0, shown = 0;
|
|
for (let i = 0; i < tags.length; i++) {
|
|
btn.textContent = '+' + (tags.length - i - 1);
|
|
const need = used + (i ? gap : 0) + widths[i] + gap + btn.getBoundingClientRect().width;
|
|
if (need > avail + 0.5) break;
|
|
used += (i ? gap : 0) + widths[i];
|
|
shown = i + 1;
|
|
}
|
|
shown = Math.max(shown, 1);
|
|
tags.forEach((t, i) => t.classList.toggle('is-off', i >= shown));
|
|
btn.textContent = '+' + (tags.length - shown);
|
|
}
|
|
|
|
function init() {
|
|
document.querySelectorAll('.skills-rows').forEach(root => {
|
|
if (root.dataset.ready) return;
|
|
root.dataset.ready = '1';
|
|
const boxes = Array.from(root.querySelectorAll('.skills-row-tags'));
|
|
const refit = () => boxes.forEach(fit);
|
|
root.addEventListener('click', e => {
|
|
const b = e.target.closest('.skills-more-btn');
|
|
if (!b) return;
|
|
b.parentElement.classList.toggle('is-open');
|
|
fit(b.parentElement);
|
|
});
|
|
if ('ResizeObserver' in window) new ResizeObserver(refit).observe(root);
|
|
else window.addEventListener('resize', refit);
|
|
if (document.fonts && document.fonts.ready) document.fonts.ready.then(refit);
|
|
refit();
|
|
});
|
|
}
|
|
|
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
else init();
|
|
})();
|