feat: постраничная навигация по главам в teacher-guide (showChapter + hash-роутинг)
This commit is contained in:
+60
-81
@@ -157,7 +157,12 @@
|
||||
.tg-hero-chip svg { width: 13px; height: 13px; }
|
||||
|
||||
/* Chapter */
|
||||
.tg-chapter { margin-bottom: 56px; }
|
||||
.tg-chapter { margin-bottom: 0; display: none; }
|
||||
.tg-chapter.tg-active { display: block; animation: tgFadeIn 0.28s ease; }
|
||||
@keyframes tgFadeIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
|
||||
/* Search mode: reveal all chapters so matches are visible */
|
||||
.tg-content.tg-search-mode .tg-chapter { display: block; margin-bottom: 32px; }
|
||||
.tg-content.tg-search-mode .tg-chapter.search-hidden { display: none; }
|
||||
.tg-chapter-header {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
padding: 28px 0 18px;
|
||||
@@ -1061,56 +1066,54 @@
|
||||
});
|
||||
lucide.createIcons();
|
||||
|
||||
function navChapterClick(chId, btn) {
|
||||
const chapter = btn.closest('.tg-nav-chapter');
|
||||
chapter.classList.toggle('open');
|
||||
scrollToChapter(chId);
|
||||
}
|
||||
|
||||
function scrollToChapter(chId) {
|
||||
const el = document.getElementById(chId);
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
function scrollToSection(secId) {
|
||||
const el = document.getElementById(secId);
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
/* ── Scroll progress ── */
|
||||
/* ── Chapter switching ── */
|
||||
const scrollEl = document.getElementById('tg-scroll');
|
||||
const progBar = document.getElementById('tg-prog-bar');
|
||||
const tgContent = document.querySelector('.tg-content');
|
||||
let _activeChId = null;
|
||||
|
||||
scrollEl.addEventListener('scroll', () => {
|
||||
const pct = scrollEl.scrollTop / (scrollEl.scrollHeight - scrollEl.clientHeight);
|
||||
progBar.style.width = Math.round(Math.min(pct, 1) * 100) + '%';
|
||||
});
|
||||
|
||||
/* ── Scroll spy (sections) ── */
|
||||
const allSections = document.querySelectorAll('.tg-section');
|
||||
const allNavSecs = document.querySelectorAll('.tg-nav-sec-link');
|
||||
|
||||
const sectionObs = new IntersectionObserver((entries) => {
|
||||
entries.forEach(e => {
|
||||
if (!e.isIntersecting) return;
|
||||
const id = e.target.id;
|
||||
allNavSecs.forEach(a => a.classList.toggle('active', a.dataset.sec === id));
|
||||
// Also open+highlight parent chapter
|
||||
const ch = CHAPTERS.find(c => c.sections.includes(id));
|
||||
if (ch) {
|
||||
document.querySelectorAll('.tg-nav-ch-btn').forEach(b => {
|
||||
const parentDiv = b.closest('.tg-nav-chapter');
|
||||
const isThis = parentDiv.dataset.ch === ch.id;
|
||||
b.classList.toggle('active', isThis);
|
||||
if (isThis && !parentDiv.classList.contains('open')) parentDiv.classList.add('open');
|
||||
});
|
||||
}
|
||||
function showChapter(chId, sectionId) {
|
||||
const newEl = document.getElementById(chId);
|
||||
if (!newEl) return;
|
||||
if (_activeChId) {
|
||||
const prev = document.getElementById(_activeChId);
|
||||
if (prev) prev.classList.remove('tg-active');
|
||||
}
|
||||
newEl.classList.add('tg-active');
|
||||
_activeChId = chId;
|
||||
history.replaceState(null, '', '#' + chId);
|
||||
scrollEl.scrollTop = 0;
|
||||
if (sectionId) {
|
||||
requestAnimationFrame(() => {
|
||||
const sec = document.getElementById(sectionId);
|
||||
if (sec) {
|
||||
const top = sec.offsetTop - 80;
|
||||
scrollEl.scrollTo({ top, behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
}
|
||||
// Nav: highlight active chapter, open its sub-list
|
||||
document.querySelectorAll('.tg-nav-chapter').forEach(div => {
|
||||
const isThis = div.dataset.ch === chId;
|
||||
div.querySelector('.tg-nav-ch-btn').classList.toggle('active', isThis);
|
||||
if (isThis && !div.classList.contains('open')) div.classList.add('open');
|
||||
});
|
||||
}, { root: scrollEl, rootMargin: '-15% 0px -70% 0px' });
|
||||
document.querySelectorAll('.tg-nav-sec-link').forEach(a => a.classList.remove('active'));
|
||||
markRead(chId);
|
||||
}
|
||||
|
||||
allSections.forEach(s => sectionObs.observe(s));
|
||||
function scrollToChapter(chId) { showChapter(chId); }
|
||||
function scrollToSection(secId) {
|
||||
const ch = CHAPTERS.find(c => c.sections.includes(secId));
|
||||
if (ch) showChapter(ch.id, secId);
|
||||
}
|
||||
|
||||
/* ── Chapter read tracking ── */
|
||||
function navChapterClick(chId, btn) {
|
||||
btn.closest('.tg-nav-chapter').classList.toggle('open');
|
||||
showChapter(chId);
|
||||
}
|
||||
|
||||
/* ── Read tracking ── */
|
||||
const READ_KEY = 'ls_tg_read';
|
||||
let readChapters = JSON.parse(localStorage.getItem(READ_KEY) || '[]');
|
||||
|
||||
@@ -1118,28 +1121,20 @@
|
||||
if (!readChapters.includes(chId)) {
|
||||
readChapters.push(chId);
|
||||
localStorage.setItem(READ_KEY, JSON.stringify(readChapters));
|
||||
updateReadUI();
|
||||
}
|
||||
updateReadUI();
|
||||
}
|
||||
|
||||
function updateReadUI() {
|
||||
document.querySelectorAll('.tg-nav-chapter').forEach(div => {
|
||||
const isRead = readChapters.includes(div.dataset.ch);
|
||||
div.querySelector('.tg-nav-ch-btn').classList.toggle('read', isRead);
|
||||
div.querySelector('.tg-nav-ch-btn').classList.toggle('read', readChapters.includes(div.dataset.ch));
|
||||
});
|
||||
const n = readChapters.length;
|
||||
document.getElementById('tg-prog-text').textContent = `${n} из ${CHAPTERS.length} глав прочитано`;
|
||||
progBar.style.width = Math.round(n / CHAPTERS.length * 100) + '%';
|
||||
}
|
||||
updateReadUI();
|
||||
|
||||
const chapterObs = new IntersectionObserver((entries) => {
|
||||
entries.forEach(e => {
|
||||
if (e.isIntersecting && e.intersectionRatio >= 0.15) markRead(e.target.id);
|
||||
});
|
||||
}, { root: scrollEl, threshold: 0.15 });
|
||||
|
||||
document.querySelectorAll('.tg-chapter').forEach(c => chapterObs.observe(c));
|
||||
|
||||
/* ── Checklist ── */
|
||||
const CL_KEY = 'ls_tg_checklist';
|
||||
const clState = JSON.parse(localStorage.getItem(CL_KEY) || '{}');
|
||||
@@ -1154,7 +1149,6 @@
|
||||
localStorage.setItem(CL_KEY, JSON.stringify(clState));
|
||||
updateChecklist();
|
||||
});
|
||||
// prevent link from triggering checkbox
|
||||
item.querySelector('.tg-cl-link')?.addEventListener('click', e => e.stopPropagation());
|
||||
});
|
||||
|
||||
@@ -1168,44 +1162,29 @@
|
||||
|
||||
/* ── Accordion ── */
|
||||
function toggleAcc(head) {
|
||||
const item = head.closest('.tg-acc-item');
|
||||
item.classList.toggle('open');
|
||||
head.closest('.tg-acc-item').classList.toggle('open');
|
||||
}
|
||||
|
||||
/* ── Nav search ── */
|
||||
/* ── Search: show all chapters matching query ── */
|
||||
document.getElementById('tg-search').addEventListener('input', function() {
|
||||
const q = this.value.trim().toLowerCase();
|
||||
if (!q) {
|
||||
tgContent.classList.remove('tg-search-mode');
|
||||
document.querySelectorAll('.tg-chapter, .tg-section').forEach(el => el.classList.remove('search-hidden'));
|
||||
return;
|
||||
}
|
||||
tgContent.classList.add('tg-search-mode');
|
||||
document.querySelectorAll('.tg-section').forEach(sec => {
|
||||
const text = sec.textContent.toLowerCase();
|
||||
sec.classList.toggle('search-hidden', !text.includes(q));
|
||||
sec.classList.toggle('search-hidden', !sec.textContent.toLowerCase().includes(q));
|
||||
});
|
||||
document.querySelectorAll('.tg-chapter').forEach(ch => {
|
||||
const visibleSecs = ch.querySelectorAll('.tg-section:not(.search-hidden)');
|
||||
ch.classList.toggle('search-hidden', visibleSecs.length === 0);
|
||||
ch.classList.toggle('search-hidden', ch.querySelectorAll('.tg-section:not(.search-hidden)').length === 0);
|
||||
});
|
||||
});
|
||||
|
||||
/* ── Card entrance animation ── */
|
||||
const chapObs = new IntersectionObserver((entries) => {
|
||||
entries.forEach(e => {
|
||||
if (e.isIntersecting) {
|
||||
e.target.style.opacity = '1';
|
||||
e.target.style.transform = 'translateY(0)';
|
||||
chapObs.unobserve(e.target);
|
||||
}
|
||||
});
|
||||
}, { root: scrollEl, threshold: 0.05 });
|
||||
|
||||
document.querySelectorAll('.tg-chapter').forEach((ch, i) => {
|
||||
ch.style.opacity = '0';
|
||||
ch.style.transform = 'translateY(20px)';
|
||||
ch.style.transition = `opacity 0.4s ease ${i * 0.03}s, transform 0.4s ease ${i * 0.03}s`;
|
||||
chapObs.observe(ch);
|
||||
});
|
||||
/* ── Init from hash or default ch-1 ── */
|
||||
const initHash = location.hash.replace('#', '');
|
||||
showChapter(CHAPTERS.find(c => c.id === initHash) ? initHash : 'ch-1');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user