feat: regroup homepage into apps/tutorial/services/libraries cards

- assign each repo a section via config.yml (blog -> apps,
  media-manager -> tutorial, auth-server -> services, rest -> libraries)
- render one card per repo with a bottom-justified action row containing
  one link per available kind (view docs / view wiki / view lib /
  view tutorial)
- switch card grid to three columns with responsive breakpoints and
  taller cards
This commit is contained in:
2026-09-11 23:49:59 +05:30
parent 42e5ed290e
commit bd8ba6e67c
3 changed files with 170 additions and 94 deletions

View File

@@ -107,19 +107,25 @@ INDEX_TEMPLATE = r"""<!DOCTYPE html>
padding:3px 8px; border-radius:6px; font-size:0.85rem; font-weight:600;
border:1px solid #484f58;
}}
.section {{ width:100%; max-width:900px; margin-bottom:2.5rem; }}
.section {{ width:100%; max-width:1200px; margin-bottom:2.5rem; }}
.section h2 {{
font-size:1.2rem; color:var(--muted); text-transform:uppercase;
letter-spacing:1.5px; margin-bottom:1rem; border-bottom:1px solid var(--border);
padding-bottom:0.4rem;
}}
.grid {{
display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
gap:1.5rem; width:100%;
display:grid; grid-template-columns:repeat(3, 1fr); gap:1.5rem; width:100%;
}}
@media (max-width: 1024px) {{
.grid {{ grid-template-columns:repeat(2, 1fr); }}
}}
@media (max-width: 640px) {{
.grid {{ grid-template-columns:1fr; }}
}}
.card {{
background:var(--card); border:1px solid var(--border); border-radius:12px;
padding:1.8rem; text-align:left;
padding:1.6rem; text-align:left; display:flex; flex-direction:column;
height:100%; min-height:200px;
transition:transform 0.15s ease,box-shadow 0.15s ease;
}}
.card:hover {{
@@ -128,11 +134,19 @@ INDEX_TEMPLATE = r"""<!DOCTYPE html>
}}
.card h3 {{ color:var(--accent); font-size:1.4rem; margin-bottom:0.4rem; }}
.card p {{ color:var(--muted); font-size:0.95rem; line-height:1.6; }}
.card a {{
color:var(--accent); text-decoration:none; display:inline-block;
margin-top:0.8rem; font-weight:500;
.card-actions {{
margin-top:auto; padding-top:1.2rem; display:flex; flex-wrap:wrap; gap:0.6rem;
}}
.card-actions a {{
color:var(--accent); text-decoration:none; font-size:0.85rem; font-weight:600;
padding:0.45rem 0.9rem; border:1px solid var(--border); border-radius:999px;
background:rgba(45,212,191,0.06);
transition:border-color 0.15s ease,background 0.15s ease,color 0.15s ease;
}}
.card-actions a:hover {{
color:var(--accent-dim); border-color:var(--accent);
background:rgba(45,212,191,0.14);
}}
.card a:hover {{ color:var(--accent-dim); text-decoration:underline; }}
footer {{ margin-top:4rem; text-align:center; color:var(--muted); font-size:0.9rem; }}
</style>
</head>
@@ -151,16 +165,45 @@ INDEX_TEMPLATE = r"""<!DOCTYPE html>
</html>"""
def _render_section(title: str, cards: list[tuple[str, str, str, str]]) -> str:
SECTION_ORDER = (
("apps", "Apps"),
("tutorial", "Tutorial"),
("services", "Services"),
("libraries", "Libraries"),
)
DEFAULT_SECTION = "libraries"
LINK_KINDS = (("api", "view docs"), ("wiki", "view wiki"), ("lib", "view lib"))
def _card_links(
entry: dict, name: str, base_url: str, kind_urls: dict[tuple[str, str], str]
) -> list[tuple[str, str]]:
"""Build the action links for a repo card from its declared kinds."""
docs = entry.get("docs") or {}
links: list[tuple[str, str]] = []
for kind, label in LINK_KINDS:
if docs.get(kind):
url = kind_urls.get((name, kind), base_url)
links.append((label, url))
if entry.get("kind") == "tutorial":
links.append(("view tutorial", base_url))
return links or [("view docs", base_url)]
def _render_section(title: str, cards: list[tuple[str, str, list[tuple[str, str]]]]) -> str:
if not cards:
return ""
lines = [f'<section class="section">', f"<h2>{title}</h2>", '<div class="grid">']
for t, desc, href, label in cards:
for t, desc, links in cards:
lines.append(f' <div class="card">')
lines.append(f" <h3>{t}</h3>")
lines.append(f" <p>{desc}</p>")
lines.append(f' <a href="{href}" target="_blank">{label} &rarr;</a>')
lines.append(f" </div>")
lines.append(' <div class="card-actions">')
for label, href in links:
lines.append(f' <a href="{href}" target="_blank">{label}</a>')
lines.append(" </div>")
lines.append(" </div>")
lines.append("</div>")
lines.append("</section>")
return "\n".join(lines)
@@ -171,46 +214,35 @@ def build_index_html(
home_urls: dict[str, str],
kind_urls: dict[tuple[str, str], str] | None = None,
) -> str:
lib_cards: list[tuple[str, str, str, str]] = []
api_cards: list[tuple[str, str, str, str]] = []
wiki_cards: list[tuple[str, str, str, str]] = []
tutorial_cards: list[tuple[str, str, str, str]] = []
kind_urls = kind_urls or {}
sections: dict[str, list[tuple[str, str, list[tuple[str, str]]]]] = {}
for entry in config.get("repos", []):
docs = entry.get("docs") or {}
name = entry["name"]
title = _safe_title(entry)
desc = _safe_description(entry)
base_url = home_urls.get(name, f"/{name}/")
if docs.get("lib"):
url = kind_urls.get((name, "lib"), base_url)
lib_cards.append((title, desc, url, "View Documentation"))
if docs.get("wiki"):
url = kind_urls.get((name, "wiki"), base_url)
wiki_cards.append((title, desc, url, "View Documentation"))
if docs.get("api"):
url = kind_urls.get((name, "api"), base_url)
api_cards.append((title, desc, url, "View Documentation"))
section = (entry.get("section") or DEFAULT_SECTION).strip().lower()
sections.setdefault(section, []).append(
(title, desc, _card_links(entry, name, base_url, kind_urls))
)
for entry in config.get("static", []):
kind = entry.get("kind")
name = entry["repo"]
title = _safe_title(entry)
desc = _safe_description(entry)
url = home_urls.get(name, f"/{name}/")
if kind == "api":
api_cards.append((title, desc, url, "View Documentation"))
elif kind == "tutorial":
tutorial_cards.append((title, desc, url, "View Tutorial"))
base_url = home_urls.get(name, f"/{name}/")
section = (entry.get("section") or DEFAULT_SECTION).strip().lower()
sections.setdefault(section, []).append(
(title, desc, _card_links(entry, name, base_url, kind_urls))
)
body = "\n\n".join(filter(None, [
_render_section("Libraries", lib_cards),
_render_section("APIs", api_cards),
_render_section("Wiki", wiki_cards),
_render_section("Tutorials", tutorial_cards),
]))
body = "\n\n".join(
filter(
None,
(_render_section(title, sections.get(key, [])) for key, title in SECTION_ORDER),
)
)
return INDEX_TEMPLATE.format(body=body)