// render grid function renderGrid() const filtered = filterTPOItems(); if (filtered.length === 0) gridContainer.innerHTML = `<div class="empty-state">🧩 No TPO tests match "$searchTerm" or range filter. Try different keywords or clear filters.</div>`; return;
for (let i = 1; i <= TOTAL_TPO; i++) tpoItems.push( id: i, number: i, range: i <= 24 ? "1-24" : (i <= 48 ? "25-48" : "49-72"), description: getDescription(i), fileSize: getFileSize(i), // each TPO gets a unique download trigger (mock zip) );
.desc font-size: 0.85rem; color: #406e86; margin-bottom: 1rem; line-height: 1.4;
.search-box i color: #6f9fbb; font-style: normal; font-weight: 500;
.tpo-card:hover transform: translateY(-5px); box-shadow: 0 18px 30px -12px rgba(0, 32, 64, 0.15); border-color: #c2dfec;
// attach info/details event const infoBtns = document.querySelectorAll('.btn-download:not(.primary)'); infoBtns.forEach(btn => btn.addEventListener('click', (e) => const tpoNum = btn.getAttribute('data-quickinfo'); if (tpoNum) showToast(`📖 TPO $tpoNum: Full-length simulation, answer keys, and audio scripts included.`, false); ); );
.stat-card span font-size: 1.4rem; font-weight: 800; margin-right: 6px; color: #0f4c6b;
// Filter logic: by range + search term (search supports number, range e.g., "1-10", "5") function filterTPOItems() let filtered = [...tpoItems]; // range filter if (activeRange !== 'all') if (activeRange === '1-24') filtered = filtered.filter(t => t.number >= 1 && t.number <= 24); else if (activeRange === '25-48') filtered = filtered.filter(t => t.number >= 25 && t.number <= 48); else if (activeRange === '49-72') filtered = filtered.filter(t => t.number >= 49 && t.number <= 72);
<div class="controls"> <div class="search-box"> <i>🔍</i> <input type="text" id="searchInput" placeholder="Search TPO number (e.g., 24, 58, 1-10)" autocomplete="off"> </div> <div class="filter-group" id="rangeFilterGroup"> <button data-range="all" class="btn-filter active">All (1-72)</button> <button data-range="1-24" class="btn-filter">📘 1–24</button> <button data-range="25-48" class="btn-filter">📙 25–48</button> <button data-range="49-72" class="btn-filter">📗 49–72</button> </div> <button id="bulkDownloadBtn" class="download-all-btn">⬇️ Download all visible (ZIP collection)</button> </div>
.download-all-btn background: #1f6e43; color: white; border: none; padding: 0.6rem 1.4rem; border-radius: 40px; font-weight: 600; font-size: 0.85rem; display: flex; align-items: center; gap: 8px; cursor: pointer; transition: background 0.2s, transform 0.1s; box-shadow: 0 4px 8px rgba(0,0,0,0.05);
.btn-download.primary background: #1b6b87; color: white; box-shadow: 0 2px 6px rgba(0,0,0,0.1);
.btn-filter background: white; border: 1px solid #cfe1ea; padding: 0.5rem 1rem; border-radius: 40px; font-weight: 500; font-size: 0.8rem; cursor: pointer; transition: all 0.2s ease; color: #2c5a74;
// range buttons handler rangeBtns.forEach(btn => btn.addEventListener('click', () => const rangeValue = btn.getAttribute('data-range'); activeRange = rangeValue; // update active style rangeBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); updateAndRender(); ); );