Glossary slider: move nav arrows below cards, add 'N / 14' counter

This commit is contained in:
ask 2026-05-17 20:27:48 +05:00
parent e0daa85982
commit 66e831f07f

View File

@ -767,17 +767,17 @@ main {
/* ── GLOSSARY (horizontal slider) ── */ /* ── GLOSSARY (horizontal slider) ── */
.glossary-slider { .glossary-slider {
margin-top: 40px; margin-top: 40px;
position: relative;
} }
.glossary { .glossary {
display: flex; display: flex;
gap: 12px; gap: 12px;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory; scroll-snap-type: x mandatory;
scroll-behavior: smooth; scroll-behavior: smooth;
scroll-padding-left: 8px; padding: 4px 4px 12px;
padding: 4px 4px 16px; /* breathing room + scrollbar gutter */ width: 100%;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
.glossary::-webkit-scrollbar { height: 6px; } .glossary::-webkit-scrollbar { height: 6px; }
@ -790,34 +790,47 @@ main {
background: #fff; background: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 0 0 280px; /* fixed card width */ flex: 0 0 280px;
width: 280px;
scroll-snap-align: start; scroll-snap-align: start;
min-height: 180px; min-height: 200px;
} }
@media (min-width: 640px) { .gloss-item { flex-basis: 320px; min-height: 200px; } } @media (min-width: 640px) { .gloss-item { flex-basis: 320px; width: 320px; min-height: 220px; } }
/* nav buttons */ /* nav controls: arrows + counter, centered below cards */
.gloss-nav { .gloss-nav {
display: none; /* mobile: rely on swipe */ margin-top: 20px;
position: absolute; display: flex;
top: -52px; right: 0; align-items: center;
gap: 8px; justify-content: center;
gap: 16px;
} }
@media (min-width: 640px) { .gloss-nav { display: flex; } }
.gloss-nav-btn { .gloss-nav-btn {
width: 36px; height: 36px; width: 40px; height: 40px;
border: 1px solid var(--border); border: 1px solid var(--border);
background: #fff; background: #fff;
border-radius: 50%; border-radius: 50%;
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: background 150ms ease, opacity 150ms ease; transition: background 150ms ease, opacity 150ms ease, border-color 150ms ease;
color: var(--text-65); color: var(--text-65);
} }
.gloss-nav-btn:hover:not(:disabled) { background: var(--hover); color: var(--text); } .gloss-nav-btn:hover:not(:disabled) {
background: var(--blue);
color: #fff;
border-color: var(--blue);
}
.gloss-nav-btn:disabled { opacity: 0.3; cursor: not-allowed; } .gloss-nav-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.gloss-nav-btn svg { width: 16px; height: 16px; } .gloss-nav-btn svg { width: 18px; height: 18px; }
.gloss-counter {
font-size: 0.8125rem;
color: var(--text-55);
font-variant-numeric: tabular-nums;
min-width: 64px;
text-align: center;
}
.gloss-trigger { .gloss-trigger {
width: 100%; width: 100%;
@ -1435,15 +1448,16 @@ footer {
</p> </p>
</div> </div>
<div class="glossary-slider"> <div class="glossary-slider">
<div class="glossary" id="glossary-list"></div>
<div class="gloss-nav"> <div class="gloss-nav">
<button type="button" id="gloss-prev" class="gloss-nav-btn" aria-label="Назад"> <button type="button" id="gloss-prev" class="gloss-nav-btn" aria-label="Назад">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="10,3 5,8 10,13"/></svg> <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="10,3 5,8 10,13"/></svg>
</button> </button>
<span class="gloss-counter" id="gloss-counter">1 / 14</span>
<button type="button" id="gloss-next" class="gloss-nav-btn" aria-label="Вперёд"> <button type="button" id="gloss-next" class="gloss-nav-btn" aria-label="Вперёд">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6,3 11,8 6,13"/></svg> <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6,3 11,8 6,13"/></svg>
</button> </button>
</div> </div>
<div class="glossary" id="glossary-list"></div>
</div> </div>
</div> </div>
</section> </section>
@ -1730,22 +1744,39 @@ const SCHEDULE = [
// Slider controls // Slider controls
const glossPrev = document.getElementById('gloss-prev'); const glossPrev = document.getElementById('gloss-prev');
const glossNext = document.getElementById('gloss-next'); const glossNext = document.getElementById('gloss-next');
function scrollByCard(direction) { const glossCounter = document.getElementById('gloss-counter');
const totalCards = GLOSSARY.length;
function cardStep() {
const firstCard = glossaryEl.querySelector('.gloss-item'); const firstCard = glossaryEl.querySelector('.gloss-item');
if (!firstCard) return; if (!firstCard) return 0;
const step = firstCard.getBoundingClientRect().width + 12; // card + gap const cs = window.getComputedStyle(glossaryEl);
glossaryEl.scrollBy({ left: direction * step, behavior: 'smooth' }); const gap = parseFloat(cs.columnGap || cs.gap || '12');
return firstCard.getBoundingClientRect().width + gap;
}
function currentIndex() {
const step = cardStep();
return step ? Math.round(glossaryEl.scrollLeft / step) : 0;
}
function scrollToIndex(i) {
const step = cardStep();
const clamped = Math.max(0, Math.min(totalCards - 1, i));
glossaryEl.scrollTo({ left: clamped * step, behavior: 'smooth' });
} }
function updateGlossNav() { function updateGlossNav() {
const maxScroll = glossaryEl.scrollWidth - glossaryEl.clientWidth - 2; const idx = currentIndex();
glossCounter.textContent = `${idx + 1} / ${totalCards}`;
const maxScroll = glossaryEl.scrollWidth - glossaryEl.clientWidth;
glossPrev.disabled = glossaryEl.scrollLeft <= 2; glossPrev.disabled = glossaryEl.scrollLeft <= 2;
glossNext.disabled = glossaryEl.scrollLeft >= maxScroll; glossNext.disabled = glossaryEl.scrollLeft >= maxScroll - 2;
} }
glossPrev.addEventListener('click', () => scrollByCard(-1));
glossNext.addEventListener('click', () => scrollByCard(1)); glossPrev.addEventListener('click', () => scrollToIndex(currentIndex() - 1));
glossNext.addEventListener('click', () => scrollToIndex(currentIndex() + 1));
glossaryEl.addEventListener('scroll', updateGlossNav, { passive: true }); glossaryEl.addEventListener('scroll', updateGlossNav, { passive: true });
window.addEventListener('resize', updateGlossNav); window.addEventListener('resize', updateGlossNav);
updateGlossNav(); // initial state after layout settles
requestAnimationFrame(updateGlossNav);
// Render projects // Render projects
const projectsEl = document.getElementById('projects-grid'); const projectsEl = document.getElementById('projects-grid');