The site logo now has two variants — one for the light theme, one for every other (dark) theme — so a logo that reads fine on black can stop inverting itself into invisibility on white. Upload page: /admin/branding.
- Dark-theme logo is the existing upload; nothing changes for installs that don't touch the new slot. It still serves as the canonical logo in JSON-LD schema, OG image, and Twitter card.
- Light-theme logo is the new optional upload. If empty, the navbar uses the dark one on every theme (current behaviour). If uploaded, only the
lighttheme picks it up — crt-green, crt-amber, c64, vt100, and future dark themes all keep the dark logo. - Mobile is forced to the light theme via the existing UA override, so the light logo kicks in there too once it's uploaded.
- Both cards in
/admin/brandinghave the same crop + navbar-preview workflow; the light-theme card previews on a white background so contrast issues are obvious before you save.
Behind the scenes:
- New config key
general.site_logo_lightinconfig/app.ini(alongside the existinggeneral.site_logo). Unset = fallback to dark. - Navbar
<img>renders withdata-logo-darkanddata-logo-lightattrs plus a synchronous pre-paint resolver that picks the correctsrcbased ondata-theme, so there's no flash and no extra fetch on the initial render. - Theme switcher in
app.jscalls a newapplyLogoForTheme()helper afterapplyTheme(), so flipping themes mid-session swaps the logo instantly with no reload. AdminBrandingController::updateBranding()handles both uploads in one POST with independent crop-coord fields (crop_x/crop_x_light, etc.) so an admin can replace either variant without disturbing the other. Audit log records each upload separately.
Validation + SVG-block behaviour from the existing upload path is unchanged — raster only (JPEG / PNG / WebP), same size limit.
. __ ____ ___ ____ _ _
/ /_| ___| / _ \___ \(_)___| |__
| '_ \___ \| | | |__) | / __| '_ \
| (_) |__) | |_| / __/| \__ \ | | |
\___/____/ \___/_____|_|___/_| |_|
D2sk - Sysop