1/* ============================================================ 2 REPERTOIRE.JS â Ðлок «РÑепеÑÑÑаÑе», Ð³Ð»Ð°Ð²Ð½Ð°Ñ ÑÑÑаниÑа 3 ТеаÑÑ Ð½Ð° Таганке 4 5 ÐÑÑ Ð¸ÑекÑÑÑа: даннÑе живÑÑ Ð² HTML (data-* аÑÑибÑÑÑ Ð½Ð° каÑÑоÑÐºÐ°Ñ ). 6 JS ÑиÑÐ°ÐµÑ DOM, ÑилÑÑÑÑÐµÑ Ð¿Ð¾ Ñабам, ÑпÑавлÑÐµÑ Ð²Ð¸Ð´Ð¸Ð¼Ð¾ÑÑÑÑ, 7 обновлÑÐµÑ ÑÑÑлки «ÐеÑÑ ÑепеÑÑÑаÑ», запÑÑÐºÐ°ÐµÑ fitCardTitles. 8 ÐÐ»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð½ÑенÑа â ÑедакÑиÑоваÑÑ ÑолÑко index.html. 9 10 ââ ÐагинаÑÐ¸Ñ ââââââââââââââââââââââââââââââââââââââââââââââââ 11 Ðа каждÑй Ñаб показÑваеÑÑÑ Ð¾Ð³ÑаниÑенное ÑиÑло каÑÑоÑек: 12 13 ⢠СÑаÑÑовÑй показ â не более STEP (6). ÐÑли в Ñабе ⤠6 каÑÑоÑек 14 и Ð¸Ñ Ð½ÐµÑÑÑное ÑиÑло â окÑÑглÑем вниз до ÑÑÑного (5â4, 3â2), 15 ÑÑÐ¾Ð±Ñ ÑеÑка 2ÃN не оÑÑавлÑла одинокÑÑ ÐºÐ°ÑÑоÑÐºÑ Ñ Ð¿ÑÑÑой ÑÑейкой. 16 ÐдинÑÑÐ²ÐµÐ½Ð½Ð°Ñ ÐºÐ°ÑÑоÑка (total = 1) показÑваеÑÑÑ ÐºÐ°Ðº еÑÑÑ. 17 18 ⢠Ðнопка «ÐоказаÑÑ ÐµÑÑ» добавлÑÐµÑ ÐµÑÑ STEP каÑÑоÑек. 19 ÐÑомежÑÑоÑнÑе ÑоÑÑоÑÐ½Ð¸Ñ Ð²Ñегда ÑÑÑнÑе (6 â 12 â 18 â¦). 20 ÐоÑледний клик, ÑаÑкÑÑваÑÑий оÑÑаÑок, показÑÐ²Ð°ÐµÑ ÐСРâ 21 даже еÑли иÑог неÑÑÑнÑй (Ð¾Ð´Ð¸Ð½Ð¾ÐºÐ°Ñ ÐºÐ°ÑÑоÑка поÑвлÑеÑÑÑ ÑолÑко 22 поÑле Ñвного ÑаÑкÑÑÑÐ¸Ñ Ð¿Ð¾Ð»ÑзоваÑелем, ÑÑо допÑÑÑимо). 23 24 ⢠Ðнопка видна, пока еÑÑÑ ÑкÑÑÑÑе каÑÑоÑки Ñаба. Ðа мобилке 25 и деÑкÑопе. ÐÑи пеÑеклÑÑении Ñаба ÑÑÑÑÑик ÑбÑаÑÑваеÑÑÑ. 26 ============================================================ */ 27 28(function () { 29 'use strict'; 30 31 /* Шаг пагинаÑии â ÑÑаÑÑовÑй макÑимÑм и ÑÐ°Ð·Ð¼ÐµÑ Ð´Ð¾Ð³ÑÑзки */ 32 const STEP = 6; 33 34 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 35 ÐÐÐФÐÐУРÐЦÐЯ ТÐÐÐÐ 36 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 37 const TABS = { 38 current: { allText: 'ÐеÑÑ ÑепеÑÑÑаÑ' }, 39 premiere: { allText: 'ÐÑе пÑемÑеÑÑ' }, 40 pushka: { allText: 'ÐÑе ÑпекÑакли по каÑÑе' }, 41 }; 42 43 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 44 ÐСÐÐÐÐÐÐ ÐÐÐÐÐÐÐÐТ 45 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 46 class RepertoireBlock { 47 constructor(rootEl) { 48 this._root = rootEl; 49 this._cardsWrap = rootEl.querySelector('.repertoire__cards'); 50 this._allBtn = rootEl.querySelector('.repertoire__all-btn'); 51 this._allHeader = rootEl.querySelector('.repertoire__all-header'); 52 this._moreWrap = rootEl.querySelector('.repertoire__more'); 53 this._moreBtn = rootEl.querySelector('.repertoire__more-btn'); 54 this._activeTab = 'current'; 55 56 this._allCards = [...rootEl.querySelectorAll('.rep-card[data-categories]')]; 57 58 /* СколÑко каÑÑоÑек показано в акÑивном Ñабе */ 59 this._shown = this._initialCount(this._matching('current').length); 60 61 /* ÐеÑвиÑнÑй ÑÐµÐ½Ð´ÐµÑ */ 62 this._updateAllLinks('current'); 63 this._render('current'); 64 65 /* ÐеÑеклÑÑение Ñабов ÑеÑез ÑнивеÑÑалÑнÑÑ ÑÑилиÑÑ */ 66 this._destroyTabs = TagankaUtils.bindTabs({ 67 tabs: rootEl.querySelectorAll('.rep-tab'), 68 transitionEls: [this._cardsWrap], 69 delay: 120, 70 onSwitch: (name) => { 71 this._activeTab = name; 72 /* СбÑаÑÑваем пагинаÑÐ¸Ñ Ð½Ð° ÑÑаÑÑовÑй показ нового Ñаба */ 73 this._shown = this._initialCount(this._matching(name).length); 74 /* 75 * СÑÑлки менÑем ÐÐ ÑендеÑа â полÑзоваÑÐµÐ»Ñ Ð²Ð¸Ð´Ð¸Ñ Ð°ÐºÑÑалÑнÑй 76 * ÑекÑÑ ÑÑазÑ, без «миганиÑ» ÑÑаÑого знаÑениÑ. 77 */ 78 this._updateAllLinks(name); 79 this._render(name); 80 }, 81 }); 82 83 /* «ÐоказаÑÑ ÐµÑÑ» */ 84 if (this._moreBtn) { 85 this._onMore = () => this._showMore(); 86 this._moreBtn.addEventListener('click', this._onMore); 87 } 88 89 this._onResize = TagankaUtils.debounce(() => this._fitTitles(), 200); 90 window.addEventListener('resize', this._onResize); 91 } 92 93 /* ÐаÑÑоÑки, пÑинадлежаÑие ÑÐ°Ð±Ñ (data-categories ÑодеÑÐ¶Ð¸Ñ tabId) */ 94 _matching(tabId) { 95 return this._allCards.filter(card => 96 (card.dataset.categories || '').split(' ').includes(tabId) 97 ); 98 } 99 100 /* СÑаÑÑовое колиÑеÑÑво: не более STEP, ÑÑÑное пÑи total ⤠STEP */ 101 _initialCount(total) { 102 if (total <= STEP) { 103 if (total <= 1) return total; // одинокÑÑ ÐºÐ°ÑÑÑ Ð½Ðµ пÑÑÑем 104 return total % 2 === 1 ? total - 1 : total; // неÑÑÑное â вниз до ÑÑÑного 105 } 106 return STEP; // total > STEP â Ñовно 6 (ÑÑÑное) 107 } 108 109 /* ÐогÑÑзка оÑеÑедной паÑÑии. ÐоÑледнÑÑ Ð¿Ð°ÑÑÐ¸Ñ ÑаÑкÑÑÐ²Ð°ÐµÑ Ð²ÑÑ. */ 110 _showMore() { 111 const total = this._matching(this._activeTab).length; 112 this._shown = Math.min(this._shown + STEP, total); 113 this._render(this._activeTab); 114 } 115 116 _render(tabId) { 117 const matching = this._matching(tabId); 118 119 /* СнаÑала пÑÑÑем вÑе каÑÑоÑки и Ñнимаем маÑÐºÐµÑ Ð¿Ð¾Ñледней */ 120 this._allCards.forEach(card => { 121 card.classList.remove('rep-card--last'); 122 card.style.display = 'none'; 123 }); 124 125 /* ÐоказÑваем пеÑвÑе this._shown каÑÑоÑек акÑивного Ñаба */ 126 const visible = matching.slice(0, this._shown); 127 visible.forEach(card => { card.style.display = 'flex'; }); 128 129 if (visible.length) { 130 visible[visible.length - 1].classList.add('rep-card--last'); 131 } 132 133 /* rep-row-sep: ÑазделиÑÐµÐ»Ñ Ð¿Ð¾Ñле 2-й каÑÑоÑки на деÑкÑопе 134 (CSS пÑÑÑÐµÑ ÐµÐ³Ð¾ â row-gap Ð´ÐµÐ»Ð°ÐµÑ ÑабоÑÑ, оÑÑавлено Ð´Ð»Ñ ÑовмеÑÑимоÑÑи) */ 135 this._cardsWrap.querySelectorAll('.rep-row-sep').forEach(s => s.remove()); 136 if (TagankaUtils.isDesktop() && visible.length > 2) { 137 const sep = document.createElement('div'); 138 sep.className = 'rep-row-sep'; 139 sep.setAttribute('aria-hidden', 'true'); 140 visible[1].insertAdjacentElement('afterend', sep); 141 } 142 143 /* ÐидимоÑÑÑ ÐºÐ½Ð¾Ð¿ÐºÐ¸ «ÐоказаÑÑ ÐµÑÑ» */ 144 if (this._moreWrap) { 145 const hasMore = this._shown < matching.length; 146 if (hasMore) this._moreWrap.removeAttribute('hidden'); 147 else this._moreWrap.setAttribute('hidden', ''); 148 } 149 150 const doFit = () => requestAnimationFrame(() => requestAnimationFrame(() => this._fitTitles())); 151 document.fonts && document.fonts.ready 152 ? document.fonts.ready.then(doFit).catch(doFit) 153 : doFit(); 154 } 155 156 _fitTitles() { 157 const sectionW = this._root.offsetWidth; 158 const w = window.innerWidth; 159 const fontMin = 56; 160 const FONT_STEP = 4; 161 162 /* Ðобилка (< 768px) â ÑнаÑала fitCardTitles подгонÑÐµÑ Ð¿Ð¾Ð´ ÑиÑÐ¸Ð½Ñ Ñлова, 163 заÑем пÑовеÑÑем колиÑеÑÑво ÑÑÑок: Ð¼Ð°ÐºÑ 3, fallback 4 пÑи fontMin. */ 164 if (w < 768) { 165 const textW = sectionW - 20; 166 const fontStart = 72; 167 168 /* ÐÐ ÐÐÐÐ 09.08: нижнÑÑ Ð³ÑаниÑа ÐºÐµÐ³Ð»Ñ ÑÑиÑаеÑÑÑ Ð¾Ñ ÑиÑÐ¸Ð½Ñ Ð±Ð¾ÐºÑа, 169 а не беÑÑÑÑÑ ÑикÑиÑованнÑми 56px. 170 171 СимпÑом: «ÐÐУС 48. ÐСТРÐÐСÐÐл на ÑкÑане 393px ÑвалÑÑ Ð¿Ð¾ÑÑеди 172 Ñлова без деÑиÑа â «ÐСТРÐÐСÐÐ / л. ÐиагноÑÑика на живой 173 ÑÑÑаниÑе: инлайновÑй ÐºÐµÐ³Ð»Ñ 56px, Ñо еÑÑÑ Ñовно fontMin â 174 fitCardTitles ÑпÑÑÑÑ Ð² пол и оÑÑановилÑÑ, а Ñлово в Ð±Ð¾ÐºÑ 373px 175 пÑи 56px не Ð²Ð»ÐµÐ·Ð°ÐµÑ (нÑжно 56.0px впÑиÑÑк). ÐалÑÑе вÑÑÑпал 176 overflow-wrap: break-word и ÑÑбил Ñлово где пÑидÑÑÑÑ. 177 178 ÐÐ°Ð¼ÐµÑ Ð¿Ð¾ вÑем 37 названиÑм ÑепеÑÑÑаÑа (canvas TextMetrics, 179 Pragmatica, letter-spacing -0.025em): пÑи 56px в Ð±Ð¾ÐºÑ 373px 180 не помеÑаÑÑÑÑ ÐÐСТСÐÐÐСÐÐЯ (нÑжно 48.4px), ÐÐТÐРЯÐÐÐÐ (54.9),
181 ÐÐРШÐÐСÐÐЯ (55.6) и ÐСТРÐÐСÐÐÐ (56.0). 182 183 textW / 8 â Ñамое ÑиÑокое Ñлово ÑепеÑÑÑаÑа Ð·Ð°Ð½Ð¸Ð¼Ð°ÐµÑ Ð¿ÑимеÑно 184 воÑÐµÐ¼Ñ ÐºÐµÐ³Ð»ÐµÐ¹ по ÑиÑине, поÑÑÐ¾Ð¼Ñ Ñакой пол гаÑанÑиÑованно ниже 185 ÑÑебÑемого на лÑбой ÑиÑине ÑкÑана, Ñ Ð·Ð°Ð¿Ð°Ñом около 3%: 186 ÑкÑан 393 â Ð±Ð¾ÐºÑ 373 â пол 47 (нÑжно ⤠48.4) 187 ÑкÑан 360 â Ð±Ð¾ÐºÑ 340 â пол 43 (нÑжно ⤠44.1) 188 ÑкÑан 320 â Ð±Ð¾ÐºÑ 300 â пол 38 (нÑжно ⤠38.9) 189 Math.min Ñ Ð¿Ñежними 56px оÑÑавлÑÐµÑ ÑÑаÑое поведение на планÑеÑÐ°Ñ , 190 где Ð±Ð¾ÐºÑ ÑиÑокий и опÑÑкаÑÑ ÐºÐµÐ³Ð»Ñ Ð½ÐµÐ·Ð°Ñем. ÐеÑкÑÐ¾Ð¿Ð½Ð°Ñ Ð²ÐµÑка 191 пÑÐ¾Ð´Ð¾Ð»Ð¶Ð°ÐµÑ Ð¸ÑполÑзоваÑÑ fontMin без изменений. */ 192 const fontMinMobile = Math.min(fontMin, Math.round(textW / 8)); 193 194 TagankaUtils.fitCardTitles(this._cardsWrap, '.rep-card__name', textW, fontStart, fontMinMobile); 195 196 /* Ðожимаем по ÑÑÑокам */ 197 const MAX_LINES = 3; 198 const MAX_LINES_FALLBACK = 4; 199 this._cardsWrap.querySelectorAll('.rep-card__name').forEach(el => { 200 if (el.offsetWidth === 0) return; 201 let size = parseFloat(el.style.fontSize) || fontStart; 202 203 while (size > fontMinMobile) { 204 const lh = parseFloat(getComputedStyle(el).lineHeight) || size * 0.9; 205 const lines = Math.round(el.scrollHeight / lh); 206 if (lines <= MAX_LINES) break; 207 size -= FONT_STEP; 208 el.style.fontSize = size + 'px'; 209 } 210 }); 211 return; 212 } 213 214 /* ÐланÑÐµÑ Ð¸ деÑкÑоп (⥠768px) â макÑималÑнÑй ÑÑиÑÑ Ð¿Ñи ⤠3 ÑÑÑÐ¾ÐºÐ°Ñ . 215 fontStart маÑÑÑабиÑÑеÑÑÑ Ñ ÑиÑиной ÑкÑана, ÑÑÐ¾Ð±Ñ Ð½Ð° ÑиÑÐ¾ÐºÐ¸Ñ Ð¼Ð¾Ð½Ð¸ÑоÑÐ°Ñ 216 длиннÑе Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ð¿Ð¾Ð»Ð½Ñли ÑÑÑоки, а не ÑжималиÑÑ Ð² 1 мелкÑÑ. 217 ÐÑклÑÑение: еÑли пÑи fontMin ÑекÑÑ Ð½Ðµ Ð²Ð»ÐµÐ·Ð°ÐµÑ Ð² 3 ÑÑÑоки â допÑÑкаем 4. */ 218 const fontStart = w >= 1680 ? 108 219 : w >= 1440 ? 72 220 : 64; 221 222 const MAX_LINES = 3; 223 const MAX_LINES_FALLBACK = 4; 224 const titles = this._cardsWrap.querySelectorAll('.rep-card__name'); 225
226 titles.forEach(el => { 227 if (el.offsetWidth === 0) return; 228 229 el.style.fontSize = fontStart + 'px'; 230 let size = fontStart; 231 232 while (size > fontMin) { 233 const lh = parseFloat(getComputedStyle(el).lineHeight) || size * 0.9; 234 const lines = Math.round(el.scrollHeight / lh); 235 const overflowX = el.scrollWidth > el.offsetWidth + 1; 236 if (lines <= MAX_LINES && !overflowX) break; 237 size -= FONT_STEP; 238 el.style.fontSize = size + 'px'; 239 } 240 241 /* Fallback: пÑи fontMin пÑовеÑÑем â еÑли > MAX_LINES, допÑÑкаем до 4 ÑÑÑок. 242 ÐÑли даже 4 не Ñ Ð²Ð°ÑÐ°ÐµÑ â оÑÑавлÑем fontMin, overflow-wrap ÑÐ´ÐµÐ»Ð°ÐµÑ Ð¾ÑÑалÑное. */ 243 if (size <= fontMin) { 244 const lh = parseFloat(getComputedStyle(el).lineHeight) || fontMin * 0.9; 245 const lines = Math.round(el.scrollHeight / lh); 246 if (lines > MAX_LINES && lines <= MAX_LINES_FALLBACK) { 247 /* 4 ÑÑÑоки пÑи минималÑном ÑÑиÑÑе â ÐÐ, оÑÑавлÑем как еÑÑÑ */ 248 } 249 } 250 }); 251 } 252 253 _updateAllLinks(tabId) { 254 const cfg = TABS[tabId] || TABS.current; 255 256 if (this._allBtn) { 257 const url = this._allBtn.dataset[`url${tabId.charAt(0).toUpperCase()}${tabId.slice(1)}`]; 258 if (url && this._allBtn.tagName === 'A') this._allBtn.href = url; 259 const node = [...this._allBtn.childNodes].find(n => n.nodeType === 3); 260 if (node) node.textContent = cfg.allText; 261 else this._allBtn.prepend(document.createTextNode(cfg.allText)); 262 } 263 264 if (this._allHeader) { 265 const url = this._allBtn 266 ? this._allBtn.dataset[`url${tabId.charAt(0).toUpperCase()}${tabId.slice(1)}`] 267 : null; 268 if (url && this._allHeader.tagName === 'A') this._allHeader.href = url; 269 const span = this._allHeader.querySelector('.repertoire__all-header-text'); 270 if (span) span.textContent = cfg.allText; 271 } 272 } 273 274 destroy() { 275 this._destroyTabs(); 276 if (this._moreBtn && this._onMore) { 277 this._moreBtn.removeEventListener('click', this._onMore); 278 } 279 window.removeEventListener('resize', this._onResize); 280 } 281 } 282 283 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 284 ÐÐÐЦÐÐÐÐÐÐЦÐЯ 285 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 286 document.addEventListener('DOMContentLoaded', () => { 287 const el = document.querySelector('.repertoire'); 288 if (el) new RepertoireBlock(el); 289 }); 290 291})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.