?
diff --git a/pagerite/app.py b/pagerite/app.py
index 3a4d1e6..2a2446b 100644
--- a/pagerite/app.py
+++ b/pagerite/app.py
@@ -288,6 +288,7 @@ async def get_settings() -> dict:
the themes and banner designs available on disk for the selectors."""
return {
"brand": data.brand,
+ "brand_html": data.brand_html,
"theme": data.theme,
"custom_css": data.custom_css,
"favicon": f"/_f/{data.favicon}" if data.favicon else "",
@@ -302,6 +303,7 @@ class SettingsIn(BaseModel):
brand: str
theme: str
custom_css: str
+ brand_html: str = ""
@app.put("/_api/settings", status_code=204)
@@ -309,6 +311,7 @@ async def put_settings(settings: SettingsIn) -> None:
"""Update site-wide settings; bumps the version so ETags invalidate."""
with kanta.transaction("update settings"):
data.brand = settings.brand
+ data.brand_html = settings.brand_html
data.theme = settings.theme
data.custom_css = settings.custom_css
data.version += 1
@@ -682,7 +685,7 @@ async def show_page(request: Request, path: str) -> HTMLResponse | Response:
if request.headers.get("if-none-match") == etag:
return Response(status_code=304)
return HTMLResponse(
- views.render_page(data.menu, path, data.brand, data.custom_css, data.theme, data.favicon),
+ views.render_page(data.menu, path, data.brand, data.custom_css, data.theme, data.favicon, data.brand_html),
headers={
"etag": etag,
"last-modified": _http_date(node.modified),
@@ -693,7 +696,7 @@ async def show_page(request: Request, path: str) -> HTMLResponse | Response:
# Category label without a landing page: placeholder with the pen
# to create it (404 — no page here, but the node is real).
return HTMLResponse(
- views.render_category(data.menu, path, data.brand, data.custom_css, data.theme, data.favicon),
+ views.render_category(data.menu, path, data.brand, data.custom_css, data.theme, data.favicon, data.brand_html),
404,
headers={
"last-modified": _http_date(node.modified),
@@ -706,4 +709,4 @@ async def show_page(request: Request, path: str) -> HTMLResponse | Response:
for slug, item in sorted_nodes(data.menu):
if item.published:
return RedirectResponse(f"/{slug}")
- return HTMLResponse(views.render_not_found(data.menu, path, data.brand, data.custom_css, data.theme, data.favicon), 404)
+ return HTMLResponse(views.render_not_found(data.menu, path, data.brand, data.custom_css, data.theme, data.favicon, data.brand_html), 404)
diff --git a/pagerite/data.py b/pagerite/data.py
index 643d4ed..d069e14 100644
--- a/pagerite/data.py
+++ b/pagerite/data.py
@@ -85,6 +85,11 @@ class Data(msgspec.Struct):
#: Site name shown in the header and
suffix; editable in the
#: site editor. Empty = no brand link in the header, no title suffix.
brand: str = "Pagerite"
+ #: Raw trusted HTML replacing the brand link entirely (a logo image,
+ #: styled markup, canvas+script...), site-wide — not per-page
+ #: overridable like banners. Rendered in the header on top of the
+ #: banner artwork, next to the nav. Empty = the plain brand link.
+ brand_html: str = ""
#: Active theme name (empty = none/base only). Themes live in
#: frontend/src/assets/themes/{theme}/theme.css, with their banner
#: artwork at pagerite/themes/{theme}/banner.svg (inlined server-side).
diff --git a/pagerite/themes/summer/theme.css b/pagerite/themes/summer/theme.css
index 7f207ec..87442e1 100644
--- a/pagerite/themes/summer/theme.css
+++ b/pagerite/themes/summer/theme.css
@@ -149,7 +149,6 @@ main {
article h1 {
color: var(--accent);
width: fit-content;
- padding-bottom: 0.12em;
text-shadow: 0 1px #ffffffaa;
background: url("grass.svg") bottom left / auto 0.35em repeat-x;
}
diff --git a/pagerite/views.py b/pagerite/views.py
index 0f3cef4..bb17eb4 100644
--- a/pagerite/views.py
+++ b/pagerite/views.py
@@ -171,8 +171,12 @@ def _layout(
)
-def _brand_link(brand: str) -> HTML:
- """Header brand link; omitted entirely when no brand is configured."""
+def _brand_link(brand: str, brand_html: str = "") -> HTML:
+ """Header brand: custom HTML (in a #brand wrapper, rendered instead of
+ the link) when configured, else the plain brand link; omitted entirely
+ when neither is set."""
+ if brand_html.strip():
+ return HTML(str(E.div(HTML(brand_html), id="brand")))
return HTML(str(E.a(brand, href="/", id="brand"))) if brand else HTML("")
@@ -391,6 +395,7 @@ def render_page(
custom_css: str = "",
theme: str = "",
favicon: str = "",
+ brand_html: str = "",
) -> str:
"""Render a full HTML page for the slug path."""
node = resolve(menu, path)[-1]
@@ -398,7 +403,7 @@ def render_page(
return str(
_layout(_page_assets(), custom_css, theme, banner_design(menu, path, theme), favicon)(
Title=f"{title} – {brand}" if brand else title,
- Brand=_brand_link(brand),
+ Brand=_brand_link(brand, brand_html),
Nav=nav_html(menu, path),
Sidebar=sidebar_html(menu, path),
Banner=banner_html(menu, path, theme),
@@ -414,6 +419,7 @@ def render_category(
custom_css: str = "",
theme: str = "",
favicon: str = "",
+ brand_html: str = "",
) -> str:
"""Render the placeholder for a content-less category label (404).
@@ -434,7 +440,7 @@ def render_category(
return str(
_layout(_page_assets(), custom_css, theme, banner_design(menu, path, theme), favicon)(
Title=f"{title} – {brand}" if brand else title,
- Brand=_brand_link(brand),
+ Brand=_brand_link(brand, brand_html),
Nav=nav_html(menu, path),
Sidebar=sidebar,
Banner=banner_html(menu, path, theme),
@@ -450,6 +456,7 @@ def render_not_found(
custom_css: str = "",
theme: str = "",
favicon: str = "",
+ brand_html: str = "",
) -> str:
"""Render a 404 page within the normal layout."""
doc = E.article
@@ -459,7 +466,7 @@ def render_not_found(
return str(
_layout(_page_assets(), custom_css, theme, banner_design(menu, path, theme), favicon)(
Title=f"Not Found – {brand}" if brand else "Not Found",
- Brand=_brand_link(brand),
+ Brand=_brand_link(brand, brand_html),
Nav=nav_html(menu, path),
Sidebar=sidebar_html(menu, path),
Banner=banner_html(menu, path, theme),