fix: expose mobile category filters
This commit is contained in:
@@ -2,3 +2,28 @@
|
|||||||
|
|
||||||
@media(max-width:560px){html,body{width:100%;min-width:0;overflow-x:hidden}.app-layout{min-height:100svh}.main{width:100%;padding:0 12px}.topbar{height:auto;min-height:64px;padding:12px 0;gap:8px}.topbar h1{font-size:18px}.top-actions{flex-shrink:0}.icon-button{min-width:40px;min-height:40px;padding:7px}.toolbar{padding:12px 0;gap:8px}.search{height:42px;min-width:0}.search input{font-size:16px}.search kbd{display:none}.result-count{display:none}.service-row{min-height:64px;gap:9px;padding:9px 1px}.service-icon{width:32px;height:32px;font-size:14px}.service-main strong{font-size:13px}.service-main span{font-size:11px}.service-arrow{display:block;width:18px;font-size:14px}.page-footer{padding:18px 2px}.sidebar{display:none}}
|
@media(max-width:560px){html,body{width:100%;min-width:0;overflow-x:hidden}.app-layout{min-height:100svh}.main{width:100%;padding:0 12px}.topbar{height:auto;min-height:64px;padding:12px 0;gap:8px}.topbar h1{font-size:18px}.top-actions{flex-shrink:0}.icon-button{min-width:40px;min-height:40px;padding:7px}.toolbar{padding:12px 0;gap:8px}.search{height:42px;min-width:0}.search input{font-size:16px}.search kbd{display:none}.result-count{display:none}.service-row{min-height:64px;gap:9px;padding:9px 1px}.service-icon{width:32px;height:32px;font-size:14px}.service-main strong{font-size:13px}.service-main span{font-size:11px}.service-arrow{display:block;width:18px;font-size:14px}.page-footer{padding:18px 2px}.sidebar{display:none}}
|
||||||
@media(max-width:380px){.main{padding:0 10px}.topbar h1{font-size:17px}.service-row{gap:8px}.service-main span{max-width:190px}}
|
@media(max-width:380px){.main{padding:0 10px}.topbar h1{font-size:17px}.service-row{gap:8px}.service-main span{max-width:190px}}
|
||||||
|
|
||||||
|
/* The admin entry must remain visible on mobile */
|
||||||
|
@media(max-width:560px){.top-actions .admin-link{display:inline-flex;align-items:center;justify-content:center;min-height:40px;padding:7px 11px;font-size:12px;font-weight:550;color:var(--text);background:var(--surface);border:1px solid var(--line);border-radius:7px}.top-actions{gap:7px}}
|
||||||
|
|
||||||
|
@media(max-width:560px){.top-actions .admin-link{display:inline-flex!important;align-items:center;justify-content:center;min-height:40px;padding:7px 12px;font-size:13px;font-weight:600;color:var(--text);background:var(--surface);border:1px solid var(--line);border-radius:7px}}
|
||||||
|
|
||||||
|
@media(max-width:560px){.top-actions{gap:7px}.top-actions .admin-link{white-space:nowrap}}
|
||||||
|
|
||||||
|
@media(max-width:360px){.topbar h1{font-size:16px}.top-actions .admin-link{padding-left:9px;padding-right:9px;font-size:12px}.icon-button{min-width:38px}}
|
||||||
|
|
||||||
|
@media(max-width:560px){.top-actions .admin-link{display:inline-flex!important}}
|
||||||
|
|
||||||
|
/* cache-bust marker: mobile-admin-visible */
|
||||||
|
|
||||||
|
/* Mobile login CTA: intentionally overrides desktop-hide rule */
|
||||||
|
@media screen and (max-width:560px){.main .topbar .top-actions .admin-link{display:inline-flex!important;visibility:visible!important;opacity:1!important}}
|
||||||
|
.mobile-categories{display:none}
|
||||||
|
@media(max-width:560px){
|
||||||
|
.mobile-categories{display:flex;gap:7px;overflow-x:auto;padding:0 0 12px;margin:0 -2px;scrollbar-width:none}
|
||||||
|
.mobile-categories::-webkit-scrollbar{display:none}
|
||||||
|
.mobile-category{flex:0 0 auto;border:1px solid var(--border);background:var(--surface);color:var(--muted);border-radius:7px;padding:7px 10px;font-size:12px;line-height:1;white-space:nowrap}
|
||||||
|
.mobile-category span{margin-left:4px;opacity:.65}
|
||||||
|
.mobile-category.active{color:var(--text);border-color:var(--text);background:var(--surface-2)}
|
||||||
|
.admin-link{display:inline-flex!important;align-items:center;justify-content:center;min-height:40px;padding:7px 10px;font-size:12px;white-space:nowrap}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ .site_title }}</title><link rel="stylesheet" href="/static/css/tonav.css"></head>
|
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ .site_title }}</title><link rel="stylesheet" href="/static/css/tonav.css?v=mobile2"></head>
|
||||||
<body class="app-shell">
|
<body class="app-shell">
|
||||||
<div class="app-layout">
|
<div class="app-layout">
|
||||||
<aside class="sidebar"><a class="brand" href="/">{{ .site_title }}</a><div class="sidebar-section"><div class="section-label">SERVICES</div><button class="nav-item active" data-category="all">All <span id="allCount"></span></button><div id="categoryNav"></div></div><div class="sidebar-footer"><a href="/admin/login">Admin sign in <span>↗</span></a></div></aside>
|
<aside class="sidebar"><a class="brand" href="/">{{ .site_title }}</a><div class="sidebar-section"><div class="section-label">SERVICES</div><button class="nav-item active" data-category="all">All <span id="allCount"></span></button><div id="categoryNav"></div></div><div class="sidebar-footer"><a href="/admin/login">Admin sign in <span>↗</span></a></div></aside>
|
||||||
<main class="main"><header class="topbar"><div><div class="eyebrow">SERVICES</div><h1>Service directory</h1></div><div class="top-actions"><button class="icon-button" id="themeBtn" title="Toggle theme">◐</button><a class="admin-link" href="/admin/login">Sign in</a></div></header>
|
<main class="main"><header class="topbar"><div><div class="eyebrow">SERVICES</div><h1>Service directory</h1></div><div class="top-actions"><a class="admin-link" href="/admin/login">登录</a><button class="icon-button" id="themeBtn" title="Toggle theme">◐</button></div></header>
|
||||||
<div class="toolbar"><label class="search"><span>⌕</span><input id="searchInput" placeholder="Search services, tags, descriptions…" autocomplete="off"><kbd>/</kbd></label><div class="result-count" id="resultCount"></div></div>
|
<div class="mobile-categories" id="mobileCategories"></div><div class="toolbar"><label class="search"><span>⌕</span><input id="searchInput" placeholder="Search services, tags, descriptions…" autocomplete="off"><kbd>/</kbd></label><div class="result-count" id="resultCount"></div></div>
|
||||||
<section class="service-list" id="servicesGrid"><div class="empty-state">Loading…</div></section>
|
<section class="service-list" id="servicesGrid"><div class="empty-state">Loading…</div></section>
|
||||||
<footer class="page-footer"><span>ToNav</span><span id="lastCheckTime"></span></footer></main></div>
|
<footer class="page-footer"><span>ToNav</span><span id="lastCheckTime"></span></footer></main></div>
|
||||||
<script>const categoriesData={{ .categories_json }};const servicesData={{ .services_json }};let currentTab='all',currentKeyword='';const map={};categoriesData.forEach(c=>map[c.id]=c.name);const esc=s=>{const d=document.createElement('div');d.textContent=s||'';return d.innerHTML};
|
<script>const categoriesData={{ .categories_json }};const servicesData={{ .services_json }};let currentTab='all',currentKeyword='';const map={};categoriesData.forEach(c=>map[c.id]=c.name);const esc=s=>{const d=document.createElement('div');d.textContent=s||'';return d.innerHTML};
|
||||||
function renderNav(){const n=document.getElementById('categoryNav');document.getElementById('allCount').textContent=servicesData.length;n.innerHTML=categoriesData.map(c=>`<button class="nav-item" data-category="${esc(c.name)}">${esc(c.name)} <span>${servicesData.filter(s=>s.category_id===c.id).length}</span></button>`).join('');document.querySelectorAll('.nav-item').forEach(b=>b.onclick=()=>{currentTab=b.dataset.category;document.querySelectorAll('.nav-item').forEach(x=>x.classList.toggle('active',x.dataset.category===currentTab));render()})}
|
function renderNav(){const n=document.getElementById('categoryNav');const m=document.getElementById('mobileCategories');document.getElementById('allCount').textContent=servicesData.length;const items=categoriesData.map(c=>`<button class="nav-item" data-category="${esc(c.name)}">${esc(c.name)} <span>${servicesData.filter(s=>s.category_id===c.id).length}</span></button>`).join('');n.innerHTML=items;m.innerHTML=`<button class="mobile-category active" data-category="all">All <span>${servicesData.length}</span></button>`+categoriesData.map(c=>`<button class="mobile-category" data-category="${esc(c.name)}">${esc(c.name)} <span>${servicesData.filter(s=>s.category_id===c.id).length}</span></button>`).join('');document.querySelectorAll('.nav-item,.mobile-category').forEach(b=>b.onclick=()=>{currentTab=b.dataset.category;document.querySelectorAll('.nav-item,.mobile-category').forEach(x=>x.classList.toggle('active',x.dataset.category===currentTab));render()})}
|
||||||
function render(){let a=currentTab==='all'?servicesData:servicesData.filter(s=>map[s.category_id]===currentTab);if(currentKeyword)a=a.filter(s=>(s.name+' '+(s.description||'')+' '+(s.tags||'')).toLowerCase().includes(currentKeyword));document.getElementById('resultCount').textContent=`${a.length} services`;document.getElementById('servicesGrid').innerHTML=a.length?a.map(s=>`<a class="service-row" href="${esc(s.url)}" target="_blank" rel="noopener"><span class="service-icon">${esc(s.icon)||'↗'}</span><span class="service-main"><strong>${esc(s.name)}</strong><span>${esc(s.description||s.url)}</span></span><span class="service-meta"><span class="status ${s.status==='online'?'online':s.status==='offline'?'offline':''}"></span>${esc(map[s.category_id]||'Uncategorized')}</span><span class="service-arrow">→</span></a>`).join(''):'<div class="empty-state">No services found</div>'}
|
function render(){let a=currentTab==='all'?servicesData:servicesData.filter(s=>map[s.category_id]===currentTab);if(currentKeyword)a=a.filter(s=>(s.name+' '+(s.description||'')+' '+(s.tags||'')).toLowerCase().includes(currentKeyword));document.getElementById('resultCount').textContent=`${a.length} services`;document.getElementById('servicesGrid').innerHTML=a.length?a.map(s=>`<a class="service-row" href="${esc(s.url)}" target="_blank" rel="noopener"><span class="service-icon">${esc(s.icon)||'↗'}</span><span class="service-main"><strong>${esc(s.name)}</strong><span>${esc(s.description||s.url)}</span></span><span class="service-meta"><span class="status ${s.status==='online'?'online':s.status==='offline'?'offline':''}"></span>${esc(map[s.category_id]||'Uncategorized')}</span><span class="service-arrow">→</span></a>`).join(''):'<div class="empty-state">No services found</div>'}
|
||||||
document.getElementById('searchInput').oninput=e=>{currentKeyword=e.target.value.toLowerCase().trim();render()};document.addEventListener('keydown',e=>{if(e.key==='/'&&document.activeElement.tagName!=='INPUT'){e.preventDefault();document.getElementById('searchInput').focus()}});document.getElementById('themeBtn').onclick=()=>{document.documentElement.classList.toggle('dark');localStorage.setItem('tonav-theme',document.documentElement.classList.contains('dark')?'dark':'light')};if(localStorage.getItem('tonav-theme')==='dark')document.documentElement.classList.add('dark');renderNav();render();document.getElementById('lastCheckTime').textContent=new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'});</script></body></html>
|
document.getElementById('searchInput').oninput=e=>{currentKeyword=e.target.value.toLowerCase().trim();render()};document.addEventListener('keydown',e=>{if(e.key==='/'&&document.activeElement.tagName!=='INPUT'){e.preventDefault();document.getElementById('searchInput').focus()}});document.getElementById('themeBtn').onclick=()=>{document.documentElement.classList.toggle('dark');localStorage.setItem('tonav-theme',document.documentElement.classList.contains('dark')?'dark':'light')};if(localStorage.getItem('tonav-theme')==='dark')document.documentElement.classList.add('dark');renderNav();render();document.getElementById('lastCheckTime').textContent=new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'});</script></body></html>
|
||||||
|
|||||||
Reference in New Issue
Block a user