export const mobileSource = " // Note: Mobile-only filtering, sorting and card rendering lives here so torrent table code stays focused on desktop rows.\n function mobileSortDef(){ return MOBILE_SORT_STEPS.find(x=>x.key===sortState.key && x.dir===sortState.dir) || MOBILE_SORT_STEPS.find(x=>x.key===sortState.key) || MOBILE_SORT_STEPS[0]; }\n\n function mobileSortLabel(){ const def=mobileSortDef(); return `${def.label} ${sortState.dir>0?'↑':'↓'}`; }\n\n function cycleMobileSort(){\n const current=MOBILE_SORT_STEPS.findIndex(x=>x.key===sortState.key && x.dir===sortState.dir);\n const next=MOBILE_SORT_STEPS[(current+1) % MOBILE_SORT_STEPS.length];\n sortState={key:next.key, dir:next.dir};\n saveTorrentSortPreference();\n if($('tableWrap'))$('tableWrap').scrollTop=0;\n if($('mobileList'))$('mobileList').scrollTop=0;\n scheduleRender(true);\n }\n\n function setMobileFilterValue(value){\n const key=String(value||'all');\n mobileActiveFilterKey=key;\n if(key.startsWith('tracker:')){\n activeTrackerFilter=key.slice(8);\n activeFilter='all';\n }else{\n activeTrackerFilter='';\n activeFilter=key || 'all';\n }\n syncFilterButtons();\n saveActiveFilterPreference();\n if($('tableWrap'))$('tableWrap').scrollTop=0;\n if($('mobileList'))$('mobileList').scrollTop=0;\n scheduleRender(true);\n }\n\n function mobileFilterDefs(){\n const arr=trackerScopedRows();\n const defs=Object.keys(FILTER_COUNT_IDS).filter(k=>k!=='moving').map(k=>[k,k==='all'?'All':k==='downloading'?'Downloading':k==='seeding'?'Seeding':k==='paused'?'Paused':k==='checking'?'Checking':k==='error'?'With error':'Stopped',filterSummaryBucket(k).count||0]);\n const movingCount=movingFilterCount();\n if(movingCount) defs.push(['moving','Moving',movingCount]);\n const counts=new Map();\n arr.forEach(t=>labelNames(t.label).forEach(l=>counts.set(l,(counts.get(l)||0)+1)));\n [...counts.keys()].sort((a,b)=>a.localeCompare(b)).forEach(l=>defs.push([`label:${l}`,l,counts.get(l),'label']));\n defs.push(['tracker:','All trackers',torrents.size,'tracker']);\n const trackerOptions=new Map((trackerSummary.trackers||[]).map(t=>[t.domain,t]));\n // Note: Preserve the selected tracker option even when the cache has not returned it yet, so the mobile select does not jump to All trackers.\n if(activeTrackerFilter && !trackerOptions.has(activeTrackerFilter)) trackerOptions.set(activeTrackerFilter, {domain:activeTrackerFilter, count:arr.length});\n [...trackerOptions.values()].forEach(t=>defs.push([`tracker:${t.domain}`,t.domain,t.count,'tracker']));\n if(mobileSmartFiltersEnabled) SMART_VIEW_DEFS.forEach(([key,label])=>defs.push([key,label,arr.filter(t=>smartViewVisible(t,key)).length,'smart']));\n return defs;\n }\n\n function renderMobileFilters(){\n const bar=$('mobileFilterBar');\n if(!bar) return;\n const allVisible=visibleRows.length>0 && visibleRows.every(t=>selected.has(t.hash));\n const someVisible=visibleRows.some(t=>selected.has(t.hash));\n const defs=mobileFilterDefs();\n const currentSelect=$('mobileFilterSelect');\n const focused=currentSelect && document.activeElement===currentSelect;\n const sig=[focused ? 'focus' : activeFilter, activeTrackerFilter, sortState.key, sortState.dir, selected.size, allVisible ? 1 : 0, someVisible ? 1 : 0, mobileSmartFiltersEnabled ? 1 : 0, defs.map(d=>`${d[0]}:${d[2]}`).join('|')].join('::');\n if(focused) return;\n if(sig===lastMobileFiltersSignature) return;\n lastMobileFiltersSignature=sig;\n const selectedMobileKey = activeTrackerFilter ? `tracker:${activeTrackerFilter}` : (mobileActiveFilterKey === 'tracker:' ? 'tracker:' : activeFilter);\n // Note: Select exactly one mobile option; \"All\" and \"All trackers\" share the same data filter but must not both render as selected.\n const opts=defs.map(([key,label,count,type])=>{ const selectedOpt = key === selectedMobileKey; return ``; }).join('');\n const bulk=selected.size?``:'';\n // Note: Mobile bulk actions reuse the existing label modal and move picker, so desktop behavior stays unchanged.\n bar.innerHTML=`
${bulk}${selected.size} selected
`;\n }\n\n function renderMobileFilters(){\n const bar=$('mobileFilterBar');\n if(!bar) return;\n const allVisible=visibleRows.length>0 && visibleRows.every(t=>selected.has(t.hash));\n const someVisible=visibleRows.some(t=>selected.has(t.hash));\n const defs=mobileFilterDefs();\n const currentSelect=$('mobileFilterSelect');\n const focused=currentSelect && document.activeElement===currentSelect;\n const sig=[focused ? 'focus' : activeFilter, activeTrackerFilter, sortState.key, sortState.dir, selected.size, allVisible ? 1 : 0, someVisible ? 1 : 0, mobileSmartFiltersEnabled ? 1 : 0, defs.map(d=>`${d[0]}:${d[2]}`).join('|')].join('::');\n if(focused) return;\n if(sig===lastMobileFiltersSignature) return;\n lastMobileFiltersSignature=sig;\n const selectedMobileKey = activeTrackerFilter ? `tracker:${activeTrackerFilter}` : (mobileActiveFilterKey === 'tracker:' ? 'tracker:' : activeFilter);\n // Note: Select exactly one mobile option; \"All\" and \"All trackers\" share the same data filter but must not both render as selected.\n const opts=defs.map(([key,label,count,type])=>{ const selectedOpt = key === selectedMobileKey; return ``; }).join('');\n const bulk=selected.size?``:'';\n // Note: Mobile bulk actions reuse the existing label modal and move picker, so desktop behavior stays unchanged.\n bar.innerHTML=`
${bulk}${selected.size} selected
`;\n }\n\n function mobileColumnValue(t, key){\n if(key==='status') return statusBadge(t);\n if(key==='size') return `Size ${esc(t.size_h||'-')}`;\n if(key==='progress') return null;\n if(key==='down_rate') return `DL ${esc(t.down_rate_h||'-')}`;\n if(key==='up_rate') return `UL ${esc(t.up_rate_h||'-')}`;\n if(key==='eta') return `ETA ${esc(t.eta_h||'-')}`;\n if(key==='seeds') return `Seeds ${esc(t.seeds??0)}`;\n if(key==='peers') return `Peers ${esc(t.peers??0)}`;\n if(key==='ratio') return `Ratio ${esc(t.ratio??'-')}`;\n if(key==='label') return `Label ${esc(t.label||'-')}`;\n if(key==='ratio_group') return `Ratio group ${esc(t.ratio_group||'-')}`;\n if(key==='down_total') return `Downloaded ${esc(t.down_total_h||'-')}`;\n // Note: Complete torrents hide this mobile line because there is nothing left to download.\n if(key==='to_download') return t.to_download_h ? `To download ${esc(t.to_download_h)}` : null;\n if(key==='up_total') return `Uploaded ${esc(t.up_total_h||'-')}`;\n if(key==='created') return `Added ${esc(formatDateTime(t.created))}`;\n if(key==='priority') return `Priority ${esc(t.priority ?? '-')}`;\n if(key==='state') return `State ${esc(t.state ?? '-')}`;\n if(key==='active') return `Active ${esc(t.active ?? '-')}`;\n if(key==='complete') return `Complete ${esc(t.complete ?? '-')}`;\n if(key==='hashing') return `Hashing ${esc(t.hashing ?? 0)}`;\n if(key==='message') return `Message ${esc(t.message||'-')}`;\n if(key==='hash') return `Hash ${esc(t.hash||'-')}`;\n return null;\n }\n\n function mobileInfoLines(t){\n const primary=[], secondary=[];\n MOBILE_COLUMN_DEFS.forEach(([key])=>{\n if(!mobileColumns[key]) return;\n const value = mobileColumnValue(t, key);\n if(!value) return;\n if(['status','size','ratio','eta','seeds','peers'].includes(key)) primary.push(value);\n else if(key!=='path') secondary.push(value);\n });\n return {primary:primary.join(' · '), secondary:secondary.join(' · ')};\n }\n";