/* ===== Page views ===== */
/* ---------- Dashboard home ---------- */
function DashboardView({ nav, favs, toggleFav }) {
const total = window.SECTIONS.length;
const catCount = window.CATEGORIES.length;
const myDownloads = window.getMyDownloads ? window.getMyDownloads() : 0;
const [tab, setTab] = useState('recent');
const recent = window.SECTIONS.slice()
.sort((a, b) => new Date(b.added) - new Date(a.added)).slice(0, 6);
const popular = window.SECTIONS.slice()
.sort((a, b) => b.popularity - a.popularity).slice(0, 6);
const topCats = window.CATEGORIES.slice()
.sort((a, b) => window.catCount(b.id) - window.catCount(a.id)).slice(0, 6);
const feed = tab === 'recent' ? recent : popular;
const openCat = (c) => nav({ view: 'category', catId: c.id });
const openSec = (s) => nav({ view: 'section', sectionId: s.id });
const spark = [40, 62, 34, 74, 52, 90, 64];
return (
{/* KPI row */}
Live library
{total}
Sections Available
{spark.map((h, i) => )}
{catCount}
Categories
{myDownloads}
Your Downloads
HTML & React exports
{favs.length}
Favorites
{/* Browse by Category (left) + tabbed feed (right) */}
Browse by Category
{topCats.map(c => )}
{feed.map((s, i) => )}
);
}
/* ---------- Browse / library ---------- */
function BrowseView({ nav, favs, toggleFav, search, route }) {
const [cats, setCats] = useState(route.catId ? [route.catId] : []);
const [fw, setFw] = useState('all');
const [sort, setSort] = useState(route.sort || 'popular');
let list = window.SECTIONS.filter(s => {
if (cats.length && !cats.includes(s.category)) return false;
if (fw !== 'all' && !s.frameworks.includes(fw)) return false;
if (search) {
const q = search.toLowerCase();
const hay = (s.name + ' ' + s.tags.join(' ') + ' ' + window.catById(s.category).label).toLowerCase();
if (!hay.includes(q)) return false;
}
return true;
});
list = list.sort((a, b) =>
sort === 'new' ? new Date(b.added) - new Date(a.added) :
sort === 'downloads' ? b.downloads - a.downloads :
b.popularity - a.popularity);
return (
Library
Browse all sections
{list.length} {list.length === 1 ? 'result' : 'results'}{search ? ' for “' + search + '”' : ''}
{['all', 'html', 'react'].map(f =>
)}
{cats.length > 0 && (
{cats.map(id => {
const c = window.catById(id);
return (
);
})}
)}
{list.length === 0
?
:
{list.map(s => nav({ view: 'section', sectionId: sec.id })} />)}
}
);
}
/* ---------- Category detail ---------- */
function CategoryView({ nav, favs, toggleFav, route }) {
const cat = window.catById(route.catId);
const list = window.SECTIONS.filter(s => s.category === cat.id).sort((a, b) => b.popularity - a.popularity);
return (
{cat.label}
{cat.blurb} · {list.length} sections
{list.map(s => nav({ view: 'section', sectionId: sec.id })} />)}
);
}
/* ---------- Collections ---------- */
function CollectionsView({ nav, favs, toggleFav }) {
return (
Curated
Collections
Hand-picked bundles ready to assemble into full pages.
{window.COLLECTIONS.map(col => {
const items = col.items.map(window.sectionById).filter(Boolean);
return (
{col.name}
{col.desc} · {items.length} sections
{items.map(s => nav({ view: 'section', sectionId: sec.id })} />)}
);
})}
);
}
/* ---------- Favorites ---------- */
function FavoritesView({ nav, favs, toggleFav }) {
const list = favs.map(window.sectionById).filter(Boolean);
return (
Saved
Your favorites
{list.length} saved {list.length === 1 ? 'section' : 'sections'}.
{list.length === 0
?
:
{list.map(s => nav({ view: 'section', sectionId: sec.id })} />)}
}
);
}
/* ---------- Section detail ---------- */
function SectionView({ nav, favs, toggleFav, route, notify, refresh }) {
const section = window.sectionById(route.sectionId);
const cat = window.catById(section.category);
const isCustom = window.SectionStore.isCustom(section.id);
const isEdited = window.SectionStore.isEdited(section.id);
const [confirmDel, setConfirmDel] = useState(false);
const [device, setDevice] = useState('desktop');
const widths = { desktop: 1280, tablet: 834, mobile: 390 };
const isFav = favs.includes(section.id);
const related = window.SECTIONS.filter(s => s.category === cat.id && s.id !== section.id).slice(0, 3);
useEffect(() => {
const el = document.querySelector('.content');
if (el) el.scrollTop = 0;
}, [route.sectionId]);
return (
{section.name}
{cat.label}
·
{section.downloads.toLocaleString()} downloads
·
Added {window.timeAgo(section.added)}
{section.pro && Pro}
{isCustom && Custom}
{isEdited && Edited}
{confirmDel
?
Delete this section?
: <>
{isEdited &&
}
>}
{[['desktop', 'desktop'], ['tablet', 'device'], ['mobile', 'device']].map(([d, ic]) =>
)}
sectionly.app/preview/{section.id}
{widths[device]}px
{related.length > 0 && <>
More in {cat.label}
{related.map(s => nav({ view: 'section', sectionId: sec.id })} />)}
>}
);
}
/* ---------- Empty state ---------- */
function EmptyState({ heart }) {
return (
{heart ? 'No favorites yet' : 'No sections found'}
{heart ? 'Tap the heart on any section to save it here.' : 'Try a different search or category filter.'}
);
}
Object.assign(window, { DashboardView, BrowseView, CategoryView, CollectionsView, FavoritesView, SectionView, EmptyState });