diff --git a/website/build.py b/website/build.py index f9e3aa55..af644bfd 100644 --- a/website/build.py +++ b/website/build.py @@ -84,6 +84,14 @@ def build_robots_txt() -> str: ) +def category_path(category: ParsedSection) -> str: + return f"/categories/{category['slug']}/" + + +def category_public_url(category: ParsedSection) -> str: + return f"{SITE_URL}categories/{category['slug']}/" + + def write_sitemap_xml(path: Path, urls: Sequence[tuple[str, str]]) -> None: ET.register_namespace("", SITEMAP_NS) urlset = ET.Element(f"{{{SITEMAP_NS}}}urlset") @@ -278,6 +286,7 @@ def build(repo_root: Path) -> None: entry["last_commit_at"] = sd.get("last_commit_at", "") entries = sort_entries(entries) + category_urls = {cat["name"]: category_path(cat) for cat in categories} env = Environment( loader=FileSystemLoader(website / "templates"), @@ -302,10 +311,27 @@ def build(repo_root: Path) -> None: repo_stars=repo_stars, build_date=build_date.strftime("%B %d, %Y"), sponsors=sponsors, + category_urls=category_urls, ), encoding="utf-8", ) + tpl_category = env.get_template("category.html") + categories_dir = site_dir / "categories" + for category in categories: + category_entries = [entry for entry in entries if category["name"] in entry["categories"]] + page_dir = categories_dir / category["slug"] + page_dir.mkdir(parents=True, exist_ok=True) + (page_dir / "index.html").write_text( + tpl_category.render( + category=category, + category_url=category_public_url(category), + entries=category_entries, + total_categories=len(categories), + ), + encoding="utf-8", + ) + static_src = website / "static" static_dst = site_dir / "static" if static_src.exists(): @@ -317,11 +343,15 @@ def build(repo_root: Path) -> None: llms_template = (website / "templates" / "llms.txt").read_text(encoding="utf-8") llms_txt = build_llms_txt(llms_template, readme_text, stars_data) (site_dir / "robots.txt").write_text(build_robots_txt(), encoding="utf-8") - write_sitemap_xml(site_dir / "sitemap.xml", [(SITE_URL, build_date.date().isoformat())]) + sitemap_date = build_date.date().isoformat() + sitemap_urls = [(SITE_URL, sitemap_date)] + [ + (category_public_url(category), sitemap_date) for category in categories + ] + write_sitemap_xml(site_dir / "sitemap.xml", sitemap_urls) (site_dir / "index.md").write_text(markdown_index, encoding="utf-8") (site_dir / "llms.txt").write_text(llms_txt, encoding="utf-8") - print(f"Built single page with {len(parsed_groups)} groups, {len(categories)} categories") + print(f"Built site with {len(parsed_groups)} groups, {len(categories)} categories") print(f"Total entries: {total_entries}") print(f"Output: {site_dir}") diff --git a/website/static/main.js b/website/static/main.js index 7353ff2c..f875f8b1 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -202,6 +202,8 @@ function getSortValue(row, col) { } function sortRows() { + if (!tbody) return; + const arr = Array.prototype.slice.call(rows); const col = activeSort.col; const order = activeSort.order; diff --git a/website/static/style.css b/website/static/style.css index ec395e98..2adeca39 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -376,18 +376,92 @@ kbd { } .hero-action:focus-visible, +.hero-brand-mini:focus-visible, .hero-topbar-link:focus-visible, .search:focus-visible, .filter-clear:focus-visible, .tag:focus-visible, .back-to-top:focus-visible, .no-results-clear:focus-visible, +.category-table a:focus-visible, .footer a:focus-visible, .sort-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; } +.category-hero { + position: relative; + overflow: clip; + background: linear-gradient(140deg, var(--hero-bg-start) 0%, var(--hero-bg-mid) 58%, var(--hero-bg-end) 100%); + color: var(--hero-text); +} + +.category-hero-shell { + position: relative; + z-index: 1; + width: min(100%, calc(var(--shell-max) + (var(--shell-pad) * 2))); + margin: 0 auto; + padding: 1.25rem var(--shell-pad) clamp(3.75rem, 8vw, 6.75rem); + display: grid; + gap: clamp(3rem, 8vw, 5.5rem); +} + +.category-hero h1 { + font-family: var(--font-display); + font-size: clamp(3.6rem, 9vw, 7rem); + line-height: 0.9; + font-weight: 600; + text-wrap: balance; +} + +.category-subtitle { + max-width: 68ch; + margin-top: 1.1rem; + color: var(--hero-muted); + font-size: clamp(1rem, 1.8vw, 1.18rem); + text-wrap: pretty; +} + +.category-results { + padding-top: clamp(2.5rem, 5vw, 3.75rem); +} + +.category-table .col-name { + width: min(42rem, 48vw); + white-space: normal; +} + +.category-table .col-name > a { + display: inline-block; +} + +.category-row-desc { + display: block; + max-width: 68ch; + margin-top: 0.32rem; + color: var(--ink-soft); + font-size: var(--text-sm); + font-weight: 500; + line-height: 1.55; + text-wrap: pretty; +} + +.category-row-desc a { + color: var(--accent-deep); + text-decoration: underline; + text-decoration-color: var(--accent-underline); + text-underline-offset: 0.18em; +} + +.category-row-desc a:hover { + color: var(--accent); +} + +.category-table .expand-content { + padding-block: 0.25rem 0.15rem; +} + .sponsor-band { padding-block: clamp(2.5rem, 5.5vw, 4rem); background: diff --git a/website/templates/base.html b/website/templates/base.html index af112095..22b56e98 100644 --- a/website/templates/base.html +++ b/website/templates/base.html @@ -3,21 +3,24 @@
{% set default_meta_title = "Awesome Python" %} {% set default_meta_description = "An opinionated guide to the best Python frameworks, libraries, and tools. Explore " ~ (entries | length) ~ " curated projects across " ~ total_categories ~ " categories, from AI and agents to data science and web development." %} - {% set canonical_url = "https://awesome-python.com/" %} + {% set default_canonical_url = "https://awesome-python.com/" %} {% set social_image_url = "https://awesome-python.com/static/og-image.png" %} {% set meta_title %}{% block title %}{{ default_meta_title }}{% endblock %}{% endset %} {% set meta_description %}{% block description %}{{ default_meta_description }}{% endblock %}{% endset %} + {% set canonical_url %}{% block canonical_url %}{{ default_canonical_url }}{% endblock %}{% endset %}{{ category.description }}
+ {% endif %} ++ Sorted by GitHub stars when available. Click any row for details. +
+| Row number | ++ + | ++ + | ++ + | +Tags | +Details | +
|---|---|---|---|---|---|
| {{ loop.index }} | ++ {{ entry.name }} + {% if entry.description %} + {{ entry.description | safe }} + {% endif %} + {% if entry.subcategories %}{{ entry.subcategories[0].name }}{% + else %}{{ category.name }}{% endif %} + | ++ {% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{% + elif entry.source_type %}{{ entry.source_type }}{% else %}—{% endif %} + | ++ {% if entry.last_commit_at %}{% else %}—{% endif %} + | ++ {% for subcat in entry.subcategories %} + + {% endfor %} + + {% if entry.groups %} + + {% endif %} + {% if entry.source_type == 'Built-in' %} + + {% endif %} + | +→ | +
| + | + + | ++ | |||
Contribute
+Tell us what it does and why it stands out.
+