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