Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1468ae7
feat(website): show project description as always-visible desc-row on…
vinta May 3, 2026
1afccd8
update description for curses
vinta May 3, 2026
fc8d1ba
feat(website): show desc-row on index page when a filter is active
vinta May 3, 2026
9de86ea
feat(website): append #library-index to tag links on non-index pages
vinta May 3, 2026
f3f92c6
feat(website): render subcategory, group, and source tags as anchor e…
vinta May 3, 2026
f57fc44
style: bump tag font size to var(--text-xs) and codify 12px minimum f…
vinta May 3, 2026
138059f
feat(website): add Awesome Python and Sponsorship links to footer
vinta May 3, 2026
b2910d5
feat(website): add homepage JSON-LD with WebSite, CollectionPage, Ite…
vinta May 3, 2026
86d2aa7
feat(website): add CollectionPage JSON-LD to category, group, and sub…
vinta May 3, 2026
2f398ac
fix(seo): align JSON-LD with Yoast/RankMath conventions
vinta May 3, 2026
c886e47
feat(website): lead category meta description with real description w…
vinta May 3, 2026
28b61a9
style(seo): switch category page title separator from pipe to hyphen
vinta May 3, 2026
a9a58ce
Merge pull request #3107 from vinta/chore/refine-ui
vinta May 3, 2026
9379e0a
style(sitemap): pretty-print generated sitemap.xml with 2-space indent
vinta May 3, 2026
db18ff4
docs(design): restructure DESIGN.md to follow Google Stitch format
vinta May 3, 2026
83bab2c
docs(DESIGN.md): add YAML frontmatter, hex approximations, typography…
vinta May 3, 2026
dc19f2d
fix formatting
vinta May 3, 2026
7640a32
docs(DESIGN.md): add flexbox/grid layout rule
vinta May 3, 2026
8243747
docs(DESIGN.md): align with actual CSS
vinta May 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 157 additions & 58 deletions DESIGN.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ _Libraries for working with graphical user interface applications._
- [nicegui](https://github.com/zauberzeug/nicegui) - An easy-to-use, Python-based UI framework, which shows up in your web browser.
- [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component.
- Terminal
- [curses](https://docs.python.org/3/library/curses.html) - Built-in wrapper for [ncurses](http://www.gnu.org/software/ncurses/) used to create terminal GUI applications.
- [curses](https://docs.python.org/3/library/curses.html) - (Python standard library) The built-in wrapper for [ncurses](http://www.gnu.org/software/ncurses/) used to create terminal GUI applications.
- [urwid](https://github.com/urwid/urwid) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc.
- Wrappers
- [gooey](https://github.com/chriskiehl/Gooey) - Turn command line programs into a full GUI application with one line.
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

98 changes: 97 additions & 1 deletion website/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,84 @@ def build_robots_txt() -> str:
return f"User-agent: *\nContent-Signal: search=yes, ai-input=yes, ai-train=yes\nAllow: /\n\nSitemap: {SITEMAP_URL}\n"


WEBSITE_ID = f"{SITE_URL}#website"
ISPARTOF_WEBSITE = {"@type": "WebSite", "@id": WEBSITE_ID}


def _website_node() -> dict:
return {
"@type": "WebSite",
"@id": WEBSITE_ID,
"name": "Awesome Python",
"url": SITE_URL,
}


def _item_list_payload(entries: Sequence[TemplateEntry]) -> dict:
return {
"@type": "ItemList",
"numberOfItems": len(entries),
"itemListElement": [
{
"@type": "ListItem",
"position": i,
"name": entry["name"],
"url": entry["url"],
}
for i, entry in enumerate(entries, start=1)
],
}


def build_homepage_json_ld(entries: Sequence[TemplateEntry], total_categories: int) -> dict:
description = (
"An opinionated guide to the best Python frameworks, libraries, and tools. "
f"Explore {len(entries)} curated projects across {total_categories} categories, "
"from AI and agents to data science and web development."
)
return {
"@context": "https://schema.org",
"@graph": [
_website_node(),
{
"@type": "CollectionPage",
"@id": SITE_URL,
"name": "Awesome Python",
"url": SITE_URL,
"description": description,
"isPartOf": ISPARTOF_WEBSITE,
"mainEntity": _item_list_payload(entries),
},
],
}


def category_meta_description(name: str, entry_count: int, description: str) -> str:
count_sentence = f"Explore {entry_count} curated Python projects in {name}."
if description:
lead = description if description.endswith((".", "!", "?")) else f"{description}."
return f"{lead} {count_sentence}"
return f"{count_sentence} Part of the Awesome Python catalog."


def build_category_json_ld(name: str, url: str, description: str, entries: Sequence[TemplateEntry]) -> dict:
return {
"@context": "https://schema.org",
"@graph": [
_website_node(),
{
"@type": "CollectionPage",
"@id": url,
"name": f"{name} Python Libraries",
"url": url,
"description": description,
"isPartOf": ISPARTOF_WEBSITE,
"mainEntity": _item_list_payload(entries),
},
],
}


def category_path(category: ParsedSection) -> str:
return f"/categories/{category['slug']}/"

Expand Down Expand Up @@ -156,7 +234,9 @@ def write_sitemap_xml(path: Path, urls: Sequence[tuple[str, str]]) -> None:
lastmod_el = ET.SubElement(url_el, f"{{{SITEMAP_NS}}}lastmod")
lastmod_el.text = lastmod

ET.ElementTree(urlset).write(path, encoding="utf-8", xml_declaration=True)
tree = ET.ElementTree(urlset)
ET.indent(tree, space=" ")
tree.write(path, encoding="utf-8", xml_declaration=True)
with path.open("ab") as f:
f.write(b"\n")

Expand Down Expand Up @@ -378,6 +458,10 @@ def build(repo_root: Path) -> None:
site_dir.mkdir(parents=True)

filter_urls_json = json.dumps(filter_urls, sort_keys=True, ensure_ascii=False).replace("</", "<\\/")
homepage_json_ld = json.dumps(
build_homepage_json_ld(entries, len(categories)),
ensure_ascii=False,
).replace("</", "<\\/")

tpl_index = env.get_template("index.html")
(site_dir / "index.html").write_text(
Expand All @@ -393,6 +477,7 @@ def build(repo_root: Path) -> None:
category_urls=category_urls,
filter_urls=filter_urls,
filter_urls_json=filter_urls_json,
homepage_json_ld=homepage_json_ld,
),
encoding="utf-8",
)
Expand All @@ -411,10 +496,20 @@ def render_category(
group_categories: Sequence[ParsedSection] | None = None,
) -> None:
page_dir.mkdir(parents=True, exist_ok=True)
category_description = category_meta_description(
category["name"], len(entries), category["description"]
)
category_json_ld = json.dumps(
build_category_json_ld(
category["name"], category_url, category_description, entries
),
ensure_ascii=False,
).replace("</", "<\\/")
(page_dir / "index.html").write_text(
tpl_category.render(
category=category,
category_url=category_url,
category_description=category_description,
entries=entries,
total_categories=len(categories),
category_urls=category_urls,
Expand All @@ -423,6 +518,7 @@ def render_category(
filter_urls_json=filter_urls_json,
parent_category=parent_category,
group_categories=group_categories,
category_json_ld=category_json_ld,
),
encoding="utf-8",
)
Expand Down
33 changes: 26 additions & 7 deletions website/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ document

rows.forEach(function (row, i) {
row._origIndex = i;
row._expandRow = row.nextElementSibling;
let next = row.nextElementSibling;
if (next && next.classList.contains("desc-row")) {
row._descRow = next;
next = next.nextElementSibling;
}
row._expandRow = next;
});

function collapseAll() {
Expand All @@ -127,6 +132,7 @@ function collapseAll() {

function applyFilters() {
const query = searchInput ? searchInput.value.toLowerCase().trim() : "";
const descRowsVisible = !isIndexDocument || activeFilter !== null;
let visibleCount = 0;

collapseAll();
Expand All @@ -142,16 +148,24 @@ function applyFilters() {
if (show && query) {
if (!row._searchText) {
let text = row.textContent.toLowerCase();
const next = row.nextElementSibling;
if (next && next.classList.contains("expand-row")) {
text += " " + next.textContent.toLowerCase();
if (row._descRow) {
text += " " + row._descRow.textContent.toLowerCase();
}
if (row._expandRow) {
text += " " + row._expandRow.textContent.toLowerCase();
}
row._searchText = text;
}
show = row._searchText.includes(query);
}

if (row.hidden !== !show) row.hidden = !show;
if (row._descRow) {
const descHidden = !show || !descRowsVisible;
if (row._descRow.hidden !== descHidden) {
row._descRow.hidden = descHidden;
}
}

if (show) {
visibleCount++;
Expand Down Expand Up @@ -262,7 +276,8 @@ function sortRows() {
const frag = document.createDocumentFragment();
arr.forEach(function (row) {
frag.appendChild(row);
frag.appendChild(row._expandRow);
if (row._descRow) frag.appendChild(row._descRow);
if (row._expandRow) frag.appendChild(row._expandRow);
});
tbody.appendChild(frag);
applyFilters();
Expand Down Expand Up @@ -291,7 +306,11 @@ if (tbody) {
// Don't toggle if clicking a link or tag button
if (e.target.closest("a") || e.target.closest(".tag")) return;

const row = e.target.closest("tr.row");
let row = e.target.closest("tr.row");
if (!row) {
const descRow = e.target.closest("tr.desc-row");
if (descRow) row = descRow.previousElementSibling;
}
if (!row) return;

const isOpen = row.classList.contains("open");
Expand Down Expand Up @@ -328,7 +347,7 @@ tags.forEach(function (tag) {
}
applyFilters();
} else if (url) {
window.location.href = url;
window.location.href = url + "#library-index";
}
});
});
Expand Down
65 changes: 61 additions & 4 deletions website/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,11 @@ kbd {
box-shadow: inset 3px 0 0 var(--accent);
}

.row:has(+ .desc-row:not([hidden])) td {
border-bottom-color: transparent;
padding-bottom: 0.35rem;
}

.row.open td {
background: linear-gradient(180deg, var(--row-open-start), var(--row-open-end));
border-bottom-color: transparent;
Expand Down Expand Up @@ -919,14 +924,66 @@ th[data-sort].sort-asc::after {
transform: rotate(90deg);
}

.desc-row td {
padding-top: 0;
padding-bottom: 1rem;
border-bottom: 1px solid var(--line);
color: var(--ink-soft);
font-size: var(--text-sm);
line-height: 1.6;
text-wrap: pretty;
overflow-wrap: break-word;
transition:
background-color 180ms ease,
border-color 180ms ease;
}

.row:not(.open) + .desc-row td {
border-bottom: 1px solid var(--line);
}

.row + .desc-row {
cursor: pointer;
}

.row:not(.open):hover td,
.row:not(.open):hover + .desc-row td {
background: var(--row-hover);
}

.row.open + .desc-row td {
background: linear-gradient(180deg, var(--row-open-start), var(--row-open-end));
border-bottom-color: transparent;
}

.desc-text {
max-width: none;
}

.desc-text a {
color: var(--accent-deep);
}

.desc-text a:hover {
color: var(--accent);
text-decoration: underline;
text-decoration-color: var(--accent-underline);
text-underline-offset: 0.2em;
}

.expand-row {
display: none;
}

.row.open + .desc-row + .expand-row,
.row.open + .expand-row {
display: table-row;
}

.desc-row:not([hidden]) + .expand-row .expand-desc {
display: none;
}

.expand-row td {
padding-top: 0.1rem;
padding-bottom: 1.15rem;
Expand Down Expand Up @@ -993,7 +1050,7 @@ th[data-sort].sort-asc::after {
background: var(--accent-soft);
color: var(--accent-deep);
padding: 0.14rem 0.48rem;
font-size: 0.6rem;
font-size: var(--text-xs);
font-weight: 700;
letter-spacing: 0.02em;
cursor: pointer;
Expand Down Expand Up @@ -1426,8 +1483,8 @@ th[data-sort].sort-asc::after {

.footer-left {
display: flex;
flex-direction: column;
gap: 0.3rem;
align-items: center;
gap: 0.6rem;
}

.footer-brand {
Expand Down Expand Up @@ -1561,7 +1618,7 @@ th[data-sort].sort-asc::after {

.tag {
padding: 0.38rem 0.65rem;
font-size: 0.65rem;
font-size: var(--text-xs);
}

.table-wrap {
Expand Down
5 changes: 4 additions & 1 deletion website/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
gtag("js", new Date());
gtag("config", "G-0LMLYE0HER");
</script>
{% block extra_head %}{% endblock %}
</head>
<body>
<a href="#content" class="skip-link">Skip to content</a>
Expand All @@ -69,7 +70,9 @@

<footer class="footer">
<div class="footer-left">
<span class="footer-brand">Awesome Python</span>
<a href="/" class="footer-brand">Awesome Python</a>
<span class="footer-sep">/</span>
<a href="/sponsorship/">Sponsorship</a>
</div>
<div class="footer-links">
<span
Expand Down
Loading